mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-31 11:08:47 +02:00
Compare commits
1 Commits
3.0.7-andr
...
fix/share-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4b07eb80f |
4
apps/desktop/package-lock.json
generated
4
apps/desktop/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "3.0.6",
|
||||
"version": "3.0.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "3.0.6",
|
||||
"version": "3.0.4",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@notesnook/desktop",
|
||||
"productName": "Notesnook",
|
||||
"description": "Your private note taking space",
|
||||
"version": "3.0.6",
|
||||
"version": "3.0.4",
|
||||
"appAppleId": "1544027013",
|
||||
"private": true,
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -232,8 +232,7 @@ export async function deriveCryptoKey(data) {
|
||||
let credentials = await Sodium.deriveKey(data.password, data.salt);
|
||||
const userKeyCipher = await encrypt(
|
||||
{
|
||||
key: await getDatabaseKey(),
|
||||
salt: NOTESNOOK_DB_KEY_SALT
|
||||
key: await getDatabaseKey()
|
||||
},
|
||||
credentials.key
|
||||
);
|
||||
@@ -258,12 +257,13 @@ export async function getCryptoKey(_name) {
|
||||
|
||||
const key = await decrypt(
|
||||
{
|
||||
key: await getDatabaseKey(),
|
||||
salt: keyCipher.salt
|
||||
key: await getDatabaseKey()
|
||||
},
|
||||
keyCipher
|
||||
);
|
||||
|
||||
DatabaseLogger.info("User key decrypted: ", !!key);
|
||||
|
||||
return key;
|
||||
} catch (e) {
|
||||
console.log("getCryptoKey", e);
|
||||
|
||||
@@ -112,10 +112,6 @@ export const SessionExpired = () => {
|
||||
if (!res) throw new Error("no token found");
|
||||
if (db.tokenManager._isTokenExpired(res))
|
||||
throw new Error("token expired");
|
||||
|
||||
const key = await db.user.getEncryptionKey();
|
||||
if (!key) throw new Error("No encryption key found.");
|
||||
|
||||
Sync.run("global", false, "full", async (complete) => {
|
||||
if (!complete) {
|
||||
let user = await db.user.getUser();
|
||||
@@ -131,6 +127,7 @@ export const SessionExpired = () => {
|
||||
setVisible(false);
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
let user = await db.user.getUser();
|
||||
if (!user) return;
|
||||
email.current = user.email;
|
||||
|
||||
@@ -351,18 +351,9 @@ export const useEditor = (
|
||||
if (!unlocked)
|
||||
throw new Error("Could not save note, vault is locked");
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if (typeof noteData.title === "string") {
|
||||
await db.notes.add({
|
||||
title: noteData.title,
|
||||
id: noteData.id
|
||||
});
|
||||
}
|
||||
|
||||
noteData.contentId = note?.contentId;
|
||||
if (data) {
|
||||
await db.vault?.save(noteData as any);
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
await db.vault?.save(noteData as any);
|
||||
clearTimeout(saveTimer);
|
||||
}
|
||||
|
||||
@@ -705,13 +696,14 @@ export const useEditor = (
|
||||
);
|
||||
}
|
||||
|
||||
console.log("readonly state changed...", note.readonly);
|
||||
useTabStore.getState().updateTab(tabId, {
|
||||
readonly: note.readonly
|
||||
});
|
||||
}
|
||||
|
||||
if (data.type === "tiptap" && note && !isLocal) {
|
||||
if (lastContentChangeTime.current[noteId] >= data.dateModified) {
|
||||
if (lastContentChangeTime.current[noteId] >= data.dateEdited) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -736,7 +728,7 @@ export const useEditor = (
|
||||
}
|
||||
} else {
|
||||
const _nextContent = data.data;
|
||||
if (_nextContent === currentContents.current[note.id]?.data) {
|
||||
if (_nextContent === currentContents.current?.data) {
|
||||
return;
|
||||
}
|
||||
lastContentChangeTime.current[note.id] = note.dateEdited;
|
||||
|
||||
@@ -59,14 +59,14 @@ const recoveryKeyMessage = {
|
||||
() => {
|
||||
eSendEvent(eOpenRecoveryKeyDialog);
|
||||
},
|
||||
false,
|
||||
true,
|
||||
async () => {
|
||||
SettingsService.set({
|
||||
recoveryKeySaved: true
|
||||
});
|
||||
clearMessage();
|
||||
},
|
||||
"Cancel"
|
||||
"I have saved my key already"
|
||||
);
|
||||
},
|
||||
data: {},
|
||||
|
||||
@@ -111,7 +111,7 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
multiDexEnabled true
|
||||
versionCode 3015
|
||||
versionCode 3012
|
||||
versionName getNpmVersion()
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
@@ -37,10 +37,4 @@ allprojects {
|
||||
|
||||
maven { url 'https://www.jitpack.io' }
|
||||
}
|
||||
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1015,7 +1015,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2104;
|
||||
CURRENT_PROJECT_VERSION = 2101;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
ENABLE_BITCODE = NO;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1089,7 +1089,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.7;
|
||||
MARKETING_VERSION = 3.0.4;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -1120,7 +1120,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 2104;
|
||||
CURRENT_PROJECT_VERSION = 2101;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1194,7 +1194,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.7;
|
||||
MARKETING_VERSION = 3.0.4;
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
@@ -1353,7 +1353,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2104;
|
||||
CURRENT_PROJECT_VERSION = 2101;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1365,7 +1365,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.7;
|
||||
MARKETING_VERSION = 3.0.4;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
@@ -1396,7 +1396,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2104;
|
||||
CURRENT_PROJECT_VERSION = 2101;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
@@ -1409,7 +1409,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.7;
|
||||
MARKETING_VERSION = 3.0.4;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -1439,7 +1439,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2104;
|
||||
CURRENT_PROJECT_VERSION = 2101;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1513,7 +1513,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.7;
|
||||
MARKETING_VERSION = 3.0.4;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
@@ -1544,7 +1544,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2104;
|
||||
CURRENT_PROJECT_VERSION = 2101;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
@@ -1619,7 +1619,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.7;
|
||||
MARKETING_VERSION = 3.0.4;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.0.7",
|
||||
"version": "3.0.4",
|
||||
"private": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"workspaces": [
|
||||
|
||||
@@ -259,15 +259,14 @@ test("unlock a note for editing", async ({ page }) => {
|
||||
await page.waitForTimeout(150);
|
||||
|
||||
await page.reload();
|
||||
await notes.waitForList();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const newContent = `${NOTE.content}${content}`;
|
||||
const editedNote = await notes.findNote({
|
||||
title: NOTE.title,
|
||||
content: newContent
|
||||
});
|
||||
if (!editedNote) throw new Error("Could not find note.");
|
||||
await editedNote.openLockedNote(PASSWORD);
|
||||
await editedNote?.openLockedNote(PASSWORD);
|
||||
await notes.editor.waitForLoading();
|
||||
expect(await notes.editor.getContent("text")).toContain(newContent);
|
||||
});
|
||||
|
||||
4
apps/web/package-lock.json
generated
4
apps/web/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/web",
|
||||
"version": "3.0.6",
|
||||
"version": "3.0.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/web",
|
||||
"version": "3.0.6",
|
||||
"version": "3.0.4",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@notesnook/web",
|
||||
"description": "Your private note taking space",
|
||||
"version": "3.0.6",
|
||||
"version": "3.0.4",
|
||||
"private": true,
|
||||
"main": "./src/app.js",
|
||||
"homepage": "https://notesnook.com/",
|
||||
|
||||
@@ -77,19 +77,12 @@ export async function introduceFeatures() {
|
||||
|
||||
export const DEFAULT_CONTEXT = { colors: [], tags: [], notebook: {} };
|
||||
|
||||
export async function createBackup(
|
||||
options: {
|
||||
rescueMode?: boolean;
|
||||
noVerify?: boolean;
|
||||
} = {}
|
||||
) {
|
||||
const { rescueMode, noVerify } = options;
|
||||
export async function createBackup(rescueMode = false) {
|
||||
const { isLoggedIn } = useUserStore.getState();
|
||||
const { encryptBackups, toggleEncryptBackups } = useSettingStore.getState();
|
||||
if (!isLoggedIn && encryptBackups) toggleEncryptBackups();
|
||||
|
||||
const verified =
|
||||
rescueMode || encryptBackups || noVerify || (await verifyAccount());
|
||||
const verified = rescueMode || encryptBackups || (await verifyAccount());
|
||||
if (!verified) {
|
||||
showToast("error", "Could not create a backup: user verification failed.");
|
||||
return false;
|
||||
|
||||
@@ -18,10 +18,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import hotkeys from "hotkeys-js";
|
||||
import { navigate } from "../navigation";
|
||||
// import { store as themestore } from "../stores/theme-store";
|
||||
import { GlobalKeyboard } from "../utils/keyboard";
|
||||
import { useEditorStore } from "../stores/editor-store";
|
||||
import { useStore as useSearchStore } from "../stores/search-store";
|
||||
import { useEditorManager } from "../components/editor/manager";
|
||||
|
||||
const KEYMAP = [
|
||||
// {
|
||||
@@ -58,24 +57,11 @@ const KEYMAP = [
|
||||
keys: ["command+f", "ctrl+f"],
|
||||
description: "Search all notes",
|
||||
global: false,
|
||||
action: (e: KeyboardEvent) => {
|
||||
const isInEditor =
|
||||
e.target instanceof HTMLElement &&
|
||||
!!e.target?.closest(".editor-container");
|
||||
if (isInEditor) {
|
||||
const activeSession = useEditorStore.getState().getActiveSession();
|
||||
if (activeSession?.type === "readonly") {
|
||||
e.preventDefault();
|
||||
const editor = useEditorManager
|
||||
.getState()
|
||||
.getEditor(activeSession.id);
|
||||
editor?.editor?.startSearch();
|
||||
}
|
||||
return;
|
||||
}
|
||||
action: (e) => {
|
||||
if (e.target?.classList.contains("ProseMirror")) return;
|
||||
e.preventDefault();
|
||||
|
||||
useSearchStore.setState({ isSearching: true, searchType: "notes" });
|
||||
navigate("/search/notes");
|
||||
}
|
||||
}
|
||||
// {
|
||||
@@ -151,11 +137,7 @@ export function registerKeyMap() {
|
||||
KEYMAP.forEach((key) => {
|
||||
hotkeys(
|
||||
key.keys.join(","),
|
||||
{
|
||||
element: key.global
|
||||
? (GlobalKeyboard as unknown as HTMLElement)
|
||||
: document.body
|
||||
},
|
||||
{ element: key.global ? GlobalKeyboard : window },
|
||||
key.action
|
||||
);
|
||||
});
|
||||
@@ -177,7 +177,7 @@ function isIgnored(key: keyof typeof NoticesData) {
|
||||
let openedToast: { hide: () => void } | null = null;
|
||||
async function saveBackup() {
|
||||
if (IS_DESKTOP_APP) {
|
||||
await createBackup({ noVerify: true });
|
||||
await createBackup();
|
||||
} else if (isUserPremium() && !IS_TESTING) {
|
||||
if (openedToast !== null) return;
|
||||
openedToast = showToast(
|
||||
|
||||
@@ -83,7 +83,7 @@ export class SharedService<T extends object> extends EventTarget {
|
||||
}
|
||||
|
||||
activate(
|
||||
portProviderFunc: () => Promise<{ port: MessagePort; onclose: () => void }>,
|
||||
portProviderFunc: () => MessagePort | Promise<MessagePort>,
|
||||
onClientConnected: () => Promise<void>
|
||||
) {
|
||||
if (this.#onDeactivate) return;
|
||||
@@ -97,7 +97,7 @@ export class SharedService<T extends object> extends EventTarget {
|
||||
navigator.locks
|
||||
.request(LOCK_NAME, { signal: this.#onDeactivate.signal }, async () => {
|
||||
// Get the port to request client ports.
|
||||
const { port, onclose } = await portProviderFunc();
|
||||
const port = await portProviderFunc();
|
||||
port.start();
|
||||
|
||||
// Listen for client requests. A separate BroadcastChannel
|
||||
@@ -159,7 +159,6 @@ export class SharedService<T extends object> extends EventTarget {
|
||||
// Release the lock only on user abort or context destruction.
|
||||
return new Promise((_, reject) => {
|
||||
this.#onDeactivate?.signal.addEventListener("abort", () => {
|
||||
onclose();
|
||||
broadcastChannel.close();
|
||||
reject(this.#onDeactivate?.signal.reason);
|
||||
});
|
||||
|
||||
@@ -233,8 +233,6 @@ addEventListener("message", async (event) => {
|
||||
await worker.open(event.data.dbName, event.data.async, event.data.uri);
|
||||
const providerPort = createSharedServicePort(worker);
|
||||
postMessage(null, [providerPort]);
|
||||
|
||||
self.addEventListener("beforeunload", () => worker.close());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -72,18 +72,14 @@ export class WaSqliteWorkerDriver implements Driver {
|
||||
|
||||
service.activate(
|
||||
() =>
|
||||
new Promise<{ port: MessagePort; onclose: () => void }>((resolve) => {
|
||||
new Promise<MessagePort>((resolve) => {
|
||||
console.log("initializing worker");
|
||||
this.needsInitialization = true;
|
||||
|
||||
const worker = new Worker();
|
||||
worker.addEventListener(
|
||||
"message",
|
||||
(event) =>
|
||||
resolve({
|
||||
port: event.ports[0],
|
||||
onclose: () => worker.terminate()
|
||||
}),
|
||||
(event) => resolve(event.ports[0]),
|
||||
{ once: true }
|
||||
);
|
||||
worker.postMessage({
|
||||
|
||||
@@ -307,7 +307,7 @@ function CalltoAction({ action, variant, sx, dismissAnnouncement }) {
|
||||
break;
|
||||
}
|
||||
case "backup": {
|
||||
await createBackup();
|
||||
await createBackup(true);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
|
||||
@@ -152,6 +152,7 @@ export function EditorActionBar() {
|
||||
activeSession &&
|
||||
activeSession.type !== "new" &&
|
||||
activeSession.type !== "locked" &&
|
||||
activeSession.type !== "readonly" &&
|
||||
activeSession.type !== "diff" &&
|
||||
activeSession.type !== "conflicted",
|
||||
onClick: editor?.startSearch
|
||||
|
||||
@@ -344,6 +344,7 @@ function TipTap(props: TipTapProps) {
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (readonly) return null;
|
||||
return (
|
||||
<>
|
||||
<ScopedThemeProvider
|
||||
@@ -417,7 +418,7 @@ function TiptapWrapper(
|
||||
editorContainer={() => {
|
||||
if (editorContainerRef.current) return editorContainerRef.current;
|
||||
const editorContainer = document.createElement("div");
|
||||
editorContainer.classList.add("selectable", "editor-container");
|
||||
editorContainer.classList.add("selectable");
|
||||
editorContainer.style.flex = "1";
|
||||
editorContainer.style.cursor = "text";
|
||||
editorContainer.style.color =
|
||||
@@ -426,7 +427,6 @@ function TiptapWrapper(
|
||||
editorContainer.style.fontSize = `${editorConfig.fontSize}px`;
|
||||
editorContainer.style.fontFamily =
|
||||
getFontById(editorConfig.fontFamily)?.font || "sans-serif";
|
||||
editorContainer.tabIndex = -1;
|
||||
editorContainerRef.current = editorContainer;
|
||||
return editorContainer;
|
||||
}}
|
||||
|
||||
@@ -524,6 +524,8 @@ const menuItems: (
|
||||
type: "button",
|
||||
key: "duplicate",
|
||||
title: "Duplicate",
|
||||
//!isSynced ||
|
||||
isDisabled: context?.locked,
|
||||
icon: Duplicate.path,
|
||||
onClick: () => store.get().duplicate(...ids),
|
||||
multiSelect: true
|
||||
|
||||
@@ -89,7 +89,32 @@ const features: Record<FeatureKeys, Feature> = {
|
||||
)
|
||||
}
|
||||
]
|
||||
: [],
|
||||
: [
|
||||
{
|
||||
title: "Bi-directional note linking",
|
||||
subtitle: "You can now link any 2 notes for easier referencing."
|
||||
},
|
||||
{
|
||||
title: "Tabs",
|
||||
subtitle:
|
||||
"Open multiple notes side by side for faster multi-tasking."
|
||||
},
|
||||
{
|
||||
title: "Nested notebooks",
|
||||
subtitle:
|
||||
"Create subnotebooks inside notebooks upto unlimited depth."
|
||||
},
|
||||
{
|
||||
title: "At rest encryption",
|
||||
subtitle:
|
||||
"All your data will now be stored encrypted on your device to keep you safe even if your device gets compromised."
|
||||
},
|
||||
{
|
||||
title: "App lock",
|
||||
subtitle:
|
||||
"App lock is now here on desktop/web apps! Put a lock on the app, and keep it safe from intruders."
|
||||
}
|
||||
],
|
||||
cta: {
|
||||
title: "Got it",
|
||||
icon: Checkmark,
|
||||
|
||||
@@ -458,7 +458,7 @@ function BackupData(props: BaseRecoveryComponentProps<"backup">) {
|
||||
"Please wait while we create a backup file for you to download."
|
||||
}}
|
||||
onSubmit={async () => {
|
||||
await createBackup({ rescueMode: true });
|
||||
await createBackup(true);
|
||||
navigate("new");
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
- Bug fixes and performance improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
@@ -1,3 +0,0 @@
|
||||
- Bug fixes and performance improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
@@ -1,3 +0,0 @@
|
||||
- Bug fixes and performance improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
@@ -123,7 +123,7 @@ describe.concurrent("merge content", (test) => {
|
||||
await loginFakeUser(db);
|
||||
const merger = new Merger(db);
|
||||
|
||||
const merged = merger.mergeContent(
|
||||
const merged = await merger.mergeContent(
|
||||
{
|
||||
type: "tiptap",
|
||||
data: "Remote",
|
||||
@@ -132,7 +132,6 @@ describe.concurrent("merge content", (test) => {
|
||||
{
|
||||
type: "tiptap",
|
||||
data: "Local",
|
||||
synced: true,
|
||||
dateEdited: Date.now() - 1000
|
||||
}
|
||||
);
|
||||
@@ -141,13 +140,13 @@ describe.concurrent("merge content", (test) => {
|
||||
expect(merged.data).toBe("Remote");
|
||||
}));
|
||||
|
||||
test("trigger conflict if local item is unsynced", () =>
|
||||
test("trigger conflict if local item dateEdited is newer", () =>
|
||||
databaseTest().then(async (db) => {
|
||||
await loginFakeUser(db);
|
||||
const merger = new Merger(db);
|
||||
|
||||
const noteId = await db.notes.add(TEST_NOTE);
|
||||
const merged = merger.mergeContent(
|
||||
const merged = await merger.mergeContent(
|
||||
{
|
||||
type: "tiptap",
|
||||
data: "Remote",
|
||||
@@ -158,7 +157,7 @@ describe.concurrent("merge content", (test) => {
|
||||
type: "tiptap",
|
||||
data: "Local",
|
||||
noteId,
|
||||
synced: false,
|
||||
|
||||
dateEdited: Date.now()
|
||||
}
|
||||
);
|
||||
@@ -167,6 +166,7 @@ describe.concurrent("merge content", (test) => {
|
||||
expect(merged.data).toBe("Local");
|
||||
expect(merged.conflicted).toBeDefined();
|
||||
expect(merged.conflicted.data).toBe("Remote");
|
||||
expect(await db.notes.conflicted.has(noteId)).toBe(true);
|
||||
}));
|
||||
|
||||
test("merge conflicts if local item is already conflicted", () =>
|
||||
|
||||
@@ -251,7 +251,7 @@ class Sync {
|
||||
.selectFrom("content")
|
||||
.select("noteId as id")
|
||||
.where("conflicted", "is not", null)
|
||||
.where("conflicted", "!=", false)
|
||||
.where("conflicted", "is not", false)
|
||||
.$castTo<string | null>()
|
||||
)
|
||||
.set({ conflicted: true })
|
||||
|
||||
@@ -318,9 +318,17 @@ export class Notes implements ICollection {
|
||||
const content = note.contentId
|
||||
? await this.db.content.get(note.contentId)
|
||||
: undefined;
|
||||
if (content && (isDeleted(content) || content.locked))
|
||||
throw new Error("Cannot duplicate a locked or deleted note.");
|
||||
const duplicateId = await this.db.notes.add({
|
||||
...clone(note),
|
||||
id: undefined,
|
||||
content: content
|
||||
? {
|
||||
type: content.type,
|
||||
data: content.data
|
||||
}
|
||||
: undefined,
|
||||
readonly: false,
|
||||
favorite: false,
|
||||
pinned: false,
|
||||
@@ -330,38 +338,19 @@ export class Notes implements ICollection {
|
||||
dateCreated: undefined,
|
||||
dateModified: undefined
|
||||
});
|
||||
if (!duplicateId) continue;
|
||||
|
||||
const contentId = await this.db.content.add({
|
||||
...clone(content),
|
||||
id: undefined,
|
||||
noteId: duplicateId,
|
||||
dateResolved: undefined,
|
||||
dateEdited: undefined,
|
||||
dateCreated: undefined,
|
||||
dateModified: undefined
|
||||
});
|
||||
|
||||
await this.db.notes.add({ id: duplicateId, contentId });
|
||||
|
||||
for (const relation of await this.db.relations.to(note).get()) {
|
||||
for (const relation of await this.db.relations
|
||||
.to(note, "notebook")
|
||||
.get()) {
|
||||
await this.db.relations.add(
|
||||
{ type: relation.fromType, id: relation.fromId },
|
||||
{ type: "notebook", id: relation.fromId },
|
||||
{
|
||||
id: duplicateId,
|
||||
type: "note"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
for (const relation of await this.db.relations.from(note).get()) {
|
||||
await this.db.relations.add(
|
||||
{
|
||||
id: duplicateId,
|
||||
type: "note"
|
||||
},
|
||||
{ type: relation.toType, id: relation.toId }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,9 +63,6 @@ export class Relations implements ICollection {
|
||||
});
|
||||
}
|
||||
|
||||
from(
|
||||
reference: ItemReference | ItemReferences
|
||||
): RelationsArray<keyof RelatableTable>;
|
||||
from(
|
||||
reference: ItemReference | ItemReferences,
|
||||
types: (keyof RelatableTable)[]
|
||||
@@ -76,19 +73,16 @@ export class Relations implements ICollection {
|
||||
): RelationsArray<TType>;
|
||||
from<TType extends keyof RelatableTable = keyof RelatableTable>(
|
||||
reference: ItemReference | ItemReferences,
|
||||
type?: TType | keyof RelatableTable[]
|
||||
type: TType | keyof RelatableTable[]
|
||||
) {
|
||||
return new RelationsArray(
|
||||
this.db,
|
||||
reference,
|
||||
type ? (Array.isArray(type) ? type : ([type] as TType[])) : undefined,
|
||||
Array.isArray(type) ? type : [type],
|
||||
"from"
|
||||
);
|
||||
}
|
||||
|
||||
to(
|
||||
reference: ItemReference | ItemReferences
|
||||
): RelationsArray<keyof RelatableTable>;
|
||||
to(
|
||||
reference: ItemReference | ItemReferences,
|
||||
types: (keyof RelatableTable)[]
|
||||
@@ -99,12 +93,12 @@ export class Relations implements ICollection {
|
||||
): RelationsArray<TType>;
|
||||
to<TType extends keyof RelatableTable = keyof RelatableTable>(
|
||||
reference: ItemReference | ItemReferences,
|
||||
type?: TType | keyof RelatableTable[]
|
||||
type: TType | keyof RelatableTable[]
|
||||
) {
|
||||
return new RelationsArray(
|
||||
this.db,
|
||||
reference,
|
||||
type ? (Array.isArray(type) ? type : ([type] as TType[])) : undefined,
|
||||
Array.isArray(type) ? type : [type],
|
||||
"to"
|
||||
);
|
||||
}
|
||||
@@ -230,26 +224,21 @@ const TABLE_MAP = {
|
||||
type RelatableTable = typeof TABLE_MAP;
|
||||
|
||||
class RelationsArray<TType extends keyof RelatableTable> {
|
||||
private table: ValueOf<RelatableTable> = TABLE_MAP[this.types[0]];
|
||||
|
||||
constructor(
|
||||
private readonly db: Database,
|
||||
private readonly reference: ItemReference | ItemReferences,
|
||||
private readonly types: TType[] | undefined,
|
||||
private readonly types: TType[],
|
||||
private readonly direction: "from" | "to"
|
||||
) {}
|
||||
|
||||
get selector() {
|
||||
if (!this.types)
|
||||
throw new Error("Cannot use selector when no tables are specified.");
|
||||
if (this.types.length > 1)
|
||||
throw new Error(
|
||||
"Cannot use selector when more than 1 tables are specified."
|
||||
);
|
||||
const table: ValueOf<RelatableTable> = TABLE_MAP[this.types[0]];
|
||||
return new FilteredSelector<ItemMap[TType]>(
|
||||
table,
|
||||
this.table,
|
||||
this.db
|
||||
.sql()
|
||||
.selectFrom<keyof DatabaseSchema>(table)
|
||||
.selectFrom<keyof DatabaseSchema>(this.table)
|
||||
.where("id", "in", (b) =>
|
||||
b
|
||||
.selectFrom("relations")
|
||||
@@ -367,12 +356,10 @@ class RelationsArray<TType extends keyof RelatableTable> {
|
||||
) => {
|
||||
if (this.direction === "to") {
|
||||
return builder
|
||||
.$if(!!this.types, (eb) =>
|
||||
eb.where(
|
||||
"fromType",
|
||||
this.types!.length > 1 ? "in" : "==",
|
||||
this.types!.length > 1 ? this.types : this.types![0]
|
||||
)
|
||||
.where(
|
||||
"fromType",
|
||||
this.types.length > 1 ? "in" : "==",
|
||||
this.types.length > 1 ? this.types : this.types[0]
|
||||
)
|
||||
.where("toType", "==", this.reference.type)
|
||||
.where(
|
||||
@@ -383,12 +370,12 @@ class RelationsArray<TType extends keyof RelatableTable> {
|
||||
: this.reference.id
|
||||
)
|
||||
.$if(
|
||||
!!this.types?.includes("note" as TType) &&
|
||||
this.types.includes("note" as TType) &&
|
||||
this.db.trash.cache.notes.length > 0,
|
||||
(b) => b.where("fromId", "not in", this.db.trash.cache.notes)
|
||||
)
|
||||
.$if(
|
||||
!!this.types?.includes("notebook" as TType) &&
|
||||
this.types.includes("notebook" as TType) &&
|
||||
this.db.trash.cache.notebooks.length > 0,
|
||||
(b) => b.where("fromId", "not in", this.db.trash.cache.notebooks)
|
||||
)
|
||||
@@ -396,12 +383,10 @@ class RelationsArray<TType extends keyof RelatableTable> {
|
||||
.$narrowType<{ id: string }>();
|
||||
} else {
|
||||
return builder
|
||||
.$if(!!this.types, (eb) =>
|
||||
eb.where(
|
||||
"toType",
|
||||
this.types!.length > 1 ? "in" : "==",
|
||||
this.types!.length > 1 ? this.types : this.types![0]
|
||||
)
|
||||
.where(
|
||||
"toType",
|
||||
this.types.length > 1 ? "in" : "==",
|
||||
this.types.length > 1 ? this.types : this.types[0]
|
||||
)
|
||||
.where("fromType", "==", this.reference.type)
|
||||
.where(
|
||||
@@ -412,12 +397,12 @@ class RelationsArray<TType extends keyof RelatableTable> {
|
||||
: this.reference.id
|
||||
)
|
||||
.$if(
|
||||
!!this.types?.includes("note" as TType) &&
|
||||
this.types.includes("note" as TType) &&
|
||||
this.db.trash.cache.notes.length > 0,
|
||||
(b) => b.where("toId", "not in", this.db.trash.cache.notes)
|
||||
)
|
||||
.$if(
|
||||
!!this.types?.includes("notebook" as TType) &&
|
||||
this.types.includes("notebook" as TType) &&
|
||||
this.db.trash.cache.notebooks.length > 0,
|
||||
(b) => b.where("toId", "not in", this.db.trash.cache.notebooks)
|
||||
)
|
||||
|
||||
@@ -184,7 +184,7 @@ export class Tiptap {
|
||||
this.data = new HTMLRewriter({
|
||||
ontag: (name, attr) => {
|
||||
const href = attr[ATTRIBUTES.href];
|
||||
if (name === "a" && href && href.startsWith("nn://")) {
|
||||
if (name === "a" && href.startsWith("nn://")) {
|
||||
const link = resolve(href);
|
||||
if (!link) return;
|
||||
attr[ATTRIBUTES.href] = link;
|
||||
@@ -244,10 +244,7 @@ export class Tiptap {
|
||||
if (types.includes("internalLinks")) {
|
||||
result.internalLinks.push(
|
||||
...findAll(
|
||||
(e) =>
|
||||
e.tagName === "a" &&
|
||||
!!e.attribs.href &&
|
||||
e.attribs.href.startsWith("nn://"),
|
||||
(e) => e.tagName === "a" && e.attribs.href.startsWith("nn://"),
|
||||
document.childNodes
|
||||
)
|
||||
.map((e) => parseInternalLink(e.attribs.href))
|
||||
|
||||
@@ -289,26 +289,6 @@ export class NNMigrationProvider implements MigrationProvider {
|
||||
async up(db) {
|
||||
await rebuildSearchIndex(db);
|
||||
}
|
||||
},
|
||||
"3": {
|
||||
async up(db) {
|
||||
await db
|
||||
.updateTable("notes")
|
||||
.where("id", "in", (eb) =>
|
||||
eb
|
||||
.selectFrom("content")
|
||||
.select("noteId as id")
|
||||
.where((eb) =>
|
||||
eb.or([
|
||||
eb("conflicted", "is", null),
|
||||
eb("conflicted", "==", false)
|
||||
])
|
||||
)
|
||||
.$castTo<string | null>()
|
||||
)
|
||||
.set({ conflicted: false })
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ export function parseInternalLink(link: string): InternalLink | undefined {
|
||||
}
|
||||
|
||||
export function isInternalLink(link: string) {
|
||||
return link && link.startsWith("nn://");
|
||||
return link.startsWith("nn://");
|
||||
}
|
||||
|
||||
function isValidInternalType(type: string): type is InternalLinkType {
|
||||
|
||||
@@ -19,13 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { Extension } from "@tiptap/core";
|
||||
import { Decoration, DecorationSet } from "prosemirror-view";
|
||||
import {
|
||||
EditorState,
|
||||
Plugin,
|
||||
PluginKey,
|
||||
Transaction,
|
||||
TextSelection
|
||||
} from "prosemirror-state";
|
||||
import { EditorState, Plugin, PluginKey, Transaction } from "prosemirror-state";
|
||||
import { SearchSettings } from "../../toolbar/stores/search-store";
|
||||
|
||||
type DispatchFn = (tr: Transaction) => void;
|
||||
@@ -280,58 +274,44 @@ export const SearchReplace = Extension.create<SearchOptions, SearchStorage>({
|
||||
},
|
||||
moveToNextResult:
|
||||
() =>
|
||||
({ state, dispatch }) => {
|
||||
({ state, dispatch, commands }) => {
|
||||
const { selectedIndex, results } = this.storage;
|
||||
if (!results || results.length <= 0) return false;
|
||||
|
||||
let nextIndex = selectedIndex + 1;
|
||||
if (isNaN(nextIndex) || nextIndex >= results.length) nextIndex = 0;
|
||||
|
||||
const { tr } = state;
|
||||
const { from, to } = results[nextIndex];
|
||||
tr.setSelection(
|
||||
TextSelection.create(
|
||||
tr.doc,
|
||||
tr.mapping.map(from),
|
||||
tr.mapping.map(to)
|
||||
)
|
||||
);
|
||||
commands.setTextSelection({ from, to });
|
||||
scrollIntoView();
|
||||
|
||||
this.storage.selectedIndex = nextIndex;
|
||||
tr.setMeta("selectedIndex", nextIndex);
|
||||
state.tr.setMeta("selectedIndex", nextIndex);
|
||||
if (dispatch) updateView(state, dispatch);
|
||||
return true;
|
||||
},
|
||||
moveToPreviousResult:
|
||||
() =>
|
||||
({ state, dispatch }) => {
|
||||
({ state, dispatch, commands }) => {
|
||||
const { selectedIndex, results } = this.storage;
|
||||
if (!results || results.length <= 0) return false;
|
||||
|
||||
let prevIndex = selectedIndex - 1;
|
||||
if (isNaN(prevIndex) || prevIndex < 0) prevIndex = results.length - 1;
|
||||
|
||||
const { tr } = state;
|
||||
const { from, to } = results[prevIndex];
|
||||
tr.setSelection(
|
||||
TextSelection.create(
|
||||
tr.doc,
|
||||
tr.mapping.map(from),
|
||||
tr.mapping.map(to)
|
||||
)
|
||||
);
|
||||
commands.setTextSelection({ from, to });
|
||||
scrollIntoView();
|
||||
|
||||
this.storage.selectedIndex = prevIndex;
|
||||
tr.setMeta("selectedIndex", prevIndex);
|
||||
state.tr.setMeta("selectedIndex", prevIndex);
|
||||
if (dispatch) updateView(state, dispatch);
|
||||
|
||||
return true;
|
||||
},
|
||||
replace:
|
||||
(term) =>
|
||||
({ chain, tr, dispatch }) => {
|
||||
({ commands, tr, dispatch }) => {
|
||||
const { selectedIndex, results } = this.storage;
|
||||
|
||||
if (!dispatch || !results || results.length <= 0) return false;
|
||||
@@ -340,7 +320,9 @@ export const SearchReplace = Extension.create<SearchOptions, SearchStorage>({
|
||||
const { from, to } = results[index];
|
||||
|
||||
tr.insertText(term, from, to);
|
||||
return chain().moveToNextResult().run();
|
||||
dispatch(tr);
|
||||
commands.moveToNextResult();
|
||||
return true;
|
||||
},
|
||||
replaceAll:
|
||||
(term) =>
|
||||
|
||||
@@ -21,7 +21,7 @@ import { useLayoutEffect } from "react";
|
||||
import { FloatingMenuProps } from "./types";
|
||||
import { SearchReplacePopup } from "../popups/search-replace";
|
||||
import { ResponsivePresenter } from "../../components/responsive";
|
||||
import { getToolbarElement } from "../utils/dom";
|
||||
import { getEditorContainer, getToolbarElement } from "../utils/dom";
|
||||
import { useEditorSearchStore } from "../stores/search-store";
|
||||
|
||||
export function SearchReplaceFloatingMenu(props: FloatingMenuProps) {
|
||||
@@ -41,11 +41,11 @@ export function SearchReplaceFloatingMenu(props: FloatingMenuProps) {
|
||||
isOpen={isSearching}
|
||||
onClose={() => editor.commands.endSearch()}
|
||||
position={{
|
||||
target: getToolbarElement(),
|
||||
target: editor.isEditable ? getToolbarElement() : getEditorContainer(),
|
||||
isTargetAbsolute: true,
|
||||
location: "below",
|
||||
location: editor.isEditable ? "below" : "top",
|
||||
align: "end",
|
||||
yOffset: 5
|
||||
yOffset: editor.isEditable ? 5 : -50
|
||||
}}
|
||||
blocking={false}
|
||||
focusOnRender={false}
|
||||
|
||||
@@ -61,9 +61,7 @@ export function Toolbar(props: ToolbarProps) {
|
||||
? editor.isEditable
|
||||
? [...MOBILE_STATIC_TOOLBAR_GROUPS, ...tools]
|
||||
: READONLY_MOBILE_STATIC_TOOLBAR_GROUPS
|
||||
: editor.isEditable
|
||||
? [...STATIC_TOOLBAR_GROUPS, ...tools]
|
||||
: [],
|
||||
: [...STATIC_TOOLBAR_GROUPS, ...tools],
|
||||
[tools, editor.isEditable, isMobile]
|
||||
);
|
||||
|
||||
|
||||
@@ -282,10 +282,9 @@ img.ProseMirror-separator {
|
||||
background-color: var(--paragraph, var(--nn_primary_paragraph)) !important;
|
||||
}
|
||||
|
||||
.search-result.selected,
|
||||
.search-result.selected::selection {
|
||||
.search-result.selected {
|
||||
background-color: var(--accent-secondary) !important;
|
||||
color: var(--accentForeground-secondary) !important;
|
||||
color: var(--accentForeground-secondary);
|
||||
}
|
||||
|
||||
.search-result {
|
||||
|
||||
Reference in New Issue
Block a user