From 357e9a77bd70c16cfba4a50c3ff079c773271274 Mon Sep 17 00:00:00 2001
From: windingwind <33902321+windingwind@users.noreply.github.com>
Date: Tue, 16 Apr 2024 00:41:51 +0800
Subject: [PATCH] add: link creator
refactor: link note -> link creator
add: link to mode in link creator
fix: locale
---
addon/chrome/content/linkCreator.xhtml | 54 +++
addon/chrome/content/linkNote.xhtml | 98 -----
.../styles/linkCreator/inboundCreator.css | 19 +
.../styles/linkCreator/linkCreator.css | 14 +
.../styles/linkCreator/noteOutline.css | 7 +
.../content/styles/linkCreator/notePicker.css | 37 ++
.../styles/linkCreator/notePreview.css | 3 +
.../styles/linkCreator/outboundCreator.css | 19 +
.../styles/{ => linkCreator}/toolbar.css | 0
addon/chrome/content/styles/linkNote.css | 30 --
addon/chrome/content/styles/notePicker.css | 43 ---
.../styles/{ => workspace}/context.css | 0
.../styles/{ => workspace}/details.css | 0
.../styles/{ => workspace}/outline.css | 26 +-
.../styles/{ => workspace}/related.css | 0
.../styles/{ => workspace}/relation.css | 0
.../styles/{ => workspace}/workspace.css | 0
addon/locale/en-US/linkCreator.ftl | 24 ++
addon/locale/it-IT/linkCreator.ftl | 24 ++
addon/locale/it-IT/outline.ftl | 20 ++
addon/locale/ru-RU/linkCreator.ftl | 24 ++
addon/locale/ru-RU/outline.ftl | 20 ++
addon/locale/tr-TR/linkCreator.ftl | 24 ++
addon/locale/tr-TR/outline.ftl | 20 ++
addon/locale/zh-CN/linkCreator.ftl | 24 ++
addon/locale/zh-CN/noteRelation.ftl | 2 +-
addon/locale/zh-CN/outline.ftl | 20 ++
src/elements/base.ts | 35 +-
src/elements/linkCreator/inboundCreator.ts | 250 +++++++++++++
src/elements/{ => linkCreator}/notePicker.ts | 47 +--
src/elements/linkCreator/notePreview.ts | 109 ++++++
src/elements/linkCreator/outboundCreator.ts | 259 +++++++++++++
src/elements/linkCreator/outlinePicker.ts | 165 +++++++++
.../{context.ts => workspace/contextPane.ts} | 6 +-
src/elements/{ => workspace}/detailsPane.ts | 6 +-
src/elements/{ => workspace}/outlinePane.ts | 22 +-
src/elements/{ => workspace}/related.ts | 4 +-
src/elements/{ => workspace}/workspace.ts | 10 +-
src/extras/customElements.ts | 22 +-
src/extras/linkCreator.ts | 85 +++++
src/extras/linkNote.ts | 339 ------------------
src/modules/editor/toolbar.ts | 8 +-
src/modules/workspace/link.ts | 2 +-
src/modules/workspace/relation.ts | 2 +-
src/utils/{linkNote.ts => linkCreator.ts} | 12 +-
45 files changed, 1348 insertions(+), 587 deletions(-)
create mode 100644 addon/chrome/content/linkCreator.xhtml
delete mode 100644 addon/chrome/content/linkNote.xhtml
create mode 100644 addon/chrome/content/styles/linkCreator/inboundCreator.css
create mode 100644 addon/chrome/content/styles/linkCreator/linkCreator.css
create mode 100644 addon/chrome/content/styles/linkCreator/noteOutline.css
create mode 100644 addon/chrome/content/styles/linkCreator/notePicker.css
create mode 100644 addon/chrome/content/styles/linkCreator/notePreview.css
create mode 100644 addon/chrome/content/styles/linkCreator/outboundCreator.css
rename addon/chrome/content/styles/{ => linkCreator}/toolbar.css (100%)
delete mode 100644 addon/chrome/content/styles/linkNote.css
delete mode 100644 addon/chrome/content/styles/notePicker.css
rename addon/chrome/content/styles/{ => workspace}/context.css (100%)
rename addon/chrome/content/styles/{ => workspace}/details.css (100%)
rename addon/chrome/content/styles/{ => workspace}/outline.css (71%)
rename addon/chrome/content/styles/{ => workspace}/related.css (100%)
rename addon/chrome/content/styles/{ => workspace}/relation.css (100%)
rename addon/chrome/content/styles/{ => workspace}/workspace.css (100%)
create mode 100644 addon/locale/en-US/linkCreator.ftl
create mode 100644 addon/locale/it-IT/linkCreator.ftl
create mode 100644 addon/locale/it-IT/outline.ftl
create mode 100644 addon/locale/ru-RU/linkCreator.ftl
create mode 100644 addon/locale/ru-RU/outline.ftl
create mode 100644 addon/locale/tr-TR/linkCreator.ftl
create mode 100644 addon/locale/tr-TR/outline.ftl
create mode 100644 addon/locale/zh-CN/linkCreator.ftl
create mode 100644 addon/locale/zh-CN/outline.ftl
create mode 100644 src/elements/linkCreator/inboundCreator.ts
rename src/elements/{ => linkCreator}/notePicker.ts (89%)
create mode 100644 src/elements/linkCreator/notePreview.ts
create mode 100644 src/elements/linkCreator/outboundCreator.ts
create mode 100644 src/elements/linkCreator/outlinePicker.ts
rename src/elements/{context.ts => workspace/contextPane.ts} (84%)
rename src/elements/{ => workspace}/detailsPane.ts (86%)
rename src/elements/{ => workspace}/outlinePane.ts (94%)
rename src/elements/{ => workspace}/related.ts (96%)
rename src/elements/{ => workspace}/workspace.ts (93%)
create mode 100644 src/extras/linkCreator.ts
delete mode 100644 src/extras/linkNote.ts
rename src/utils/{linkNote.ts => linkCreator.ts} (75%)
diff --git a/addon/chrome/content/linkCreator.xhtml b/addon/chrome/content/linkCreator.xhtml
new file mode 100644
index 0000000..c1ced93
--- /dev/null
+++ b/addon/chrome/content/linkCreator.xhtml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addon/chrome/content/linkNote.xhtml b/addon/chrome/content/linkNote.xhtml
deleted file mode 100644
index 74c85e6..0000000
--- a/addon/chrome/content/linkNote.xhtml
+++ /dev/null
@@ -1,98 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addon/chrome/content/styles/linkCreator/inboundCreator.css b/addon/chrome/content/styles/linkCreator/inboundCreator.css
new file mode 100644
index 0000000..fbd1643
--- /dev/null
+++ b/addon/chrome/content/styles/linkCreator/inboundCreator.css
@@ -0,0 +1,19 @@
+bn-inbound-creator {
+ gap: 16px;
+ overflow: auto;
+
+ bn-note-picker {
+ border: var(--material-border);
+ min-width: 600px;
+ }
+
+ bn-note-outline {
+ border: var(--material-border);
+ min-width: 300px;
+ }
+
+ bn-note-preview {
+ border: var(--material-border);
+ min-width: 450px;
+ }
+}
diff --git a/addon/chrome/content/styles/linkCreator/linkCreator.css b/addon/chrome/content/styles/linkCreator/linkCreator.css
new file mode 100644
index 0000000..9763f9c
--- /dev/null
+++ b/addon/chrome/content/styles/linkCreator/linkCreator.css
@@ -0,0 +1,14 @@
+.container {
+ min-height: 0;
+ height: 100%;
+ margin: 0;
+}
+
+.content-container {
+ overflow: auto;
+}
+
+/* TODO: remove fx115 workaround */
+tab {
+ color: unset !important;
+}
diff --git a/addon/chrome/content/styles/linkCreator/noteOutline.css b/addon/chrome/content/styles/linkCreator/noteOutline.css
new file mode 100644
index 0000000..89617de
--- /dev/null
+++ b/addon/chrome/content/styles/linkCreator/noteOutline.css
@@ -0,0 +1,7 @@
+bn-note-outline {
+ flex-direction: column;
+}
+
+#bn-link-insert-position-container {
+ align-items: center;
+}
diff --git a/addon/chrome/content/styles/linkCreator/notePicker.css b/addon/chrome/content/styles/linkCreator/notePicker.css
new file mode 100644
index 0000000..d9bee6d
--- /dev/null
+++ b/addon/chrome/content/styles/linkCreator/notePicker.css
@@ -0,0 +1,37 @@
+bn-note-picker {
+ flex-direction: column;
+
+ #collections-items-container {
+ display: flex;
+ height: 100%;
+ max-height: 50%;
+ border-bottom: var(--material-border);
+ user-select: none;
+ }
+
+ #zotero-collections-tree-container {
+ border-right: var(--material-border);
+ }
+
+ #zotero-collections-tree {
+ background: var(--material-sidepane);
+ }
+
+ #select-items-dialog {
+ display: flex;
+ padding: 0;
+
+ #zotero-select-items-container {
+ gap: 0;
+ }
+
+ #collections-items-container {
+ margin-bottom: 0;
+ }
+ }
+
+ #bn-select-opened-notes-container {
+ min-width: 200px;
+ max-height: 50%;
+ }
+}
diff --git a/addon/chrome/content/styles/linkCreator/notePreview.css b/addon/chrome/content/styles/linkCreator/notePreview.css
new file mode 100644
index 0000000..417b34a
--- /dev/null
+++ b/addon/chrome/content/styles/linkCreator/notePreview.css
@@ -0,0 +1,3 @@
+bn-note-preview {
+ flex-direction: column;
+}
diff --git a/addon/chrome/content/styles/linkCreator/outboundCreator.css b/addon/chrome/content/styles/linkCreator/outboundCreator.css
new file mode 100644
index 0000000..fc71339
--- /dev/null
+++ b/addon/chrome/content/styles/linkCreator/outboundCreator.css
@@ -0,0 +1,19 @@
+bn-outbound-creator {
+ gap: 16px;
+ overflow: auto;
+
+ bn-note-picker {
+ border: var(--material-border);
+ min-width: 600px;
+ }
+
+ bn-note-outline {
+ border: var(--material-border);
+ min-width: 300px;
+ }
+
+ bn-note-preview {
+ border: var(--material-border);
+ min-width: 450px;
+ }
+}
diff --git a/addon/chrome/content/styles/toolbar.css b/addon/chrome/content/styles/linkCreator/toolbar.css
similarity index 100%
rename from addon/chrome/content/styles/toolbar.css
rename to addon/chrome/content/styles/linkCreator/toolbar.css
diff --git a/addon/chrome/content/styles/linkNote.css b/addon/chrome/content/styles/linkNote.css
deleted file mode 100644
index fbe5ddd..0000000
--- a/addon/chrome/content/styles/linkNote.css
+++ /dev/null
@@ -1,30 +0,0 @@
-.container {
- min-height: 0;
- height: 100%;
- margin: 0;
-}
-
-#top-container {
- gap: 16px;
- overflow: auto;
- padding: 2em;
-}
-
-bn-note-picker {
- border: var(--material-border);
- min-width: 600px;
-}
-
-#bn-select-note-outline-container {
- border: var(--material-border);
- min-width: 300px;
-}
-
-#bn-note-preview-container {
- border: var(--material-border);
- min-width: 450px;
-}
-
-#bn-link-insert-position-container {
- align-items: center;
-}
diff --git a/addon/chrome/content/styles/notePicker.css b/addon/chrome/content/styles/notePicker.css
deleted file mode 100644
index 3be12c3..0000000
--- a/addon/chrome/content/styles/notePicker.css
+++ /dev/null
@@ -1,43 +0,0 @@
-bn-note-picker {
- flex-direction: column;
-}
-
-.container {
- min-height: 0;
- height: 100%;
- margin: 0;
-}
-
-#select-items-dialog #zotero-select-items-container {
- gap: 0;
-}
-
-#collections-items-container {
- display: flex;
- height: 100%;
- max-height: 50%;
- border-bottom: var(--material-border);
- user-select: none;
-}
-
-#zotero-collections-tree-container {
- border-right: var(--material-border);
-}
-
-#zotero-collections-tree {
- background: var(--material-sidepane);
-}
-
-#select-items-dialog {
- display: flex;
- padding: 0;
-}
-
-#select-items-dialog #collections-items-container {
- margin-bottom: 0;
-}
-
-#bn-select-opened-notes-container {
- min-width: 200px;
- max-height: 50%;
-}
diff --git a/addon/chrome/content/styles/context.css b/addon/chrome/content/styles/workspace/context.css
similarity index 100%
rename from addon/chrome/content/styles/context.css
rename to addon/chrome/content/styles/workspace/context.css
diff --git a/addon/chrome/content/styles/details.css b/addon/chrome/content/styles/workspace/details.css
similarity index 100%
rename from addon/chrome/content/styles/details.css
rename to addon/chrome/content/styles/workspace/details.css
diff --git a/addon/chrome/content/styles/outline.css b/addon/chrome/content/styles/workspace/outline.css
similarity index 71%
rename from addon/chrome/content/styles/outline.css
rename to addon/chrome/content/styles/workspace/outline.css
index c79e561..6e6f4c9 100644
--- a/addon/chrome/content/styles/outline.css
+++ b/addon/chrome/content/styles/workspace/outline.css
@@ -8,6 +8,19 @@ bn-outline,
bn-outline {
min-width: 100px;
flex-direction: column;
+
+ .zotero-tb-button {
+ width: 28px;
+ height: 28px;
+ margin: 0px 4px 0px 4px;
+ padding: 0px 4px 0px 4px;
+ fill: currentColor;
+ -moz-context-properties: fill, fill-opacity;
+ }
+
+ .zotero-tb-button[type="menu"] {
+ width: 40px;
+ }
}
@media (-moz-platform: macos) {
@@ -22,19 +35,6 @@ bn-outline {
padding: 6px 8px;
}
-bn-outline .zotero-tb-button {
- width: 28px;
- height: 28px;
- margin: 0px 4px 0px 4px;
- padding: 0px 4px 0px 4px;
- fill: currentColor;
- -moz-context-properties: fill, fill-opacity;
-}
-
-bn-outline .zotero-tb-button[type="menu"] {
- width: 40px;
-}
-
#__addonRef__-setOutline {
list-style-image: url("chrome://__addonRef__/content/icons/outline-20.svg");
}
diff --git a/addon/chrome/content/styles/related.css b/addon/chrome/content/styles/workspace/related.css
similarity index 100%
rename from addon/chrome/content/styles/related.css
rename to addon/chrome/content/styles/workspace/related.css
diff --git a/addon/chrome/content/styles/relation.css b/addon/chrome/content/styles/workspace/relation.css
similarity index 100%
rename from addon/chrome/content/styles/relation.css
rename to addon/chrome/content/styles/workspace/relation.css
diff --git a/addon/chrome/content/styles/workspace.css b/addon/chrome/content/styles/workspace/workspace.css
similarity index 100%
rename from addon/chrome/content/styles/workspace.css
rename to addon/chrome/content/styles/workspace/workspace.css
diff --git a/addon/locale/en-US/linkCreator.ftl b/addon/locale/en-US/linkCreator.ftl
new file mode 100644
index 0000000..abd790c
--- /dev/null
+++ b/addon/locale/en-US/linkCreator.ftl
@@ -0,0 +1,24 @@
+title =
+ .title = Link Creator
+tab-inbound =
+ .label = Mention in
+tab-outbound =
+ .label = Link to
+
+inbound-step1-content = Step 1. Mention in note:
+inbound-step2-content = Step 2. Insert to:
+inbound-step3-content = Step 3. Preview:
+inbound-step3-middle =
+ { $show ->
+ [true] mentions
+ *[other] { "" }
+ }
+
+outbound-step1-content = Step 1. Link to note:
+outbound-step2-content = Step 2. Insert to:
+outbound-step3-content = Step 3. Preview:
+outbound-step3-middle =
+ { $show ->
+ [true] links to
+ *[other] { "" }
+ }
diff --git a/addon/locale/it-IT/linkCreator.ftl b/addon/locale/it-IT/linkCreator.ftl
new file mode 100644
index 0000000..34a00d9
--- /dev/null
+++ b/addon/locale/it-IT/linkCreator.ftl
@@ -0,0 +1,24 @@
+title =
+ .title = Link Creator
+tab-inbound =
+ .label = Mention in
+tab-outbound =
+ .label = Link to
+
+inbound-step1-content = Step 1. Mention in note:
+inbound-step2-content = Step 2. Insert link to:
+inbound-step3-content = Step 3. Preview:
+inbound-step3-middle =
+ { $show ->
+ [true] mentions
+ *[other] { "" }
+ }
+
+outbound-step1-content = Step 1. Link to note:
+outbound-step2-content = Step 2. Insert link to:
+outbound-step3-content = Step 3. Preview:
+outbound-step3-middle =
+ { $show ->
+ [true] links to
+ *[other] { "" }
+ }
diff --git a/addon/locale/it-IT/outline.ftl b/addon/locale/it-IT/outline.ftl
new file mode 100644
index 0000000..aa31812
--- /dev/null
+++ b/addon/locale/it-IT/outline.ftl
@@ -0,0 +1,20 @@
+setOutline =
+ .tooltiptext = Change outline mode
+useTreeView =
+ .label = Tree View
+useMindMap =
+ .label = Mind Map
+useBubbleMap =
+ .label = Bubble Map
+saveOutline =
+ .tooltiptext = Save as...
+saveOutlineImage =
+ .label = Outline image
+ .tooltiptext = Only in mind map/bubble map mode
+saveOutlineSVG =
+ .label = Outline SVG
+ .tooltiptext = Only in mind map/bubble map mode
+saveOutlineFreeMind =
+ .label = Outline FreeMind
+saveMore =
+ .label = MarkDown, Docx, PDF...
diff --git a/addon/locale/ru-RU/linkCreator.ftl b/addon/locale/ru-RU/linkCreator.ftl
new file mode 100644
index 0000000..34a00d9
--- /dev/null
+++ b/addon/locale/ru-RU/linkCreator.ftl
@@ -0,0 +1,24 @@
+title =
+ .title = Link Creator
+tab-inbound =
+ .label = Mention in
+tab-outbound =
+ .label = Link to
+
+inbound-step1-content = Step 1. Mention in note:
+inbound-step2-content = Step 2. Insert link to:
+inbound-step3-content = Step 3. Preview:
+inbound-step3-middle =
+ { $show ->
+ [true] mentions
+ *[other] { "" }
+ }
+
+outbound-step1-content = Step 1. Link to note:
+outbound-step2-content = Step 2. Insert link to:
+outbound-step3-content = Step 3. Preview:
+outbound-step3-middle =
+ { $show ->
+ [true] links to
+ *[other] { "" }
+ }
diff --git a/addon/locale/ru-RU/outline.ftl b/addon/locale/ru-RU/outline.ftl
new file mode 100644
index 0000000..aa31812
--- /dev/null
+++ b/addon/locale/ru-RU/outline.ftl
@@ -0,0 +1,20 @@
+setOutline =
+ .tooltiptext = Change outline mode
+useTreeView =
+ .label = Tree View
+useMindMap =
+ .label = Mind Map
+useBubbleMap =
+ .label = Bubble Map
+saveOutline =
+ .tooltiptext = Save as...
+saveOutlineImage =
+ .label = Outline image
+ .tooltiptext = Only in mind map/bubble map mode
+saveOutlineSVG =
+ .label = Outline SVG
+ .tooltiptext = Only in mind map/bubble map mode
+saveOutlineFreeMind =
+ .label = Outline FreeMind
+saveMore =
+ .label = MarkDown, Docx, PDF...
diff --git a/addon/locale/tr-TR/linkCreator.ftl b/addon/locale/tr-TR/linkCreator.ftl
new file mode 100644
index 0000000..34a00d9
--- /dev/null
+++ b/addon/locale/tr-TR/linkCreator.ftl
@@ -0,0 +1,24 @@
+title =
+ .title = Link Creator
+tab-inbound =
+ .label = Mention in
+tab-outbound =
+ .label = Link to
+
+inbound-step1-content = Step 1. Mention in note:
+inbound-step2-content = Step 2. Insert link to:
+inbound-step3-content = Step 3. Preview:
+inbound-step3-middle =
+ { $show ->
+ [true] mentions
+ *[other] { "" }
+ }
+
+outbound-step1-content = Step 1. Link to note:
+outbound-step2-content = Step 2. Insert link to:
+outbound-step3-content = Step 3. Preview:
+outbound-step3-middle =
+ { $show ->
+ [true] links to
+ *[other] { "" }
+ }
diff --git a/addon/locale/tr-TR/outline.ftl b/addon/locale/tr-TR/outline.ftl
new file mode 100644
index 0000000..aa31812
--- /dev/null
+++ b/addon/locale/tr-TR/outline.ftl
@@ -0,0 +1,20 @@
+setOutline =
+ .tooltiptext = Change outline mode
+useTreeView =
+ .label = Tree View
+useMindMap =
+ .label = Mind Map
+useBubbleMap =
+ .label = Bubble Map
+saveOutline =
+ .tooltiptext = Save as...
+saveOutlineImage =
+ .label = Outline image
+ .tooltiptext = Only in mind map/bubble map mode
+saveOutlineSVG =
+ .label = Outline SVG
+ .tooltiptext = Only in mind map/bubble map mode
+saveOutlineFreeMind =
+ .label = Outline FreeMind
+saveMore =
+ .label = MarkDown, Docx, PDF...
diff --git a/addon/locale/zh-CN/linkCreator.ftl b/addon/locale/zh-CN/linkCreator.ftl
new file mode 100644
index 0000000..880ca79
--- /dev/null
+++ b/addon/locale/zh-CN/linkCreator.ftl
@@ -0,0 +1,24 @@
+title =
+ .title = 链接精灵
+tab-inbound =
+ .label = 提及
+tab-outbound =
+ .label = 指向
+
+inbound-step1-content = 第一步-在此处提及:
+inbound-step2-content = 第二步-插入链接到:
+inbound-step3-content = 第三步-预览:
+inbound-step3-middle =
+ { $show ->
+ [true] 提及了
+ *[other] { "" }
+ }
+
+outbound-step1-content = 第一步-链接指向:
+outbound-step2-content = 第二步-插入链接到:
+outbound-step3-content = 第三步-预览:
+outbound-step3-middle =
+ { $show ->
+ [true] 指向了
+ *[other] { "" }
+ }
\ No newline at end of file
diff --git a/addon/locale/zh-CN/noteRelation.ftl b/addon/locale/zh-CN/noteRelation.ftl
index ae98c1d..902c871 100644
--- a/addon/locale/zh-CN/noteRelation.ftl
+++ b/addon/locale/zh-CN/noteRelation.ftl
@@ -1,7 +1,7 @@
note-relation-header =
.label = 关系图
note-relation-sidenav =
- .tooltiptext = 关系图
+ .tooltiptext = 关系网
note-relation-refresh =
.tooltiptext = 刷新
diff --git a/addon/locale/zh-CN/outline.ftl b/addon/locale/zh-CN/outline.ftl
new file mode 100644
index 0000000..b404257
--- /dev/null
+++ b/addon/locale/zh-CN/outline.ftl
@@ -0,0 +1,20 @@
+setOutline =
+ .tooltiptext = 切换大纲模式
+useTreeView =
+ .label = 目录树
+useMindMap =
+ .label = 思维导图
+useBubbleMap =
+ .label = 气泡关系图
+saveOutline =
+ .tooltiptext = 另存为...
+saveOutlineImage =
+ .label = 大纲图片
+ .tooltiptext = 仅适用于思维导图/气泡关系图模式
+saveOutlineSVG =
+ .label = 大纲SVG
+ .tooltiptext = 仅适用于思维导图/气泡关系图模式
+saveOutlineFreeMind =
+ .label = 大纲FreeMind思维导图
+saveMore =
+ .label = MarkDown, Docx, PDF...
diff --git a/src/elements/base.ts b/src/elements/base.ts
index 65627d8..881a510 100644
--- a/src/elements/base.ts
+++ b/src/elements/base.ts
@@ -2,11 +2,37 @@ import { config } from "../../package.json";
export class PluginCEBase extends XULElementBase {
_addon!: typeof addon;
+ useShadowRoot = false;
connectedCallback(): void {
this._addon = Zotero[config.addonInstance];
Zotero.UIProperties.registerRoot(this);
- super.connectedCallback();
+ if (!this.useShadowRoot) {
+ super.connectedCallback();
+ return;
+ }
+ this.attachShadow({ mode: "open" });
+ // Following the connectedCallback from XULElementBase
+ let content = this.content;
+ if (content) {
+ content = document.importNode(content, true);
+ this.shadowRoot?.append(content);
+ }
+
+ MozXULElement.insertFTLIfNeeded("branding/brand.ftl");
+ MozXULElement.insertFTLIfNeeded("zotero.ftl");
+ // @ts-ignore
+ if (document.l10n && this.shadowRoot) {
+ // @ts-ignore
+ document.l10n.connectRoot(this.shadowRoot);
+ }
+
+ // @ts-ignore
+ window.addEventListener("unload", this._handleWindowUnload);
+
+ // @ts-ignore
+ this.initialized = true;
+ this.init();
}
_wrapID(key: string) {
@@ -24,7 +50,12 @@ export class PluginCEBase extends XULElementBase {
}
_queryID(key: string) {
- return this.querySelector(`#${this._wrapID(key)}`) as XUL.Element | null;
+ const selector = `#${this._wrapID(key)}`;
+ return (this.querySelector(selector) ||
+ this.shadowRoot?.querySelector(selector)) as
+ | XUL.Element
+ | HTMLElement
+ | null;
}
_parseContentID(dom: DocumentFragment) {
diff --git a/src/elements/linkCreator/inboundCreator.ts b/src/elements/linkCreator/inboundCreator.ts
new file mode 100644
index 0000000..320c56b
--- /dev/null
+++ b/src/elements/linkCreator/inboundCreator.ts
@@ -0,0 +1,250 @@
+import { config } from "../../../package.json";
+import { PluginCEBase } from "../base";
+import { NotePicker } from "./notePicker";
+import { NotePreview } from "./notePreview";
+import { OutlinePicker } from "./outlinePicker";
+import { getPref, setPref } from "../../utils/prefs";
+
+export class InboundCreator extends PluginCEBase {
+ notePicker!: NotePicker;
+ noteOutline!: OutlinePicker;
+ notePreview!: NotePreview;
+
+ // Where the link is generated from
+ currentNote: Zotero.Item | undefined;
+ // Where the link is inserted to
+ targetNote: Zotero.Item | undefined;
+
+ positionData: NoteNodeData | undefined;
+
+ _openedNoteIDs: number[] = [];
+
+ loaded: boolean = false;
+
+ get content() {
+ return MozXULElement.parseXULToFragment(`
+
+
+
+
+
+
+`);
+ }
+
+ get openedNoteIDs() {
+ return this._openedNoteIDs;
+ }
+
+ set openedNoteIDs(val) {
+ this._openedNoteIDs = val;
+ }
+
+ get item() {
+ return this.currentNote;
+ }
+
+ set item(val) {
+ this.currentNote = val;
+ }
+
+ async load(io: any) {
+ if (this.loaded) return;
+ this.openedNoteIDs = io.openedNoteIDs || [];
+ this.item = Zotero.Items.get(io.currentNoteID);
+ this.loadNotePicker();
+ this.loadNoteOutline();
+ this.loadNotePreview();
+ this.loadInsertPosition();
+ this.loaded = true;
+
+ this.scrollToSection("picker");
+ }
+
+ async accept(io: any) {
+ if (!this.targetNote) return;
+ const content = await this.getContentToInsert();
+
+ io.targetNoteID = this.targetNote.id;
+ io.content = content;
+ io.lineIndex = this.getIndexToInsert();
+ }
+
+ async loadNotePicker() {
+ this.notePicker = this.querySelector("bn-note-picker") as NotePicker;
+ this.notePicker.openedNoteIDs = this.openedNoteIDs;
+ await this.notePicker.load();
+
+ this.notePicker.addEventListener("selectionchange", (event: any) => {
+ this.targetNote = event.detail.selectedNote;
+ this.updatePickerTitle(this.targetNote);
+ this.noteOutline.item = this.targetNote;
+ this.noteOutline.render();
+ this.positionData = undefined;
+ if (this.targetNote) this.scrollToSection("outline");
+ });
+
+ const content = document.createElement("span");
+ content.dataset.l10nId = `${config.addonRef}-inbound-step1-content`;
+ content.classList.add("toolbar-header", "content");
+ const title = document.createElement("span");
+ title.id = "selected-note-title";
+ title.classList.add("toolbar-header", "highlight");
+ this.notePicker
+ .querySelector("#search-toolbar .toolbar-start")
+ ?.append(content, title);
+ }
+
+ loadNoteOutline() {
+ this.noteOutline = this.querySelector("bn-note-outline") as OutlinePicker;
+
+ this.noteOutline.load();
+
+ this.noteOutline.addEventListener("selectionchange", (event: any) => {
+ this.positionData = event.detail.selectedSection;
+ this.updateNotePreview();
+ this.updateOutlineTitle();
+ });
+
+ const content = document.createElement("span");
+ content.dataset.l10nId = `${config.addonRef}-inbound-step2-content`;
+ content.classList.add("toolbar-header", "content");
+ const title = document.createElement("span");
+ title.id = "selected-outline-title";
+ title.classList.add("toolbar-header", "highlight");
+ this.noteOutline
+ .querySelector(".toolbar .toolbar-start")
+ ?.append(content, title);
+ }
+
+ loadInsertPosition() {
+ const insertPosition = this.querySelector(
+ "#bn-link-insert-position",
+ ) as HTMLSelectElement;
+ insertPosition.value = getPref("insertLinkPosition") as string;
+
+ insertPosition.addEventListener("command", () => {
+ setPref("insertLinkPosition", insertPosition.value);
+ this.updateNotePreview();
+ });
+ }
+
+ loadNotePreview() {
+ this.notePreview = this.querySelector("bn-note-preview") as NotePreview;
+
+ const content = document.createElement("span");
+ content.dataset.l10nId = `${config.addonRef}-inbound-step3-content`;
+ content.classList.add("toolbar-header", "content");
+
+ const fromTitle = document.createElement("span");
+ fromTitle.id = "preview-note-from-title";
+ fromTitle.classList.add("toolbar-header", "highlight");
+
+ const middleTitle = document.createElement("span");
+ middleTitle.id = "preview-note-middle-title";
+ middleTitle.dataset.l10nId = `${config.addonRef}-inbound-step3-middle`;
+ middleTitle.classList.add("toolbar-header", "content");
+
+ const toTitle = document.createElement("span");
+ toTitle.id = "preview-note-to-title";
+ toTitle.classList.add("toolbar-header", "highlight");
+ this.notePreview
+ .querySelector(".toolbar .toolbar-start")
+ ?.append(content, fromTitle, middleTitle, toTitle);
+ }
+
+ updatePickerTitle(noteItem?: Zotero.Item) {
+ const title = noteItem ? noteItem.getNoteTitle() : "";
+ this.querySelector("#selected-note-title")!.textContent = title;
+ }
+
+ updateOutlineTitle() {
+ const title = this.positionData?.name || "";
+ this.querySelector("#selected-outline-title")!.textContent = title;
+ }
+
+ updatePreviewTitle() {
+ this.querySelector("#preview-note-from-title")!.textContent =
+ this.targetNote?.getNoteTitle() || "No title";
+ (
+ this.querySelector("#preview-note-middle-title") as HTMLElement
+ ).dataset.l10nArgs = `{"show": "true"}`;
+ this.querySelector("#preview-note-to-title")!.textContent =
+ this.currentNote?.getNoteTitle() || "No title";
+ }
+
+ async updateNotePreview() {
+ if (!this.loaded || !this.targetNote) return;
+
+ const lines = await this._addon.api.note.getLinesInNote(this.targetNote, {
+ convertToHTML: true,
+ });
+ let index = this.getIndexToInsert();
+ if (index < 0) {
+ index = lines.length;
+ } else {
+ this.scrollToSection("preview");
+ }
+ const before = lines.slice(0, index).join("\n");
+ const after = lines.slice(index).join("\n");
+
+ // TODO: use index or section
+ const middle = await this.getContentToInsert();
+
+ this.notePreview.render({ before, middle, after });
+ this.updatePreviewTitle();
+ }
+
+ scrollToSection(type: "picker" | "outline" | "preview") {
+ if (!this.loaded) return;
+ const querier = {
+ picker: "bn-note-picker",
+ outline: "bn-note-outline",
+ preview: "bn-note-preview",
+ };
+ const container = this.querySelector(querier[type]);
+ if (!container) return;
+ container.scrollIntoView({
+ behavior: "smooth",
+ inline: "center",
+ });
+ }
+
+ async getContentToInsert() {
+ if (!this.currentNote || !this.targetNote) return "";
+ const forwardLink = this._addon.api.convert.note2link(this.currentNote, {});
+ const content = await this._addon.api.template.runTemplate(
+ "[QuickInsertV2]",
+ "link, linkText, subNoteItem, noteItem",
+ [
+ forwardLink,
+ this.currentNote.getNoteTitle().trim() || forwardLink,
+ this.currentNote,
+ this.targetNote,
+ ],
+ {
+ dryRun: true,
+ },
+ );
+ return content;
+ }
+
+ getIndexToInsert() {
+ if (!this.positionData) return -1;
+ let position = getPref("insertLinkPosition") as string;
+ if (!["start", "end"].includes(position)) {
+ position = "end";
+ }
+ let index = {
+ start: this.positionData.lineIndex + 1,
+ end: this.positionData.endIndex + 1,
+ }[position];
+ if (index === undefined) {
+ index = -1;
+ }
+ return index;
+ }
+}
diff --git a/src/elements/notePicker.ts b/src/elements/linkCreator/notePicker.ts
similarity index 89%
rename from src/elements/notePicker.ts
rename to src/elements/linkCreator/notePicker.ts
index 95f9eaf..3fcfe1e 100644
--- a/src/elements/notePicker.ts
+++ b/src/elements/linkCreator/notePicker.ts
@@ -1,6 +1,6 @@
-import { config } from "../../package.json";
+import { config } from "../../../package.json";
import { VirtualizedTableHelper } from "zotero-plugin-toolkit/dist/helpers/virtualizedTable";
-import { PluginCEBase } from "./base";
+import { PluginCEBase } from "../base";
const _require = window.require;
const CollectionTree = _require("chrome://zotero/content/collectionTree.js");
@@ -16,12 +16,14 @@ export class NotePicker extends PluginCEBase {
activeSelectionType: "library" | "tabs" | "none" = "none";
+ uid = Zotero.Utilities.randomString(8);
+
get content() {
return MozXULElement.parseXULToFragment(`
@@ -52,7 +54,7 @@ export class NotePicker extends PluginCEBase {
id="bn-select-opened-notes-content"
class="container virtualized-table-container"
>
-
+
@@ -67,14 +69,9 @@ export class NotePicker extends PluginCEBase {
this.openedNotesView.render();
return;
}
- this.loadOpenedNotes();
}
async init() {
- await this.loadLibraryNotes();
- this.loadQuickSearch();
- await this.loadOpenedNotes();
-
window.addEventListener("unload", () => {
this.destroy();
});
@@ -85,6 +82,12 @@ export class NotePicker extends PluginCEBase {
if (this.itemsView) this.itemsView.unregister();
}
+ async load() {
+ await this.loadLibraryNotes();
+ this.loadQuickSearch();
+ await this.loadOpenedNotes();
+ }
+
async loadLibraryNotes() {
this.itemsView = await ItemTree.init(
this.querySelector("#zotero-items-tree"),
@@ -143,15 +146,15 @@ export class NotePicker extends PluginCEBase {
searchBox,
);
- Zotero.updateQuickSearchBox(document);
+ searchBox.updateMode();
}
async loadOpenedNotes() {
const renderLock = Zotero.Promise.defer();
this.openedNotesView = new VirtualizedTableHelper(window)
- .setContainerId("bn-select-opened-notes-tree")
+ .setContainerId(`bn-select-opened-notes-tree-${this.uid}`)
.setProp({
- id: `bn-select-opened-notes-table`,
+ id: `bn-select-opened-notes-table-${this.uid}`,
columns: [
{
dataKey: "title",
@@ -172,7 +175,7 @@ export class NotePicker extends PluginCEBase {
};
})
.setProp("onSelectionChange", (selection) => {
- this.onOpenedNoteSelected();
+ this.onOpenedNoteSelected(selection);
})
// For find-as-you-type
.setProp(
@@ -276,29 +279,29 @@ export class NotePicker extends PluginCEBase {
this.dispatchSelectionChange();
}
- onOpenedNoteSelected() {
+ onOpenedNoteSelected(selection: { selected: Set }) {
this.activeSelectionType = "tabs";
- this.dispatchSelectionChange();
+ this.dispatchSelectionChange(selection);
}
- dispatchSelectionChange() {
+ dispatchSelectionChange(selection?: { selected: Set }) {
this.dispatchEvent(
- new CustomEvent("selectionChange", {
+ new CustomEvent("selectionchange", {
detail: {
- selectedNote: this.getSelectedNotes()[0],
+ selectedNote: this.getSelectedNotes(selection)[0],
},
}),
);
}
- getSelectedNotes(): Zotero.Item[] {
+ getSelectedNotes(selection?: { selected: Set }): Zotero.Item[] {
if (this.activeSelectionType == "none") {
return [];
} else if (this.activeSelectionType == "library") {
return this.itemsView.getSelectedItems();
}
- return Array.from(this.openedNotesView.treeInstance.selection.selected).map(
- (index) => this.openedNotes[index],
- );
+ return Array.from(
+ (selection || this.openedNotesView.treeInstance.selection).selected,
+ ).map((index) => this.openedNotes[index]);
}
}
diff --git a/src/elements/linkCreator/notePreview.ts b/src/elements/linkCreator/notePreview.ts
new file mode 100644
index 0000000..5236a05
--- /dev/null
+++ b/src/elements/linkCreator/notePreview.ts
@@ -0,0 +1,109 @@
+import { config } from "../../../package.json";
+import { VirtualizedTableHelper } from "zotero-plugin-toolkit/dist/helpers/virtualizedTable";
+import { PluginCEBase } from "../base";
+import TreeModel = require("tree-model");
+import { waitUtilAsync } from "../../utils/wait";
+
+export class NotePreview extends PluginCEBase {
+ _item?: Zotero.Item;
+ noteOutlineView!: VirtualizedTableHelper;
+ noteOutline: TreeModel.Node[] = [];
+
+ get content() {
+ return MozXULElement.parseXULToFragment(`
+
+
+
+
+
+
+
+
+
+
+
+`);
+ }
+
+ async init() {}
+
+ async render(options: { before: string; middle: string; after: string }) {
+ const iframe = this.querySelector("#bn-note-preview") as HTMLIFrameElement;
+
+ const activeElement = document.activeElement as HTMLElement;
+
+ iframe!.contentDocument!.documentElement.innerHTML = `
+
+
+
+
+
+
+
+
+ ${options.before}
+ ${options.middle}
+ ${options.after}
+
+
+ `;
+ activeElement?.focus();
+ await waitUtilAsync(
+ () => iframe.contentDocument?.readyState === "complete",
+ );
+
+ // Scroll the inserted section into the center of the iframe
+ const inserted = iframe.contentDocument?.getElementById("inserted");
+ if (inserted) {
+ const rect = inserted.getBoundingClientRect();
+ const container = inserted.parentElement!;
+ container.scrollTo({
+ top:
+ container.scrollTop +
+ rect.top -
+ container.clientHeight / 2 +
+ rect.height,
+ behavior: "smooth",
+ });
+ }
+ }
+}
diff --git a/src/elements/linkCreator/outboundCreator.ts b/src/elements/linkCreator/outboundCreator.ts
new file mode 100644
index 0000000..5d38625
--- /dev/null
+++ b/src/elements/linkCreator/outboundCreator.ts
@@ -0,0 +1,259 @@
+import { config } from "../../../package.json";
+import { PluginCEBase } from "../base";
+import { NotePicker } from "./notePicker";
+import { NotePreview } from "./notePreview";
+import { OutlinePicker } from "./outlinePicker";
+import { getPref, setPref } from "../../utils/prefs";
+
+export class OutboundCreator extends PluginCEBase {
+ notePicker!: NotePicker;
+ noteOutline!: OutlinePicker;
+ notePreview!: NotePreview;
+
+ // Where the link is inserted to
+ currentNote: Zotero.Item | undefined;
+ // Where the link is generated from
+ targetNote: Zotero.Item | undefined;
+
+ positionData: NoteNodeData | undefined;
+
+ _openedNoteIDs: number[] = [];
+
+ _currentLineIndex: number | undefined;
+
+ loaded: boolean = false;
+
+ get content() {
+ return MozXULElement.parseXULToFragment(`
+
+
+
+
+
+
+`);
+ }
+
+ get openedNoteIDs() {
+ return this._openedNoteIDs;
+ }
+
+ set openedNoteIDs(val) {
+ this._openedNoteIDs = val;
+ }
+
+ get item() {
+ return this.currentNote;
+ }
+
+ set item(val) {
+ this.currentNote = val;
+ }
+
+ async load(io: any) {
+ if (this.loaded) return;
+ this.openedNoteIDs = io.openedNoteIDs || [];
+ this._currentLineIndex = io.currentLineIndex;
+ this.item = Zotero.Items.get(io.currentNoteID);
+ this.loadNotePicker();
+ this.loadNoteOutline();
+ this.loadNotePreview();
+ this.loadInsertPosition();
+ this.loaded = true;
+
+ this.scrollToSection("picker");
+ }
+
+ async accept(io: any) {
+ if (!this.targetNote) return;
+ const content = await this.getContentToInsert();
+
+ io.targetNoteID = this.currentNote!.id;
+ io.content = content;
+ io.lineIndex = this.getIndexToInsert();
+ }
+
+ async loadNotePicker() {
+ this.notePicker = this.querySelector("bn-note-picker") as NotePicker;
+ this.notePicker.openedNoteIDs = this.openedNoteIDs;
+ await this.notePicker.load();
+
+ this.notePicker.addEventListener("selectionchange", (event: any) => {
+ this.targetNote = event.detail.selectedNote;
+ this.updatePickerTitle(this.targetNote);
+ this.updateNotePreview();
+ if (this.targetNote) this.scrollToSection("outline");
+ });
+
+ const content = document.createElement("span");
+ content.innerHTML = "";
+ content.dataset.l10nId = `${config.addonRef}-outbound-step1-content`;
+ content.classList.add("toolbar-header", "content");
+ const title = document.createElement("span");
+ title.id = "selected-note-title";
+ title.classList.add("toolbar-header", "highlight");
+ this.notePicker
+ .querySelector("#search-toolbar .toolbar-start")
+ ?.append(content, title);
+ }
+
+ loadNoteOutline() {
+ this.noteOutline = this.querySelector("bn-note-outline") as OutlinePicker;
+
+ this.noteOutline.load();
+ this.noteOutline.item = this.currentNote;
+ if (typeof this._currentLineIndex === "number") {
+ this.noteOutline.lineIndex = this._currentLineIndex;
+ }
+ this.noteOutline.render();
+ this.positionData = undefined;
+ this.updateNotePreview();
+
+ this.noteOutline.addEventListener("selectionchange", (event: any) => {
+ this.positionData = event.detail.selectedSection;
+ this.updateNotePreview();
+ this.updateOutlineTitle();
+ });
+
+ const content = document.createElement("span");
+ content.dataset.l10nId = `${config.addonRef}-outbound-step2-content`;
+ content.classList.add("toolbar-header", "content");
+ const title = document.createElement("span");
+ title.id = "selected-outline-title";
+ title.classList.add("toolbar-header", "highlight");
+ this.noteOutline
+ .querySelector(".toolbar .toolbar-start")
+ ?.append(content, title);
+ }
+
+ loadInsertPosition() {
+ const insertPosition = this.querySelector(
+ "#bn-link-insert-position",
+ ) as HTMLSelectElement;
+ insertPosition.value = getPref("insertLinkPosition") as string;
+
+ insertPosition.addEventListener("command", () => {
+ setPref("insertLinkPosition", insertPosition.value);
+ this.updateNotePreview();
+ });
+ }
+
+ loadNotePreview() {
+ this.notePreview = this.querySelector("bn-note-preview") as NotePreview;
+
+ const content = document.createElement("span");
+ content.dataset.l10nId = `${config.addonRef}-outbound-step3-content`;
+ content.classList.add("toolbar-header", "content");
+
+ const fromTitle = document.createElement("span");
+ fromTitle.id = "preview-note-from-title";
+ fromTitle.classList.add("toolbar-header", "highlight");
+
+ const middleTitle = document.createElement("span");
+ middleTitle.id = "preview-note-middle-title";
+ middleTitle.dataset.l10nId = `${config.addonRef}-outbound-step3-middle`;
+ middleTitle.classList.add("toolbar-header", "content");
+
+ const toTitle = document.createElement("span");
+ toTitle.id = "preview-note-to-title";
+ toTitle.classList.add("toolbar-header", "highlight");
+ this.notePreview
+ .querySelector(".toolbar .toolbar-start")
+ ?.append(content, fromTitle, middleTitle, toTitle);
+ }
+
+ updatePickerTitle(noteItem?: Zotero.Item) {
+ const title = noteItem ? noteItem.getNoteTitle() : "";
+ this.querySelector("#selected-note-title")!.textContent = title;
+ }
+
+ updateOutlineTitle() {
+ const title = this.positionData?.name || "";
+ this.querySelector("#selected-outline-title")!.textContent = title;
+ }
+
+ updatePreviewTitle() {
+ this.querySelector("#preview-note-from-title")!.textContent =
+ this.currentNote?.getNoteTitle() || "No title";
+ (
+ this.querySelector("#preview-note-middle-title") as HTMLElement
+ ).dataset.l10nArgs = `{"show": "true"}`;
+ this.querySelector("#preview-note-to-title")!.textContent =
+ this.targetNote?.getNoteTitle() || "No title";
+ }
+
+ async updateNotePreview() {
+ if (!this.loaded || !this.currentNote) return;
+
+ const lines = await this._addon.api.note.getLinesInNote(this.currentNote, {
+ convertToHTML: true,
+ });
+ let index = this.getIndexToInsert();
+ if (index < 0) {
+ index = lines.length;
+ } else {
+ this.scrollToSection("preview");
+ }
+ const before = lines.slice(0, index).join("\n");
+ const after = lines.slice(index).join("\n");
+
+ // TODO: use index or section
+ const middle = await this.getContentToInsert();
+
+ this.notePreview.render({ before, middle, after });
+ this.updatePreviewTitle();
+ }
+
+ scrollToSection(type: "picker" | "outline" | "preview") {
+ if (!this.loaded) return;
+ const querier = {
+ picker: "bn-note-picker",
+ outline: "bn-note-outline",
+ preview: "bn-note-preview",
+ };
+ const container = this.querySelector(querier[type]);
+ if (!container) return;
+ container.scrollIntoView({
+ behavior: "smooth",
+ inline: "center",
+ });
+ }
+
+ async getContentToInsert() {
+ if (!this.currentNote || !this.targetNote) return "";
+ const forwardLink = this._addon.api.convert.note2link(this.targetNote, {});
+ const content = await this._addon.api.template.runTemplate(
+ "[QuickInsertV2]",
+ "link, linkText, subNoteItem, noteItem",
+ [
+ forwardLink,
+ this.targetNote.getNoteTitle().trim() || forwardLink,
+ this.targetNote,
+ this.currentNote,
+ ],
+ {
+ dryRun: true,
+ },
+ );
+ return content;
+ }
+
+ getIndexToInsert() {
+ if (!this.positionData) return -1;
+ let position = getPref("insertLinkPosition") as string;
+ if (!["start", "end"].includes(position)) {
+ position = "end";
+ }
+ let index = {
+ start: this.positionData.lineIndex + 1,
+ end: this.positionData.endIndex + 1,
+ }[position];
+ if (index === undefined) {
+ index = -1;
+ }
+ return index;
+ }
+}
diff --git a/src/elements/linkCreator/outlinePicker.ts b/src/elements/linkCreator/outlinePicker.ts
new file mode 100644
index 0000000..7faf716
--- /dev/null
+++ b/src/elements/linkCreator/outlinePicker.ts
@@ -0,0 +1,165 @@
+import { config } from "../../../package.json";
+import { VirtualizedTableHelper } from "zotero-plugin-toolkit/dist/helpers/virtualizedTable";
+import { PluginCEBase } from "../base";
+import TreeModel = require("tree-model");
+
+export class OutlinePicker extends PluginCEBase {
+ _item?: Zotero.Item;
+ _lineIndex?: number;
+
+ noteOutlineView!: VirtualizedTableHelper;
+ noteOutline: TreeModel.Node[] = [];
+
+ uid = Zotero.Utilities.randomString(8);
+
+ get content() {
+ return MozXULElement.parseXULToFragment(`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`);
+ }
+
+ get item() {
+ return this._item;
+ }
+
+ set item(item: Zotero.Item | undefined) {
+ this._item = item;
+ }
+
+ set lineIndex(index: number | undefined) {
+ this._lineIndex = index;
+ }
+
+ get lineIndex() {
+ return this._lineIndex;
+ }
+
+ async load() {
+ this.loadNoteOutline();
+ }
+
+ async loadNoteOutline() {
+ const renderLock = Zotero.Promise.defer();
+ this.noteOutlineView = new VirtualizedTableHelper(window)
+ .setContainerId(`bn-select-note-outline-tree-${this.uid}`)
+ .setProp({
+ id: `bn-select-note-outline-table-${this.uid}`,
+ columns: [
+ {
+ dataKey: "level",
+ label: "Level",
+ width: 50,
+ staticWidth: true,
+ },
+ {
+ dataKey: "name",
+ label: "Table of Contents",
+ flex: 1,
+ },
+ ],
+ showHeader: true,
+ multiSelect: false,
+ staticColumns: true,
+ disableFontSizeScaling: true,
+ })
+ .setProp("getRowCount", () => this.noteOutline.length || 0)
+ .setProp("getRowData", (index) => {
+ const model = this.noteOutline[index]?.model;
+ if (!model) return { level: 0, name: "**Unknown**" };
+ return {
+ level: model.level,
+ name:
+ (model.level > 0 ? "··".repeat(model.level - 1) : "") + model.name,
+ };
+ })
+ .setProp("onSelectionChange", (selection) => {
+ this.onOutlineSelected(selection);
+ })
+ // For find-as-you-type
+ .setProp(
+ "getRowString",
+ (index) => this.noteOutline[index]?.model.name || "",
+ )
+ .render(-1, () => {
+ renderLock.resolve();
+ });
+ await renderLock.promise;
+
+ // if (openedNotes.length === 1) {
+ // openedNotesView.treeInstance.selection.select(0);
+ // }
+ }
+
+ onOutlineSelected(selection: { selected: Set }) {
+ this.dispatchSelectionChange(selection);
+ }
+
+ async render() {
+ if (!this.item) {
+ return;
+ }
+ this.noteOutline = this._addon.api.note.getNoteTreeFlattened(this.item);
+ // Fake a cursor position
+ if (typeof this.lineIndex === "number") {
+ this.noteOutline.unshift({
+ model: {
+ level: 0,
+ name: `🖋️Cursor (L${this._lineIndex})`,
+ lineIndex: this._lineIndex,
+ endIndex: this._lineIndex,
+ },
+ } as any);
+ }
+
+ this.noteOutlineView?.render(undefined);
+ }
+
+ dispatchSelectionChange(selection: { selected: Set }) {
+ this.dispatchEvent(
+ new CustomEvent("selectionchange", {
+ detail: {
+ selectedSection: this.getSelectedSection(selection),
+ },
+ }),
+ );
+ }
+
+ getSelectedSection(selection?: { selected: Set }): NoteNodeData {
+ return this.noteOutline[
+ (selection || this.noteOutlineView.treeInstance.selection).selected
+ .values()
+ .next().value
+ ]?.model;
+ }
+}
diff --git a/src/elements/context.ts b/src/elements/workspace/contextPane.ts
similarity index 84%
rename from src/elements/context.ts
rename to src/elements/workspace/contextPane.ts
index dee1d8d..60cb271 100644
--- a/src/elements/context.ts
+++ b/src/elements/workspace/contextPane.ts
@@ -1,5 +1,5 @@
-import { config } from "../../package.json";
-import { PluginCEBase } from "./base";
+import { config } from "../../../package.json";
+import { PluginCEBase } from "../base";
export class ContextPane extends PluginCEBase {
_item?: Zotero.Item;
@@ -21,7 +21,7 @@ export class ContextPane extends PluginCEBase {
diff --git a/src/elements/detailsPane.ts b/src/elements/workspace/detailsPane.ts
similarity index 86%
rename from src/elements/detailsPane.ts
rename to src/elements/workspace/detailsPane.ts
index 6104a7f..98e824b 100644
--- a/src/elements/detailsPane.ts
+++ b/src/elements/workspace/detailsPane.ts
@@ -1,12 +1,12 @@
-import { config } from "../../package.json";
+import { config } from "../../../package.json";
const ItemDetails = customElements.get("item-details")! as any;
-export class NoteDetails extends ItemDetails {
+export class DetailsPane extends ItemDetails {
content = MozXULElement.parseXULToFragment(`
diff --git a/src/elements/outlinePane.ts b/src/elements/workspace/outlinePane.ts
similarity index 94%
rename from src/elements/outlinePane.ts
rename to src/elements/workspace/outlinePane.ts
index 600721a..6fe3aa8 100644
--- a/src/elements/outlinePane.ts
+++ b/src/elements/workspace/outlinePane.ts
@@ -1,18 +1,18 @@
import { FilePickerHelper } from "zotero-plugin-toolkit/dist/helpers/filePicker";
-import { config } from "../../package.json";
-import { showHintWithLink } from "../utils/hint";
-import { formatPath } from "../utils/str";
-import { waitUtilAsync } from "../utils/wait";
-import { OutlineType } from "../utils/workspace";
-import { PluginCEBase } from "./base";
+import { config } from "../../../package.json";
+import { showHintWithLink } from "../../utils/hint";
+import { formatPath } from "../../utils/str";
+import { waitUtilAsync } from "../../utils/wait";
+import { OutlineType } from "../../utils/workspace";
+import { PluginCEBase } from "../base";
import {
getEditorInstance,
moveHeading,
updateHeadingTextAtLine,
-} from "../utils/editor";
-import { getNoteLinkParams } from "../utils/link";
-import { getNoteTree, getNoteTreeNodeById } from "../utils/note";
-import { getPref } from "../utils/prefs";
+} from "../../utils/editor";
+import { getNoteLinkParams } from "../../utils/link";
+import { getNoteTree, getNoteTreeNodeById } from "../../utils/note";
+import { getPref } from "../../utils/prefs";
export class OutlinePane extends PluginCEBase {
_outlineType: OutlineType = OutlineType.empty;
@@ -42,7 +42,7 @@ export class OutlinePane extends PluginCEBase {
diff --git a/src/elements/related.ts b/src/elements/workspace/related.ts
similarity index 96%
rename from src/elements/related.ts
rename to src/elements/workspace/related.ts
index 64e32e4..7aacd07 100644
--- a/src/elements/related.ts
+++ b/src/elements/workspace/related.ts
@@ -1,5 +1,5 @@
// @ts-nocheck
-import { config } from "../../package.json";
+import { config } from "../../../package.json";
const RelatedBox = customElements.get("related-box")! as typeof XULElementBase;
@@ -11,7 +11,7 @@ export class NoteRelatedBox extends RelatedBox {
diff --git a/src/extras/customElements.ts b/src/extras/customElements.ts
index c0a649c..4ee11ea 100644
--- a/src/extras/customElements.ts
+++ b/src/extras/customElements.ts
@@ -1,16 +1,24 @@
-import { ContextPane } from "../elements/context";
-import { NoteDetails } from "../elements/detailsPane";
-import { NotePicker } from "../elements/notePicker";
-import { OutlinePane } from "../elements/outlinePane";
-import { NoteRelatedBox } from "../elements/related";
-import { Workspace } from "../elements/workspace";
+import { ContextPane } from "../elements/workspace/contextPane";
+import { DetailsPane } from "../elements/workspace/detailsPane";
+import { OutlinePicker } from "../elements/linkCreator/outlinePicker";
+import { NotePicker } from "../elements/linkCreator/notePicker";
+import { NotePreview } from "../elements/linkCreator/notePreview";
+import { OutlinePane } from "../elements/workspace/outlinePane";
+import { NoteRelatedBox } from "../elements/workspace/related";
+import { Workspace } from "../elements/workspace/workspace";
+import { InboundCreator } from "../elements/linkCreator/inboundCreator";
+import { OutboundCreator } from "../elements/linkCreator/outboundCreator";
const elements = {
"bn-context": ContextPane,
"bn-outline": OutlinePane,
- "bn-details": NoteDetails as unknown as CustomElementConstructor,
+ "bn-details": DetailsPane as unknown as CustomElementConstructor,
"bn-workspace": Workspace,
"bn-note-picker": NotePicker,
+ "bn-note-outline": OutlinePicker,
+ "bn-note-preview": NotePreview,
+ "bn-inbound-creator": InboundCreator,
+ "bn-outbound-creator": OutboundCreator,
"bn-related-box": NoteRelatedBox,
};
diff --git a/src/extras/linkCreator.ts b/src/extras/linkCreator.ts
new file mode 100644
index 0000000..7767394
--- /dev/null
+++ b/src/extras/linkCreator.ts
@@ -0,0 +1,85 @@
+import { getPref, setPref } from "../utils/prefs";
+import { InboundCreator } from "../elements/linkCreator/inboundCreator";
+import { OutboundCreator } from "../elements/linkCreator/outboundCreator";
+
+let tabbox: XUL.TabBox;
+let inboundCreator: InboundCreator;
+let outboundCreator: OutboundCreator;
+
+let io: {
+ currentNoteID: number;
+ currentLineIndex?: number;
+ openedNoteIDs?: number[];
+ deferred: _ZoteroTypes.DeferredPromise;
+
+ targetNoteID?: number;
+ content?: string;
+ lineIndex?: number;
+};
+
+window.onload = async function () {
+ if (document.readyState === "complete") {
+ setTimeout(init, 0);
+ return;
+ }
+ document.addEventListener("DOMContentLoaded", init, { once: true });
+};
+
+window.onunload = function () {
+ io.deferred && io.deferred.resolve();
+ setPref(
+ "windows.linkCreator.size",
+ `${document.documentElement.getAttribute(
+ "width",
+ )},${document.documentElement.getAttribute("height")}`,
+ );
+ setPref("windows.linkCreator.tabIndex", tabbox.selectedIndex);
+};
+
+function init() {
+ // Set font size from pref
+ const sbc = document.getElementById("top-container");
+ Zotero.UIProperties.registerRoot(sbc);
+
+ setTimeout(() => {
+ const size = ((getPref("windows.linkCreator.size") as string) || "").split(
+ ",",
+ );
+ window.resizeTo(Number(size[0] || "800"), Number(size[1] || "600"));
+ }, 0);
+
+ // @ts-ignore
+ io = window.arguments[0];
+
+ tabbox = document.querySelector("#top-container")!;
+ tabbox.selectedIndex =
+ (getPref("windows.linkCreator.tabIndex") as number) || 0;
+ tabbox.addEventListener("select", loadSelectedPanel);
+
+ inboundCreator = document.querySelector(
+ "bn-inbound-creator",
+ ) as InboundCreator;
+ outboundCreator = document.querySelector(
+ "bn-outbound-creator",
+ ) as OutboundCreator;
+ loadSelectedPanel();
+
+ document.addEventListener("dialogaccept", doAccept);
+}
+
+async function loadSelectedPanel() {
+ const content = getSelectedContent();
+ await content.load(io);
+}
+
+async function acceptSelectedPanel() {
+ await getSelectedContent().accept(io);
+}
+
+function getSelectedContent() {
+ return tabbox.selectedPanel.querySelector("[data-bn-type=content]") as any;
+}
+
+async function doAccept() {
+ await acceptSelectedPanel();
+}
diff --git a/src/extras/linkNote.ts b/src/extras/linkNote.ts
deleted file mode 100644
index f7f7a1f..0000000
--- a/src/extras/linkNote.ts
+++ /dev/null
@@ -1,339 +0,0 @@
-import { VirtualizedTableHelper } from "zotero-plugin-toolkit/dist/helpers/virtualizedTable";
-import { config } from "../../package.json";
-import Addon from "../addon";
-import { waitUtilAsync } from "../utils/wait";
-import { getPref, setPref } from "../utils/prefs";
-import { NotePicker } from "../elements/notePicker";
-
-let initialized = false;
-
-let notePicker: NotePicker;
-
-let noteOutlineView: VirtualizedTableHelper;
-let currentNote: Zotero.Item;
-let targetNote: Zotero.Item | undefined;
-let noteOutline: ReturnType = [];
-
-let positionData: NoteNodeData | undefined;
-
-// @ts-ignore
-window.addon = Zotero[config.addonRef];
-
-let io: {
- currentNoteID: number;
- openedNoteIDs?: number[];
- deferred: _ZoteroTypes.DeferredPromise;
-
- targetNoteID?: number;
- content?: string;
- lineIndex?: number;
-};
-
-window.onload = async function () {
- // Set font size from pref
- const sbc = document.getElementById("top-container");
- Zotero.UIProperties.registerRoot(sbc);
-
- setTimeout(() => {
- const size = ((getPref("windows.linkNote.size") as string) || "").split(
- ",",
- );
- window.resizeTo(Number(size[0] || "800"), Number(size[1] || "600"));
- console.log(size);
- }, 300);
-
- // @ts-ignore
- io = window.arguments[0];
-
- loadNotePicker();
-
- loadInsertPosition();
-
- loadNoteOutline();
-
- document.addEventListener("dialogaccept", doAccept);
-
- currentNote = Zotero.Items.get(io.currentNoteID);
-
- initialized = true;
-
- scrollToSection("picker");
-};
-
-window.onunload = function () {
- io.deferred && io.deferred.resolve();
- setPref(
- "windows.linkNote.size",
- `${document.documentElement.getAttribute(
- "width",
- )},${document.documentElement.getAttribute("height")}`,
- );
-};
-
-function loadNotePicker() {
- notePicker = document.querySelector("bn-note-picker") as NotePicker;
- notePicker.openedNoteIDs = io.openedNoteIDs || [];
- const content = document.createElement("span");
- content.innerHTML = "Step 1. Choose target note:";
- content.classList.add("toolbar-header", "content");
- const title = document.createElement("span");
- title.id = "selected-note-title";
- title.classList.add("toolbar-header", "highlight");
- notePicker
- .querySelector("#search-toolbar .toolbar-start")
- ?.append(content, title);
- notePicker.addEventListener("selectionChange", (event: any) => {
- updateSelectedNotesTitle(event.detail.selectedNote);
- updateNoteOutline(event.detail.selectedNote);
- });
-}
-
-function loadInsertPosition() {
- const insertPosition = document.getElementById(
- "bn-link-insert-position",
- ) as HTMLSelectElement;
- insertPosition.value = getPref("insertLinkPosition") as string;
- insertPosition.addEventListener("command", () => {
- setPref("insertLinkPosition", insertPosition.value);
- updateNotePreview();
- });
-}
-
-async function loadNoteOutline() {
- const renderLock = Zotero.Promise.defer();
- noteOutlineView = new VirtualizedTableHelper(window)
- .setContainerId("bn-select-note-outline-tree")
- .setProp({
- id: `bn-select-note-outline-table`,
- columns: [
- {
- dataKey: "level",
- label: "Level",
- width: 50,
- staticWidth: true,
- },
- {
- dataKey: "name",
- label: "Table of Contents",
- flex: 1,
- },
- ],
- showHeader: true,
- multiSelect: false,
- staticColumns: true,
- disableFontSizeScaling: true,
- })
- .setProp("getRowCount", () => noteOutline.length || 0)
- .setProp("getRowData", (index) => {
- const model = noteOutline[index]?.model;
- if (!model) return { level: 0, name: "**Unknown**" };
- return {
- level: model.level,
- name: "··".repeat(model.level - 1) + model.name,
- };
- })
- .setProp("onSelectionChange", (selection) => {
- onOutlineSelected(selection);
- })
- // For find-as-you-type
- .setProp("getRowString", (index) => noteOutline[index]?.model.name || "")
- .render(-1, () => {
- renderLock.resolve();
- });
- await renderLock.promise;
-
- // if (openedNotes.length === 1) {
- // openedNotesView.treeInstance.selection.select(0);
- // }
-}
-
-function onOutlineSelected(selection: { selected: Set }) {
- positionData = noteOutline[selection.selected.values().next().value]?.model;
- updateNotePreview();
- updateSelectedOutlineTitle();
-}
-
-function updateSelectedNotesTitle(noteItem?: Zotero.Item) {
- const title = noteItem ? noteItem.getNoteTitle() : "";
- document.querySelector("#selected-note-title")!.textContent = title;
-}
-
-function updateSelectedOutlineTitle() {
- const selectedOutline =
- noteOutline[
- noteOutlineView.treeInstance.selection.selected.values().next().value
- ];
- const title = selectedOutline ? selectedOutline.model.name : "";
- document.querySelector("#selected-outline-title")!.textContent = title;
-}
-
-function updatePreviewTitle() {
- document.querySelector("#preview-note-from-title")!.textContent =
- currentNote.getNoteTitle() || "No title";
- document.querySelector("#preview-note-middle-title")!.textContent = "to";
- document.querySelector("#preview-note-to-title")!.textContent =
- targetNote?.getNoteTitle() || "No title";
-}
-
-async function updateNoteOutline(noteItem?: Zotero.Item) {
- if (!noteItem) {
- targetNote = undefined;
- noteOutline = [];
- } else {
- targetNote = noteItem;
- noteOutline = addon.api.note.getNoteTreeFlattened(targetNote);
- }
- noteOutlineView?.render(undefined);
- // Set default line index to the end of the note
- positionData = undefined;
- if (targetNote) scrollToSection("outline");
-}
-
-async function updateNotePreview() {
- if (!initialized || !targetNote) return;
- const lines = await addon.api.note.getLinesInNote(targetNote, {
- convertToHTML: true,
- });
- let index = getIndexToInsert();
- if (index < 0) {
- index = lines.length;
- } else {
- scrollToSection("preview");
- }
- const before = lines.slice(0, index).join("\n");
- const after = lines.slice(index).join("\n");
-
- // TODO: use index or section
- const content = await getContentToInsert();
-
- const iframe = document.querySelector(
- "#bn-note-preview",
- ) as HTMLIFrameElement;
-
- const activeElement = document.activeElement as HTMLElement; // 保存当前活动元素
-
- iframe!.contentDocument!.documentElement.innerHTML = `
-
-
-
-
-
-
-
-
- ${before}
- ${content}
- ${after}
-
-
-`;
- activeElement?.focus();
- await waitUtilAsync(() => iframe.contentDocument?.readyState === "complete");
-
- // Scroll the inserted section into the center of the iframe
- const inserted = iframe.contentDocument?.getElementById("inserted");
- if (inserted) {
- const rect = inserted.getBoundingClientRect();
- const container = inserted.parentElement!;
- container.scrollTo({
- top:
- container.scrollTop +
- rect.top -
- container.clientHeight / 2 +
- rect.height,
- behavior: "smooth",
- });
- }
-
- updatePreviewTitle();
-}
-
-function scrollToSection(type: "picker" | "outline" | "preview") {
- if (!initialized) return;
- const querier = {
- picker: "#zotero-select-items-container",
- outline: "#bn-select-note-outline-container",
- preview: "#bn-note-preview-container",
- };
- const container = document.querySelector(querier[type]);
- if (!container) return;
- container.scrollIntoView({
- behavior: "smooth",
- inline: "center",
- });
-}
-
-async function getContentToInsert() {
- const forwardLink = addon.api.convert.note2link(currentNote, {});
- const content = await addon.api.template.runTemplate(
- "[QuickInsertV2]",
- "link, linkText, subNoteItem, noteItem",
- [
- forwardLink,
- currentNote.getNoteTitle().trim() || forwardLink,
- currentNote,
- targetNote,
- ],
- {
- dryRun: true,
- },
- );
- return content;
-}
-
-function getIndexToInsert() {
- if (!positionData) return -1;
- let position = getPref("insertLinkPosition") as string;
- if (!["start", "end"].includes(position)) {
- position = "end";
- }
- let index = {
- start: positionData.lineIndex + 1,
- end: positionData.endIndex + 1,
- }[position];
- if (index === undefined) {
- index = -1;
- }
- return index;
-}
-
-async function doAccept() {
- if (!targetNote) return;
- const content = await getContentToInsert();
-
- io.targetNoteID = targetNote.id;
- io.content = content;
- io.lineIndex = getIndexToInsert();
-}
diff --git a/src/modules/editor/toolbar.ts b/src/modules/editor/toolbar.ts
index 3c1f4df..5054839 100644
--- a/src/modules/editor/toolbar.ts
+++ b/src/modules/editor/toolbar.ts
@@ -4,7 +4,7 @@ import { getLineAtCursor, getSectionAtCursor } from "../../utils/editor";
import { showHint } from "../../utils/hint";
import { getNoteLink } from "../../utils/link";
import { getString } from "../../utils/locale";
-import { openLinkNoteDialog } from "../../utils/linkNote";
+import { openLinkCreator } from "../../utils/linkCreator";
import { slice } from "../../utils/str";
export async function initEditorToolbar(editor: Zotero.EditorInstance) {
@@ -19,13 +19,15 @@ export async function initEditorToolbar(editor: Zotero.EditorInstance) {
classList: ["toolbar-button"],
properties: {
innerHTML: ICONS.addon,
- title: "Link current note to another note",
+ title: "Link creator",
},
listeners: [
{
type: "click",
listener: (e) => {
- openLinkNoteDialog(noteItem);
+ openLinkCreator(noteItem, {
+ lineIndex: getLineAtCursor(editor),
+ });
},
},
],
diff --git a/src/modules/workspace/link.ts b/src/modules/workspace/link.ts
index 2ad86be..9e9c66e 100644
--- a/src/modules/workspace/link.ts
+++ b/src/modules/workspace/link.ts
@@ -1,5 +1,5 @@
import { config } from "../../../package.json";
-import { Workspace } from "../../elements/workspace";
+import { Workspace } from "../../elements/workspace/workspace";
export function registerNoteLinkSection(type: "inbound" | "outbound") {
const key = Zotero.ItemPaneManager.registerSection({
diff --git a/src/modules/workspace/relation.ts b/src/modules/workspace/relation.ts
index 59c550c..17f0171 100644
--- a/src/modules/workspace/relation.ts
+++ b/src/modules/workspace/relation.ts
@@ -18,7 +18,7 @@ export function registerNoteRelation() {