From fa143e3c72d0770e7d3896b2907c6eb75ac60896 Mon Sep 17 00:00:00 2001
From: windingwind <33902321+windingwind@users.noreply.github.com>
Date: Wed, 30 Aug 2023 22:12:23 +0800
Subject: [PATCH] add: support note link with section
---
addon/locale/en-US/addon.ftl | 3 +-
addon/locale/ru-RU/addon.ftl | 3 +-
addon/locale/zh-CN/addon.ftl | 3 +-
src/api.ts | 4 +
src/hooks.ts | 2 +
src/modules/editor/toolbar.ts | 165 +++++++++++++++++++------------
src/modules/noteLink.ts | 3 +-
src/modules/workspace/content.ts | 10 +-
src/modules/workspace/message.ts | 3 +-
src/utils/editor.ts | 43 +++++++-
src/utils/link.ts | 21 ++--
11 files changed, 183 insertions(+), 77 deletions(-)
diff --git a/addon/locale/en-US/addon.ftl b/addon/locale/en-US/addon.ftl
index 3ddc5c0..10b3c22 100644
--- a/addon/locale/en-US/addon.ftl
+++ b/addon/locale/en-US/addon.ftl
@@ -91,7 +91,8 @@ editor-toolbar-settings-openWorkspace = Open Note Workspace
editor-toolbar-settings-setWorkspace = Set as Workspace Note
editor-toolbar-settings-previewInWorkspace = Preview in Workspace
editor-toolbar-settings-insertTemplate = Insert Template to Cursor Line
-editor-toolbar-settings-copyLink = Copy Note Link
+editor-toolbar-settings-copyLink = Copy Note Link at Line ({ $line })
+editor-toolbar-settings-copyLinkAtSection = Copy Note Link at Section ({ $section })
editor-toolbar-settings-openParent = Open Attachment
editor-toolbar-settings-export = Export Current Note...
editor-toolbar-settings-refreshSyncing = Sync Now
diff --git a/addon/locale/ru-RU/addon.ftl b/addon/locale/ru-RU/addon.ftl
index 3e1556b..2116512 100644
--- a/addon/locale/ru-RU/addon.ftl
+++ b/addon/locale/ru-RU/addon.ftl
@@ -91,7 +91,8 @@ editor-toolbar-settings-openWorkspace=Открыть пространство з
editor-toolbar-settings-setWorkspace=Установить как заметку раб. пространства
editor-toolbar-settings-previewInWorkspace=Предпросмотр в рабочем пространстве
editor-toolbar-settings-insertTemplate=Вставить шаблон в строку курсора
-editor-toolbar-settings-copyLink=Скопировать ссылку заметки
+editor-toolbar-settings-copyLink = Copy Note Link at Line ({ $line })
+editor-toolbar-settings-copyLinkAtSection = Copy Note Link at Section ({ $section })
editor-toolbar-settings-openParent=Открыть вложение
editor-toolbar-settings-export=Экспортировать текущую заметку...
editor-toolbar-settings-refreshSyncing=Синхронизировать сейчас
diff --git a/addon/locale/zh-CN/addon.ftl b/addon/locale/zh-CN/addon.ftl
index 35cd80e..8231687 100644
--- a/addon/locale/zh-CN/addon.ftl
+++ b/addon/locale/zh-CN/addon.ftl
@@ -91,7 +91,8 @@ editor-toolbar-settings-openWorkspace=打开笔记工作区
editor-toolbar-settings-setWorkspace=设为工作区笔记
editor-toolbar-settings-previewInWorkspace=在工作区预览
editor-toolbar-settings-insertTemplate=插入模板到光标行
-editor-toolbar-settings-copyLink=复制笔记链接
+editor-toolbar-settings-copyLink=复制当前行({ $line })笔记链接
+editor-toolbar-settings-copyLinkAtSection=复制当前节({ $section })笔记链接
editor-toolbar-settings-openParent=打开附件
editor-toolbar-settings-export=导出当前笔记...
editor-toolbar-settings-refreshSyncing=立即同步
diff --git a/src/api.ts b/src/api.ts
index f85b259..7800710 100644
--- a/src/api.ts
+++ b/src/api.ts
@@ -51,8 +51,10 @@ import {
insert,
del,
scroll,
+ scrollToSection,
getTextBetweenLines,
getLineAtCursor,
+ getSectionAtCursor,
getPositionAtLine,
getTextBetween,
getRangeAtCursor,
@@ -124,8 +126,10 @@ const editor = {
move,
replace,
scroll,
+ scrollToSection,
getRangeAtCursor,
getLineAtCursor,
+ getSectionAtCursor,
getPositionAtLine,
getTextBetween,
getTextBetweenLines,
diff --git a/src/hooks.ts b/src/hooks.ts
index b20d945..4ac2ead 100644
--- a/src/hooks.ts
+++ b/src/hooks.ts
@@ -178,6 +178,7 @@ function onOpenNote(
mode: "auto" | "preview" | "workspace" | "standalone" = "auto",
options: {
lineIndex?: number;
+ sectionName?: string;
} = {},
) {
const noteItem = Zotero.Items.get(noteId);
@@ -217,6 +218,7 @@ function onSetWorkspaceNote(
type: "main" | "preview" = "main",
options: {
lineIndex?: number;
+ sectionName?: string;
} = {},
) {
if (type === "main") {
diff --git a/src/modules/editor/toolbar.ts b/src/modules/editor/toolbar.ts
index 439b21d..229b16c 100644
--- a/src/modules/editor/toolbar.ts
+++ b/src/modules/editor/toolbar.ts
@@ -1,6 +1,6 @@
import { config } from "../../../package.json";
import { ICONS } from "../../utils/config";
-import { getLineAtCursor } from "../../utils/editor";
+import { getLineAtCursor, getSectionAtCursor } from "../../utils/editor";
import { showHint } from "../../utils/hint";
import { getNoteLink, getNoteLinkParams } from "../../utils/link";
import { getString } from "../../utils/locale";
@@ -49,6 +49,8 @@ export async function initEditorToolbar(editor: Zotero.EditorInstance) {
return;
}
+ const currentLine = getLineAtCursor(editor);
+ const currentSection = getSectionAtCursor(editor);
const settingsMenuData: PopupData[] = [
{
id: makeId("settings-openWorkspace"),
@@ -72,71 +74,110 @@ export async function initEditorToolbar(editor: Zotero.EditorInstance) {
addon.hooks.onSetWorkspaceNote(e.editor._item.id, "preview");
},
},
- {
- type: "splitter",
- },
- {
- id: makeId("settings-export"),
- text: getString("editor.toolbar.settings.export"),
- callback: (e) => {
- if (addon.api.sync.isSyncNote(noteItem.id)) {
- addon.hooks.onShowSyncInfo(noteItem.id);
- } else {
- addon.hooks.onShowExportNoteOptions([noteItem.id]);
- }
- },
- },
- {
- type: "splitter",
- },
- {
- id: makeId("settings-insertTemplate"),
- text: getString("editor.toolbar.settings.insertTemplate"),
- callback: (e) => {
- addon.hooks.onShowTemplatePicker("insert", {
- noteId: e.editor._item.id,
- lineIndex: getLineAtCursor(e.editor),
- });
- },
- },
- {
- type: "splitter",
- },
- {
- id: makeId("settings-copyLink"),
- text: getString("editor.toolbar.settings.copyLink"),
- callback: (e) => {
- const link =
- getNoteLink(e.editor._item, {
- lineIndex: getLineAtCursor(e.editor),
- }) || "";
- new ztoolkit.Clipboard()
- .addText(link, "text/unicode")
- .addText(
- `${
- e.editor._item.getNoteTitle().trim() || link
- }`,
- "text/html",
- )
- .copy();
- showHint(`Link ${link} copied`);
- },
- },
];
+ if (currentLine >= 0) {
+ settingsMenuData.push(
+ ...([
+ {
+ type: "splitter",
+ },
+ {
+ id: makeId("settings-export"),
+ text: getString("editor.toolbar.settings.export"),
+ callback: (e) => {
+ if (addon.api.sync.isSyncNote(noteItem.id)) {
+ addon.hooks.onShowSyncInfo(noteItem.id);
+ } else {
+ addon.hooks.onShowExportNoteOptions([noteItem.id]);
+ }
+ },
+ },
+ {
+ type: "splitter",
+ },
+ {
+ id: makeId("settings-insertTemplate"),
+ text: getString("editor.toolbar.settings.insertTemplate"),
+ callback: (e) => {
+ addon.hooks.onShowTemplatePicker("insert", {
+ noteId: e.editor._item.id,
+ lineIndex: currentLine,
+ });
+ },
+ },
+ {
+ type: "splitter",
+ },
+ {
+ id: makeId("settings-copyLink"),
+ text: getString("editor.toolbar.settings.copyLink", {
+ args: {
+ line: currentLine,
+ },
+ }),
+ callback: (e) => {
+ const link =
+ getNoteLink(e.editor._item, {
+ lineIndex: currentLine,
+ }) || "";
+ new ztoolkit.Clipboard()
+ .addText(link, "text/unicode")
+ .addText(
+ `${
+ e.editor._item.getNoteTitle().trim() || link
+ }`,
+ "text/html",
+ )
+ .copy();
+ showHint(`Link ${link} copied`);
+ },
+ },
+ {
+ id: makeId("settings-copyLinkAtSection"),
+ text: getString("editor.toolbar.settings.copyLinkAtSection", {
+ args: {
+ section: currentSection,
+ },
+ }),
+ callback: (e) => {
+ const link =
+ getNoteLink(e.editor._item, {
+ sectionName: currentSection,
+ }) || "";
+ new ztoolkit.Clipboard()
+ .addText(link, "text/unicode")
+ .addText(
+ `${
+ e.editor._item.getNoteTitle().trim() || link
+ }`,
+ "text/html",
+ )
+ .copy();
+ showHint(`Link ${link} copied`);
+ },
+ },
+ ]),
+ );
+ }
+
const parentAttachment = await noteItem.parentItem?.getBestAttachment();
if (parentAttachment) {
- settingsMenuData.push({
- type: "splitter",
- });
- settingsMenuData.push({
- id: makeId("settings-openParent"),
- text: getString("editor.toolbar.settings.openParent"),
- callback: (e) => {
- ZoteroPane.viewAttachment([parentAttachment.id]);
- Zotero.Notifier.trigger("open", "file", parentAttachment.id);
- },
- });
+ settingsMenuData.push(
+ ...([
+ {
+ type: "splitter",
+ },
+ {
+ id: makeId("settings-openParent"),
+ text: getString("editor.toolbar.settings.openParent"),
+ callback: (e) => {
+ ZoteroPane.viewAttachment([parentAttachment.id]);
+ Zotero.Notifier.trigger("open", "file", parentAttachment.id);
+ },
+ },
+ ]),
+ );
}
if (addon.api.sync.isSyncNote(noteItem.id)) {
diff --git a/src/modules/noteLink.ts b/src/modules/noteLink.ts
index 653e9f3..368e724 100644
--- a/src/modules/noteLink.ts
+++ b/src/modules/noteLink.ts
@@ -7,7 +7,8 @@ export function registerNoteLinkProxyHandler() {
const linkParams = getNoteLinkParams(uri.spec);
if (linkParams.noteItem) {
addon.hooks.onOpenNote(linkParams.noteItem.id, "auto", {
- lineIndex: linkParams.lineIndex || undefined,
+ lineIndex: linkParams.lineIndex,
+ sectionName: linkParams.sectionName,
});
}
},
diff --git a/src/modules/workspace/content.ts b/src/modules/workspace/content.ts
index 1f2a863..d7ab9e0 100644
--- a/src/modules/workspace/content.ts
+++ b/src/modules/workspace/content.ts
@@ -1,6 +1,5 @@
import { config } from "../../../package.json";
import { ICONS } from "../../utils/config";
-import { scroll } from "../../utils/editor";
import { getString } from "../../utils/locale";
import { getNoteTreeFlattened } from "../../utils/note";
import { getPref } from "../../utils/prefs";
@@ -227,6 +226,7 @@ export async function initWorkspaceEditor(
noteId: number,
options: {
lineIndex?: number;
+ sectionName?: string;
} = {},
) {
const noteItem = Zotero.Items.get(noteId);
@@ -252,7 +252,13 @@ export async function initWorkspaceEditor(
await waitUtilAsync(() => Boolean(editorElem._editorInstance));
await editorElem._editorInstance._initPromise;
if (typeof options.lineIndex === "number") {
- scroll(editorElem._editorInstance, options.lineIndex);
+ addon.api.editor.scroll(editorElem._editorInstance, options.lineIndex);
+ }
+ if (typeof options.sectionName === "string") {
+ addon.api.editor.scrollToSection(
+ editorElem._editorInstance,
+ options.sectionName,
+ );
}
return;
}
diff --git a/src/modules/workspace/message.ts b/src/modules/workspace/message.ts
index f24c5ae..61ae324 100644
--- a/src/modules/workspace/message.ts
+++ b/src/modules/workspace/message.ts
@@ -1,7 +1,6 @@
import {
getEditorInstance,
moveHeading,
- scroll,
updateHeadingTextAtLine,
} from "../../utils/editor";
import { showHintWithLink } from "../../utils/hint";
@@ -19,7 +18,7 @@ export async function messageHandler(ev: MessageEvent) {
if (!editor) {
return;
}
- scroll(editor, ev.data.lineIndex);
+ addon.api.editor.scroll(editor, ev.data.lineIndex);
return;
}
case "openNote": {
diff --git a/src/utils/editor.ts b/src/utils/editor.ts
index 2e357fe..83852f1 100644
--- a/src/utils/editor.ts
+++ b/src/utils/editor.ts
@@ -1,5 +1,6 @@
import TreeModel = require("tree-model");
import { TextSelection } from "prosemirror-state";
+import { getNoteTreeFlattened } from "./note";
export {
insert,
@@ -7,12 +8,14 @@ export {
move,
replace,
scroll,
+ scrollToSection,
getEditorInstance,
moveHeading,
updateHeadingTextAtLine,
getEditorCore,
getRangeAtCursor,
getLineAtCursor,
+ getSectionAtCursor,
getPositionAtLine,
getPositionAtCursor,
getURLAtCursor,
@@ -130,6 +133,16 @@ function scroll(editor: Zotero.EditorInstance, lineIndex: number) {
core.view.dom.parentElement?.scrollTo(0, offset);
}
+function scrollToSection(editor: Zotero.EditorInstance, sectionName: string) {
+ const item = editor._item;
+ const sectionTree = getNoteTreeFlattened(item);
+ const sectionNode = sectionTree.find(
+ (node) => node.model.name.trim() === sectionName.trim(),
+ );
+ if (!sectionNode) return;
+ scroll(editor, sectionNode.model.lineIndex);
+}
+
function getEditorInstance(noteId: number) {
const editor = Zotero.Notes._editorInstances.find(
(e) =>
@@ -150,7 +163,11 @@ function getEditorAPI(editor: Zotero.EditorInstance) {
function getPositionAtCursor(editor: Zotero.EditorInstance) {
const selection = getEditorCore(editor).view.state.selection;
- return selection.$anchor.after(selection.$anchor.depth);
+ try {
+ return selection.$anchor.after(selection.$anchor.depth);
+ } catch (e) {
+ return -1;
+ }
}
function getRangeAtCursor(editor: Zotero.EditorInstance) {
@@ -163,6 +180,9 @@ function getRangeAtCursor(editor: Zotero.EditorInstance) {
function getLineAtCursor(editor: Zotero.EditorInstance) {
const position = getPositionAtCursor(editor);
+ if (position < 0) {
+ return -1;
+ }
const lastPos = getEditorCore(editor).view.state.tr.doc.content.size;
let i = 0;
let currentPos = getPositionAtLine(editor, 0);
@@ -176,6 +196,27 @@ function getLineAtCursor(editor: Zotero.EditorInstance) {
return i;
}
+function getSectionAtCursor(editor: Zotero.EditorInstance): string | undefined {
+ const lineIndex = getLineAtCursor(editor);
+ if (lineIndex < 0) return undefined;
+ const item = editor._item;
+ const sectionTree = getNoteTreeFlattened(item);
+ let sectionNode;
+ for (let i = 0; i < sectionTree.length; i++) {
+ if (
+ // Is before cursor
+ sectionTree[i].model.lineIndex <= lineIndex &&
+ // Is last node, or next node is after cursor
+ (i === sectionTree.length - 1 ||
+ sectionTree[i + 1].model.lineIndex > lineIndex)
+ ) {
+ sectionNode = sectionTree[i];
+ break;
+ }
+ }
+ return sectionNode?.model.name;
+}
+
function getDOMAtLine(
editor: Zotero.EditorInstance,
lineIndex: number,
diff --git a/src/utils/link.ts b/src/utils/link.ts
index 71f9c71..7474828 100644
--- a/src/utils/link.ts
+++ b/src/utils/link.ts
@@ -18,8 +18,9 @@ export function getNoteLinkParams(link: string) {
noteItem: Zotero.Items.getByLibraryAndKey(libraryID, noteKey || "") as
| Zotero.Item
| false,
- ignore: Boolean(url.searchParams.get("ignore")),
- lineIndex: typeof line === "string" ? parseInt(line) : null,
+ ignore: Boolean(url.searchParams.get("ignore")) || undefined,
+ lineIndex: typeof line === "string" ? parseInt(line) : undefined,
+ sectionName: url.searchParams.get("section") || undefined,
};
} catch (e: unknown) {
return {
@@ -27,8 +28,9 @@ export function getNoteLinkParams(link: string) {
libraryID: -1,
noteKey: undefined,
noteItem: false as const,
- ignore: null,
- lineIndex: null,
+ ignore: undefined,
+ lineIndex: undefined,
+ sectionName: undefined,
};
}
}
@@ -36,8 +38,9 @@ export function getNoteLinkParams(link: string) {
export function getNoteLink(
noteItem: Zotero.Item,
options: {
- ignore?: boolean | null;
- lineIndex?: number | null;
+ ignore?: boolean;
+ lineIndex?: number;
+ sectionName?: string;
} = {},
) {
const libraryID = noteItem.libraryID;
@@ -71,6 +74,12 @@ export function getNoteLink(
if (options.lineIndex) {
link = addParam(link, `line=${options.lineIndex}`);
}
+ if (options.sectionName) {
+ link = addParam(
+ link,
+ `section=${encodeURIComponent(options.sectionName)}`,
+ );
+ }
}
return link;
}