Compare commits

..

1 Commits

Author SHA1 Message Date
Ammar Ahmed
b4b07eb80f mobile: fix share widget title issues
1. Do not change title of note when choosing append to note
2. Fix color of title in widget
2024-05-16 12:06:02 +05:00
39 changed files with 142 additions and 243 deletions

View File

@@ -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": {

View File

@@ -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",

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -59,14 +59,14 @@ const recoveryKeyMessage = {
() => {
eSendEvent(eOpenRecoveryKeyDialog);
},
false,
true,
async () => {
SettingsService.set({
recoveryKeySaved: true
});
clearMessage();
},
"Cancel"
"I have saved my key already"
);
},
data: {},

View File

@@ -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'

View File

@@ -37,10 +37,4 @@ allprojects {
maven { url 'https://www.jitpack.io' }
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
kotlinOptions {
jvmTarget = "11"
}
}
}
}

View File

@@ -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)";

View File

@@ -1,6 +1,6 @@
{
"name": "@notesnook/mobile",
"version": "3.0.7",
"version": "3.0.4",
"private": true,
"license": "GPL-3.0-or-later",
"workspaces": [

View File

@@ -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);
});

View File

@@ -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": {

View File

@@ -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/",

View File

@@ -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;

View File

@@ -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
);
});

View File

@@ -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(

View File

@@ -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);
});

View File

@@ -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());
}
});

View File

@@ -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({

View File

@@ -307,7 +307,7 @@ function CalltoAction({ action, variant, sx, dismissAnnouncement }) {
break;
}
case "backup": {
await createBackup();
await createBackup(true);
break;
}
default: {

View File

@@ -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

View File

@@ -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;
}}

View File

@@ -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

View File

@@ -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,

View File

@@ -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");
}}
>

View File

@@ -1,3 +0,0 @@
- Bug fixes and performance improvements
Thank you for using Notesnook!

View File

@@ -1,3 +0,0 @@
- Bug fixes and performance improvements
Thank you for using Notesnook!

View File

@@ -1,3 +0,0 @@
- Bug fixes and performance improvements
Thank you for using Notesnook!

View File

@@ -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", () =>

View File

@@ -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 })

View File

@@ -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 }
);
}
}
}

View File

@@ -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)
)

View File

@@ -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))

View File

@@ -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();
}
}
};
}

View File

@@ -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 {

View File

@@ -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) =>

View File

@@ -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}

View File

@@ -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]
);

View File

@@ -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 {