add: link creator
refactor: link note -> link creator add: link to mode in link creator fix: locale
This commit is contained in:
parent
b6c0415267
commit
357e9a77bd
|
|
@ -0,0 +1,54 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- prettier-ignore -->
|
||||
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||
<!-- prettier-ignore -->
|
||||
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
|
||||
<!-- prettier-ignore -->
|
||||
<?xml-stylesheet href="chrome://zotero-platform/content/zotero.css" type="text/css"?>
|
||||
<!-- prettier-ignore -->
|
||||
<?xml-stylesheet href="chrome://__addonRef__/content/styles/linkCreator/toolbar.css" type="text/css"?>
|
||||
<!-- prettier-ignore -->
|
||||
<?xml-stylesheet href="chrome://__addonRef__/content/styles/linkCreator/linkCreator.css" type="text/css"?>
|
||||
<!-- prettier-ignore -->
|
||||
<!DOCTYPE window>
|
||||
<window
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
id="bn-note-picker"
|
||||
data-l10n-id="__addonRef__-title"
|
||||
windowtype="__addonRef__-link-note"
|
||||
persist="screenX screenY width height sizemode"
|
||||
style="min-width: 40em"
|
||||
>
|
||||
<linkset>
|
||||
<html:link rel="localization" href="__addonRef__-linkCreator.ftl" />
|
||||
</linkset>
|
||||
|
||||
<script src="chrome://zotero/content/include.js"></script>
|
||||
<script src="chrome://zotero/content/customElements.js"></script>
|
||||
<script src="chrome://__addonRef__/content/scripts/customElements.js"></script>
|
||||
<script src="chrome://__addonRef__/content/scripts/linkCreator.js"></script>
|
||||
|
||||
<dialog buttons="accept, cancel">
|
||||
<tabbox id="top-container" class="container">
|
||||
<tabs>
|
||||
<tab data-l10n-id="__addonRef__-tab-inbound"></tab>
|
||||
<tab data-l10n-id="__addonRef__-tab-outbound"></tab>
|
||||
</tabs>
|
||||
<tabpanels class="container">
|
||||
<tabpanel class="content-container">
|
||||
<bn-inbound-creator
|
||||
id="bn-inbound-creator"
|
||||
data-bn-type="content"
|
||||
></bn-inbound-creator>
|
||||
</tabpanel>
|
||||
<tabpanel class="content-container">
|
||||
<bn-outbound-creator
|
||||
id="bn-outbound-creator"
|
||||
data-bn-type="content"
|
||||
></bn-outbound-creator>
|
||||
</tabpanel>
|
||||
</tabpanels>
|
||||
</tabbox>
|
||||
</dialog>
|
||||
</window>
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- prettier-ignore -->
|
||||
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||
<!-- prettier-ignore -->
|
||||
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
|
||||
<!-- prettier-ignore -->
|
||||
<?xml-stylesheet href="chrome://zotero-platform/content/zotero.css" type="text/css"?>
|
||||
<!-- prettier-ignore -->
|
||||
<?xml-stylesheet href="chrome://__addonRef__/content/styles/toolbar.css" type="text/css"?>
|
||||
<!-- prettier-ignore -->
|
||||
<?xml-stylesheet href="chrome://__addonRef__/content/styles/linkNote.css" type="text/css"?>
|
||||
<!-- prettier-ignore -->
|
||||
<!DOCTYPE window>
|
||||
<window
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
id="bn-note-picker"
|
||||
windowtype="__addonRef__-link-note"
|
||||
persist="screenX screenY width height sizemode"
|
||||
style="min-width: 40em"
|
||||
>
|
||||
<script src="chrome://zotero/content/include.js"></script>
|
||||
<script src="chrome://zotero/content/customElements.js"></script>
|
||||
<script src="chrome://__addonRef__/content/scripts/customElements.js"></script>
|
||||
<script src="chrome://__addonRef__/content/scripts/linkNote.js"></script>
|
||||
|
||||
<dialog buttons="accept, cancel">
|
||||
<hbox id="top-container" class="container">
|
||||
<bn-note-picker></bn-note-picker>
|
||||
<vbox id="bn-select-note-outline-container" class="container">
|
||||
<hbox class="toolbar">
|
||||
<hbox class="toolbar-start">
|
||||
<html:span class="toolbar-header content"
|
||||
>Step 2. Insert to:
|
||||
</html:span>
|
||||
<html:span
|
||||
id="selected-outline-title"
|
||||
class="toolbar-header highlight"
|
||||
></html:span>
|
||||
</hbox>
|
||||
<hbox class="toolbar-middle"></hbox>
|
||||
<hbox class="toolbar-end"></hbox>
|
||||
</hbox>
|
||||
<vbox
|
||||
id="bn-select-note-outline-content"
|
||||
class="container virtualized-table-container"
|
||||
>
|
||||
<html:div id="bn-select-note-outline-tree"></html:div>
|
||||
</vbox>
|
||||
<hbox id="bn-link-insert-position-container">
|
||||
<label>At section</label>
|
||||
<radiogroup id="bn-link-insert-position" orient="horizontal">
|
||||
<radio
|
||||
id="bn-link-insert-position-top"
|
||||
label="Start"
|
||||
value="start"
|
||||
></radio>
|
||||
<radio
|
||||
id="bn-link-insert-position-bottom"
|
||||
label="End"
|
||||
value="end"
|
||||
></radio>
|
||||
</radiogroup>
|
||||
</hbox>
|
||||
</vbox>
|
||||
<vbox id="bn-note-preview-container" class="container">
|
||||
<hbox class="toolbar">
|
||||
<hbox class="toolbar-start">
|
||||
<html:span class="toolbar-header content"
|
||||
>Step 3. Preview:
|
||||
</html:span>
|
||||
<html:span
|
||||
id="preview-note-from-title"
|
||||
class="toolbar-header highlight"
|
||||
></html:span>
|
||||
<html:span
|
||||
id="preview-note-middle-title"
|
||||
class="toolbar-header content"
|
||||
></html:span>
|
||||
<html:span
|
||||
id="preview-note-to-title"
|
||||
class="toolbar-header highlight"
|
||||
></html:span>
|
||||
</hbox>
|
||||
<hbox class="toolbar-middle"></hbox>
|
||||
<hbox class="toolbar-end"></hbox>
|
||||
</hbox>
|
||||
<vbox id="bn-note-preview-content" class="container">
|
||||
<iframe
|
||||
id="bn-note-preview"
|
||||
class="container"
|
||||
type="content"
|
||||
></iframe>
|
||||
</vbox>
|
||||
</vbox>
|
||||
</hbox>
|
||||
</dialog>
|
||||
</window>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
.container {
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* TODO: remove fx115 workaround */
|
||||
tab {
|
||||
color: unset !important;
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
bn-note-outline {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#bn-link-insert-position-container {
|
||||
align-items: center;
|
||||
}
|
||||
|
|
@ -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%;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
bn-note-preview {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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%;
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -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] { "" }
|
||||
}
|
||||
|
|
@ -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] { "" }
|
||||
}
|
||||
|
|
@ -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...
|
||||
|
|
@ -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] { "" }
|
||||
}
|
||||
|
|
@ -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...
|
||||
|
|
@ -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] { "" }
|
||||
}
|
||||
|
|
@ -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...
|
||||
|
|
@ -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] { "" }
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
note-relation-header =
|
||||
.label = 关系图
|
||||
note-relation-sidenav =
|
||||
.tooltiptext = 关系图
|
||||
.tooltiptext = 关系网
|
||||
note-relation-refresh =
|
||||
.tooltiptext = 刷新
|
||||
|
||||
|
|
|
|||
|
|
@ -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...
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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(`
|
||||
<linkset>
|
||||
<html:link
|
||||
rel="stylesheet"
|
||||
href="chrome://${config.addonRef}/content/styles/linkCreator/inboundCreator.css"
|
||||
></html:link>
|
||||
</linkset>
|
||||
<bn-note-picker></bn-note-picker>
|
||||
<bn-note-outline></bn-note-outline>
|
||||
<bn-note-preview></bn-note-preview>
|
||||
`);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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(`
|
||||
<linkset>
|
||||
<html:link
|
||||
rel="stylesheet"
|
||||
href="chrome://${config.addonRef}/content/styles/notePicker.css"
|
||||
href="chrome://${config.addonRef}/content/styles/linkCreator/notePicker.css"
|
||||
></html:link>
|
||||
</linkset>
|
||||
<vbox id="select-items-dialog" class="container">
|
||||
|
|
@ -52,7 +54,7 @@ export class NotePicker extends PluginCEBase {
|
|||
id="bn-select-opened-notes-content"
|
||||
class="container virtualized-table-container"
|
||||
>
|
||||
<html:div id="bn-select-opened-notes-tree"></html:div>
|
||||
<html:div id="bn-select-opened-notes-tree-${this.uid}"></html:div>
|
||||
</vbox>
|
||||
</vbox>
|
||||
</vbox>
|
||||
|
|
@ -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<number> }) {
|
||||
this.activeSelectionType = "tabs";
|
||||
this.dispatchSelectionChange();
|
||||
this.dispatchSelectionChange(selection);
|
||||
}
|
||||
|
||||
dispatchSelectionChange() {
|
||||
dispatchSelectionChange(selection?: { selected: Set<number> }) {
|
||||
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<number> }): 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]);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<NoteNodeData>[] = [];
|
||||
|
||||
get content() {
|
||||
return MozXULElement.parseXULToFragment(`
|
||||
<linkset>
|
||||
<html:link
|
||||
rel="stylesheet"
|
||||
href="chrome://${config.addonRef}/content/styles/linkCreator/notePreview.css"
|
||||
></html:link>
|
||||
</linkset>
|
||||
<hbox class="toolbar">
|
||||
<hbox class="toolbar-start"></hbox>
|
||||
<hbox class="toolbar-middle"></hbox>
|
||||
<hbox class="toolbar-end"></hbox>
|
||||
</hbox>
|
||||
<vbox id="bn-note-preview-content" class="container">
|
||||
<iframe
|
||||
id="bn-note-preview"
|
||||
class="container"
|
||||
type="content"
|
||||
></iframe>
|
||||
</vbox>
|
||||
`);
|
||||
}
|
||||
|
||||
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 = `<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="chrome://zotero-platform/content/zotero.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="chrome://${config.addonRef}/content/lib/css/github-markdown.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="chrome://${config.addonRef}/content/lib/css/katex.min.css"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<style>
|
||||
html {
|
||||
color-scheme: light dark;
|
||||
background: var(--material-sidepane);
|
||||
}
|
||||
body {
|
||||
overflow-x: clip;
|
||||
}
|
||||
#inserted {
|
||||
border: var(--material-border);
|
||||
box-shadow: 0 2px 5px color-mix(in srgb, var(--material-background) 15%, transparent);
|
||||
border-radius: 4px;
|
||||
background: var(--material-background);
|
||||
padding: 10px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
#inserted:hover {
|
||||
box-shadow: 0 5px 15px color-mix(in srgb, var(--material-background) 20%, transparent);
|
||||
background: var(--color-background50);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>${options.before}</div>
|
||||
<div id="inserted">${options.middle}</div>
|
||||
<div>${options.after}</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
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",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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(`
|
||||
<linkset>
|
||||
<html:link
|
||||
rel="stylesheet"
|
||||
href="chrome://${config.addonRef}/content/styles/linkCreator/outboundCreator.css"
|
||||
></html:link>
|
||||
</linkset>
|
||||
<bn-note-picker></bn-note-picker>
|
||||
<bn-note-outline></bn-note-outline>
|
||||
<bn-note-preview></bn-note-preview>
|
||||
`);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<NoteNodeData>[] = [];
|
||||
|
||||
uid = Zotero.Utilities.randomString(8);
|
||||
|
||||
get content() {
|
||||
return MozXULElement.parseXULToFragment(`
|
||||
<linkset>
|
||||
<html:link
|
||||
rel="stylesheet"
|
||||
href="chrome://${config.addonRef}/content/styles/linkCreator/noteOutline.css"
|
||||
></html:link>
|
||||
</linkset>
|
||||
<hbox class="toolbar">
|
||||
<hbox class="toolbar-start"></hbox>
|
||||
<hbox class="toolbar-middle"></hbox>
|
||||
<hbox class="toolbar-end"></hbox>
|
||||
</hbox>
|
||||
<vbox
|
||||
id="bn-select-note-outline-content"
|
||||
class="container virtualized-table-container"
|
||||
>
|
||||
<html:div id="bn-select-note-outline-tree-${this.uid}"></html:div>
|
||||
</vbox>
|
||||
<hbox id="bn-link-insert-position-container">
|
||||
<label>At section</label>
|
||||
<radiogroup id="bn-link-insert-position" orient="horizontal">
|
||||
<radio
|
||||
id="bn-link-insert-position-top"
|
||||
label="Start"
|
||||
value="start"
|
||||
></radio>
|
||||
<radio
|
||||
id="bn-link-insert-position-bottom"
|
||||
label="End"
|
||||
value="end"
|
||||
></radio>
|
||||
</radiogroup>
|
||||
</hbox>
|
||||
`);
|
||||
}
|
||||
|
||||
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<number> }) {
|
||||
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<number> }) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("selectionchange", {
|
||||
detail: {
|
||||
selectedSection: this.getSelectedSection(selection),
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
getSelectedSection(selection?: { selected: Set<number> }): NoteNodeData {
|
||||
return this.noteOutline[
|
||||
(selection || this.noteOutlineView.treeInstance.selection).selected
|
||||
.values()
|
||||
.next().value
|
||||
]?.model;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
|||
<linkset>
|
||||
<html:link
|
||||
rel="stylesheet"
|
||||
href="chrome://${config.addonRef}/content/styles/context.css"
|
||||
href="chrome://${config.addonRef}/content/styles/workspace/context.css"
|
||||
></html:link>
|
||||
</linkset>
|
||||
<bn-details id="container" class="container"></bn-details>
|
||||
|
|
@ -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(`
|
||||
<linkset>
|
||||
<html:link
|
||||
rel="stylesheet"
|
||||
href="chrome://${config.addonRef}/content/styles/details.css"
|
||||
href="chrome://${config.addonRef}/content/styles/workspace/details.css"
|
||||
></html:link>
|
||||
</linkset>
|
||||
<hbox id="zotero-view-item-container" class="zotero-view-item-container" flex="1">
|
||||
|
|
@ -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 {
|
|||
<linkset>
|
||||
<html:link
|
||||
rel="stylesheet"
|
||||
href="chrome://${config.addonRef}/content/styles/outline.css"
|
||||
href="chrome://${config.addonRef}/content/styles/workspace/outline.css"
|
||||
></html:link>
|
||||
</linkset>
|
||||
<hbox id="left-toolbar">
|
||||
|
|
@ -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 {
|
|||
<linkset>
|
||||
<html:link
|
||||
rel="stylesheet"
|
||||
href="chrome://${config.addonRef}/content/styles/related.css"
|
||||
href="chrome://${config.addonRef}/content/styles/workspace/related.css"
|
||||
></html:link>
|
||||
</linkset>
|
||||
<collapsible-section
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { config } from "../../package.json";
|
||||
import { waitUtilAsync } from "../utils/wait";
|
||||
import { PluginCEBase } from "./base";
|
||||
import { ContextPane } from "./context";
|
||||
import { config } from "../../../package.json";
|
||||
import { waitUtilAsync } from "../../utils/wait";
|
||||
import { PluginCEBase } from "../base";
|
||||
import { ContextPane } from "./contextPane";
|
||||
import { OutlinePane } from "./outlinePane";
|
||||
|
||||
export class Workspace extends PluginCEBase {
|
||||
|
|
@ -20,7 +20,7 @@ export class Workspace extends PluginCEBase {
|
|||
<linkset>
|
||||
<html:link
|
||||
rel="stylesheet"
|
||||
href="chrome://${config.addonRef}/content/styles/workspace.css"
|
||||
href="chrome://${config.addonRef}/content/styles/workspace/workspace.css"
|
||||
></html:link>
|
||||
</linkset>
|
||||
<hbox id="top-container" class="container">
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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<void>;
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
@ -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<Addon["api"]["note"]["getNoteTreeFlattened"]> = [];
|
||||
|
||||
let positionData: NoteNodeData | undefined;
|
||||
|
||||
// @ts-ignore
|
||||
window.addon = Zotero[config.addonRef];
|
||||
|
||||
let io: {
|
||||
currentNoteID: number;
|
||||
openedNoteIDs?: number[];
|
||||
deferred: _ZoteroTypes.DeferredPromise<void>;
|
||||
|
||||
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<number> }) {
|
||||
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 = `<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="chrome://zotero-platform/content/zotero.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="chrome://${config.addonRef}/content/lib/css/github-markdown.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="chrome://${config.addonRef}/content/lib/css/katex.min.css"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<style>
|
||||
html {
|
||||
color-scheme: light dark;
|
||||
background: var(--material-sidepane);
|
||||
}
|
||||
body {
|
||||
overflow-x: clip;
|
||||
}
|
||||
#inserted {
|
||||
border: var(--material-border);
|
||||
box-shadow: 0 2px 5px color-mix(in srgb, var(--material-background) 15%, transparent);
|
||||
border-radius: 4px;
|
||||
background: var(--material-background);
|
||||
padding: 10px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
#inserted:hover {
|
||||
box-shadow: 0 5px 15px color-mix(in srgb, var(--material-background) 20%, transparent);
|
||||
background: var(--color-background50);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>${before}</div>
|
||||
<div id="inserted">${content}</div>
|
||||
<div>${after}</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
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();
|
||||
}
|
||||
|
|
@ -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),
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export function registerNoteRelation() {
|
|||
<linkset>
|
||||
<html:link
|
||||
rel="stylesheet"
|
||||
href="chrome://${config.addonRef}/content/styles/relation.css"
|
||||
href="chrome://${config.addonRef}/content/styles/workspace/relation.css"
|
||||
></html:link>
|
||||
</linkset>
|
||||
<iframe
|
||||
|
|
|
|||
|
|
@ -1,18 +1,24 @@
|
|||
import { config } from "../../package.json";
|
||||
import { addLineToNote } from "./note";
|
||||
|
||||
export { openLinkNoteDialog };
|
||||
export { openLinkCreator };
|
||||
|
||||
async function openLinkNoteDialog(currentNote: Zotero.Item) {
|
||||
async function openLinkCreator(
|
||||
currentNote: Zotero.Item,
|
||||
options?: {
|
||||
lineIndex: number;
|
||||
},
|
||||
) {
|
||||
const io = {
|
||||
openedNoteIDs: Zotero_Tabs._tabs
|
||||
.map((tab) => tab.data?.itemID)
|
||||
.filter((id) => id && id != currentNote.id),
|
||||
currentNoteID: currentNote.id,
|
||||
currentLineIndex: options?.lineIndex,
|
||||
deferred: Zotero.Promise.defer(),
|
||||
} as any;
|
||||
window.openDialog(
|
||||
`chrome://${config.addonRef}/content/linkNote.xhtml`,
|
||||
`chrome://${config.addonRef}/content/linkCreator.xhtml`,
|
||||
"_blank",
|
||||
"chrome,modal,centerscreen,resizable=yes",
|
||||
io,
|
||||
Loading…
Reference in New Issue