add: support note link with section

This commit is contained in:
windingwind 2023-08-30 22:12:23 +08:00
parent 22dbe5dbb3
commit fa143e3c72
11 changed files with 183 additions and 77 deletions

View File

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

View File

@ -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=Синхронизировать сейчас

View File

@ -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=立即同步

View File

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

View File

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

View File

@ -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(
`<a href="${link}">${
e.editor._item.getNoteTitle().trim() || link
}</a>`,
"text/html",
)
.copy();
showHint(`Link ${link} copied`);
},
},
];
if (currentLine >= 0) {
settingsMenuData.push(
...(<PopupData[]>[
{
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(
`<a href="${link}">${
e.editor._item.getNoteTitle().trim() || link
}</a>`,
"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(
`<a href="${link}#${currentSection}">${
e.editor._item.getNoteTitle().trim() || link
}</a>`,
"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(
...(<PopupData[]>[
{
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)) {

View File

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

View File

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

View File

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

View File

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

View File

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