Compare commits

..

3 Commits

Author SHA1 Message Date
Ammar Ahmed
5e68dd129e Update android.publish.yml
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-02-27 01:03:48 +05:00
Ammar Ahmed
2350d0e41d mobile: minor fix 2024-02-27 01:02:39 +05:00
Ammar Ahmed
fbd5c8e3cd mobile: fix github release 2024-02-27 01:01:07 +05:00
93 changed files with 457 additions and 1217 deletions

View File

@@ -28,32 +28,6 @@ jobs:
- name: Make Gradlew Executable
run: cd apps/mobile/native/android && chmod +x ./gradlew
- name: Build unsigned app bundle
run: yarn release:android:bundle
- name: Sign app bundle for Playstore release
id: sign_app
uses: r0adkll/sign-android-release@master
with:
releaseDirectory: apps/mobile/native/android/app/build/outputs/bundle/release
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.ALIAS }}
keyStorePassword: ${{ secrets.KEY_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: "33.0.0"
- name: Publish to Playstore
id: deploy
uses: r0adkll/upload-google-play@v1.1.1
with:
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
packageName: com.streetwriters.notesnook
releaseFiles: ${{steps.sign_app.outputs.signedReleaseFile}}
track: production
status: completed
whatsNewDirectory: apps/mobile/native/android/releasenotes/
- name: Build apks for Github release
run: yarn release:android

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/desktop",
"version": "2.6.16",
"version": "2.6.15",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/desktop",
"version": "2.6.16",
"version": "2.6.15",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "2.6.16",
"version": "2.6.15",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/index.js",

View File

@@ -54,14 +54,6 @@ export const osIntegrationRouter = t.router({
config.zoomFactor = factor;
}),
proxyRules: t.procedure.query(() => config.proxyRules),
setProxyRules: t.procedure
.input(z.string().optional())
.mutation(({ input: proxyRules }) => {
globalThis.window?.webContents.session.setProxy({ proxyRules });
config.proxyRules = proxyRules || "";
}),
privacyMode: t.procedure.query(() => config.privacyMode),
setPrivacyMode: t.procedure
.input(z.object({ enabled: z.boolean() }))

View File

@@ -118,13 +118,5 @@ export const spellCheckerRouter = t.router({
.mutation(({ input: { enabled } }) => {
globalThis.window?.webContents.session.setSpellCheckerEnabled(enabled);
config.isSpellCheckerEnabled = enabled;
}),
words: t.procedure.query(() =>
globalThis.window?.webContents.session.listWordsInSpellCheckerDictionary()
),
deleteWord: t.procedure.input(z.string()).mutation(({ input: word }) => {
globalThis.window?.webContents.session.removeWordFromSpellCheckerDictionary(
word
);
})
})
});

View File

@@ -112,7 +112,6 @@ async function createWindow() {
mainWindow.webContents.session.setSpellCheckerDictionaryDownloadURL(
"http://dictionaries.notesnook.com/"
);
mainWindow.webContents.session.setProxy({ proxyRules: config.proxyRules });
mainWindow.once("closed", () => {
globalThis.window = null;

View File

@@ -25,11 +25,7 @@ Type=Application
Version=${app.getVersion()}
Name=${app.getName()}
Comment=${app.getName()} startup script
Exec=${
process.env.APPIMAGE
? `${process.env.APPIMAGE}${hidden ? " --hidden" : ""}`
: `${process.execPath}${hidden ? " --hidden" : ""}`
}
Exec=${process.execPath}${hidden ? " --hidden" : ""}
StartupNotify=false
Terminal=false`;

View File

@@ -41,8 +41,7 @@ export const config = {
isSpellCheckerEnabled: true,
zoomFactor: 1,
theme: nativeTheme.themeSource,
automaticUpdates: true,
proxyRules: ""
automaticUpdates: true
};
type ConfigKey = keyof typeof config;

View File

@@ -131,17 +131,6 @@ function setupMenu() {
);
}
if (params.mediaType === "image")
menu.append(
new MenuItem({
id: "copy-image",
label: "Copy Image",
click() {
globalThis.window?.webContents.copyImageAt(params.x, params.y);
}
})
);
if (params.isEditable)
menu.append(
new MenuItem({

View File

@@ -49,7 +49,7 @@ export const AttachmentDialog = ({ note }) => {
const { height } = useSettingStore((state) => state.dimensions);
const [attachments, setAttachments] = useState(
note
? db.attachments.ofNote(note?.id, "all")
? db.attachments.ofNote(note.id, "all")
: [...(db.attachments.all || [])]
);
@@ -59,8 +59,8 @@ export const AttachmentDialog = ({ note }) => {
const [currentFilter, setCurrentFilter] = useState("all");
const onChangeText = (text) => {
const attachments = note?.id
? db.attachments.ofNote(note?.id, "all")
const attachments = note
? db.attachments.ofNote(note.id, "all")
: [...(db.attachments.all || [])];
attachmentSearchValue.current = text;
@@ -137,11 +137,10 @@ export const AttachmentDialog = ({ note }) => {
];
const filterAttachments = (type, _attachments) => {
const attachments = _attachments
? _attachments
: note
? db.attachments.ofNote(note?.id, "all")
: [...(db.attachments.all || [])];
const attachments =
_attachments || note
? db.attachments.ofNote(note.id, "all")
: [...(db.attachments.all || [])];
switch (type) {
case "all":

View File

@@ -26,15 +26,12 @@ import {
ToastEvent
} from "../../services/event-manager";
import { useUserStore } from "../../stores/use-user-store";
import { eCloseSheet, eOpenRecoveryKeyDialog } from "../../utils/events";
import { eCloseSheet } from "../../utils/events";
import DialogHeader from "../dialog/dialog-header";
import { Button } from "../ui/button";
import Input from "../ui/input";
import { Notice } from "../ui/notice";
import Seperator from "../ui/seperator";
import { Dialog } from "../dialog";
import BackupService from "../../services/backup";
import { sleep } from "../../utils/time";
export const ChangePassword = () => {
const passwordInputRef = useRef();
@@ -67,9 +64,6 @@ export const ChangePassword = () => {
}
setLoading(true);
try {
const result = await BackupService.run(false, "change-password-dialog");
if (!result) throw new Error("Failed to create backup");
await db.user.clearSessions();
await db.user.changePassword(oldPassword.current, password.current);
ToastEvent.show({
@@ -79,8 +73,6 @@ export const ChangePassword = () => {
});
setLoading(false);
eSendEvent(eCloseSheet);
await sleep(300);
eSendEvent(eOpenRecoveryKeyDialog);
} catch (e) {
setLoading(false);
ToastEvent.show({
@@ -100,7 +92,6 @@ export const ChangePassword = () => {
padding: 12
}}
>
<Dialog context="change-password-dialog" />
<DialogHeader
title="Change password"
paragraph="Enter your old and new passwords"
@@ -138,14 +129,7 @@ export const ChangePassword = () => {
/>
<Notice
text={`Changing password is an irreversible process. You will be logged out from all your devices. Please make sure you do not close the app while your password is changing and have good internet connection.`}
type="alert"
/>
<View style={{ height: 10 }} />
<Notice
text={`Once your password is changed, please make sure to save the new account recovery key.`}
text="Changing password is a non-undoable process. You will be logged out from all your devices. Please make sure you do not close the app while your password is changing and have good internet connection."
type="alert"
/>

View File

@@ -40,10 +40,7 @@ import { Button } from "../ui/button";
export const Dialog = ({ context = "global" }) => {
const { colors } = useThemeColors();
const [visible, setVisible] = useState(false);
const [checked, setChecked] = useState(false);
const values = useRef({
inputValue: undefined
});
const [inputValue, setInputValue] = useState(null);
const inputRef = useRef();
const [dialogInfo, setDialogInfo] = useState({
title: "",
@@ -79,8 +76,7 @@ export const Dialog = ({ context = "global" }) => {
if (dialogInfo.positivePress) {
inputRef.current?.blur();
let result = await dialogInfo.positivePress(
values.current.inputValue || dialogInfo.defaultValue,
checked
inputValue || dialogInfo.defaultValue
);
if (result === false) {
return;
@@ -95,16 +91,14 @@ export const Dialog = ({ context = "global" }) => {
if (!data.context) data.context = "global";
if (data.context !== context) return;
setDialogInfo(data);
setChecked(false);
values.current.inputValue = data.defaultValue;
setVisible(true);
setInputValue(data.defaultValue);
},
[context]
);
const hide = () => {
setChecked(false);
values.current.inputValue = undefined;
setInputValue(null);
setVisible(false);
};
@@ -112,6 +106,7 @@ export const Dialog = ({ context = "global" }) => {
if (dialogInfo.onClose) {
await dialogInfo.onClose();
}
hide();
};
@@ -164,7 +159,7 @@ export const Dialog = ({ context = "global" }) => {
fwdRef={inputRef}
autoCapitalize="none"
onChangeText={(value) => {
values.current.inputValue = value;
setInputValue(value);
}}
testID="input-value"
secureTextEntry={dialogInfo.secureTextEntry}
@@ -181,10 +176,10 @@ export const Dialog = ({ context = "global" }) => {
<>
<Button
onPress={() => {
setChecked(!checked);
setInputValue(!inputValue);
}}
icon={
checked
inputValue
? "check-circle-outline"
: "checkbox-blank-circle-outline"
}
@@ -192,10 +187,9 @@ export const Dialog = ({ context = "global" }) => {
justifyContent: "flex-start"
}}
height={35}
iconSize={20}
width="100%"
title={dialogInfo.check.info}
type={checked ? dialogInfo.check.type : "gray"}
type={inputValue ? dialogInfo.check.type : "gray"}
/>
</>
) : null}

View File

@@ -101,7 +101,7 @@ export const SectionHeader = React.memo(
alignItems: "center"
}}
>
{index === 0 ? (
{index === 0 && (
<>
<Button
onPress={() => {
@@ -109,6 +109,7 @@ export const SectionHeader = React.memo(
component: <Sort screen={screen} type={type} />
});
}}
hidden={screen === "Reminders"}
tooltipText="Change sorting of items in list"
fwdRef={sortRef}
title={groupBy}
@@ -159,7 +160,7 @@ export const SectionHeader = React.memo(
size={SIZE.lg - 2}
/>
</>
) : null}
)}
</View>
</View>
);

View File

@@ -17,19 +17,17 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { getFormattedDate } from "@notesnook/common";
import { EVENTS } from "@notesnook/core/dist/common";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { ActivityIndicator, Platform, View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import RNFetchBlob from "react-native-blob-util";
import DocumentPicker from "react-native-document-picker";
import DocumentPicker, {
DocumentPickerResponse
} from "react-native-document-picker";
import * as ScopedStorage from "react-native-scoped-storage";
import { unzip } from "react-native-zip-archive";
import { db } from "../../../common/database";
import storage from "../../../common/database/storage";
import { cacheDir, copyFileAsync } from "../../../common/filesystem/utils";
import {
ToastEvent,
eSubscribeEvent,
@@ -37,6 +35,7 @@ import {
} from "../../../services/event-manager";
import SettingsService from "../../../services/settings";
import { initialize } from "../../../stores";
import { useThemeColors } from "@notesnook/theme";
import { eCloseRestoreDialog, eOpenRestoreDialog } from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import { Dialog } from "../../dialog";
@@ -47,6 +46,9 @@ import { Button } from "../../ui/button";
import Seperator from "../../ui/seperator";
import SheetWrapper from "../../ui/sheet";
import Paragraph from "../../ui/typography/paragraph";
import { getFormattedDate } from "@notesnook/common";
import { unzip } from "react-native-zip-archive";
import { cacheDir, copyFileAsync } from "../../../common/filesystem/utils";
const RestoreDataSheet = () => {
const [visible, setVisible] = useState(false);
@@ -182,7 +184,7 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
title: "Encrypted backup",
input: true,
inputPlaceholder: "Password",
paragraph: "Please enter password of this backup file",
paragraph: "Please enter password of this backup file to restore it",
positiveText: "Restore",
secureTextEntry: true,
onClose: () => {
@@ -190,17 +192,10 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
resolve(undefined);
},
negativeText: "Cancel",
positivePress: async (password, isEncryptionKey) => {
resolve({
value: password,
isEncryptionKey
});
positivePress: async (password) => {
resolve(password);
resolved = true;
return true;
},
check: {
info: "Use encryption key",
type: "transparent"
}
});
});
@@ -240,8 +235,8 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
}
};
const restoreBackup = async (backup, password, key) => {
await db.backup.import(backup, password, key);
const restoreBackup = async (backup, password) => {
await db.backup.import(backup, password);
await db.initCollections();
initialize();
ToastEvent.show({
@@ -284,7 +279,6 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
}
let password;
let key;
console.log(`Found ${backupFiles?.length} files to restore from backup`);
for (const path of backupFiles) {
@@ -295,16 +289,10 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
if (parsed.encrypted && !password) {
console.log("Backup is encrypted...", "requesting password");
const { value, isEncryptionKey } = await withPassword();
if (isEncryptionKey) {
key = value;
} else {
password = value;
}
if (!password && !key) throw new Error("Failed to decrypt backup");
password = await withPassword();
if (!password) throw new Error("Failed to decrypt backup");
}
await db.backup.import(parsed, password, key);
await db.backup.import(parsed, password);
console.log("Imported", path);
}
// Remove files from cache
@@ -335,19 +323,10 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
async function restoreFromNNBackup(backup) {
try {
if (backup.data.iv && backup.data.salt) {
const { value, isEncryptionKey } = await withPassword();
let key;
let password;
if (isEncryptionKey) {
key = value;
} else {
password = value;
}
if (key || password) {
const password = await withPassword();
if (password) {
try {
await restoreBackup(backup, password, key);
await restoreBackup(backup, password);
close();
setRestoring(false);
} catch (e) {

View File

@@ -88,14 +88,10 @@ const Sort = ({ type, screen }) => {
? groupOptions.groupBy === "abc" ||
groupOptions.sortBy === "title"
? "A - Z"
: groupOptions.sortBy === "dueDate"
? "Earliest first"
: "Old - New"
: groupOptions.groupBy === "abc" ||
groupOptions.sortBy === "title"
? "Z - A"
: groupOptions.sortBy === "dueDate"
? "Latest first"
: "New - Old"
}
icon={
@@ -146,7 +142,6 @@ const Sort = ({ type, screen }) => {
/>
) : (
Object.keys(SORT).map((item) =>
(item === "dueDate" && screen !== "Reminders") ||
(item === "title" && groupOptions.groupBy !== "none") ||
((screen !== "Tags" || screen !== "Reminders") &&
item === "dateModified") ||

View File

@@ -481,7 +481,7 @@ export const useAppEvents = () => {
subscribeToIAPListeners();
} catch (e) {
DatabaseLogger.error(error);
DatabaseLogger.error(e);
ToastEvent.error(e, "An error occurred", "global");
}

View File

@@ -53,7 +53,6 @@ export type Settings = {
dateFormat: string;
timeFormat: string;
fontScale: number;
markdownShortcuts: boolean;
};
export type EditorProps = {

View File

@@ -157,9 +157,6 @@ export const useEditorEvents = (
const defaultFontFamily = useSettingStore(
(state) => state.settings.defaultFontFamily
);
const markdownShortcuts = useSettingStore(
(state) => state.settings.markdownShortcuts
);
const tools = useDragState((state) => state.data);
@@ -205,8 +202,7 @@ export const useEditorEvents = (
fontFamily: SettingsService.get().defaultFontFamily,
dateFormat: db.settings?.getDateFormat(),
timeFormat: db.settings?.getTimeFormat(),
fontScale,
markdownShortcuts
fontScale
});
}, [
fullscreen,
@@ -226,8 +222,7 @@ export const useEditorEvents = (
dateFormat,
timeFormat,
loading,
fontScale,
markdownShortcuts
fontScale
]);
const onBackPress = useCallback(async () => {

View File

@@ -584,13 +584,6 @@ export const settingsGroups: SettingSection[] = [
component: "title-format",
description: "Customize the formatting for new note title",
type: "component"
},
{
id: "toggle-markdown",
name: "Markdown shortcuts",
property: "markdownShortcuts",
description: "Toggle markdown in the editor",
type: "switch"
}
]
}

View File

@@ -213,12 +213,10 @@ async function run(progress, context) {
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
updateNextBackupTime();
let showBackupCompleteSheet =
progress && SettingsService.get().showBackupCompleteSheet;
let showBackupCompleteSheet = SettingsService.get().showBackupCompleteSheet;
if (context) return path;
await sleep(300);
if (showBackupCompleteSheet) {
presentBackupCompleteSheet(backupFilePath);
} else {
@@ -235,7 +233,7 @@ async function run(progress, context) {
return path;
} catch (e) {
await sleep(300);
progress && eSendEvent(eCloseSheet);
eSendEvent(eCloseSheet);
ToastEvent.error(e, "Backup failed!");
return null;
}

View File

@@ -262,7 +262,7 @@ const onEvent = async ({ type, detail }: Event) => {
if (status.isInternetReachable) {
try {
if (!globalThis["IS_MAIN_APP_RUNNING" as never]) {
await db.sync({ type: "send", force: false });
await db.sync(false, false);
} else {
console.log("main app running, skipping sync");
}
@@ -904,7 +904,7 @@ async function pinNote(id: string) {
const note = db.notes?.note(id as string) as any;
let text = await convertNoteToText(note as any, false);
if (!text) text = "";
const html = text.replace(/\n/g, "<br />");
let html = text.replace(/\n/g, "<br />");
Notifications.displayNotification({
title: note.title,
message: note.headline || text,

View File

@@ -79,11 +79,7 @@ const run = async (
try {
await BackgroundSync.doInBackground(async () => {
try {
await db.sync({
type: full ? "full" : "send",
force: forced,
lastSyncTime
});
await db.sync(full, forced, lastSyncTime);
} catch (e) {
error = e;
}

View File

@@ -32,10 +32,7 @@ export const useReminderStore = create<ReminderStore>((set) => ({
reminders: [],
setReminders: () => {
set({
reminders: groupReminders(
(db.reminders?.all as Reminder[]) || [],
db.settings?.getGroupOptions("reminders")
)
reminders: groupReminders((db.reminders?.all as Reminder[]) || [])
});
},
cleareReminders: () => set({ reminders: [] })

View File

@@ -74,7 +74,6 @@ export type Settings = {
colorScheme: "dark" | "light";
lighTheme: ThemeDefinition;
darkTheme: ThemeDefinition;
markdownShortcuts?: boolean;
};
type DimensionsType = {
@@ -155,8 +154,7 @@ export const defaultSettings: SettingStore["settings"] = {
defaultFontSize: 16,
colorScheme: "light",
lighTheme: ThemeLight,
darkTheme: ThemeDark,
markdownShortcuts: true
darkTheme: ThemeDark
};
export const useSettingStore = create<SettingStore>((set, get) => ({

View File

@@ -41,8 +41,7 @@ export const SORT = {
dateModified: "Date modified",
dateEdited: "Date edited",
dateCreated: "Date created",
title: "Title",
dueDate: "Due date"
title: "Title"
};
export const itemSkus = [

View File

@@ -36,7 +36,7 @@ function confirmDeleteAllNotes(items, type, context) {
}?`,
positiveText: "Delete",
negativeText: "Cancel",
positivePress: (_inputValue, value) => {
positivePress: (value) => {
setTimeout(() => {
resolve({ delete: true, deleteNotes: value });
});

View File

@@ -61,7 +61,10 @@ export const getGithubVersion = async () => {
if (!res?.ok) return null;
const data = (await res?.json()) as GithubRelease[];
const versions = data?.filter((tag) => tag.tag_name.endsWith("android"));
const versions = data?.filter(
(tag) =>
tag.tag_name.endsWith("android") && !tag.tag_name.endsWith("beta-android")
);
const latestVersion = versions[0];
const version = latestVersion.tag_name.replace("-android", "");
return {

View File

@@ -111,7 +111,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled true
versionCode 2084
versionCode 2076
versionName getNpmVersion()
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'

View File

@@ -997,7 +997,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2085;
CURRENT_PROJECT_VERSION = 2067;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1071,7 +1071,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.6.17;
MARKETING_VERSION = 2.6.15;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1102,7 +1102,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2085;
CURRENT_PROJECT_VERSION = 2067;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1176,7 +1176,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.6.17;
MARKETING_VERSION = 2.6.15;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = (
"$(inherited)",
@@ -1335,7 +1335,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2085;
CURRENT_PROJECT_VERSION = 2067;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1347,7 +1347,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.17;
MARKETING_VERSION = 2.6.15;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
@@ -1378,7 +1378,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2085;
CURRENT_PROJECT_VERSION = 2067;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1391,7 +1391,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.17;
MARKETING_VERSION = 2.6.15;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1421,7 +1421,7 @@
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2085;
CURRENT_PROJECT_VERSION = 2067;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1495,7 +1495,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.17;
MARKETING_VERSION = 2.6.15;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
@@ -1526,7 +1526,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2085;
CURRENT_PROJECT_VERSION = 2067;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1601,7 +1601,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.17;
MARKETING_VERSION = 2.6.15;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -1,6 +1,6 @@
{
"name": "@notesnook/mobile",
"version": "2.6.17",
"version": "2.6.16",
"private": true,
"license": "GPL-3.0-or-later",
"workspaces": [
@@ -45,4 +45,4 @@
"react-native": "0.72.0",
"react-native-iap": "12.11.0"
}
}
}

View File

@@ -295,7 +295,7 @@ const ShareView = ({ quicknote = false }) => {
try {
if (!globalThis["IS_MAIN_APP_RUNNING"]) {
await db.sync({ type: "send", force: false });
await db.sync(false, false);
} else {
console.log("main app running, skipping sync");
}

View File

@@ -1377,7 +1377,7 @@
},
"../web": {
"name": "@notesnook/web",
"version": "2.6.14",
"version": "2.6.15",
"license": "GPL-3.0-or-later",
"dependencies": {
"@aws-sdk/util-base64-browser": "^3.208.0",

View File

@@ -241,8 +241,8 @@ export class NoteContextMenuModel extends BaseProperties {
);
if (format === "html") {
return content
.replace(/(name="created-at" content=")(.+?)"/, '$1xxx"')
.replace(/(name="updated-at" content=")(.+?)"/, '$1xxx"');
.replace(/(name="created-on" content=")(.+?)"/, '$1xxx"')
.replace(/(name="last-edited-on" content=")(.+?)"/, '$1xxx"');
}
return content;
}

View File

@@ -8,8 +8,8 @@
content="This is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1"
/>
<title>Test 1 - Notesnook</title>
<meta name="created-at" content="xxx" />
<meta name="updated-at" content="xxx" />
<meta name="created-at" content="22-12-2023 11:33 AM" />
<meta name="updated-at" content="22-12-2023 11:33 AM" />

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/web",
"version": "2.6.16",
"version": "2.6.15",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/web",
"version": "2.6.16",
"version": "2.6.15",
"license": "GPL-3.0-or-later",
"dependencies": {
"@aws-sdk/util-base64-browser": "^3.208.0",

View File

@@ -1,7 +1,7 @@
{
"name": "@notesnook/web",
"description": "Your private note taking space",
"version": "2.6.16",
"version": "2.6.15",
"private": true,
"main": "./src/app.js",
"homepage": "https://notesnook.com/",

View File

@@ -381,6 +381,13 @@ function getDialogData(type: string) {
subtitle: "Please enter your vault password to continue.",
positiveButtonText: "Unlock"
};
case "ask_backup_password":
return {
title: "Encrypted backup",
subtitle:
"Please enter the password to decrypt and restore this backup.",
positiveButtonText: "Restore"
};
case "change_account_password":
return {
title: "Change account password",
@@ -456,17 +463,6 @@ export function showPasswordDialog(
));
}
export function showBackupPasswordDialog(
validate: (outputs: {
password?: string;
key?: string;
}) => boolean | Promise<boolean>
) {
return showDialog("BackupPasswordDialog", (Dialog, perform) => (
<Dialog onClose={() => perform(false)} validate={validate} />
));
}
export function showRecoveryKeyDialog() {
return showDialog("RecoveryKeyDialog", (Dialog, perform) => (
<Dialog onDone={() => perform(true)} />

View File

@@ -151,7 +151,7 @@ export async function exportNote(
: rawContent);
const exported = await note
.export(format === "pdf" ? "html" : format, content, !disableTemplate)
.export(format === "pdf" ? "html" : format, content, disableTemplate)
.catch((e: Error) => {
console.error(note.data, e);
showToast("error", `Failed to export note "${note.title}": ${e.message}`);

View File

@@ -18,7 +18,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {
showBackupPasswordDialog,
showFeatureDialog,
showPasswordDialog,
showReminderDialog
@@ -27,8 +26,7 @@ import Config from "../utils/config";
import { hashNavigate, getCurrentHash } from "../navigation";
import { db } from "./db";
import { sanitizeFilename } from "@notesnook/common";
import { useStore as useUserStore } from "../stores/user-store";
import { useStore as useSettingStore } from "../stores/setting-store";
import { store as userstore } from "../stores/user-store";
import { showToast } from "../utils/toast";
import { SUBSCRIPTION_STATUS } from "./constants";
import { readFile, showFilePicker } from "../utils/file-picker";
@@ -80,17 +78,8 @@ export async function introduceFeatures() {
export const DEFAULT_CONTEXT = { colors: [], tags: [], notebook: {} };
export async function createBackup() {
const { isLoggedIn } = useUserStore.getState();
const { encryptBackups, toggleEncryptBackups } = useSettingStore.getState();
if (!isLoggedIn && encryptBackups) toggleEncryptBackups();
const verified = encryptBackups || (await verifyAccount());
if (!verified) {
showToast("error", "Could not create a backup: user verification failed.");
return;
}
const encryptedBackups = isLoggedIn && encryptBackups;
const encryptBackups =
userstore.get().isLoggedIn && Config.get("encryptBackups", false);
const filename = sanitizeFilename(
`notesnook-backup-${getFormattedDate(Date.now())}`
@@ -112,7 +101,7 @@ export async function createBackup() {
await new ReadableStream({
start() {},
async pull(controller) {
for await (const file of db.backup!.export("web", encryptedBackups)) {
for await (const file of db.backup!.export("web", encryptBackups)) {
report({
text: `Saving chunk ${file.path}`
});
@@ -161,9 +150,9 @@ export async function restoreBackupFile(backupFile: File) {
const backup = JSON.parse(await readFile(backupFile));
if (backup.data.iv && backup.data.salt) {
await showBackupPasswordDialog(async ({ password, key }) => {
if (!password && !key) return false;
const error = await restoreWithProgress(backup, password, key);
await showPasswordDialog("ask_backup_password", async ({ password }) => {
if (!password) return false;
const error = await restoreWithProgress(backup, password);
return !error;
});
} else {
@@ -177,7 +166,6 @@ export async function restoreBackupFile(backupFile: File) {
type: "modal",
action: async (report) => {
let cachedPassword: string | undefined = undefined;
let cachedKey: string | undefined = undefined;
// const { read, totalFiles } = await Reader(backupFile);
const entries: ZipEntry[] = [];
let filesProcessed = 0;
@@ -195,20 +183,20 @@ export async function restoreBackupFile(backupFile: File) {
for (const entry of entries) {
const backup = JSON.parse(await entry.text());
if (backup.encrypted) {
if (!cachedPassword && !cachedKey) {
const result = await showBackupPasswordDialog(
async ({ password, key }) => {
if (!password && !key) return false;
await db.backup?.import(backup, password, key);
if (!cachedPassword) {
const result = await showPasswordDialog(
"ask_backup_password",
async ({ password }) => {
if (!password) return false;
await db.backup?.import(backup, password);
cachedPassword = password;
cachedKey = key;
return true;
}
);
if (!result) break;
} else await db.backup?.import(backup, cachedPassword, cachedKey);
} else await db.backup?.import(backup, cachedPassword);
} else {
await db.backup?.import(backup);
await db.backup?.import(backup, null);
}
report({
@@ -228,8 +216,7 @@ export async function restoreBackupFile(backupFile: File) {
async function restoreWithProgress(
backup: Record<string, unknown>,
password?: string,
key?: string
password?: string
) {
return await TaskManager.startTask<Error | void>({
title: "Restoring backup",
@@ -256,7 +243,7 @@ async function restoreWithProgress(
);
report({ text: `Restoring...` });
return restore(backup, password, key);
return restore(backup, password);
}
});
}
@@ -283,7 +270,7 @@ export function totalSubscriptionConsumed(user: User) {
export async function showUpgradeReminderDialogs() {
if (IS_TESTING) return;
const user = useUserStore.getState().user;
const user = userstore.get().user;
if (!user || !user.subscription || user.subscription?.expiry === 0) return;
const consumed = totalSubscriptionConsumed(user);
@@ -296,13 +283,9 @@ export async function showUpgradeReminderDialogs() {
}
}
async function restore(
backup: Record<string, unknown>,
password?: string,
key?: string
) {
async function restore(backup: Record<string, unknown>, password?: string) {
try {
await db.backup?.import(backup, password, key);
await db.backup?.import(backup, password);
showToast("success", "Backup restored!");
} catch (e) {
logger.error(e as Error, "Could not restore the backup");

View File

@@ -94,21 +94,24 @@ class Vault {
}
static lockNote(id) {
return db.vault
.add(id)
.then(() => true)
.catch(({ message }) => {
switch (message) {
case db.vault.ERRORS.noVault:
return Vault.createVault().then(() => Vault.lockNote(id));
case db.vault.ERRORS.vaultLocked:
return Vault.unlockVault().then(() => Vault.lockNote(id));
default:
showToast("error", message);
console.error(message);
return false;
}
});
return new Promise(function lock(resolve) {
db.vault
.add(id)
.then(resolve)
.catch(({ message }) => {
switch (message) {
case db.vault.ERRORS.noVault:
return Vault.createVault();
case db.vault.ERRORS.vaultLocked:
return Vault.unlockVault();
default:
showToast("error", message);
console.error(message);
return false;
}
})
.then((result) => result && lock(resolve));
});
}
static askPassword(action) {

View File

@@ -54,7 +54,7 @@ import { ScopedThemeProvider } from "../theme-provider";
import { Lightbox } from "../lightbox";
import { Allotment } from "allotment";
import { showToast } from "../../utils/toast";
import { debounce, getFormattedDate } from "@notesnook/common";
import { getFormattedDate } from "@notesnook/common";
const PDFPreview = React.lazy(() => import("../pdf-preview"));
@@ -445,38 +445,6 @@ function EditorChrome(
isMobile: false
};
const editorMargins = useStore((store) => store.editorMargins);
const editorContainerRef = useRef<HTMLElement>(null);
const editorScrollRef = useRef<HTMLElement>(null);
useEffect(() => {
if (!editorScrollRef.current) return;
function onResize(
entries: ResizeObserverEntry[],
_observer: ResizeObserver
) {
const editor = editorContainerRef.current?.querySelector(
".ProseMirror"
) as HTMLElement | undefined;
const parent = editorScrollRef.current?.getBoundingClientRect();
const child = editorContainerRef.current?.getBoundingClientRect();
if (!parent || !child || !editor || entries.length <= 0) return;
const CONTAINER_MARGIN = 30;
const negativeSpace = Math.abs(
parent.left - child.left - CONTAINER_MARGIN
);
editor.style.marginLeft = `-${negativeSpace}px`;
editor.style.marginRight = `-${negativeSpace}px`;
editor.style.paddingLeft = `${negativeSpace}px`;
editor.style.paddingRight = `${negativeSpace}px`;
}
const observer = new ResizeObserver(debounce(onResize, 500));
observer.observe(editorScrollRef.current);
return () => {
observer.disconnect();
};
}, []);
if (headless) return <>{children}</>;
@@ -501,12 +469,10 @@ function EditorChrome(
<Toolbar />
<FlexScrollContainer
scrollRef={editorScrollRef}
className="editorScroll"
style={{ display: "flex", flexDirection: "column", flex: 1 }}
>
<Flex
ref={editorContainerRef}
variant="columnFill"
className="editor"
sx={{
@@ -515,7 +481,7 @@ function EditorChrome(
width: "100%"
}}
pl={6}
pr={6}
pr={2}
onClick={onRequestFocus}
>
{!isMobile && (

View File

@@ -141,9 +141,6 @@ function TipTap(props: TipTapProps) {
);
const dateFormat = useSettingsStore((store) => store.dateFormat);
const timeFormat = useSettingsStore((store) => store.timeFormat);
const markdownShortcuts = useSettingsStore(
(store) => store.markdownShortcuts
);
const { toolbarConfig } = useToolbarConfig();
const { isSearching, toggleSearch } = useSearch();
@@ -183,7 +180,6 @@ function TipTap(props: TipTapProps) {
}
}
},
enableInputRules: markdownShortcuts,
downloadOptions,
doubleSpacedLines,
dateFormat,
@@ -294,14 +290,7 @@ function TipTap(props: TipTapProps) {
},
getAttachmentData: onGetAttachmentData
};
}, [
readonly,
nonce,
doubleSpacedLines,
dateFormat,
timeFormat,
markdownShortcuts
]);
}, [readonly, nonce, doubleSpacedLines, dateFormat, timeFormat]);
const editor = useTiptap(
tiptapOptions,

View File

@@ -56,27 +56,22 @@ type GroupingMenuOptions = {
isUngrouped: boolean;
};
const groupByMenu: (options: GroupingMenuOptions) => MenuItem | null = (
options
) =>
options.groupingKey === "reminders"
? null
: {
type: "button",
key: "groupBy",
title: "Group by",
icon: GroupBy.path,
menu: {
items: map(options, [
{ key: "none", title: "None" },
{ key: "default", title: "Default" },
{ key: "year", title: "Year" },
{ key: "month", title: "Month" },
{ key: "week", title: "Week" },
{ key: "abc", title: "A - Z" }
])
}
};
const groupByMenu: (options: GroupingMenuOptions) => MenuItem = (options) => ({
type: "button",
key: "groupBy",
title: "Group by",
icon: GroupBy.path,
menu: {
items: map(options, [
{ key: "none", title: "None" },
{ key: "default", title: "Default" },
{ key: "year", title: "Year" },
{ key: "month", title: "Month" },
{ key: "week", title: "Week" },
{ key: "abc", title: "A - Z" }
])
}
});
const orderByMenu: (options: GroupingMenuOptions) => MenuItem = (options) => ({
type: "button",
@@ -95,20 +90,12 @@ const orderByMenu: (options: GroupingMenuOptions) => MenuItem = (options) => ({
{
key: "asc",
title:
options.groupOptions.sortBy === "title"
? "A - Z"
: options.groupOptions.sortBy === "dueDate"
? "Earliest first"
: "Oldest - newest"
options.groupOptions.sortBy === "title" ? "A - Z" : "Oldest - newest"
},
{
key: "desc",
title:
options.groupOptions.sortBy === "title"
? "Z - A"
: options.groupOptions.sortBy === "dueDate"
? "Latest first"
: "Newest - oldest"
options.groupOptions.sortBy === "title" ? "Z - A" : "Newest - oldest"
}
])
}
@@ -143,11 +130,6 @@ const sortByMenu: (options: GroupingMenuOptions) => MenuItem = (options) => ({
title: "Date modified",
isHidden: options.groupingKey !== "tags"
},
{
key: "dueDate",
title: "Due date",
isHidden: options.groupingKey !== "reminders"
},
{
key: "title",
title: "Title",
@@ -349,26 +331,26 @@ function GroupHeader(props: GroupHeaderProps) {
isUngrouped: false,
refresh
};
const groupBy = groupByMenu({
...menuOptions,
parentKey: "groupBy"
});
const menuItems = [
orderByMenu({
...menuOptions,
parentKey: "sortDirection"
}),
sortByMenu({
...menuOptions,
parentKey: "sortBy"
})
];
if (groupBy) menuItems.push(groupBy);
openMenu(menuItems, {
title: groupBy ? "Group & sort" : "Sort"
});
openMenu(
[
orderByMenu({
...menuOptions,
parentKey: "sortDirection"
}),
sortByMenu({
...menuOptions,
parentKey: "sortBy"
}),
groupByMenu({
...menuOptions,
parentKey: "groupBy"
})
],
{
title: "Group & sort"
}
);
}}
/>
)}

View File

@@ -84,7 +84,7 @@ function IconTag(props: IconTagProps) {
>
<Icon
size={11}
color={highlight ? "accent" : "icon"}
// color={styles?.icon?.color || (highlight ? "primary" : "icon")}
sx={{ ...styles?.icon, flexShrink: 0 }}
/>
<Text

View File

@@ -85,7 +85,6 @@ function Reminder(props: ReminderProps) {
gap: 1
}}
>
{reminder.disabled ? null : <PriorityIcon size={14} />}
{reminder.disabled ? (
<IconTag icon={ReminderOff} text={"Disabled"} testId={"disabled"} />
) : (
@@ -96,6 +95,7 @@ function Reminder(props: ReminderProps) {
testId={"reminder-time"}
/>
)}
{reminder.disabled ? null : <PriorityIcon size={14} />}
{reminder.mode === "repeat" && reminder.recurringMode && (
<IconTag
icon={Refresh}

View File

@@ -54,7 +54,6 @@ type FlexScrollContainerProps = {
id?: string;
className?: string;
style?: React.CSSProperties;
scrollRef?: React.Ref<HTMLElement>;
} & MacScrollbarProps;
export function FlexScrollContainer({
@@ -62,13 +61,11 @@ export function FlexScrollContainer({
children,
style,
className,
scrollRef,
...restProps
}: PropsWithChildren<FlexScrollContainerProps>) {
return (
<MacScrollbar
{...restProps}
ref={scrollRef}
id={id}
className={className}
style={style}

View File

@@ -1,115 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useState } from "react";
import { Perform } from "../common/dialog-controller";
import Field from "../components/field";
import Dialog from "../components/dialog";
import { Box, Button } from "@theme-ui/components";
export type PromptDialogProps = {
onClose: Perform;
validate: (outputs: {
password?: string;
key?: string;
}) => boolean | Promise<boolean>;
};
export default function BackupPasswordDialog(props: PromptDialogProps) {
const { onClose, validate } = props;
const [error, setError] = useState<string>();
const [isLoading, setIsLoading] = useState(false);
const [isEncryptionKey, setIsEncryptionKey] = useState(false);
return (
<Dialog
isOpen={true}
title={"Encrypted backup"}
description={
"Please enter the password to decrypt and restore this backup."
}
onClose={() => onClose(false)}
positiveButton={{
form: "backupPasswordForm",
type: "submit",
loading: isLoading,
disabled: isLoading,
text: "Restore"
}}
negativeButton={{ text: "Cancel", onClick: () => onClose(false) }}
>
<Box
id="backupPasswordForm"
as="form"
onSubmit={async (e) => {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
try {
setIsLoading(true);
setError(undefined);
const key = formData.get("key") as string;
const password = formData.get("password") as string;
if (!key && !password) return;
if (await validate({ key, password })) {
onClose(true);
} else {
setError("Wrong password.");
}
} catch (e) {
setError((e as Error).message);
} finally {
setIsLoading(false);
}
}}
>
{isEncryptionKey ? (
<Field
required
autoFocus
data-test-id="dialog-key"
label="Encryption key"
type="password"
id="key"
name="key"
/>
) : (
<Field
required
autoFocus
data-test-id="dialog-password"
label="Password"
type="password"
autoComplete="current-password"
id="password"
name="password"
/>
)}
</Box>
<Button variant="anchor" onClick={() => setIsEncryptionKey((s) => !s)}>
{isEncryptionKey
? "Don't have encryption key? Use password."
: "Forgot password? Use encryption key."}
</Button>
</Dialog>
);
}

View File

@@ -91,34 +91,20 @@ const features: Record<FeatureKeys, Feature> = {
]
: [
{
title: "Sort reminders by due date",
title: "Daily trash cleanup interval",
subtitle:
"You can now sort your reminders by due date to quickly see which reminders are upcoming."
"You can now set the trash cleanup interval to Daily in addition to Weekly, Monthly & Yearly."
},
{
title: "Restore backups using encryption key",
title: "Organized bulk exports",
subtitle:
"If you forget your password but still have your recovery key, you can now use that to restore your backups."
"Bulk exports are now automatically sorted/organized into folders based on your Notebook/Topic organization."
},
{
title: "Disable markdown in editor",
title: "New domain for Monographs",
subtitle:
"If you find automatic markdown shortcuts annoying, you can now turn those off from Settings > Editor > Disable markdown shortcuts."
},
{
title: "Improved password reset",
subtitle:
"Resetting password should now be much more reliable and safer."
},
...(IS_DESKTOP_APP
? [
{
title: "Proxy settings",
subtitle:
"Desktop app now support setting a custom proxy url to route all network through it. Useful if you are behind a firewall and would like to bypass it."
}
]
: [])
"Monographs will now be published to monogr.ph instead of monograph.notesnook.com. Don't worry, all your published notes will automatically redirect."
}
],
cta: {
title: "Got it",

View File

@@ -52,9 +52,6 @@ const MigrationDialog = React.lazy(() => import("./migration-dialog"));
const EmailChangeDialog = React.lazy(() => import("./email-change-dialog"));
const AddTagsDialog = React.lazy(() => import("./add-tags-dialog"));
const ThemeDetailsDialog = React.lazy(() => import("./theme-details-dialog"));
const BackupPasswordDialog = React.lazy(
() => import("./backup-password-dialog")
);
export const Dialogs = {
AddNotebookDialog,
@@ -82,6 +79,5 @@ export const Dialogs = {
EmailChangeDialog,
AddTagsDialog,
SettingsDialog,
ThemeDetailsDialog,
BackupPasswordDialog
ThemeDetailsDialog
};

View File

@@ -63,11 +63,7 @@ function PasswordDialog(props) {
loading: isLoading,
disabled: isLoading
}}
negativeButton={{
text: "Cancel",
onClick: () => props.onClose(false),
disabled: isLoading
}}
negativeButton={{ text: "Cancel", onClick: () => props.onClose(false) }}
>
<Box
id="passwordForm"

View File

@@ -19,12 +19,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { SettingsGroup } from "./types";
import { useStore as useUserStore } from "../../stores/user-store";
import { createBackup, verifyAccount } from "../../common";
import { verifyAccount } from "../../common";
import {
show2FARecoveryCodesDialog,
showMultifactorDialog,
showPasswordDialog,
showRecoveryKeyDialog
showPasswordDialog
} from "../../common/dialog-controller";
import { db } from "../../common/db";
import { showToast } from "../../utils/toast";
@@ -46,22 +45,19 @@ export const AuthenticationSettings: SettingsGroup[] = [
title: "Change password",
variant: "secondary",
action: async () => {
await createBackup();
const result = await showPasswordDialog(
"change_account_password",
async (data) => {
await db.user?.clearSessions();
return (
(await db.user?.changePassword(
db.user?.changePassword(
data.oldPassword,
data.newPassword
)) || false
) || false
);
}
);
if (result) {
showToast("success", "Account password changed!");
await showRecoveryKeyDialog();
}
if (result) showToast("success", "Account password changed!");
}
}
]

View File

@@ -43,7 +43,17 @@ export const BackupExportSettings: SettingsGroup[] = [
{
type: "button",
title: "Create backup",
action: createBackup,
action: async () => {
if (
!useUserStore.getState().isLoggedIn &&
useSettingStore.getState().encryptBackups
)
useSettingStore.getState().toggleEncryptBackups();
const verified =
useSettingStore.getState().encryptBackups ||
(await verifyAccount());
if (verified) await createBackup();
},
variant: "secondary"
}
]

View File

@@ -1,53 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Button, Text } from "@theme-ui/components";
import { FlexScrollContainer } from "../../../components/scroll-container";
import { useSpellChecker } from "../../../hooks/use-spell-checker";
export function DictionaryWords() {
const words = useSpellChecker((store) => store.words);
const deleteWord = useSpellChecker((store) => store.deleteWord);
return (
<>
<FlexScrollContainer
suppressAutoHide
style={{
maxHeight: 400,
display: "flex",
flexDirection: "column"
}}
>
<Text variant="body" sx={{ my: 1 }}>
You have {words.length} custom dictionary words.
</Text>
{words.map((word) => (
<Button
variant="menuitem"
sx={{ textAlign: "left", p: 1 }}
onClick={() => deleteWord(word)}
>
{word}
</Button>
))}
</FlexScrollContainer>
</>
);
}

View File

@@ -29,7 +29,6 @@ import { useSpellChecker } from "../../hooks/use-spell-checker";
import { SpellCheckerLanguages } from "./components/spell-checker-languages";
import { CustomizeToolbar } from "./components/customize-toolbar";
import { DictionaryWords } from "./components/dictionary-words";
export const EditorSettings: SettingsGroup[] = [
{
@@ -101,7 +100,7 @@ symbols (e.g. 202305261253)`,
key: "double-spacing",
title: "Double spaced paragraphs",
description:
"Use double spacing between paragraphs when you press Enter in the editor.",
"Use double spacing between paragraphs and when you press Enter in the editor.",
onStateChange: (listener) =>
useSettingStore.subscribe((c) => c.doubleSpacedParagraphs, listener),
components: [
@@ -112,27 +111,6 @@ symbols (e.g. 202305261253)`,
useSettingStore.getState().toggleDoubleSpacedParagraphs()
}
]
},
{
key: "markdown-shortcuts",
title: "Markdown shortcuts",
description: `Markdown shortcuts are triggered whenever you input a specific character combination.
For example:
1. Typing '/date' adds the current Date
2. Wrapping something in '**' turns it into bold text
3. Typing '1.' automatically creates a numbered list.
4. etc.`,
onStateChange: (listener) =>
useSettingStore.subscribe((c) => c.markdownShortcuts, listener),
components: [
{
type: "toggle",
isToggled: () => useSettingStore.getState().markdownShortcuts,
toggle: () => useSettingStore.getState().toggleMarkdownShortcuts()
}
]
}
]
},
@@ -171,16 +149,6 @@ For example:
component: SpellCheckerLanguages
}
]
},
{
key: "custom-dictionay-words",
title: "Custom dictionary words",
components: [
{
type: "custom",
component: DictionaryWords
}
]
}
]
},

View File

@@ -36,8 +36,7 @@ import {
Privacy,
Pro,
ShieldLock,
Sync,
Proxy
Sync
} from "../../components/icons";
import { Perform } from "../../common/dialog-controller";
import NavigationItem from "../../components/navigation-menu/navigation-item";
@@ -66,6 +65,7 @@ import {
import { AppearanceSettings } from "./appearance-settings";
import { debounce } from "@notesnook/common";
import { SubscriptionSettings } from "./subscription-settings";
import { alpha } from "@theme-ui/color";
import { ScopedThemeProvider } from "../../components/theme-provider";
type SettingsDialogProps = { onClose: Perform };

View File

@@ -115,7 +115,7 @@ What data is collected & when?`,
key: "custom-cors",
title: "Custom CORS proxy",
description:
"CORS proxy is required to directly download images from within the Notesnook app. It allows Notesnook to bypass browser restrictions by using a proxy. You can set a custom self-hosted proxy URL to increase your privacy.",
"CORS proxy is required to directly download images from within the Notesnook app. It allows Notesnook to bypass browser restrictions by using a proxy. You can set a custom self-hosted proxy URL to increase your privacy",
onStateChange: (listener) =>
useSettingStore.subscribe((s) => s.telemetry, listener),
components: [
@@ -144,30 +144,6 @@ What data is collected & when?`,
variant: "secondary"
}
]
},
{
key: "proxy-config",
title: "Proxy",
description: `Setup an HTTP/HTTPS/SOCKS proxy.
For example:
http://foobar:80
socks4://proxy.example.com
http://username:password@foobar:80
To remove the proxy, simply erase everything in the input.`,
onStateChange: (listener) =>
useSettingStore.subscribe((c) => c.proxyRules, listener),
components: [
{
type: "input",
inputType: "text",
defaultValue: () => useSettingStore.getState().proxyRules || "",
onChange: (value) => {
useSettingStore.getState().setProxyRules(value);
}
}
]
}
]
}

View File

@@ -27,7 +27,6 @@ class SpellCheckerStore extends BaseStore<SpellCheckerStore> {
languages: Language[] = [];
enabled = true;
enabledLanguages: Language[] = [];
words: string[] = [];
toggleSpellChecker = async () => {
const enabled = this.get().enabled;
@@ -45,19 +44,14 @@ class SpellCheckerStore extends BaseStore<SpellCheckerStore> {
};
refresh = async () => {
console.log("SPELL CHECK", await desktop?.spellChecker.isEnabled.query());
this.set({
enabledLanguages:
(await desktop?.spellChecker.enabledLanguages.query()) || [],
languages: (await desktop?.spellChecker.languages.query()) || [],
enabled: await desktop?.spellChecker.isEnabled.query(),
words: (await desktop?.spellChecker.words.query()) || []
enabled: await desktop?.spellChecker.isEnabled.query()
});
};
deleteWord = async (word: string) => {
await desktop?.spellChecker.deleteWord.mutate(word);
await this.get().refresh();
};
}
const [useSpellChecker] = createStore(SpellCheckerStore);

View File

@@ -298,11 +298,7 @@ class AppStore extends BaseStore {
this.updateSyncStatus("syncing");
try {
const result = await db.sync({
type: full ? "full" : "send",
force,
serverLastSynced: lastSynced
});
const result = await db.sync(full, force, lastSynced);
if (!result) return this.updateSyncStatus("failed");
this.updateSyncStatus("completed", true);
@@ -311,6 +307,7 @@ class AppStore extends BaseStore {
if (pendingSync) {
logger.info("Running pending sync", pendingSync);
pendingSync = false;
await this.get().sync(pendingSync.full, false);
}
} catch (err) {

View File

@@ -290,14 +290,12 @@ class EditorStore extends BaseStore {
state: SESSION_STATES.new
};
});
setTimeout(() => {
noteStore.setSelectedNote(0);
this.toggleProperties(false);
if (shouldNavigate)
hashNavigate(`/notes/create`, { replace: true, addNonce: true });
appStore.setIsEditorOpen(false);
setDocumentTitle();
}, 100);
noteStore.setSelectedNote(0);
this.toggleProperties(false);
if (shouldNavigate)
hashNavigate(`/notes/create`, { replace: true, addNonce: true });
setTimeout(() => appStore.setIsEditorOpen(false), 100);
setDocumentTitle();
};
setTitle = (noteId, title) => {

View File

@@ -116,7 +116,7 @@ class NoteStore extends BaseStore {
unlock = async (id) => {
return await Vault.unlockNote(id).then(async (res) => {
if (editorStore.get().session.id === id)
await editorStore.clearSession(true);
await editorStore.openSession(id);
this.refreshItem(id);
return res;
});

View File

@@ -37,13 +37,7 @@ class ReminderStore extends BaseStore {
refresh = (reset = true) => {
const reminders = db.reminders.all;
this.set(
(state) =>
(state.reminders = groupReminders(
reminders,
db.settings.getGroupOptions("reminders")
))
);
this.set((state) => (state.reminders = groupReminders(reminders)));
if (reset) {
resetReminders(reminders);
notestore.refresh();

View File

@@ -38,7 +38,6 @@ class SettingStore extends BaseStore {
PATHS.backupsDirectory
);
doubleSpacedParagraphs = Config.get("doubleSpacedLines", true);
markdownShortcuts = Config.get("markdownShortcuts", true);
notificationsSettings = Config.get("notifications", { reminder: true });
zoomFactor = 1.0;
@@ -59,10 +58,6 @@ class SettingStore extends BaseStore {
desktopIntegrationSettings = undefined;
autoUpdates = true;
isFlatpak = false;
/**
* @type {string|undefined}
*/
proxyRules = undefined;
refresh = async () => {
this.set({
@@ -75,8 +70,7 @@ class SettingStore extends BaseStore {
await desktop?.integration.desktopIntegration.query(),
privacyMode: await desktop?.integration.privacyMode.query(),
zoomFactor: await desktop?.integration.zoomFactor.query(),
autoUpdates: await desktop?.updater.autoUpdates.query(),
proxyRules: await desktop.integration.proxyRules.query()
autoUpdates: await desktop?.updater.autoUpdates.query()
});
};
@@ -105,11 +99,6 @@ class SettingStore extends BaseStore {
this.set({ zoomFactor });
};
setProxyRules = async (proxyRules) => {
await desktop?.integration.setProxyRules.mutate(proxyRules);
this.set({ proxyRules });
};
setEncryptBackups = (encryptBackups) => {
this.set({ encryptBackups });
Config.set("encryptBackups", encryptBackups);
@@ -167,14 +156,6 @@ class SettingStore extends BaseStore {
Config.set("doubleSpacedLines", !doubleSpacedParagraphs);
};
toggleMarkdownShortcuts = (toggleState) => {
this.set((state) => {
state.markdownShortcuts =
toggleState !== undefined ? toggleState : !state.markdownShortcuts;
Config.set("markdownShortcuts", state.markdownShortcuts);
});
};
toggleTelemetry = () => {
const telemetry = this.get().telemetry;
this.set({ telemetry: !telemetry });

View File

@@ -26,7 +26,6 @@ import { LogMessage } from "@notesnook/logger";
import { DatabasePersistence, NNStorage } from "../interfaces/storage";
import { ZipFile, createZipStream } from "./streams/zip-stream";
import { createWriteStream } from "./stream-saver";
import { sanitizeFilename } from "@notesnook/common";
let logger: typeof _logger;
async function initalizeLogger(persistence: DatabasePersistence = "db") {
@@ -37,23 +36,19 @@ async function initalizeLogger(persistence: DatabasePersistence = "db") {
async function downloadLogs() {
if (!logManager) return;
const allLogs = await logManager.get();
let i = 0;
const textEncoder = new TextEncoder();
await new ReadableStream<ZipFile>({
pull(controller) {
const log = allLogs[i++];
if (!log) {
controller.close();
return;
for (const log of allLogs) {
controller.enqueue({
path: log.key,
data: textEncoder.encode(
(log.logs as LogMessage[])
.map((line) => JSON.stringify(line))
.join("\n")
)
});
}
controller.enqueue({
path: sanitizeFilename(log.key, { replacement: "-" }),
data: textEncoder.encode(
(log.logs as LogMessage[])
.map((line) => JSON.stringify(line))
.join("\n")
)
});
}
})
.pipeThrough(createZipStream())

View File

@@ -358,7 +358,7 @@ function RecoveryKeyMethod(props: BaseRecoveryComponentProps<"method:key">) {
const user = await db.user?.getUser();
if (!user) throw new Error("User not authenticated");
await db.storage?.write(`_uk_@${user.email}@_k`, form.recoveryKey);
await db.sync({ type: "fetch", force: true });
await db.sync(true, true);
navigate("backup");
}}
>
@@ -507,7 +507,7 @@ function NewPassword(props: BaseRecoveryComponentProps<"new">) {
if (formData?.backupFile) {
await restoreBackupFile(formData?.backupFile);
await db.sync({ type: "full", force: true });
await db.sync(true, true);
}
navigate("final");

View File

@@ -1,7 +0,0 @@
- Restore backups using encryption key in addition to password
- Toggle markdown shortcuts in the editor
- Improved user experience and reliability when changing account password
- Fixed doing a search in attachment manager causes a crash
- Bug fixes and minor improvements
Thank you for using Notesnook!

View File

@@ -223,7 +223,7 @@ test(
const handler = vitest.fn();
deviceB.eventManager.subscribe(EVENTS.syncProgress, handler);
await deviceB.sync({ type: "full" });
await deviceB.sync(true);
expect(handler).not.toHaveBeenCalled();
@@ -415,7 +415,7 @@ async function initializeDevice(id, capabilities = []) {
await device.user.resetUser(false);
await device.sync({ type: "full" });
await device.sync(true, false);
console.timeEnd(`Init ${id}`);
return device;
@@ -448,13 +448,7 @@ function syncAndWait(deviceA, deviceB, force = false) {
(full, force, lastSynced) => {
console.log("sync requested by device A", full, force, lastSynced);
ref2.unsubscribe();
deviceB
.sync({
type: full ? "full" : "send",
force,
serverLastSynced: lastSynced
})
.catch(reject);
deviceB.sync(full, force, lastSynced).catch(reject);
}
);
@@ -472,6 +466,6 @@ function syncAndWait(deviceA, deviceB, force = false) {
deviceB.syncer.sync.syncing
);
deviceA.sync({ type: "full", force }).catch(reject);
deviceA.sync(true, force).catch(reject);
});
}

View File

@@ -17,9 +17,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { authenticator } from "otplib";
import { databaseTest } from "../__tests__/utils";
import { login, USER } from "./utils";
import { login, user } from "./utils";
import { test, expect } from "vitest";
// test("signup user and check for token", async () => {
@@ -51,55 +50,7 @@ test(
await login(db);
const userData = await db.user.getUser();
expect(userData.email).toBe(USER.email);
}),
30000
);
test(
"login user after entering invalid mfa once",
() =>
databaseTest().then(async (db) => {
await db.user.authenticateEmail(USER.email);
await expect(
db.user.authenticateMultiFactorCode(201022, "app")
).rejects.toThrowError(
/Please provide a valid multi-factor authentication/
);
const token = authenticator.generate(USER.totpSecret);
await db.user.authenticateMultiFactorCode(token, "app");
await expect(
db.user.authenticatePassword(USER.email, USER.password, USER.hashed)
).resolves.toBeFalsy();
await expect(db.user.tokenManager.getToken()).resolves.toBeDefined();
}),
30000
);
test(
"login user after entering incorrect password once",
() =>
databaseTest().then(async (db) => {
await db.user.authenticateEmail(USER.email);
const token = authenticator.generate(USER.totpSecret);
await db.user.authenticateMultiFactorCode(token, "app");
await expect(
db.user.authenticatePassword(USER.email, "wrong_password")
).rejects.toThrowError(/Password is incorrect./);
await db.user.authenticatePassword(
USER.email,
USER.password,
USER.hashed
);
await expect(db.user.tokenManager.getToken()).resolves.toBeDefined();
expect(userData.email).toBe(user.email);
}),
30000
);

View File

@@ -19,14 +19,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { authenticator } from "otplib";
export const USER = {
export const user = {
email: process.env.USER_EMAIL,
password: process.env.USER_PASSWORD,
hashed: process.env.USER_HASHED_PASSWORD,
totpSecret: process.env.USER_TOTP_SECRET
};
export async function login(db, user = USER) {
export async function login(db) {
await db.user.authenticateEmail(user.email);
const token = authenticator.generate(user.totpSecret);

View File

@@ -7,13 +7,13 @@
"": {
"name": "@notesnook/core",
"version": "7.4.1",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
"@microsoft/signalr": "^8.0.0",
"@microsoft/signalr": "^7.0.10",
"@microsoft/signalr-protocol-msgpack": "^7.0.10",
"@notesnook/logger": "file:../logger",
"@readme/data-urls": "^3.0.0",
"@streetwriters/showdown": "^3.0.5-alpha",
"@streetwriters/showdown": "^3.0.4-alpha",
"async-mutex": "^0.3.2",
"dayjs": "1.11.9",
"entities": "^4.3.1",
@@ -494,9 +494,9 @@
}
},
"node_modules/@microsoft/signalr": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-8.0.0.tgz",
"integrity": "sha512-K/wS/VmzRWePCGqGh8MU8OWbS1Zvu7DG7LSJS62fBB8rJUXwwj4axQtqrAAwKGUZHQF6CuteuQR9xMsVpM2JNA==",
"version": "7.0.10",
"resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-7.0.10.tgz",
"integrity": "sha512-tOEn32i5EatAx4sZbzmLgcBc2VbKQmx+F4rI2/Ioq2MnBaYcFxbDzOoZgISIS4IR9H1ij/sKoU8zQOAFC8GJKg==",
"dependencies": {
"abort-controller": "^3.0.0",
"eventsource": "^2.0.2",
@@ -505,6 +505,23 @@
"ws": "^7.4.5"
}
},
"node_modules/@microsoft/signalr-protocol-msgpack": {
"version": "7.0.10",
"resolved": "https://registry.npmjs.org/@microsoft/signalr-protocol-msgpack/-/signalr-protocol-msgpack-7.0.10.tgz",
"integrity": "sha512-iZacNFQ3+BT3wZjFN2qcuQQJWK0ZlyCek4plWw1QrFqqOMBYEwPY4BCbLcwNZcTiOpTK65es1CCf3Yxb6lwlVQ==",
"dependencies": {
"@microsoft/signalr": ">=7.0.10",
"@msgpack/msgpack": "^2.7.0"
}
},
"node_modules/@msgpack/msgpack": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz",
"integrity": "sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==",
"engines": {
"node": ">= 10"
}
},
"node_modules/@notesnook/crypto": {
"resolved": "../crypto",
"link": true
@@ -581,9 +598,9 @@
}
},
"node_modules/@streetwriters/showdown": {
"version": "3.0.5-alpha",
"resolved": "https://registry.npmjs.org/@streetwriters/showdown/-/showdown-3.0.5-alpha.tgz",
"integrity": "sha512-jD9JFhxLDx6XeyZOLVB0zWtwGduwNiFpxn5rxu6ThyKyWGnu1O+L1w04WLC1L56pyEhypr3Tsk24dzo2Se/50g==",
"version": "3.0.4-alpha",
"resolved": "https://registry.npmjs.org/@streetwriters/showdown/-/showdown-3.0.4-alpha.tgz",
"integrity": "sha512-R2UJzMXyJz312RxekXGQIQvLsdZ0eSa4Z7dRXzf/fL2XsPPtyEzkZce0RoZn/mmKfDym5XKnjcVxnF3hXgjVaw==",
"bin": {
"showdown": "bin/showdown.js"
},
@@ -3165,9 +3182,9 @@
}
},
"@microsoft/signalr": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-8.0.0.tgz",
"integrity": "sha512-K/wS/VmzRWePCGqGh8MU8OWbS1Zvu7DG7LSJS62fBB8rJUXwwj4axQtqrAAwKGUZHQF6CuteuQR9xMsVpM2JNA==",
"version": "7.0.10",
"resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-7.0.10.tgz",
"integrity": "sha512-tOEn32i5EatAx4sZbzmLgcBc2VbKQmx+F4rI2/Ioq2MnBaYcFxbDzOoZgISIS4IR9H1ij/sKoU8zQOAFC8GJKg==",
"requires": {
"abort-controller": "^3.0.0",
"eventsource": "^2.0.2",
@@ -3176,6 +3193,20 @@
"ws": "^7.4.5"
}
},
"@microsoft/signalr-protocol-msgpack": {
"version": "7.0.10",
"resolved": "https://registry.npmjs.org/@microsoft/signalr-protocol-msgpack/-/signalr-protocol-msgpack-7.0.10.tgz",
"integrity": "sha512-iZacNFQ3+BT3wZjFN2qcuQQJWK0ZlyCek4plWw1QrFqqOMBYEwPY4BCbLcwNZcTiOpTK65es1CCf3Yxb6lwlVQ==",
"requires": {
"@microsoft/signalr": ">=7.0.10",
"@msgpack/msgpack": "^2.7.0"
}
},
"@msgpack/msgpack": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz",
"integrity": "sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ=="
},
"@notesnook/crypto": {
"version": "file:../crypto",
"requires": {
@@ -3247,9 +3278,9 @@
}
},
"@streetwriters/showdown": {
"version": "3.0.5-alpha",
"resolved": "https://registry.npmjs.org/@streetwriters/showdown/-/showdown-3.0.5-alpha.tgz",
"integrity": "sha512-jD9JFhxLDx6XeyZOLVB0zWtwGduwNiFpxn5rxu6ThyKyWGnu1O+L1w04WLC1L56pyEhypr3Tsk24dzo2Se/50g=="
"version": "3.0.4-alpha",
"resolved": "https://registry.npmjs.org/@streetwriters/showdown/-/showdown-3.0.4-alpha.tgz",
"integrity": "sha512-R2UJzMXyJz312RxekXGQIQvLsdZ0eSa4Z7dRXzf/fL2XsPPtyEzkZce0RoZn/mmKfDym5XKnjcVxnF3hXgjVaw=="
},
"@tootallnate/once": {
"version": "2.0.0",
@@ -4158,8 +4189,7 @@
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"dev": true,
"requires": {}
"dev": true
}
}
},
@@ -4944,8 +4974,7 @@
"ws": {
"version": "7.5.9",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
"requires": {}
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q=="
},
"xml-name-validator": {
"version": "4.0.0",

View File

@@ -35,14 +35,14 @@
"build": "tsc",
"watch": "tsc --watch",
"test:e2e": "cross-env IS_E2E=true vitest run",
"test": "vitest run",
"postinstall": "patch-package"
"test": "vitest run"
},
"dependencies": {
"@microsoft/signalr": "^8.0.0",
"@microsoft/signalr": "^7.0.10",
"@microsoft/signalr-protocol-msgpack": "^7.0.10",
"@notesnook/logger": "file:../logger",
"@readme/data-urls": "^3.0.0",
"@streetwriters/showdown": "^3.0.5-alpha",
"@streetwriters/showdown": "^3.0.4-alpha",
"async-mutex": "^0.3.2",
"dayjs": "1.11.9",
"entities": "^4.3.1",

View File

@@ -1,52 +0,0 @@
diff --git a/node_modules/@microsoft/signalr/dist/browser/signalr.js b/node_modules/@microsoft/signalr/dist/browser/signalr.js
index 86f7968..9bc9745 100644
--- a/node_modules/@microsoft/signalr/dist/browser/signalr.js
+++ b/node_modules/@microsoft/signalr/dist/browser/signalr.js
@@ -347,7 +347,7 @@ class Platform {
// Node apps shouldn't have a window object, but WebWorkers don't either
// so we need to check for both WebWorker and window
static get isNode() {
- return typeof process !== "undefined" && process.release && process.release.name === "node";
+ return typeof process !== "undefined" && process.release && process.release.name === "node" && process.type !== "renderer";
}
}
/** @private */
diff --git a/node_modules/@microsoft/signalr/dist/cjs/Utils.js b/node_modules/@microsoft/signalr/dist/cjs/Utils.js
index 2db55f7..228d318 100644
--- a/node_modules/@microsoft/signalr/dist/cjs/Utils.js
+++ b/node_modules/@microsoft/signalr/dist/cjs/Utils.js
@@ -45,7 +45,7 @@ class Platform {
// Node apps shouldn't have a window object, but WebWorkers don't either
// so we need to check for both WebWorker and window
static get isNode() {
- return typeof process !== "undefined" && process.release && process.release.name === "node";
+ return typeof process !== "undefined" && process.release && process.release.name === "node" && process.type !== "renderer";
}
}
exports.Platform = Platform;
diff --git a/node_modules/@microsoft/signalr/dist/esm/Utils.js b/node_modules/@microsoft/signalr/dist/esm/Utils.js
index a8962ee..2fd2558 100644
--- a/node_modules/@microsoft/signalr/dist/esm/Utils.js
+++ b/node_modules/@microsoft/signalr/dist/esm/Utils.js
@@ -41,7 +41,7 @@ export class Platform {
// Node apps shouldn't have a window object, but WebWorkers don't either
// so we need to check for both WebWorker and window
static get isNode() {
- return typeof process !== "undefined" && process.release && process.release.name === "node";
+ return typeof process !== "undefined" && process.release && process.release.name === "node" && process.type !== "renderer";
}
}
/** @private */
diff --git a/node_modules/@microsoft/signalr/dist/webworker/signalr.js b/node_modules/@microsoft/signalr/dist/webworker/signalr.js
index 86f7968..9bc9745 100644
--- a/node_modules/@microsoft/signalr/dist/webworker/signalr.js
+++ b/node_modules/@microsoft/signalr/dist/webworker/signalr.js
@@ -347,7 +347,7 @@ class Platform {
// Node apps shouldn't have a window object, but WebWorkers don't either
// so we need to check for both WebWorker and window
static get isNode() {
- return typeof process !== "undefined" && process.release && process.release.name === "node";
+ return typeof process !== "undefined" && process.release && process.release.name === "node" && process.type !== "renderer";
}
}
/** @private */

View File

@@ -33,6 +33,7 @@ import Constants from "../utils/constants";
import { EV, EVENTS } from "../common";
import Settings from "./settings";
import Migrations from "./migrations";
import Outbox from "./outbox";
import UserManager from "./user-manager";
import http from "../utils/http";
import Monographs from "./monographs";
@@ -89,6 +90,7 @@ class Database {
this.backup = new Backup(this);
this.settings = new Settings(this);
this.migrations = new Migrations(this);
this.outbox = new Outbox(this);
this.monographs = new Monographs(this);
this.offers = new Offers();
this.debug = new Debug();
@@ -125,6 +127,7 @@ class Database {
await this.initCollections();
await this.outbox.init();
await this.migrations.init();
this.isInitialized = true;
if (this.migrations.required()) {
@@ -272,17 +275,8 @@ class Database {
return (await this.storage.read("lastSynced")) || 0;
}
/**
*
* @param {{
* type: "full" | "fetch" | "send";
* force?: boolean;
* serverLastSynced?: number;
* }} options
* @returns
*/
sync(options) {
return this.syncer.start(options);
sync(full = true, force = false, lastSynced = null) {
return this.syncer.start(full, force, lastSynced);
}
/**

View File

@@ -0,0 +1,57 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class Outbox {
/**
*
* @param {import("./index").default} db
*/
constructor(db) {
this._db = db;
this.outbox = {};
}
async init() {
this.outbox = (await this._db.storage.read("outbox")) || {};
for (var id in this.outbox) {
const data = this.outbox[id];
switch (id) {
case "reset_password":
case "change_password":
if (await this._db.user._updatePassword(id, data))
await this.delete(id);
break;
}
}
}
async add(id, data, action) {
this.outbox[id] = data;
await this._db.storage.write("outbox", this.outbox);
await action();
await this.delete(id);
}
delete(id) {
delete this.outbox[id];
return this._db.storage.write("outbox", this.outbox);
}
}
export default Outbox;

View File

@@ -99,10 +99,8 @@ class Settings {
? "dateDeleted"
: key === "tags"
? "dateCreated"
: key === "reminders"
? "dueDate"
: "dateEdited",
sortDirection: key === "reminders" ? "asc" : "desc"
sortDirection: "desc"
}
);
}

View File

@@ -31,6 +31,7 @@ import * as signalr from "@microsoft/signalr";
import Merger from "./merger";
import Conflicts from "./conflicts";
import { AutoSync } from "./auto-sync";
import { MessagePackHubProtocol } from "@microsoft/signalr-protocol-msgpack";
import { logger } from "../../logger";
import { Mutex } from "async-mutex";
import { migrateItem } from "../../migrations";
@@ -52,10 +53,10 @@ export default class SyncManager {
this._db = db;
}
async start(options) {
async start(full, force, serverLastSynced) {
try {
await this.sync.autoSync.start();
await this.sync.start(options);
await this.sync.start(full, force, serverLastSynced);
return true;
} catch (e) {
var isHubException = e.message.includes("HubException:");
@@ -143,7 +144,7 @@ class Sync {
}
}
})
.withHubProtocol(new signalr.JsonHubProtocol())
.withHubProtocol(new MessagePackHubProtocol({ ignoreUndefined: true }))
.build();
this.connection.serverTimeoutInMilliseconds = 60 * 1000 * 5;
EV.subscribe(EVENTS.userLoggedOut, async () => {
@@ -175,20 +176,18 @@ class Sync {
/**
*
* @param {{
* type: "full" | "fetch" | "send";
* force?: boolean;
* serverLastSynced?: number;
* }} options
* @param {boolean} full
* @param {boolean} force
* @param {number} serverLastSynced
*/
async start(options) {
async start(full, force, serverLastSynced) {
if (!(await checkSyncStatus(SYNC_CHECK_IDS.sync))) {
await this.connection.stop();
return;
}
if (!(await this.db.user.getUser())) return;
this.logger.info("Starting sync", options);
this.logger.info("Starting sync", { full, force, serverLastSynced });
this.connection.onclose((error) => {
this.db.eventManager.publish(EVENTS.syncAborted);
@@ -197,21 +196,15 @@ class Sync {
throw new Error("Connection closed.");
});
const { lastSynced, oldLastSynced } = await this.init(options.force);
const { lastSynced, oldLastSynced } = await this.init(force);
this.logger.info("Initialized sync", { lastSynced, oldLastSynced });
const newLastSynced = Date.now();
const serverResponse =
options.type === "fetch" || options.type === "full"
? await this.fetch(lastSynced)
: null;
const serverResponse = full ? await this.fetch(lastSynced) : null;
this.logger.info("Data fetched", serverResponse);
if (
(options.type === "send" || options.type === "full") &&
(await this.send(lastSynced, options.force, newLastSynced))
) {
if (await this.send(lastSynced, force, newLastSynced)) {
this.logger.info("New data sent");
await this.stop(newLastSynced);
} else if (serverResponse) {
@@ -219,7 +212,7 @@ class Sync {
await this.stop(serverResponse.lastSynced);
} else {
this.logger.info("Nothing to do.");
await this.stop(options.serverLastSynced || oldLastSynced);
await this.stop(serverLastSynced || oldLastSynced);
}
if (!(await checkSyncStatus(SYNC_CHECK_IDS.autoSync))) {

View File

@@ -165,9 +165,6 @@ class Merger {
if (remoteItem.deleted)
return this._db.attachments.merge(null, remoteItem);
// rare case but if remote attachment doesn't have metadata it's probably broken so we should just skip it in this case.
if (!remoteItem.metadata) break;
const localItem = this._db.attachments.attachment(
remoteItem.metadata.hash
);

View File

@@ -129,43 +129,39 @@ class UserManager {
) {
if (!email || !password) throw new Error("email & password are required.");
const token = await this.tokenManager.getToken();
if (!token) throw new Error("No token found.");
const token = await this.tokenManager.getAccessToken();
if (!token) throw new Error("Unauthorized.");
email = email.toLowerCase();
if (!hashedPassword) {
hashedPassword = await this._storage.hash(password, email);
}
try {
await this.tokenManager.saveToken(
await http.post(
`${constants.AUTH_HOST}${ENDPOINTS.token}`,
{
grant_type: "mfa_password",
client_id: "notesnook",
scope: "notesnook.sync offline_access IdentityServerApi",
password: hashedPassword
},
token.access_token
)
);
const user = await this.fetchUser();
if (!user) throw new Error("Unauthorized.");
await this.tokenManager.saveToken(
await http.post(
`${constants.AUTH_HOST}${ENDPOINTS.token}`,
{
grant_type: "mfa_password",
client_id: "notesnook",
scope: "notesnook.sync offline_access IdentityServerApi",
password: hashedPassword
},
token
)
);
if (!sessionExpired) {
await this._storage.write("lastSynced", 0);
}
const user = await this.fetchUser();
if (!user) throw new Error("Unauthorized.");
await this._storage.deriveCryptoKey(`_uk_@${user.email}`, {
password,
salt: user.salt
});
EV.publish(EVENTS.userLoggedIn, user);
} catch (e) {
await this.tokenManager.saveToken(token);
throw e;
await this._storage.deriveCryptoKey(`_uk_@${user.email}`, {
password,
salt: user.salt
});
if (!sessionExpired) {
await this._storage.write("lastSynced", 0);
}
EV.publish(EVENTS.userLoggedIn, user);
}
/**
@@ -458,44 +454,43 @@ class UserManager {
const attachmentsKey = await this.getAttachmentsKey();
data.encryptionKey = data.encryptionKey || (await this.getEncryptionKey());
await this._db.outbox.add(type, data, async () => {
if (data.encryptionKey) await this._db.sync(true, true);
await this.clearSessions();
await this._storage.deriveCryptoKey(`_uk_@${email}`, {
password: new_password,
salt
});
if (data.encryptionKey) await this._db.sync({ type: "fetch", force: true });
if (!(await this.resetUser(false))) return;
await this._storage.deriveCryptoKey(`_uk_@${email}`, {
password: new_password,
salt
});
if (attachmentsKey) {
const userEncryptionKey = await this.getEncryptionKey();
if (!userEncryptionKey) return;
user.attachmentsKey = await this._storage.encrypt(
userEncryptionKey,
JSON.stringify(attachmentsKey)
);
await this.updateUser(user);
}
if (!(await this.resetUser(false))) return;
await this._db.sync(false, true);
await this._db.sync({ type: "send", force: true });
if (old_password)
old_password = await this._storage.hash(old_password, email);
if (new_password)
new_password = await this._storage.hash(new_password, email);
if (attachmentsKey) {
const userEncryptionKey = await this.getEncryptionKey();
if (!userEncryptionKey) return;
user.attachmentsKey = await this._storage.encrypt(
userEncryptionKey,
JSON.stringify(attachmentsKey)
await http.patch(
`${constants.AUTH_HOST}${ENDPOINTS.patchUser}`,
{
type,
old_password,
new_password
},
token
);
await this.updateUser(user);
}
if (old_password)
old_password = await this._storage.hash(old_password, email);
if (new_password)
new_password = await this._storage.hash(new_password, email);
await http.patch(
`${constants.AUTH_HOST}${ENDPOINTS.patchUser}`,
{
type,
old_password,
new_password
},
token
);
});
return true;
}

View File

@@ -200,7 +200,7 @@ export function isReminderToday(reminder) {
/**
* @param {Reminder} reminder
*/
export function getUpcomingReminderTime(reminder) {
function getUpcomingReminderTime(reminder) {
if (reminder.mode === "once") return reminder.date;
// this is only the time (hour & minutes); date is not included
const time = dayjs(reminder.date);

View File

@@ -149,9 +149,8 @@ exports[`convert HTML to markdown with outlinelists > html-to-md-outlinelists.md
`;
exports[`convert HTML to markdown with singleSpacedParagraphs > html-to-md-singleSpacedParagraphs.md 1`] = `
"hello world
"hello world
hello world 2
"
`;

View File

@@ -177,10 +177,8 @@ export default class Backup {
/**
*
* @param {any} backup the backup data
* @param {string} [password]
* @param {string} [key]
*/
async import(backup, password, key) {
async import(backup, password) {
if (!backup) return;
if (!this._validate(backup)) throw new Error("Invalid backup.");
@@ -190,14 +188,12 @@ export default class Backup {
let db = backup.data;
const isEncrypted = db.salt && db.iv && db.cipher;
if (backup.encrypted || isEncrypted) {
if (!password && !key)
if (!password)
throw new Error(
"Please provide a password or an encryption key to decrypt this backup & restore it."
"Please provide a password to decrypt this backup & restore it."
);
key = key
? { key, salt: db.salt }
: await this._db.storage.generateCryptoKey(password, db.salt);
const key = await this._db.storage.generateCryptoKey(password, db.salt);
if (!key)
throw new Error("Could not generate encryption key for backup.");

View File

@@ -22,7 +22,7 @@ const _ignore = "";
/**
* @typedef {{
* groupBy: "abc" | "year" | "month" | "week" | "none" | undefined,
* sortBy: "dateCreated" | "dateDeleted" | "dateEdited" | "dateModified" | "title" | "dueDate",
* sortBy: "dateCreated" | "dateDeleted" | "dateEdited" | "dateModified" | "title",
* sortDirection: "desc" | "asc"
* }} GroupOptions
*/

View File

@@ -17,10 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {
getUpcomingReminderTime,
isReminderActive
} from "../collections/reminders";
import { isReminderActive } from "../collections/reminders";
import "../types";
import { getWeekGroupFromTimestamp, MONTHS_FULL } from "./date";
@@ -44,10 +41,6 @@ const comparators = {
asc: (a, b) => a.dateDeleted - b.dateDeleted,
desc: (a, b) => b.dateDeleted - a.dateDeleted
},
dueDate: {
asc: (a, b) => getUpcomingReminderTime(a) - getUpcomingReminderTime(b),
desc: (a, b) => getUpcomingReminderTime(b) - getUpcomingReminderTime(a)
},
title: {
asc: (a, b) =>
getTitle(a).localeCompare(getTitle(b), undefined, { numeric: true }),
@@ -147,23 +140,12 @@ export function groupArray(
* @param {GroupOptions} options
* @returns {(Reminder | {type: "header", title: string})[]} Grouped array
*/
export function groupReminders(
array,
options = {
sortBy: "dateEdited",
sortDirection: "desc"
}
) {
export function groupReminders(array) {
const groups = new Map([
["Active", []],
["Inactive", []]
]);
if (options.sortBy && options.sortDirection) {
const selector = comparators[options.sortBy][options.sortDirection];
array.sort(selector);
}
array.forEach((item) => {
const groupTitle = isReminderActive(item) ? "Active" : "Inactive";
addToGroup(groups, groupTitle, item);

View File

@@ -100,16 +100,9 @@ const Tiptap = ({ settings }: { settings: Settings }) => {
corsHost: settings.corsProxy
},
dateFormat: settings.dateFormat,
timeFormat: settings.timeFormat as "12-hour" | "24-hour" | undefined,
enableInputRules: settings.markdownShortcuts
timeFormat: settings.timeFormat as "12-hour" | "24-hour" | undefined
},
[
layout,
settings.readonly,
tick,
settings.doubleSpacedLines,
settings.markdownShortcuts
]
[layout, settings.readonly, tick, settings.doubleSpacedLines]
);
const update = useCallback(() => {

View File

@@ -52,7 +52,6 @@ export type Settings = {
timeFormat: string;
dateFormat: string;
fontScale: number;
markdownShortcuts: boolean;
};
/* eslint-disable no-var */

View File

@@ -1,8 +1,8 @@
diff --git a/node_modules/@tiptap/core/dist/index.js b/node_modules/@tiptap/core/dist/index.js
index 38c9884..4a9e10c 100644
index 38c9884..2dad7fe 100644
--- a/node_modules/@tiptap/core/dist/index.js
+++ b/node_modules/@tiptap/core/dist/index.js
@@ -1876,8 +1876,23 @@ const lift = (typeOrName, attributes = {}) => ({ state, dispatch }) => {
@@ -1876,8 +1876,21 @@ const lift = (typeOrName, attributes = {}) => ({ state, dispatch }) => {
return lift$1(state, dispatch);
};
@@ -15,8 +15,6 @@ index 38c9884..4a9e10c 100644
+ const { selection, storedMarks } = state;
+ const marks = storedMarks || (selection.$to.parentOffset && selection.$from.marks());
+
+ if (!marks) return dispatch(tr);
+
+ const { splittableMarks } = editor.extensionManager;
+ const filteredMarks = marks.filter((mark) =>
+ splittableMarks.includes(mark.type.name)

View File

@@ -38,7 +38,6 @@ export class ClipboardDOMParser extends ProsemirrorDOMParser {
parseSlice(dom: Node, options?: ParseOptions | undefined): Slice {
if (dom instanceof HTMLElement || dom instanceof Document) {
convertGoogleDocsChecklist(dom);
formatCodeblocks(dom);
convertBrToSingleSpacedParagraphs(dom);
}
@@ -76,43 +75,29 @@ export function convertBrToSingleSpacedParagraphs(dom: HTMLElement | Document) {
for (const br of dom.querySelectorAll("br")) {
let paragraph = br.closest("p");
if (!paragraph) {
// we split and wrap all text nodes into their own single spaced
// paragraphs
const nodes = getSiblingTextNodes(br);
if (nodes.length > 0) {
paragraph = document.createElement("p");
paragraph.dataset.spacing = "single";
paragraph.append(...nodes);
br.replaceWith(paragraph);
continue;
}
// we convert the next pargraph into a single spaced paragraph
if (br.nextElementSibling instanceof HTMLParagraphElement) {
br.nextElementSibling.dataset.spacing = "single";
}
// just convert all br tags into single spaced paragraphs
const newParagraph = document.createElement("p");
newParagraph.dataset.spacing = "single";
br.replaceWith(newParagraph);
// if no paragraph is found over the br, we add one.
if (!paragraph && br.parentElement) {
const parent = br.parentElement;
const p = document.createElement("p");
p.append(...parent.childNodes);
parent.append(p);
paragraph = p;
}
// if paragraph is empty, we clean out the paragraph and move on.
if (
paragraph &&
(paragraph.childNodes.length === 1 ||
!paragraph.textContent ||
paragraph.textContent.trim().length === 0)
) {
// if paragraph is empty, we clean out the paragraph and move on.
paragraph.innerHTML = "";
continue;
}
if (paragraph) {
splitOn(paragraph, br);
const children = Array.from(paragraph.childNodes);
const children = Array.from(paragraph.childNodes.values());
const newParagraph = document.createElement("p");
newParagraph.dataset.spacing = "single";
newParagraph.append(...children.slice(children.indexOf(br) + 1));
@@ -122,18 +107,6 @@ export function convertBrToSingleSpacedParagraphs(dom: HTMLElement | Document) {
}
}
export function convertGoogleDocsChecklist(dom: HTMLElement | Document) {
for (const li of dom.querySelectorAll(`ul li[role="checkbox"]`)) {
if (!li.parentElement?.classList.contains("checklist"))
li.parentElement!.classList.add("checklist");
li.className = "checklist--item";
if (li.firstElementChild?.tagName === "IMG") li.firstElementChild.remove();
if (li.getAttribute("aria-checked") === "true") {
li.classList.add("checked");
}
}
}
function splitOn(bound: Element, cutElement: Element) {
let grandparent: ParentNode | null = null;
for (
@@ -150,13 +123,3 @@ function splitOn(bound: Element, cutElement: Element) {
}
}
}
function getSiblingTextNodes(element: ChildNode) {
const siblings = [];
let sibling: ChildNode | null = element;
while ((sibling = sibling.previousSibling)) {
if (sibling.nodeType === Node.ELEMENT_NODE) break;
else if (sibling.nodeType === Node.TEXT_NODE) siblings.push(sibling);
}
return siblings;
}

View File

@@ -11,12 +11,14 @@ exports[`convert br tags to paragraphs 4`] = `"<p>line <span><em>1</em></span></
exports[`convert br tags to paragraphs 5`] = `"<p></p>"`;
exports[`convert br tags to paragraphs 6`] = `
"<!--StartFragment--><p data-spacing=\\"single\\">A troll, they call me, but I have no wish
</p><p data-spacing=\\"single\\">
"<p>
<!--StartFragment-->A troll, they call me, but I have no wish</p><p data-spacing=\\"single\\">
to be associated with those dolls</p><p data-spacing=\\"single\\">
</p><p data-spacing=\\"single\\">
We lack religion, purpose, politics,</p><p data-spacing=\\"single\\">
and yet, we somehow manage to get by.</p>"
and yet, we somehow manage to get by.</p><p data-spacing=\\"single\\">
</p>"
`;
exports[`convert br tags to paragraphs 7`] = `
@@ -52,36 +54,6 @@ exports[`convert br tags to paragraphs 9`] = `
</div>"
`;
exports[`convert br tags to paragraphs 10`] = `
"<div>
<p style=\\"line-height: 100%; margin-bottom: 0in\\">
I am not talking to you</p><p data-spacing=\\"single\\">
</p>
<p>I am talking to you</p>
</div>"
`;
exports[`convert br tags to paragraphs 11`] = `
"<div>
<!--StartFragment--><p><span>Hello</span></p><p data-spacing=\\"single\\"></p><p data-spacing=\\"single\\"></p><p data-spacing=\\"single\\"></p><p data-spacing=\\"single\\"></p><p data-spacing=\\"single\\"></p><p data-spacing=\\"single\\"><span>world</span></p><!--EndFragment-->
</div>"
`;
exports[`convert br tags to paragraphs 12`] = `
"<div>
<!--StartFragment--><meta charset=\\"utf-8\\"><h1><span>Write notes</span></h1><p data-spacing=\\"single\\"></p><p dir=\\"ltr\\" data-spacing=\\"single\\"><span>Welcome of </span><a><span>Notesnook</span></a><span>, an syncing.</span></p><p data-spacing=\\"single\\"></p><p dir=\\"ltr\\" data-spacing=\\"single\\"><span>Enjoy the read!</span></p><p data-spacing=\\"single\\"></p><p dir=\\"ltr\\" data-spacing=\\"single\\"><span><span><img></span></span></p><p data-spacing=\\"single\\"></p><p dir=\\"ltr\\" data-spacing=\\"single\\"><span>vision?</span></p><p data-spacing=\\"single\\"></p><p dir=\\"ltr\\" data-spacing=\\"single\\"><span>everyone means</span></p><!--EndFragment-->
</div>"
`;
exports[`convert google docs checklist 1`] = `
"<div>
<!--StartFragment--><meta charset=\\"utf-8\\"><ul id=\\"docs-internal-guid-0d9a5db3-7fff-ab55-e7ca-b178e1031970\\" class=\\"checklist\\"><li dir=\\"ltr\\" role=\\"checkbox\\" aria-checked=\\"false\\" aria-level=\\"1\\" class=\\"checklist--item\\"><p dir=\\"ltr\\" role=\\"presentation\\"><span>Adsjkfhasdf</span></p></li><li dir=\\"ltr\\" role=\\"checkbox\\" aria-checked=\\"false\\" aria-level=\\"1\\" class=\\"checklist--item\\"><p dir=\\"ltr\\" role=\\"presentation\\"><span>Asdfsadf</span></p></li><li dir=\\"ltr\\" role=\\"checkbox\\" aria-checked=\\"true\\" aria-level=\\"1\\" class=\\"checklist--item checked\\"><p dir=\\"ltr\\" role=\\"presentation\\"><span>Asdfsda</span></p></li><li dir=\\"ltr\\" role=\\"checkbox\\" aria-checked=\\"true\\" aria-level=\\"1\\" class=\\"checklist--item checked\\"><p dir=\\"ltr\\" role=\\"presentation\\"><span>Fasd</span></p></li><li dir=\\"ltr\\" role=\\"checkbox\\" aria-checked=\\"true\\" aria-level=\\"1\\" class=\\"checklist--item checked\\"><p dir=\\"ltr\\" role=\\"presentation\\"><span>Fasd</span></p></li><li dir=\\"ltr\\" role=\\"checkbox\\" aria-checked=\\"false\\" aria-level=\\"1\\" class=\\"checklist--item\\"><p dir=\\"ltr\\" role=\\"presentation\\"><span>F</span></p></li><li dir=\\"ltr\\" role=\\"checkbox\\" aria-checked=\\"true\\" aria-level=\\"1\\" class=\\"checklist--item checked\\"><p dir=\\"ltr\\" role=\\"presentation\\"><span>akcasb</span></p></li><ul class=\\"checklist\\"><li dir=\\"ltr\\" role=\\"checkbox\\" aria-checked=\\"true\\" aria-level=\\"2\\" class=\\"checklist--item checked\\"><p dir=\\"ltr\\" role=\\"presentation\\"><span>Asdf</span></p></li><li dir=\\"ltr\\" role=\\"checkbox\\" aria-checked=\\"false\\" aria-level=\\"2\\" class=\\"checklist--item\\"><p dir=\\"ltr\\" role=\\"presentation\\"><span>Asdcasdc</span></p></li><li dir=\\"ltr\\" role=\\"checkbox\\" aria-checked=\\"false\\" aria-level=\\"2\\" class=\\"checklist--item\\"><p dir=\\"ltr\\" role=\\"presentation\\"><span>sdac</span></p></li></ul><li dir=\\"ltr\\" role=\\"checkbox\\" aria-checked=\\"false\\" aria-level=\\"1\\" class=\\"checklist--item\\"><p dir=\\"ltr\\" role=\\"presentation\\"><span>Asdfsda</span></p></li></ul><!--EndFragment-->
</div>"
`;
exports[`properly format codeblocks 1`] = `
"<div>
<!--StartFragment--><p>Sure! Here's an implementation of a word counter for Thai that considers each syllable consisting of a consonant sound followed by a vowel sound as a word:</p><pre><code>javascript</code></pre><pre class=\\"language-javascript\\"><code>function countThaiWords(text) {

View File

@@ -20,8 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { test } from "vitest";
import {
formatCodeblocks,
convertBrToSingleSpacedParagraphs,
convertGoogleDocsChecklist
convertBrToSingleSpacedParagraphs
} from "../clipboard-dom-parser";
const cases = [
@@ -78,31 +77,11 @@ and yet, we somehow manage to get by.<br>
you</p>
</div>`
],
[
`<div>
<p style="line-height: 100%; margin-bottom: 0in">
I am not talking to you</p>
<br/>
<p>I am talking to you</p>
</div>`
],
[
`<div>
<!--StartFragment--><p><span>Hello</span></p><br><br><br><br><br><p><span >world</span></p><!--EndFragment-->
</div>`
],
[
`<div>
<!--StartFragment--><meta charset="utf-8"><h1><span >Write notes</span></h1><br><p dir="ltr" ><span >Welcome of </span><a><span >Notesnook</span></a><span >, an syncing.</span></p><br><p dir="ltr" ><span >Enjoy the read!</span></p><br><p dir="ltr" ><span ><span ><img></span></span></p><br><p dir="ltr" ><span >vision?</span></p><br><p dir="ltr" ><span >everyone means</span></p><!--EndFragment-->
</div>`
]
];
for (const testCase of cases) {
const [html] = testCase;
const [html, expected] = testCase;
test(`convert br tags to paragraphs`, (t) => {
const element = new DOMParser().parseFromString(html, "text/html");
convertBrToSingleSpacedParagraphs(element);
@@ -140,17 +119,3 @@ for (const codeBlock of codeBlocks) {
t.expect(element.body.innerHTML.trim()).toMatchSnapshot();
});
}
const checkLists = [
`<div>
<!--StartFragment--><meta charset="utf-8"><ul id="docs-internal-guid-0d9a5db3-7fff-ab55-e7ca-b178e1031970"><li dir="ltr" role="checkbox" aria-checked="false" aria-level="1"><img alt="unchecked" aria-roledescription="checkbox" ><p dir="ltr" role="presentation"><span >Adsjkfhasdf</span></p></li><li dir="ltr" role="checkbox" aria-checked="false" aria-level="1"><img alt="unchecked" aria-roledescription="checkbox" ><p dir="ltr" role="presentation"><span >Asdfsadf</span></p></li><li dir="ltr" role="checkbox" aria-checked="true" aria-level="1"><img alt="checked" aria-roledescription="checkbox" ><p dir="ltr" role="presentation"><span >Asdfsda</span></p></li><li dir="ltr" role="checkbox" aria-checked="true" aria-level="1"><img alt="checked" aria-roledescription="checkbox" ><p dir="ltr" role="presentation"><span >Fasd</span></p></li><li dir="ltr" role="checkbox" aria-checked="true" aria-level="1"><img alt="checked" aria-roledescription="checkbox" ><p dir="ltr" role="presentation"><span >Fasd</span></p></li><li dir="ltr" role="checkbox" aria-checked="false" aria-level="1"><img alt="unchecked" aria-roledescription="checkbox" ><p dir="ltr" role="presentation"><span >F</span></p></li><li dir="ltr" role="checkbox" aria-checked="true" aria-level="1"><img alt="checked" aria-roledescription="checkbox" ><p dir="ltr" role="presentation"><span >akcasb</span></p></li><ul ><li dir="ltr" role="checkbox" aria-checked="true" aria-level="2"><img alt="checked" aria-roledescription="checkbox" ><p dir="ltr" role="presentation"><span >Asdf</span></p></li><li dir="ltr" role="checkbox" aria-checked="false" aria-level="2"><img alt="unchecked" aria-roledescription="checkbox" ><p dir="ltr" role="presentation"><span >Asdcasdc</span></p></li><li dir="ltr" role="checkbox" aria-checked="false" aria-level="2"><img alt="unchecked" aria-roledescription="checkbox" ><p dir="ltr" role="presentation"><span >sdac</span></p></li></ul><li dir="ltr" role="checkbox" aria-checked="false" aria-level="1"><img alt="unchecked" aria-roledescription="checkbox" ><p dir="ltr" role="presentation"><span >Asdfsda</span></p></li></ul><!--EndFragment-->
</div>`
];
for (const checkList of checkLists) {
test(`convert google docs checklist`, (t) => {
const element = new DOMParser().parseFromString(checkList, "text/html");
convertGoogleDocsChecklist(element);
t.expect(element.body.innerHTML.trim()).toMatchSnapshot();
});
}

View File

@@ -230,9 +230,7 @@ function TableColumnToolbar(props: TableToolbarProps) {
yOffset: 2
});
columnToolsRef.current.style.left = `${
pos.left - (table.current.parentElement?.scrollLeft || 0)
}px`;
columnToolsRef.current.style.left = `${pos.left}px`;
columnToolsRef.current.style.top = `${pos.top}px`;
}

View File

@@ -105,14 +105,8 @@ export function ColorTool(props: ColorToolProps) {
}}
cacheKey={`custom_${cacheKey}`}
onChange={(color) => {
const currentColor = config.get(cacheKey);
if (currentColor && currentColor === color) {
onColorChange();
config.set(cacheKey, null);
} else {
onColorChange(color);
config.set(cacheKey, color);
}
onColorChange(color);
config.set(cacheKey, color);
}}
onClose={() => setIsOpen(false)}
title={title}

View File

@@ -1,6 +1,6 @@
.ProseMirror p span {
.ProseMirror span * {
font-family: inherit;
}

View File

@@ -68,15 +68,8 @@ const error: ThemeUIStyleObject = {
}
};
const radio: ThemeUIStyleObject = {
"input:focus ~ &": {
backgroundColor: `border-secondary`
}
};
export const inputVariants = {
input: defaultVariant,
error,
clean,
radio
clean
};

View File

@@ -25,9 +25,9 @@ import parser from "yargs-parser";
import { fdir } from "fdir";
import Listr from "listr";
const THREADS = Math.max(4, process.env.THREADS || os.cpus().length / 2);
const args = parser(process.argv, { alias: { scope: ["s"], offline: ["o"] } });
const IS_CI = process.env.CI;
const THREADS = Math.max(4, process.env.THREADS || os.cpus().length / 2);
const scopes = {
mobile: "apps/mobile",
web: "apps/web",
@@ -38,14 +38,6 @@ const scopes = {
themes: "servers/themes",
themebuilder: "apps/theme-builder"
};
// packages that we shouldn't run npm rebuild for
const IGNORED_NATIVE_PACKAGES = [
// optional dependency of pdfjs-dist, we can ignore
// it because it's only needed in non-browser environments
"canvas",
// optional dependency only used on Node.js platform
"@azure/msal-node-runtime"
];
if (args.scope && !scopes[args.scope])
throw new Error(`Scope must be one of ${Object.keys(scopes).join(", ")}`);
@@ -103,10 +95,12 @@ async function bootstrapPackages(dependencies) {
console.timeEnd("Took");
}
function execute(cmd, cwd, outputs) {
function bootstrapPackage(cwd, outputs) {
return new Promise((resolve, reject) =>
exec(
cmd,
`npm ${IS_CI ? "ci" : "i"} --legacy-peer-deps --no-audit --no-fund ${
args.offline ? "--offline" : "--prefer-offline"
} --progress=false`,
{
cwd,
env: process.env,
@@ -115,6 +109,7 @@ function execute(cmd, cwd, outputs) {
(err, stdout, stderr) => {
if (err) return reject(err);
outputs.stdout.push("> " + cwd);
outputs.stdout.push(stdout);
outputs.stderr.push(stderr);
@@ -124,32 +119,6 @@ function execute(cmd, cwd, outputs) {
);
}
async function bootstrapPackage(cwd, outputs) {
const cmd = `npm ${
IS_CI ? "ci" : "i"
} --legacy-peer-deps --no-audit --no-fund ${
args.offline ? "--offline" : "--prefer-offline"
} --progress=false --ignore-scripts`;
outputs.stdout.push("> " + cwd);
await execute(cmd, cwd, outputs);
const postInstallCommands = [];
const packages = await needsRebuild(cwd);
if (packages.length > 0) {
postInstallCommands.push(`npm rebuild ${packages.join(" ")}`);
}
if (await hasScript(cwd, "postinstall"))
postInstallCommands.push(`npm run postinstall `);
for (const cmd of postInstallCommands) {
await execute(cmd, cwd, outputs);
}
}
async function findDependencies(scope) {
try {
const packageJsonPath = path.join(scope, "package.json");
@@ -182,39 +151,3 @@ function filterDependencies(basePath, dependencies) {
path.resolve(path.join(basePath, value.replace("file:", "")))
);
}
async function needsRebuild(cwd) {
const scripts = ["preinstall", "install", "postinstall"];
const packages = await new fdir()
.glob("**/package.json")
.withFullPaths()
.crawl(path.join(cwd, "node_modules"))
.withPromise();
return (
await Promise.all(
packages.map(async (path) => {
const pkg = await readFile(path, "utf-8")
.then(JSON.parse)
.catch(Object);
if (
!pkg ||
!pkg.scripts ||
IGNORED_NATIVE_PACKAGES.includes(pkg.name) ||
!scripts.some((s) => pkg.scripts[s])
)
return;
return pkg.name;
})
)
).filter(Boolean);
}
async function hasScript(cwd, scriptName) {
const pkg = await readFile(path.join(cwd, "package.json"), "utf-8")
.then(JSON.parse)
.catch(Object);
return pkg && pkg.scripts && pkg.scripts[scriptName];
}