From 415705a3bf7a30235fb991d84490797ff223518d Mon Sep 17 00:00:00 2001
From: windingwind <33902321+windingwind@users.noreply.github.com>
Date: Thu, 14 Mar 2024 22:20:25 +0800
Subject: [PATCH 01/23] add: note tabs
---
addon/chrome/content/bubbleMap.html | 19 +-
addon/chrome/content/icons/outline-20.svg | 12 +
addon/chrome/content/icons/save-20.svg | 9 +
addon/chrome/content/mindMap.html | 21 +-
addon/chrome/content/styles/context.css | 3 +
addon/chrome/content/styles/details.css | 3 +
addon/chrome/content/styles/editor.css | 49 +++
addon/chrome/content/styles/outline.css | 39 ++
addon/chrome/content/styles/workspace.css | 11 +
addon/chrome/content/treeView.html | 23 +-
addon/locale/en-US/outline.ftl | 20 +
package.json | 2 +-
scripts/build-extras.mjs | 14 +-
scripts/build.mjs | 1 +
src/addon.ts | 48 ---
src/api.ts | 7 +-
src/elements/base.ts | 60 +++
src/elements/context.ts | 47 +++
src/elements/detailsPane.ts | 30 ++
src/elements/outlinePane.ts | 379 +++++++++++++++++
src/elements/workspace.ts | 116 +++++
src/extras/workspace.ts | 17 +
src/hooks.ts | 148 ++-----
src/modules/annotationTagAction.ts | 42 --
src/modules/createNote.ts | 40 +-
src/modules/editor/inject.ts | 38 +-
src/modules/editor/toolbar.ts | 196 ++++-----
src/modules/export/api.ts | 2 +-
src/modules/menu.ts | 171 +++-----
src/modules/viewItems.ts | 28 ++
src/modules/workspace/content.ts | 489 +---------------------
src/modules/workspace/message.ts | 84 ----
src/modules/workspace/tab.ts | 367 ++--------------
src/modules/workspace/window.ts | 21 +-
src/utils/note.ts | 17 +-
src/utils/wait.ts | 12 +-
typings/global.d.ts | 19 +
37 files changed, 1116 insertions(+), 1488 deletions(-)
create mode 100644 addon/chrome/content/icons/outline-20.svg
create mode 100644 addon/chrome/content/icons/save-20.svg
create mode 100644 addon/chrome/content/styles/context.css
create mode 100644 addon/chrome/content/styles/details.css
create mode 100644 addon/chrome/content/styles/editor.css
create mode 100644 addon/chrome/content/styles/outline.css
create mode 100644 addon/chrome/content/styles/workspace.css
create mode 100644 addon/locale/en-US/outline.ftl
create mode 100644 src/elements/base.ts
create mode 100644 src/elements/context.ts
create mode 100644 src/elements/detailsPane.ts
create mode 100644 src/elements/outlinePane.ts
create mode 100644 src/elements/workspace.ts
create mode 100644 src/extras/workspace.ts
delete mode 100644 src/modules/annotationTagAction.ts
create mode 100644 src/modules/viewItems.ts
delete mode 100644 src/modules/workspace/message.ts
diff --git a/addon/chrome/content/bubbleMap.html b/addon/chrome/content/bubbleMap.html
index d194737..74f350e 100644
--- a/addon/chrome/content/bubbleMap.html
+++ b/addon/chrome/content/bubbleMap.html
@@ -172,12 +172,12 @@
),
$(go.Panel, "Auto"),
);
- window.parent.postMessage({ type: "ready" }, "*");
+ window.postMessage({ type: "ready" }, "*");
getData();
}
function getData() {
- window.parent.postMessage({ type: "getMindMapData" }, "*");
+ window.postMessage({ type: "getMindMapData" }, "*");
}
function setData(nodes) {
@@ -219,17 +219,16 @@
var oldnode = adorn.adornedPart;
var olddata = oldnode.data;
if (olddata.noteLink) {
- window.parent.postMessage(
+ window.postMessage(
{ type: "openNote", link: olddata.noteLink, id: olddata.key },
"*",
);
} else {
- window.parent.postMessage(
+ window.postMessage(
{
type: "jumpNode",
lineIndex: olddata.lineIndex,
id: olddata.key,
- workspaceType: window.workspaceType || "tab",
},
"*",
);
@@ -243,12 +242,11 @@
alert("Link cannot be edited in mind map");
return false;
}
- window.parent.postMessage(
+ window.postMessage(
{
type: "editNode",
lineIndex: data.lineIndex,
text: data.text,
- workspaceType: window.workspaceType || "tab",
},
"*",
);
@@ -259,30 +257,31 @@
switch (e.data.type) {
case "setMindMapData":
setData(e.data.nodes);
- window.workspaceType = e.data.workspaceType;
break;
case "saveImage":
const imgString = Diagram.makeImageData({
scale: 1,
});
- window.parent.postMessage(
+ window.postMessage(
{
type: "saveImageReturn",
image: imgString,
},
"*",
);
+ break;
case "saveSVG":
const svgElement = Diagram.makeSvg({
scale: 1,
});
- window.parent.postMessage(
+ window.postMessage(
{
type: "saveSVGReturn",
image: svgElement.outerHTML,
},
"*",
);
+ break;
default:
break;
}
diff --git a/addon/chrome/content/icons/outline-20.svg b/addon/chrome/content/icons/outline-20.svg
new file mode 100644
index 0000000..99e7236
--- /dev/null
+++ b/addon/chrome/content/icons/outline-20.svg
@@ -0,0 +1,12 @@
+
\ No newline at end of file
diff --git a/addon/chrome/content/icons/save-20.svg b/addon/chrome/content/icons/save-20.svg
new file mode 100644
index 0000000..69edd92
--- /dev/null
+++ b/addon/chrome/content/icons/save-20.svg
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/addon/chrome/content/mindMap.html b/addon/chrome/content/mindMap.html
index 3a65026..833de88 100644
--- a/addon/chrome/content/mindMap.html
+++ b/addon/chrome/content/mindMap.html
@@ -146,7 +146,7 @@
});
// read in the predefined graph using the JSON format data held in the "mySavedModel" textarea
- window.parent.postMessage({ type: "ready" }, "*");
+ window.postMessage({ type: "ready" }, "*");
getData();
}
@@ -240,7 +240,7 @@
}
function getData() {
- window.parent.postMessage({ type: "getMindMapData" }, "*");
+ window.postMessage({ type: "getMindMapData" }, "*");
}
function setData(nodes) {
@@ -281,17 +281,16 @@
var oldnode = adorn.adornedPart;
var olddata = oldnode.data;
if (olddata.noteLink) {
- window.parent.postMessage(
+ window.postMessage(
{ type: "openNote", link: olddata.noteLink, id: olddata.key },
"*",
);
} else {
- window.parent.postMessage(
+ window.postMessage(
{
type: "jumpNode",
lineIndex: olddata.lineIndex,
id: olddata.key,
- workspaceType: window.workspaceType || "tab",
},
"*",
);
@@ -305,12 +304,11 @@
alert("Link cannot be edited in mind map");
return false;
}
- window.parent.postMessage(
+ window.postMessage(
{
type: "editNode",
lineIndex: data.lineIndex,
text: data.text,
- workspaceType: window.workspaceType || "tab",
},
"*",
);
@@ -321,30 +319,31 @@
switch (e.data.type) {
case "setMindMapData":
setData(e.data.nodes);
- window.workspaceType = e.data.workspaceType;
break;
case "saveImage":
const imgString = Diagram.makeImageData({
scale: 1,
});
- window.parent.postMessage(
+ window.postMessage(
{
type: "saveImageReturn",
image: imgString,
},
"*",
);
+ break;
case "saveSVG":
const svgElement = Diagram.makeSvg({
scale: 1,
});
- window.parent.postMessage(
+ window.postMessage(
{
type: "saveSVGReturn",
image: svgElement.outerHTML,
},
"*",
);
+ break;
default:
break;
}
@@ -354,7 +353,7 @@
try {
init();
} catch (e) {
- window.parent.postMessage({ type: "error", event: e }, "*");
+ window.postMessage({ type: "error", event: e }, "*");
}
});
diff --git a/addon/chrome/content/styles/context.css b/addon/chrome/content/styles/context.css
new file mode 100644
index 0000000..253e369
--- /dev/null
+++ b/addon/chrome/content/styles/context.css
@@ -0,0 +1,3 @@
+bn-context {
+ min-width: 182px;
+}
\ No newline at end of file
diff --git a/addon/chrome/content/styles/details.css b/addon/chrome/content/styles/details.css
new file mode 100644
index 0000000..25f9fdb
--- /dev/null
+++ b/addon/chrome/content/styles/details.css
@@ -0,0 +1,3 @@
+bn-details pane-header {
+ display: none;
+}
\ No newline at end of file
diff --git a/addon/chrome/content/styles/editor.css b/addon/chrome/content/styles/editor.css
new file mode 100644
index 0000000..797a4a0
--- /dev/null
+++ b/addon/chrome/content/styles/editor.css
@@ -0,0 +1,49 @@
+.primary-editor > h1::before {
+ margin-left: -64px !important;
+ padding-left: 40px !important;
+ content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%2218px%22%20height%3D%2218px%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2015.56%22%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill%3A%23666%3B%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Ctitle%3E%E6%9C%AA%E6%A0%87%E9%A2%98-1%3C%2Ftitle%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M12.29%2C16.8H11.14V12.33H6.07V16.8H4.92V7H6.07v4.3h5.07V7h1.15Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M18.05%2C16.8H16.93V8.41a4%2C4%2C0%2C0%2C1-.9.53%2C6.52%2C6.52%2C0%2C0%2C1-1.14.44l-.32-1a8.2%2C8.2%2C0%2C0%2C0%2C1.67-.67%2C6.31%2C6.31%2C0%2C0%2C0%2C1.39-1h.42Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M21%2C5a2.25%2C2.25%2C0%2C0%2C1%2C2.25%2C2.25v9.56A2.25%2C2.25%2C0%2C0%2C1%2C21%2C19H3A2.25%2C2.25%2C0%2C0%2C1%2C.75%2C16.78V7.22A2.25%2C2.25%2C0%2C0%2C1%2C3%2C5H21m0-.75H3a3%2C3%2C0%2C0%2C0-3%2C3v9.56a3%2C3%2C0%2C0%2C0%2C3%2C3H21a3%2C3%2C0%2C0%2C0%2C3-3V7.22a3%2C3%2C0%2C0%2C0-3-3Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3C%2Fsvg%3E") !important;
+}
+.primary-editor > h2::before {
+ margin-left: -64px !important;
+ padding-left: 40px !important;
+ content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%2218px%22%20height%3D%2218px%22%20%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2015.56%22%3E%3Cdefs%3E%3Cstyle%3E.a%7Bfill%3A%23666%3B%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cpath%20class%3D%22a%22%20d%3D%22M11.17%2C16.8H10V12.33H5V16.8H3.8V7H5v4.3H10V7h1.15Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22a%22%20d%3D%22M14.14%2C16.8v-.48a4.1%2C4.1%2C0%2C0%2C1%2C.14-1.11%2C2.86%2C2.86%2C0%2C0%2C1%2C.45-.91%2C5.49%2C5.49%2C0%2C0%2C1%2C.83-.86c.33-.29.75-.61%2C1.24-1a7.43%2C7.43%2C0%2C0%2C0%2C.9-.73%2C3.9%2C3.9%2C0%2C0%2C0%2C.57-.7%2C2.22%2C2.22%2C0%2C0%2C0%2C.3-.66%2C2.87%2C2.87%2C0%2C0%2C0%2C.11-.77%2C1.89%2C1.89%2C0%2C0%2C0-.47-1.32%2C1.66%2C1.66%2C0%2C0%2C0-1.28-.5A3.17%2C3.17%2C0%2C0%2C0%2C15.7%2C8a3.49%2C3.49%2C0%2C0%2C0-1.08.76l-.68-.65a4.26%2C4.26%2C0%2C0%2C1%2C1.39-1A4%2C4%2C0%2C0%2C1%2C17%2C6.84a2.62%2C2.62%2C0%2C0%2C1%2C2.83%2C2.67%2C3.58%2C3.58%2C0%2C0%2C1-.15%2C1%2C3.09%2C3.09%2C0%2C0%2C1-.41.9%2C5.53%2C5.53%2C0%2C0%2C1-.67.81%2C9%2C9%2C0%2C0%2C1-.95.79c-.46.32-.84.59-1.13.82a4.68%2C4.68%2C0%2C0%2C0-.71.64%2C2%2C2%2C0%2C0%2C0-.38.6%2C2.08%2C2.08%2C0%2C0%2C0-.11.69h4.88v1Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22a%22%20d%3D%22M21%2C5a2.25%2C2.25%2C0%2C0%2C1%2C2.25%2C2.25v9.56A2.25%2C2.25%2C0%2C0%2C1%2C21%2C19H3A2.25%2C2.25%2C0%2C0%2C1%2C.75%2C16.78V7.22A2.25%2C2.25%2C0%2C0%2C1%2C3%2C5H21m0-.75H3a3%2C3%2C0%2C0%2C0-3%2C3v9.56a3%2C3%2C0%2C0%2C0%2C3%2C3H21a3%2C3%2C0%2C0%2C0%2C3-3V7.22a3%2C3%2C0%2C0%2C0-3-3Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3C%2Fsvg%3E") !important;
+}
+.primary-editor > h3::before {
+ margin-left: -64px !important;
+ padding-left: 40px !important;
+ content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%2218px%22%20height%3D%2218px%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2015.56%22%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill%3A%23666%3B%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M11.17%2C16.8H10V12.33H5V16.8H3.8V7H5v4.3H10V7h1.15Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M14%2C16.14l.51-.8a4.75%2C4.75%2C0%2C0%2C0%2C1.1.52%2C4.27%2C4.27%2C0%2C0%2C0%2C1.12.16%2C2.29%2C2.29%2C0%2C0%2C0%2C1.64-.52A1.77%2C1.77%2C0%2C0%2C0%2C19%2C14.17a1.7%2C1.7%2C0%2C0%2C0-.68-1.48%2C3.6%2C3.6%2C0%2C0%2C0-2.06-.48H15.4v-1h.77A3%2C3%2C0%2C0%2C0%2C18%2C10.81a1.65%2C1.65%2C0%2C0%2C0%2C.6-1.41%2C1.47%2C1.47%2C0%2C0%2C0-.47-1.19A1.67%2C1.67%2C0%2C0%2C0%2C17%2C7.79a3.33%2C3.33%2C0%2C0%2C0-2.08.73l-.59-.75a4.4%2C4.4%2C0%2C0%2C1%2C1.28-.71A4.35%2C4.35%2C0%2C0%2C1%2C17%2C6.84a2.84%2C2.84%2C0%2C0%2C1%2C2%2C.65%2C2.21%2C2.21%2C0%2C0%2C1%2C.74%2C1.78%2C2.35%2C2.35%2C0%2C0%2C1-.49%2C1.5%2C2.7%2C2.7%2C0%2C0%2C1-1.46.89v0a2.74%2C2.74%2C0%2C0%2C1%2C1.65.74%2C2.15%2C2.15%2C0%2C0%2C1%2C.66%2C1.65%2C2.64%2C2.64%2C0%2C0%2C1-.9%2C2.12%2C3.44%2C3.44%2C0%2C0%2C1-2.34.78%2C5.3%2C5.3%2C0%2C0%2C1-1.48-.2A5%2C5%2C0%2C0%2C1%2C14%2C16.14Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M21%2C5a2.25%2C2.25%2C0%2C0%2C1%2C2.25%2C2.25v9.56A2.25%2C2.25%2C0%2C0%2C1%2C21%2C19H3A2.25%2C2.25%2C0%2C0%2C1%2C.75%2C16.78V7.22A2.25%2C2.25%2C0%2C0%2C1%2C3%2C5H21m0-.75H3a3%2C3%2C0%2C0%2C0-3%2C3v9.56a3%2C3%2C0%2C0%2C0%2C3%2C3H21a3%2C3%2C0%2C0%2C0%2C3-3V7.22a3%2C3%2C0%2C0%2C0-3-3Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3C%2Fsvg%3E") !important;
+}
+.primary-editor > h4::before {
+ margin-left: -64px !important;
+ padding-left: 40px !important;
+ content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%2218px%22%20height%3D%2218px%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2015.56%22%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill%3A%23666%3B%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M11.17%2C16.8H10V12.33H5V16.8H3.8V7H5v4.3H10V7h1.15Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M19.43%2C6.92v6.59h1.05v1.05H19.43V16.9H18.31V14.56H13.66v-1c.43-.49.87-1%2C1.31-1.57s.87-1.13%2C1.27-1.7S17%2C9.14%2C17.36%2C8.57a16.51%2C16.51%2C0%2C0%2C0%2C.86-1.65Zm-4.49%2C6.59h3.37V8.63c-.34.61-.67%2C1.15-1%2C1.63s-.6.91-.87%2C1.3-.56.74-.81%2C1Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M21%2C5a2.25%2C2.25%2C0%2C0%2C1%2C2.25%2C2.25v9.56A2.25%2C2.25%2C0%2C0%2C1%2C21%2C19H3A2.25%2C2.25%2C0%2C0%2C1%2C.75%2C16.78V7.22A2.25%2C2.25%2C0%2C0%2C1%2C3%2C5H21m0-.75H3a3%2C3%2C0%2C0%2C0-3%2C3v9.56a3%2C3%2C0%2C0%2C0%2C3%2C3H21a3%2C3%2C0%2C0%2C0%2C3-3V7.22a3%2C3%2C0%2C0%2C0-3-3Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3C%2Fsvg%3E") !important;
+}
+.primary-editor > h5::before {
+ margin-left: -64px !important;
+ padding-left: 40px !important;
+ content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%2218px%22%20height%3D%2218px%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2015.56%22%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill%3A%23666%3B%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M11.17%2C16.8H10V12.33H5V16.8H3.8V7H5v4.3H10V7h1.15Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M14%2C16l.58-.76a3.67%2C3.67%2C0%2C0%2C0%2C1%2C.58A3.44%2C3.44%2C0%2C0%2C0%2C16.8%2C16a2.17%2C2.17%2C0%2C0%2C0%2C1.58-.6A2%2C2%2C0%2C0%2C0%2C19%2C13.88a1.85%2C1.85%2C0%2C0%2C0-.64-1.5%2C2.83%2C2.83%2C0%2C0%2C0-1.86-.54c-.27%2C0-.55%2C0-.86%2C0s-.58%2C0-.81.06L15.17%2C7H19.7V8H16.14l-.2%2C2.88.47%2C0h.43a3.5%2C3.5%2C0%2C0%2C1%2C2.43.79%2C2.74%2C2.74%2C0%2C0%2C1%2C.88%2C2.16%2C3%2C3%2C0%2C0%2C1-.94%2C2.3%2C3.41%2C3.41%2C0%2C0%2C1-2.4.87%2C4.45%2C4.45%2C0%2C0%2C1-1.5-.24A4.81%2C4.81%2C0%2C0%2C1%2C14%2C16Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M21%2C5a2.25%2C2.25%2C0%2C0%2C1%2C2.25%2C2.25v9.56A2.25%2C2.25%2C0%2C0%2C1%2C21%2C19H3A2.25%2C2.25%2C0%2C0%2C1%2C.75%2C16.78V7.22A2.25%2C2.25%2C0%2C0%2C1%2C3%2C5H21m0-.75H3a3%2C3%2C0%2C0%2C0-3%2C3v9.56a3%2C3%2C0%2C0%2C0%2C3%2C3H21a3%2C3%2C0%2C0%2C0%2C3-3V7.22a3%2C3%2C0%2C0%2C0-3-3Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3C%2Fsvg%3E") !important;
+}
+.primary-editor > h6::before {
+ margin-left: -64px !important;
+ padding-left: 40px !important;
+ content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%2218px%22%20height%3D%2218px%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2015.56%22%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill%3A%23666%3B%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M11.17%2C16.8H10V12.33H5V16.8H3.8V7H5v4.3H10V7h1.15Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M20.18%2C13.7a3.24%2C3.24%2C0%2C0%2C1-.88%2C2.38%2C2.94%2C2.94%2C0%2C0%2C1-2.2.9%2C2.69%2C2.69%2C0%2C0%2C1-2.31-1.17A5.59%2C5.59%2C0%2C0%2C1%2C14%2C12.49a12.18%2C12.18%2C0%2C0%2C1%2C.2-2.14%2C5.16%2C5.16%2C0%2C0%2C1%2C.84-2A3.65%2C3.65%2C0%2C0%2C1%2C16.27%2C7.2%2C3.71%2C3.71%2C0%2C0%2C1%2C18%2C6.84%2C3.14%2C3.14%2C0%2C0%2C1%2C19%2C7a3.59%2C3.59%2C0%2C0%2C1%2C1%2C.5l-.56.77a2.3%2C2.3%2C0%2C0%2C0-1.49-.48A2.3%2C2.3%2C0%2C0%2C0%2C16.79%2C8a3%2C3%2C0%2C0%2C0-.92.85%2C3.79%2C3.79%2C0%2C0%2C0-.56%2C1.25%2C6.56%2C6.56%2C0%2C0%2C0-.19%2C1.65h0a2.61%2C2.61%2C0%2C0%2C1%2C1-.84%2C2.91%2C2.91%2C0%2C0%2C1%2C1.23-.28%2C2.63%2C2.63%2C0%2C0%2C1%2C2%2C.85A3.09%2C3.09%2C0%2C0%2C1%2C20.18%2C13.7ZM19%2C13.78a2.28%2C2.28%2C0%2C0%2C0-.5-1.62%2C1.67%2C1.67%2C0%2C0%2C0-1.29-.54%2C2%2C2%2C0%2C0%2C0-1.5.58%2C2%2C2%2C0%2C0%2C0-.56%2C1.4%2C2.65%2C2.65%2C0%2C0%2C0%2C.55%2C1.74%2C1.85%2C1.85%2C0%2C0%2C0%2C2.78.1A2.38%2C2.38%2C0%2C0%2C0%2C19%2C13.78Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M21%2C5a2.25%2C2.25%2C0%2C0%2C1%2C2.25%2C2.25v9.56A2.25%2C2.25%2C0%2C0%2C1%2C21%2C19H3A2.25%2C2.25%2C0%2C0%2C1%2C.75%2C16.78V7.22A2.25%2C2.25%2C0%2C0%2C1%2C3%2C5H21m0-.75H3a3%2C3%2C0%2C0%2C0-3%2C3v9.56a3%2C3%2C0%2C0%2C0%2C3%2C3H21a3%2C3%2C0%2C0%2C0%2C3-3V7.22a3%2C3%2C0%2C0%2C0-3-3Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3C%2Fsvg%3E") !important;
+}
+.primary-editor > p,
+.primary-editor h1,
+.primary-editor h2,
+.primary-editor h3,
+.primary-editor h4,
+.primary-editor h5,
+.primary-editor h6,
+.primary-editor pre,
+.primary-editor blockquote,
+.primary-editor table,
+.primary-editor ul,
+.primary-editor ol,
+.primary-editor hr {
+ max-width: unset;
+}
+
+body[no-bn-toolbar] #BetterNotes-toolbar {
+ display: none;
+}
diff --git a/addon/chrome/content/styles/outline.css b/addon/chrome/content/styles/outline.css
new file mode 100644
index 0000000..4598945
--- /dev/null
+++ b/addon/chrome/content/styles/outline.css
@@ -0,0 +1,39 @@
+bn-outline,
+.container {
+ width: 100%;
+ height: 100%;
+ background: var(--material-sidepane);
+}
+
+bn-outline {
+ min-width: 100px;
+ flex-direction: column;
+}
+
+@media (-moz-platform: macos) {
+ #__addonRef__-left-toolbar {
+ -moz-window-dragging: drag;
+ }
+}
+
+#__addonRef__-left-toolbar {
+ background: var(--material-toolbar);
+ border-bottom: var(--material-panedivider);
+ padding: 6px 8px;
+}
+
+bn-outline .zotero-tb-button {
+ width: 28px;
+ height: 28px;
+ margin: 0px 4px 0px 4px;
+ fill: currentColor;
+ -moz-context-properties: fill, fill-opacity;
+}
+
+#__addonRef__-setOutline {
+ list-style-image: url("chrome://__addonRef__/content/icons/outline-20.svg");
+}
+
+#__addonRef__-saveOutline {
+ list-style-image: url("chrome://__addonRef__/content/icons/save-20.svg");
+}
diff --git a/addon/chrome/content/styles/workspace.css b/addon/chrome/content/styles/workspace.css
new file mode 100644
index 0000000..5ad9b5a
--- /dev/null
+++ b/addon/chrome/content/styles/workspace.css
@@ -0,0 +1,11 @@
+bn-workspace,
+.container {
+ width: 100%;
+ height: 100%;
+ background: var(--material-sidepane);
+ flex-grow: 1;
+}
+
+bn-workspace #__addonRef__-editor-main #links-container {
+ display: none;
+}
diff --git a/addon/chrome/content/treeView.html b/addon/chrome/content/treeView.html
index 3476800..e9b1cbe 100644
--- a/addon/chrome/content/treeView.html
+++ b/addon/chrome/content/treeView.html
@@ -43,7 +43,7 @@
}
#treeview {
- padding: 20px;
+ padding: 8px;
}
.drive-header {
@@ -72,13 +72,18 @@
color: var(--fill-primary);
}
+ .dx-treeview-item {
+ border-radius: 5px;
+ cursor: auto;
+ }
+
.dx-treeview-toggle-item-visibility {
color: var(--fill-primary);
}
:not(.dx-state-focused) > .dx-treeview-item.dx-state-hover {
color: var(--fill-primary);
- background-color: var(--fill-quinary);
+ background-color: transparent;
}
.dx-state-focused > .dx-treeview-item {
@@ -94,7 +99,7 @@
var cachedNodes = null;
function init() {
window.addEventListener("message", handler, false);
- window.parent.postMessage({ type: "ready" }, "*");
+ window.postMessage({ type: "ready" }, "*");
getData();
}
@@ -151,7 +156,7 @@
return;
}
- window.parent.postMessage(
+ window.postMessage(
{
type: "moveNode",
fromID: parseInt(fromNode.itemData.id),
@@ -295,7 +300,7 @@
function jumpNode(e) {
var itemData = e.itemData;
if (itemData.noteLink) {
- window.parent.postMessage(
+ window.postMessage(
{
type: "openNote",
link: itemData.noteLink,
@@ -304,12 +309,11 @@
"*",
);
} else {
- window.parent.postMessage(
+ window.postMessage(
{
type: "jumpNode",
lineIndex: itemData.lineIndex,
id: parseInt(itemData.id),
- workspaceType: window.workspaceType || "tab",
},
"*",
);
@@ -319,7 +323,7 @@
const noteIcon = ``;
function getData() {
- window.parent.postMessage({ type: "getMindMapData" }, "*");
+ window.postMessage({ type: "getMindMapData" }, "*");
}
function setData(nodes, expandLevel) {
@@ -384,7 +388,6 @@
console.log(e);
if (e.data.type === "setMindMapData") {
setData(e.data.nodes, e.data.expandLevel);
- window.workspaceType = e.data.workspaceType;
}
}
@@ -392,7 +395,7 @@
try {
init();
} catch (e) {
- window.parent.postMessage({ type: "error", event: e }, "*");
+ window.postMessage({ type: "error", event: e }, "*");
}
});
diff --git a/addon/locale/en-US/outline.ftl b/addon/locale/en-US/outline.ftl
new file mode 100644
index 0000000..aa31812
--- /dev/null
+++ b/addon/locale/en-US/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/package.json b/package.json
index 66f3ca5..d99b862 100644
--- a/package.json
+++ b/package.json
@@ -58,7 +58,7 @@
"unist-util-visit": "^5.0.0",
"unist-util-visit-parents": "^6.0.1",
"yamljs": "^0.3.0",
- "zotero-plugin-toolkit": "^2.3.23"
+ "zotero-plugin-toolkit": "^2.3.26"
},
"devDependencies": {
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
diff --git a/scripts/build-extras.mjs b/scripts/build-extras.mjs
index cefdd93..d00cda6 100644
--- a/scripts/build-extras.mjs
+++ b/scripts/build-extras.mjs
@@ -5,19 +5,9 @@ const buildDir = "build";
export async function main() {
await build({
- entryPoints: ["src/extras/editorScript.ts"],
+ entryPoints: ["./src/extras/*.ts"],
+ outdir: path.join(buildDir, "addon/chrome/content/scripts"),
bundle: true,
- outfile: path.join(
- buildDir,
- "addon/chrome/content/scripts/editorScript.js",
- ),
- target: ["firefox102"],
- }).catch(() => exit(1));
-
- await build({
- entryPoints: ["src/extras/docxWorker.ts"],
- bundle: true,
- outfile: path.join(buildDir, "addon/chrome/content/scripts/docxWorker.js"),
target: ["firefox102"],
}).catch(() => exit(1));
}
diff --git a/scripts/build.mjs b/scripts/build.mjs
index 96bd4a1..495ef39 100644
--- a/scripts/build.mjs
+++ b/scripts/build.mjs
@@ -49,6 +49,7 @@ function replaceString(buildTime) {
`${buildDir}/addon/**/*.xhtml`,
`${buildDir}/addon/**/*.html`,
`${buildDir}/addon/**/*.json`,
+ `${buildDir}/addon/**/*.css`,
`${buildDir}/addon/prefs.js`,
`${buildDir}/addon/manifest.json`,
`${buildDir}/addon/bootstrap.js`,
diff --git a/src/addon.ts b/src/addon.ts
index c378645..fe9fbbe 100644
--- a/src/addon.ts
+++ b/src/addon.ts
@@ -7,7 +7,6 @@ import { LargePrefHelper } from "zotero-plugin-toolkit/dist/helpers/largePref";
import ToolkitGlobal from "zotero-plugin-toolkit/dist/managers/toolkitGlobal";
import { getPref, setPref } from "./utils/prefs";
-import { OutlineType } from "./utils/workspace";
import { SyncDataType } from "./modules/sync/managerWindow";
import hooks from "./hooks";
import api from "./api";
@@ -46,21 +45,6 @@ class Addon {
};
};
notify: Array>;
- workspace: {
- mainId: number;
- previewId: number;
- tab: {
- active: boolean;
- id?: string;
- container?: XUL.Box;
- };
- window: {
- active: boolean;
- window?: Window;
- container?: XUL.Box;
- };
- outline: OutlineType;
- };
imageViewer: {
window?: Window;
srcList: string[];
@@ -105,38 +89,6 @@ class Addon {
diff: {},
},
notify: [],
- workspace: {
- get mainId(): number {
- return parseInt(getPref("mainKnowledgeID") as string);
- },
- set mainId(id: number) {
- setPref("mainKnowledgeID", id);
- const recentMainNoteIds = getPref("recentMainNoteIds") as string;
- const recentMainNoteIdsArr = recentMainNoteIds
- ? recentMainNoteIds.split(",").map((id) => parseInt(id))
- : [];
- const idx = recentMainNoteIdsArr.indexOf(id);
- if (idx !== -1) {
- recentMainNoteIdsArr.splice(idx, 1);
- }
- recentMainNoteIdsArr.unshift(id);
- setPref(
- "recentMainNoteIds",
- recentMainNoteIdsArr
- .slice(0, 10)
- .filter((id) => Zotero.Items.get(id).isNote())
- .join(","),
- );
- },
- previewId: -1,
- tab: {
- active: false,
- },
- window: {
- active: false,
- },
- outline: OutlineType.treeView,
- },
imageViewer: {
window: undefined,
srcList: [],
diff --git a/src/api.ts b/src/api.ts
index 4922069..5bbabaf 100644
--- a/src/api.ts
+++ b/src/api.ts
@@ -44,7 +44,6 @@ import {
DEFAULT_TEMPLATES,
} from "./modules/template/data";
import { renderTemplatePreview } from "./modules/template/preview";
-import { getWorkspaceEditor } from "./modules/workspace/content";
import { parseCitationHTML } from "./utils/citation";
import {
getEditorInstance,
@@ -65,11 +64,10 @@ import {
addLineToNote,
updateRelatedNotes,
getRelatedNoteIds,
+ getNoteTreeFlattened,
} from "./utils/note";
-const workspace = {
- getWorkspaceEditor,
-};
+const workspace = {};
const sync = {
isSyncNote,
@@ -148,6 +146,7 @@ const note = {
insert: addLineToNote,
updateRelatedNotes,
getRelatedNoteIds,
+ getNoteTreeFlattened,
};
export default {
diff --git a/src/elements/base.ts b/src/elements/base.ts
new file mode 100644
index 0000000..76e10b7
--- /dev/null
+++ b/src/elements/base.ts
@@ -0,0 +1,60 @@
+import { config } from "../../package.json";
+
+export class PluginCEBase extends XULElementBase {
+ _addon!: typeof addon;
+
+ connectedCallback(): void {
+ this._addon = Zotero[config.addonInstance];
+ Zotero.UIProperties.registerRoot(this);
+ super.connectedCallback();
+ }
+
+ _wrapID(key: string) {
+ if (key.startsWith(config.addonRef)) {
+ return key;
+ }
+ return `${config.addonRef}-${key}`;
+ }
+
+ _unwrapID(id: string) {
+ if (id.startsWith(config.addonRef)) {
+ return id.slice(config.addonRef.length + 1);
+ }
+ return id;
+ }
+
+ _queryID(key: string) {
+ return this.querySelector(`#${this._wrapID(key)}`) as XUL.Element | null;
+ }
+
+ _parseContentID(dom: DocumentFragment) {
+ dom.querySelectorAll("*[id]").forEach(elem => {
+ elem.id = this._wrapID(elem.id);
+ })
+ return dom;
+ }
+
+ _loadPersist() {
+ const persistValues = Zotero.Prefs.get("pane.persist") as string;
+ if (!persistValues) return;
+ const serializedValues = JSON.parse(persistValues) as Record<
+ string,
+ Record
+ >;
+
+ for (const id in serializedValues) {
+ const el = this.querySelector(`#${id}`) as HTMLElement;
+ if (!el) {
+ continue;
+ }
+
+ const elValues = serializedValues[id];
+ for (const attr in elValues) {
+ el.setAttribute(attr, elValues[attr]);
+ if (["width", "height"].includes(attr)) {
+ el.style[attr as any] = `${elValues[attr]}px`;
+ }
+ }
+ }
+ }
+}
diff --git a/src/elements/context.ts b/src/elements/context.ts
new file mode 100644
index 0000000..febba36
--- /dev/null
+++ b/src/elements/context.ts
@@ -0,0 +1,47 @@
+import { config } from "../../package.json";
+import { PluginCEBase } from "./base";
+
+export class ContextPane extends PluginCEBase {
+ _item?: Zotero.Item;
+
+ _details!: any;
+ _sidenav: any;
+
+ get item() {
+ return this._item;
+ }
+
+ set item(val) {
+ this._item = val;
+ }
+
+ get content() {
+ return this._parseContentID(
+ MozXULElement.parseXULToFragment(`
+
+
+
+
+
+`),
+ );
+ }
+
+ init(): void {
+ this._details = this._queryID("container");
+ this._sidenav = this._queryID("sidenav");
+ }
+
+ render() {
+ if (!this.item) return;
+ this._details.mode = this.item.isEditable() ? null : "view";
+ this._details.item = this.item;
+ this._details.parentID = this.item.parentID;
+ this._details.sidenav = this._sidenav;
+ this._details.render();
+ this._sidenav.toggleDefaultStatus();
+ }
+}
diff --git a/src/elements/detailsPane.ts b/src/elements/detailsPane.ts
new file mode 100644
index 0000000..197197b
--- /dev/null
+++ b/src/elements/detailsPane.ts
@@ -0,0 +1,30 @@
+import { config } from "../../package.json";
+const ItemDetails = customElements.get("item-details")! as any;
+
+export class NoteDetails extends ItemDetails {
+ content = MozXULElement.parseXULToFragment(`
+
+
+
+
+
+
+
+
+
+
+
+
+`);
+
+ forceUpdateSideNav() {
+ this._sidenav
+ .querySelectorAll("toolbarbutton")
+ .forEach((elem: HTMLElement) => (elem.parentElement!.hidden = true));
+ super.forceUpdateSideNav();
+ }
+}
diff --git a/src/elements/outlinePane.ts b/src/elements/outlinePane.ts
new file mode 100644
index 0000000..81d816e
--- /dev/null
+++ b/src/elements/outlinePane.ts
@@ -0,0 +1,379 @@
+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 {
+ getEditorInstance,
+ moveHeading,
+ updateHeadingTextAtLine,
+} 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;
+ _item?: Zotero.Item;
+ _editorElement!: EditorElement;
+
+ _outlineContainer!: HTMLIFrameElement;
+ _notifierID!: string;
+
+ static outlineSources = [
+ "",
+ `chrome://${config.addonRef}/content/treeView.html`,
+ `chrome://${config.addonRef}/content/mindMap.html`,
+ `chrome://${config.addonRef}/content/bubbleMap.html`,
+ ];
+
+ static outlineMenuIDs = {
+ "": OutlineType.empty,
+ useTreeView: OutlineType.treeView,
+ useMindMap: OutlineType.mindMap,
+ useBubbleMap: OutlineType.bubbleMap,
+ };
+
+ get content() {
+ return this._parseContentID(
+ MozXULElement.parseXULToFragment(`
+
+
+
+
+
+
+
+
+
+
+
+`),
+ );
+ }
+
+ get outlineType() {
+ return this._outlineType;
+ }
+
+ set outlineType(newType) {
+ if (newType === OutlineType.empty) {
+ newType = OutlineType.treeView;
+ }
+ if (newType > OutlineType.bubbleMap) {
+ newType = OutlineType.treeView;
+ }
+
+ this._outlineType = newType;
+ }
+
+ get item() {
+ return this._item;
+ }
+
+ set item(val) {
+ this._item = val;
+ }
+
+ get editor() {
+ return this._editorElement._editorInstance;
+ }
+
+ init(): void {
+ MozXULElement.insertFTLIfNeeded(`${config.addonRef}-outline.ftl`);
+
+ this._outlineContainer = this._queryID("outline") as unknown as HTMLIFrameElement;
+
+ this._queryID("left-toolbar")?.addEventListener(
+ "command",
+ this.toolbarButtonCommandHandler,
+ );
+
+ this._notifierID = Zotero.Notifier.registerObserver(
+ this,
+ ["item"],
+ "attachmentsBox",
+ );
+
+ this._loadPersist();
+ }
+
+ destroy(): void {
+ Zotero.Notifier.unregisterObserver(this._notifierID);
+ this._outlineContainer.contentWindow?.removeEventListener(
+ "message",
+ this.messageHandler,
+ );
+ }
+
+ notify(
+ event: string,
+ type: string,
+ ids: number[] | string[],
+ extraData: { [key: string]: any },
+ ) {
+ if (!this.item) return;
+ if (event === "modify" && type === "item") {
+ if ((ids as number[]).includes(this.item.id)) {
+ this.updateOutline();
+ if (getPref("workspace.autoUpdateRelatedNotes")) {
+ this._addon.api.note.updateRelatedNotes(this.item.id);
+ }
+ }
+ }
+ }
+
+ async render() {
+ if (this.outlineType === OutlineType.empty) {
+ this.outlineType = OutlineType.treeView;
+ }
+ await this.updateOutline();
+ }
+
+ async updateOutline() {
+ if (!this.item) return;
+
+ this._outlineContainer.contentWindow?.removeEventListener(
+ "message",
+ this.messageHandler,
+ );
+
+ this._outlineContainer.setAttribute("src", OutlinePane.outlineSources[this.outlineType]);
+
+ await waitUtilAsync(
+ () => this._outlineContainer.contentWindow?.document.readyState === "complete",
+ );
+ this._outlineContainer.contentWindow?.addEventListener(
+ "message",
+ this.messageHandler,
+ );
+ this._outlineContainer.contentWindow?.postMessage(
+ {
+ type: "setMindMapData",
+ nodes: this._addon.api.note.getNoteTreeFlattened(this.item, {
+ keepLink: !!getPref("workspace.outline.keepLinks"),
+ }),
+ expandLevel: getPref("workspace.outline.expandLevel"),
+ },
+ "*",
+ );
+
+ // Update button hidden
+ const isTreeView = this.outlineType === OutlineType.treeView;
+ for (const key of ["saveImage", "saveSVG"]) {
+ const elem = this._queryID(key);
+ if (isTreeView) {
+ elem?.setAttribute("disabled", "true");
+ } else {
+ elem?.removeAttribute("disabled");
+ }
+ }
+
+ // Update set outline menu
+ this._queryID("setOutlinePopup")?.childNodes.forEach((elem) =>
+ (elem as XUL.MenuItem).removeAttribute("checked"),
+ );
+ this._queryID(
+ Object.keys(OutlinePane.outlineMenuIDs)[this.outlineType],
+ )?.setAttribute("checked", "true");
+ }
+
+ saveImage(type: "saveSVG" | "saveImage") {
+ this._outlineContainer.contentWindow?.postMessage(
+ {
+ type,
+ },
+ "*",
+ );
+ }
+
+ async saveFreeMind() {
+ if (!this.item?.id) return;
+ // TODO: uncouple this part
+ const filename = await new FilePickerHelper(
+ `${Zotero.getString("fileInterface.export")} FreeMind XML`,
+ "save",
+ [["FreeMind XML File(*.mm)", "*.mm"]],
+ `${this.item.getNoteTitle()}.mm`,
+ ).open();
+ if (filename) {
+ await addon.api.$export.saveFreeMind(filename, this.item.id);
+ }
+ }
+
+ toolbarButtonCommandHandler = async (ev: Event) => {
+ if (!this.item) return;
+ const type = this._unwrapID((ev.target as XUL.ToolBarButton).id);
+ switch (type) {
+ case "useTreeView":
+ case "useMindMap":
+ case "useBubbleMap": {
+ this.outlineType = OutlinePane.outlineMenuIDs[type];
+ await this.updateOutline();
+ break;
+ }
+ case "saveImage":
+ case "saveSVG": {
+ this.saveImage(type);
+ break;
+ }
+ case "saveFreeMind": {
+ this.saveFreeMind();
+ break;
+ }
+ case "saveMore": {
+ this._addon.hooks.onShowExportNoteOptions([this.item.id]);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ };
+
+ messageHandler = async (ev: MessageEvent) => {
+ switch (ev.data.type) {
+ case "jumpNode": {
+ if (!this.editor) {
+ return;
+ }
+ this._addon.api.editor.scroll(this.editor, ev.data.lineIndex);
+ return;
+ }
+ case "openNote": {
+ const linkParams = getNoteLinkParams(ev.data.link);
+ if (!linkParams.noteItem) {
+ return;
+ }
+ this._addon.hooks.onOpenNote(linkParams.noteItem.id, "preview", {
+ lineIndex: linkParams.lineIndex || undefined,
+ });
+ return;
+ }
+ case "moveNode": {
+ if (!this.item) return;
+ const tree = getNoteTree(this.item);
+ const fromNode = getNoteTreeNodeById(this.item, ev.data.fromID, tree);
+ const toNode = getNoteTreeNodeById(this.item, ev.data.toID, tree);
+ moveHeading(
+ getEditorInstance(this.item.id),
+ fromNode!,
+ toNode!,
+ ev.data.moveType,
+ );
+ return;
+ }
+ case "editNode": {
+ if (!this.editor) {
+ return;
+ }
+ updateHeadingTextAtLine(
+ this.editor,
+ ev.data.lineIndex,
+ ev.data.text.replace(/[\r\n]/g, ""),
+ );
+ return;
+ }
+ case "saveSVGReturn": {
+ const filename = await new FilePickerHelper(
+ `${Zotero.getString("fileInterface.export")} SVG Image`,
+ "save",
+ [["SVG File(*.svg)", "*.svg"]],
+ `${this.item?.getNoteTitle()}.svg`,
+ ).open();
+ if (filename) {
+ await Zotero.File.putContentsAsync(
+ formatPath(filename),
+ ev.data.image,
+ );
+ showHintWithLink(
+ `Image Saved to ${filename}`,
+ "Show in Folder",
+ (ev) => {
+ Zotero.File.reveal(filename);
+ },
+ );
+ }
+ return;
+ }
+ case "saveImageReturn": {
+ const filename = await new FilePickerHelper(
+ `${Zotero.getString("fileInterface.export")} PNG Image`,
+ "save",
+ [["PNG File(*.png)", "*.png"]],
+ `${this.item?.getNoteTitle()}.png`,
+ ).open();
+ if (filename) {
+ const parts = ev.data.image.split(",");
+ const bstr = atob(parts[1]);
+ let n = bstr.length;
+ const u8arr = new Uint8Array(n);
+ while (n--) {
+ u8arr[n] = bstr.charCodeAt(n);
+ }
+ await IOUtils.write(formatPath(filename), u8arr);
+ showHintWithLink(
+ `Image Saved to ${filename}`,
+ "Show in Folder",
+ (ev) => {
+ Zotero.File.reveal(filename);
+ },
+ );
+ }
+ return;
+ }
+ default:
+ return;
+ }
+ };
+}
diff --git a/src/elements/workspace.ts b/src/elements/workspace.ts
new file mode 100644
index 0000000..235b02e
--- /dev/null
+++ b/src/elements/workspace.ts
@@ -0,0 +1,116 @@
+import { config } from "../../package.json";
+import { waitUtilAsync } from "../utils/wait";
+import { PluginCEBase } from "./base";
+import { ContextPane } from "./context";
+import { OutlinePane } from "./outlinePane";
+
+export class Workspace extends PluginCEBase {
+ _item?: Zotero.Item;
+
+ _editorElement!: EditorElement;
+ _outline!: OutlinePane;
+ _context!: ContextPane;
+
+ get content() {
+ return this._parseContentID(
+ MozXULElement.parseXULToFragment(`
+
+
+
+
+
+
+
+
+
+
+
+
+
+`),
+ );
+ }
+
+ get containerType() {
+ return this.getAttribute("container-type") || "";
+ }
+
+ set containerType(val: string) {
+ this.setAttribute("container-type", val);
+ }
+
+ get item() {
+ return this._item;
+ }
+
+ set item(val) {
+ this._item = val;
+ this._outline.item = val;
+ this._context.item = val;
+ }
+
+ get editor() {
+ return this._editorElement._editorInstance;
+ }
+
+ init(): void {
+ // MozXULElement.insertFTLIfNeeded(`${config.addonRef}-workspace.ftl`);
+
+ this._outline = this._queryID("left-container") as unknown as OutlinePane;
+ this._editorElement = this._queryID("editor-main") as EditorElement;
+ this._outline._editorElement = this._editorElement;
+
+ this._context = this._queryID("right-container") as unknown as ContextPane;
+
+ this._loadPersist();
+ }
+
+ destroy(): void {}
+
+ async render() {
+ await this._outline.render();
+ await this.updateEditor();
+ await this._context.render();
+ }
+
+ async updateEditor() {
+ const editorElem = this._queryID("editor-main") as EditorElement;
+ await waitUtilAsync(() => Boolean(editorElem._initialized));
+ if (!editorElem._initialized) {
+ throw new Error("initNoteEditor: waiting initialization failed");
+ }
+ editorElem.mode = "edit";
+ editorElem.viewMode = "library";
+ editorElem.parent = this.item?.parentItem;
+ editorElem.item = this.item;
+ await waitUtilAsync(() => Boolean(editorElem._editorInstance));
+ await editorElem._editorInstance._initPromise;
+ // Hide BN toolbar
+ editorElem._editorInstance._iframeWindow.document.body.setAttribute(
+ "no-bn-toolbar",
+ "true",
+ );
+ // TODO: implement jump to
+ // if (typeof options.lineIndex === "number") {
+ // addon.api.editor.scroll(editorElem._editorInstance, options.lineIndex);
+ // }
+ // if (typeof options.sectionName === "string") {
+ // addon.api.editor.scrollToSection(
+ // editorElem._editorInstance,
+ // options.sectionName,
+ // );
+ // }
+ return;
+ }
+}
diff --git a/src/extras/workspace.ts b/src/extras/workspace.ts
new file mode 100644
index 0000000..2b134de
--- /dev/null
+++ b/src/extras/workspace.ts
@@ -0,0 +1,17 @@
+import { ContextPane } from "../elements/context";
+import { NoteDetails } from "../elements/detailsPane";
+import { OutlinePane } from "../elements/outlinePane";
+import { Workspace } from "../elements/workspace";
+
+const elements = {
+ "bn-context": ContextPane,
+ "bn-outline": OutlinePane,
+ "bn-details": NoteDetails,
+ "bn-workspace": Workspace,
+};
+
+for (const [key, constructor] of Object.entries(elements)) {
+ if (!customElements.get(key)) {
+ customElements.define(key, constructor);
+ }
+}
diff --git a/src/hooks.ts b/src/hooks.ts
index a687c68..88fdcb7 100644
--- a/src/hooks.ts
+++ b/src/hooks.ts
@@ -11,15 +11,9 @@ import { registerMenus } from "./modules/menu";
import {
registerWorkspaceTab,
openWorkspaceTab,
+ onTabSelect,
} from "./modules/workspace/tab";
-import {
- initWorkspace,
- initWorkspaceEditor,
- toggleNotesPane,
- toggleOutlinePane,
- togglePreviewPane,
- updateOutline,
-} from "./modules/workspace/content";
+import { initWorkspace } from "./modules/workspace/content";
import { registerNotify } from "./modules/notify";
import { openWorkspaceWindow } from "./modules/workspace/window";
import { registerReaderAnnotationButton } from "./modules/reader";
@@ -34,16 +28,12 @@ import { showSyncDiff } from "./modules/sync/diffWindow";
import { showSyncInfo } from "./modules/sync/infoWindow";
import { showSyncManager } from "./modules/sync/managerWindow";
import { showTemplateEditor } from "./modules/template/editorWindow";
-import {
- createNoteFromTemplate,
- createWorkspaceNote,
- createNoteFromMD,
-} from "./modules/createNote";
-import { annotationTagAction } from "./modules/annotationTagAction";
+import { createNoteFromTemplate, createNoteFromMD } from "./modules/createNote";
import { createZToolkit } from "./utils/ztoolkit";
import { waitUtilAsync } from "./utils/wait";
import { initSyncList } from "./modules/sync/api";
import { getPref } from "./utils/prefs";
+import { patchViewItems } from "./modules/viewItems";
async function onStartup() {
await Promise.all([
@@ -74,6 +64,11 @@ async function onStartup() {
async function onMainWindowLoad(win: Window): Promise {
await waitUtilAsync(() => document.readyState === "complete");
+
+ Services.scriptloader.loadSubScript(
+ `chrome://${config.addonRef}/content/scripts/workspace.js`,
+ win,
+ );
// Create ztoolkit for every window
addon.data.ztoolkit = createZToolkit();
@@ -84,6 +79,8 @@ async function onMainWindowLoad(win: Window): Promise {
registerWorkspaceTab(win);
initTemplates();
+
+ patchViewItems(win);
}
async function onMainWindowUnload(win: Window): Promise {
@@ -102,25 +99,16 @@ function onShutdown(): void {
* Any operations should be placed in a function to keep this funcion clear.
*/
function onNotify(
- event: string,
- type: string,
- ids: number[] | string[],
- extraData: { [key: string]: any },
+ event: Parameters<_ZoteroTypes.Notifier.Notify>["0"],
+ type: Parameters<_ZoteroTypes.Notifier.Notify>["1"],
+ ids: Parameters<_ZoteroTypes.Notifier.Notify>["2"],
+ extraData: Parameters<_ZoteroTypes.Notifier.Notify>["3"],
) {
if (extraData.skipBN) {
return;
}
- // Workspace main note update
- if (event === "modify" && type === "item") {
- if ((ids as number[]).includes(addon.data.workspace.mainId)) {
- addon.data.workspace.tab.active &&
- updateOutline(addon.data.workspace.tab.container!);
- addon.data.workspace.window.active &&
- updateOutline(addon.data.workspace.window.container!);
- if (getPref("workspace.autoUpdateRelatedNotes")) {
- addon.api.note.updateRelatedNotes(addon.data.workspace.mainId);
- }
- }
+ if (event === "select" && type === "tab") {
+ onTabSelect(extraData[ids[0]].type);
}
if (event === "modify" && type === "item") {
const modifiedNotes = Zotero.Items.get(ids).filter((item) => item.isNote());
@@ -131,10 +119,6 @@ function onNotify(
reason: "item-modify",
});
}
- }
- // Insert annotation when assigning tag starts with @
- if (event === "add" && type === "item-tag") {
- annotationTagAction(ids as number[], extraData);
} else {
return;
}
@@ -170,23 +154,14 @@ function onOpenNote(
return;
}
if (mode === "auto") {
- if (noteId === addon.data.workspace.mainId) {
- mode = "workspace";
- } else if (
- addon.data.workspace.tab.active ||
- addon.data.workspace.window.active
- ) {
- mode = "preview";
- } else {
- mode = "standalone";
- }
+ mode = "workspace";
}
switch (mode) {
case "preview":
- addon.hooks.onSetWorkspaceNote(noteId, "preview", options);
+ // addon.hooks.onSetWorkspaceNote(noteId, "preview", options);
break;
case "workspace":
- addon.hooks.onSetWorkspaceNote(noteId, "main", options);
+ // addon.hooks.onSetWorkspaceNote(noteId, "main", options);
break;
case "standalone":
ZoteroPane.openNoteWindow(noteId);
@@ -196,60 +171,13 @@ function onOpenNote(
}
}
-function onSetWorkspaceNote(
- noteId: number,
- type: "main" | "preview" = "main",
- options: {
- lineIndex?: number;
- sectionName?: string;
- } = {},
-) {
- if (type === "main") {
- addon.data.workspace.mainId = noteId;
- addon.data.workspace.tab.active &&
- updateOutline(addon.data.workspace.tab.container!);
- addon.data.workspace.window.active &&
- updateOutline(addon.data.workspace.window.container!);
- }
- if (addon.data.workspace.window.active) {
- initWorkspaceEditor(
- addon.data.workspace.window.container!,
- type,
- noteId,
- options,
- );
- type === "preview" &&
- addon.hooks.onToggleWorkspacePane(
- "preview",
- true,
- addon.data.workspace.window.container,
- );
- addon.data.workspace.window.window?.focus();
- }
- if (addon.data.workspace.tab.active) {
- initWorkspaceEditor(
- addon.data.workspace.tab.container!,
- type,
- noteId,
- options,
- );
- type === "preview" &&
- addon.hooks.onToggleWorkspacePane(
- "preview",
- true,
- addon.data.workspace.tab.container,
- );
- Zotero_Tabs.select(addon.data.workspace.tab.id!);
- }
-}
-
-function onOpenWorkspace(type: "tab" | "window" = "tab") {
+function onOpenWorkspace(item: Zotero.Item, type: "tab" | "window" = "tab") {
if (type === "window") {
- openWorkspaceWindow();
+ openWorkspaceWindow(item);
return;
}
if (type === "tab") {
- openWorkspaceTab();
+ openWorkspaceTab(item);
return;
}
}
@@ -261,19 +189,19 @@ function onToggleWorkspacePane(
visibility?: boolean,
container?: XUL.Box,
) {
- switch (type) {
- case "outline":
- toggleOutlinePane(visibility, container);
- break;
- case "preview":
- togglePreviewPane(visibility, container);
- break;
- case "notes":
- toggleNotesPane(visibility);
- break;
- default:
- break;
- }
+ // switch (type) {
+ // case "outline":
+ // toggleOutlinePane(visibility, container);
+ // break;
+ // case "preview":
+ // togglePreviewPane(visibility, container);
+ // break;
+ // case "notes":
+ // toggleNotesPane(visibility);
+ // break;
+ // default:
+ // break;
+ // }
}
const onSyncing = callSyncing;
@@ -296,8 +224,6 @@ const onShowSyncDiff = showSyncDiff;
const onShowTemplateEditor = showTemplateEditor;
-const onCreateWorkspaceNote = createWorkspaceNote;
-
const onCreateNoteFromTemplate = createNoteFromTemplate;
const onCreateNoteFromMD = createNoteFromMD;
@@ -315,7 +241,6 @@ export default {
onPrefsEvent,
onOpenNote,
onInitWorkspace,
- onSetWorkspaceNote,
onOpenWorkspace,
onToggleWorkspacePane,
onSyncing,
@@ -328,7 +253,6 @@ export default {
onShowSyncInfo,
onShowSyncManager,
onShowTemplateEditor,
- onCreateWorkspaceNote,
onCreateNoteFromTemplate,
onCreateNoteFromMD,
};
diff --git a/src/modules/annotationTagAction.ts b/src/modules/annotationTagAction.ts
deleted file mode 100644
index 0293ba3..0000000
--- a/src/modules/annotationTagAction.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { addLineToNote, getNoteTreeFlattened } from "../utils/note";
-
-export { annotationTagAction };
-
-async function annotationTagAction(
- ids: Array,
- extraData: Record,
-) {
- const workspaceNote = Zotero.Items.get(addon.data.workspace.mainId);
- if (!workspaceNote || !workspaceNote.isNote()) {
- return;
- }
- const nodes = getNoteTreeFlattened(workspaceNote);
- const headings: string[] = nodes.map((node) => node.model.name);
-
- for (const tagId of ids.filter((t) =>
- (extraData[t].tag as string).startsWith("@"),
- )) {
- const tagName = (extraData[tagId].tag as string).slice(1).trim();
- if (headings.includes(tagName) || tagName === "@") {
- let lineIndex: number;
- if (tagName === "@") {
- lineIndex = -1;
- } else {
- const targetNode = nodes.find((node) => node.model.name === tagName);
- lineIndex = targetNode?.model.endIndex;
- }
-
- const annotationItem = Zotero.Items.get((tagId as string).split("-")[0]);
- if (!annotationItem.isAnnotation()) {
- continue;
- }
- await addLineToNote(
- workspaceNote,
- await addon.api.convert.annotations2html([annotationItem], {
- noteItem: workspaceNote,
- }),
- lineIndex,
- );
- }
- }
-}
diff --git a/src/modules/createNote.ts b/src/modules/createNote.ts
index 702db0c..5740bbe 100644
--- a/src/modules/createNote.ts
+++ b/src/modules/createNote.ts
@@ -1,45 +1,7 @@
import { getString } from "../utils/locale";
-import { config } from "../../package.json";
import { formatPath } from "../utils/str";
-export { createWorkspaceNote, createNoteFromTemplate, createNoteFromMD };
-
-async function createWorkspaceNote() {
- const currentCollection = ZoteroPane.getSelectedCollection();
- if (!currentCollection) {
- window.alert(getString("alert.notValidCollectionError"));
- return;
- }
- const confirmOperation = window.confirm(
- `${getString(
- "menuAddNote.newMainNote.confirmHead",
- // @ts-ignore
- )} '${currentCollection.getName()}' ${getString(
- "menuAddNote.newMainNote.confirmTail",
- )}`,
- );
- if (!confirmOperation) {
- return;
- }
- const header = window.prompt(
- getString("menuAddNote.newMainNote.enterNoteTitle"),
- `New Note ${new Date().toLocaleString()}`,
- );
- const noteID = await ZoteroPane.newNote();
- const noteItem = Zotero.Items.get(noteID);
- noteItem.setNote(
- `${header}
\n
`,
- );
- await noteItem.saveTx();
- addon.hooks.onSetWorkspaceNote(noteID, "main");
- if (
- !addon.data.workspace.tab.active &&
- !addon.data.workspace.window.active &&
- window.confirm(getString("menuAddNote.newMainNote.openWorkspaceTab"))
- ) {
- addon.hooks.onOpenWorkspace("tab");
- }
-}
+export { createNoteFromTemplate, createNoteFromMD };
function getLibraryParentId() {
return ZoteroPane.getSelectedItems().filter((item) => item.isRegularItem())[0]
diff --git a/src/modules/editor/inject.ts b/src/modules/editor/inject.ts
index eb4afea..379e061 100644
--- a/src/modules/editor/inject.ts
+++ b/src/modules/editor/inject.ts
@@ -16,47 +16,13 @@ export async function injectEditorScripts(win: Window) {
);
}
-export function injectEditorCSS(win: Window) {
+export async function injectEditorCSS(win: Window) {
ztoolkit.UI.appendElement(
{
tag: "style",
id: "betternotes-style",
properties: {
- innerHTML: `
- .primary-editor > h1::before {
- margin-left: -64px !important;
- padding-left: 40px !important;
- content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%2218px%22%20height%3D%2218px%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2015.56%22%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill%3A%23666%3B%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Ctitle%3E%E6%9C%AA%E6%A0%87%E9%A2%98-1%3C%2Ftitle%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M12.29%2C16.8H11.14V12.33H6.07V16.8H4.92V7H6.07v4.3h5.07V7h1.15Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M18.05%2C16.8H16.93V8.41a4%2C4%2C0%2C0%2C1-.9.53%2C6.52%2C6.52%2C0%2C0%2C1-1.14.44l-.32-1a8.2%2C8.2%2C0%2C0%2C0%2C1.67-.67%2C6.31%2C6.31%2C0%2C0%2C0%2C1.39-1h.42Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M21%2C5a2.25%2C2.25%2C0%2C0%2C1%2C2.25%2C2.25v9.56A2.25%2C2.25%2C0%2C0%2C1%2C21%2C19H3A2.25%2C2.25%2C0%2C0%2C1%2C.75%2C16.78V7.22A2.25%2C2.25%2C0%2C0%2C1%2C3%2C5H21m0-.75H3a3%2C3%2C0%2C0%2C0-3%2C3v9.56a3%2C3%2C0%2C0%2C0%2C3%2C3H21a3%2C3%2C0%2C0%2C0%2C3-3V7.22a3%2C3%2C0%2C0%2C0-3-3Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3C%2Fsvg%3E") !important;
- }
- .primary-editor > h2::before {
- margin-left: -64px !important;
- padding-left: 40px !important;
- content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%2218px%22%20height%3D%2218px%22%20%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2015.56%22%3E%3Cdefs%3E%3Cstyle%3E.a%7Bfill%3A%23666%3B%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cpath%20class%3D%22a%22%20d%3D%22M11.17%2C16.8H10V12.33H5V16.8H3.8V7H5v4.3H10V7h1.15Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22a%22%20d%3D%22M14.14%2C16.8v-.48a4.1%2C4.1%2C0%2C0%2C1%2C.14-1.11%2C2.86%2C2.86%2C0%2C0%2C1%2C.45-.91%2C5.49%2C5.49%2C0%2C0%2C1%2C.83-.86c.33-.29.75-.61%2C1.24-1a7.43%2C7.43%2C0%2C0%2C0%2C.9-.73%2C3.9%2C3.9%2C0%2C0%2C0%2C.57-.7%2C2.22%2C2.22%2C0%2C0%2C0%2C.3-.66%2C2.87%2C2.87%2C0%2C0%2C0%2C.11-.77%2C1.89%2C1.89%2C0%2C0%2C0-.47-1.32%2C1.66%2C1.66%2C0%2C0%2C0-1.28-.5A3.17%2C3.17%2C0%2C0%2C0%2C15.7%2C8a3.49%2C3.49%2C0%2C0%2C0-1.08.76l-.68-.65a4.26%2C4.26%2C0%2C0%2C1%2C1.39-1A4%2C4%2C0%2C0%2C1%2C17%2C6.84a2.62%2C2.62%2C0%2C0%2C1%2C2.83%2C2.67%2C3.58%2C3.58%2C0%2C0%2C1-.15%2C1%2C3.09%2C3.09%2C0%2C0%2C1-.41.9%2C5.53%2C5.53%2C0%2C0%2C1-.67.81%2C9%2C9%2C0%2C0%2C1-.95.79c-.46.32-.84.59-1.13.82a4.68%2C4.68%2C0%2C0%2C0-.71.64%2C2%2C2%2C0%2C0%2C0-.38.6%2C2.08%2C2.08%2C0%2C0%2C0-.11.69h4.88v1Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22a%22%20d%3D%22M21%2C5a2.25%2C2.25%2C0%2C0%2C1%2C2.25%2C2.25v9.56A2.25%2C2.25%2C0%2C0%2C1%2C21%2C19H3A2.25%2C2.25%2C0%2C0%2C1%2C.75%2C16.78V7.22A2.25%2C2.25%2C0%2C0%2C1%2C3%2C5H21m0-.75H3a3%2C3%2C0%2C0%2C0-3%2C3v9.56a3%2C3%2C0%2C0%2C0%2C3%2C3H21a3%2C3%2C0%2C0%2C0%2C3-3V7.22a3%2C3%2C0%2C0%2C0-3-3Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3C%2Fsvg%3E") !important;
- }
- .primary-editor > h3::before {
- margin-left: -64px !important;
- padding-left: 40px !important;
- content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%2218px%22%20height%3D%2218px%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2015.56%22%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill%3A%23666%3B%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M11.17%2C16.8H10V12.33H5V16.8H3.8V7H5v4.3H10V7h1.15Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M14%2C16.14l.51-.8a4.75%2C4.75%2C0%2C0%2C0%2C1.1.52%2C4.27%2C4.27%2C0%2C0%2C0%2C1.12.16%2C2.29%2C2.29%2C0%2C0%2C0%2C1.64-.52A1.77%2C1.77%2C0%2C0%2C0%2C19%2C14.17a1.7%2C1.7%2C0%2C0%2C0-.68-1.48%2C3.6%2C3.6%2C0%2C0%2C0-2.06-.48H15.4v-1h.77A3%2C3%2C0%2C0%2C0%2C18%2C10.81a1.65%2C1.65%2C0%2C0%2C0%2C.6-1.41%2C1.47%2C1.47%2C0%2C0%2C0-.47-1.19A1.67%2C1.67%2C0%2C0%2C0%2C17%2C7.79a3.33%2C3.33%2C0%2C0%2C0-2.08.73l-.59-.75a4.4%2C4.4%2C0%2C0%2C1%2C1.28-.71A4.35%2C4.35%2C0%2C0%2C1%2C17%2C6.84a2.84%2C2.84%2C0%2C0%2C1%2C2%2C.65%2C2.21%2C2.21%2C0%2C0%2C1%2C.74%2C1.78%2C2.35%2C2.35%2C0%2C0%2C1-.49%2C1.5%2C2.7%2C2.7%2C0%2C0%2C1-1.46.89v0a2.74%2C2.74%2C0%2C0%2C1%2C1.65.74%2C2.15%2C2.15%2C0%2C0%2C1%2C.66%2C1.65%2C2.64%2C2.64%2C0%2C0%2C1-.9%2C2.12%2C3.44%2C3.44%2C0%2C0%2C1-2.34.78%2C5.3%2C5.3%2C0%2C0%2C1-1.48-.2A5%2C5%2C0%2C0%2C1%2C14%2C16.14Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M21%2C5a2.25%2C2.25%2C0%2C0%2C1%2C2.25%2C2.25v9.56A2.25%2C2.25%2C0%2C0%2C1%2C21%2C19H3A2.25%2C2.25%2C0%2C0%2C1%2C.75%2C16.78V7.22A2.25%2C2.25%2C0%2C0%2C1%2C3%2C5H21m0-.75H3a3%2C3%2C0%2C0%2C0-3%2C3v9.56a3%2C3%2C0%2C0%2C0%2C3%2C3H21a3%2C3%2C0%2C0%2C0%2C3-3V7.22a3%2C3%2C0%2C0%2C0-3-3Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3C%2Fsvg%3E") !important;
- }
- .primary-editor > h4::before {
- margin-left: -64px !important;
- padding-left: 40px !important;
- content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%2218px%22%20height%3D%2218px%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2015.56%22%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill%3A%23666%3B%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M11.17%2C16.8H10V12.33H5V16.8H3.8V7H5v4.3H10V7h1.15Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M19.43%2C6.92v6.59h1.05v1.05H19.43V16.9H18.31V14.56H13.66v-1c.43-.49.87-1%2C1.31-1.57s.87-1.13%2C1.27-1.7S17%2C9.14%2C17.36%2C8.57a16.51%2C16.51%2C0%2C0%2C0%2C.86-1.65Zm-4.49%2C6.59h3.37V8.63c-.34.61-.67%2C1.15-1%2C1.63s-.6.91-.87%2C1.3-.56.74-.81%2C1Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M21%2C5a2.25%2C2.25%2C0%2C0%2C1%2C2.25%2C2.25v9.56A2.25%2C2.25%2C0%2C0%2C1%2C21%2C19H3A2.25%2C2.25%2C0%2C0%2C1%2C.75%2C16.78V7.22A2.25%2C2.25%2C0%2C0%2C1%2C3%2C5H21m0-.75H3a3%2C3%2C0%2C0%2C0-3%2C3v9.56a3%2C3%2C0%2C0%2C0%2C3%2C3H21a3%2C3%2C0%2C0%2C0%2C3-3V7.22a3%2C3%2C0%2C0%2C0-3-3Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3C%2Fsvg%3E") !important;
- }
- .primary-editor > h5::before {
- margin-left: -64px !important;
- padding-left: 40px !important;
- content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%2218px%22%20height%3D%2218px%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2015.56%22%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill%3A%23666%3B%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M11.17%2C16.8H10V12.33H5V16.8H3.8V7H5v4.3H10V7h1.15Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M14%2C16l.58-.76a3.67%2C3.67%2C0%2C0%2C0%2C1%2C.58A3.44%2C3.44%2C0%2C0%2C0%2C16.8%2C16a2.17%2C2.17%2C0%2C0%2C0%2C1.58-.6A2%2C2%2C0%2C0%2C0%2C19%2C13.88a1.85%2C1.85%2C0%2C0%2C0-.64-1.5%2C2.83%2C2.83%2C0%2C0%2C0-1.86-.54c-.27%2C0-.55%2C0-.86%2C0s-.58%2C0-.81.06L15.17%2C7H19.7V8H16.14l-.2%2C2.88.47%2C0h.43a3.5%2C3.5%2C0%2C0%2C1%2C2.43.79%2C2.74%2C2.74%2C0%2C0%2C1%2C.88%2C2.16%2C3%2C3%2C0%2C0%2C1-.94%2C2.3%2C3.41%2C3.41%2C0%2C0%2C1-2.4.87%2C4.45%2C4.45%2C0%2C0%2C1-1.5-.24A4.81%2C4.81%2C0%2C0%2C1%2C14%2C16Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M21%2C5a2.25%2C2.25%2C0%2C0%2C1%2C2.25%2C2.25v9.56A2.25%2C2.25%2C0%2C0%2C1%2C21%2C19H3A2.25%2C2.25%2C0%2C0%2C1%2C.75%2C16.78V7.22A2.25%2C2.25%2C0%2C0%2C1%2C3%2C5H21m0-.75H3a3%2C3%2C0%2C0%2C0-3%2C3v9.56a3%2C3%2C0%2C0%2C0%2C3%2C3H21a3%2C3%2C0%2C0%2C0%2C3-3V7.22a3%2C3%2C0%2C0%2C0-3-3Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3C%2Fsvg%3E") !important;
- }
- .primary-editor > h6::before {
- margin-left: -64px !important;
- padding-left: 40px !important;
- content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%2218px%22%20height%3D%2218px%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2015.56%22%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill%3A%23666%3B%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M11.17%2C16.8H10V12.33H5V16.8H3.8V7H5v4.3H10V7h1.15Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M20.18%2C13.7a3.24%2C3.24%2C0%2C0%2C1-.88%2C2.38%2C2.94%2C2.94%2C0%2C0%2C1-2.2.9%2C2.69%2C2.69%2C0%2C0%2C1-2.31-1.17A5.59%2C5.59%2C0%2C0%2C1%2C14%2C12.49a12.18%2C12.18%2C0%2C0%2C1%2C.2-2.14%2C5.16%2C5.16%2C0%2C0%2C1%2C.84-2A3.65%2C3.65%2C0%2C0%2C1%2C16.27%2C7.2%2C3.71%2C3.71%2C0%2C0%2C1%2C18%2C6.84%2C3.14%2C3.14%2C0%2C0%2C1%2C19%2C7a3.59%2C3.59%2C0%2C0%2C1%2C1%2C.5l-.56.77a2.3%2C2.3%2C0%2C0%2C0-1.49-.48A2.3%2C2.3%2C0%2C0%2C0%2C16.79%2C8a3%2C3%2C0%2C0%2C0-.92.85%2C3.79%2C3.79%2C0%2C0%2C0-.56%2C1.25%2C6.56%2C6.56%2C0%2C0%2C0-.19%2C1.65h0a2.61%2C2.61%2C0%2C0%2C1%2C1-.84%2C2.91%2C2.91%2C0%2C0%2C1%2C1.23-.28%2C2.63%2C2.63%2C0%2C0%2C1%2C2%2C.85A3.09%2C3.09%2C0%2C0%2C1%2C20.18%2C13.7ZM19%2C13.78a2.28%2C2.28%2C0%2C0%2C0-.5-1.62%2C1.67%2C1.67%2C0%2C0%2C0-1.29-.54%2C2%2C2%2C0%2C0%2C0-1.5.58%2C2%2C2%2C0%2C0%2C0-.56%2C1.4%2C2.65%2C2.65%2C0%2C0%2C0%2C.55%2C1.74%2C1.85%2C1.85%2C0%2C0%2C0%2C2.78.1A2.38%2C2.38%2C0%2C0%2C0%2C19%2C13.78Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M21%2C5a2.25%2C2.25%2C0%2C0%2C1%2C2.25%2C2.25v9.56A2.25%2C2.25%2C0%2C0%2C1%2C21%2C19H3A2.25%2C2.25%2C0%2C0%2C1%2C.75%2C16.78V7.22A2.25%2C2.25%2C0%2C0%2C1%2C3%2C5H21m0-.75H3a3%2C3%2C0%2C0%2C0-3%2C3v9.56a3%2C3%2C0%2C0%2C0%2C3%2C3H21a3%2C3%2C0%2C0%2C0%2C3-3V7.22a3%2C3%2C0%2C0%2C0-3-3Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3C%2Fsvg%3E") !important;
- }
- .primary-editor > p, .primary-editor h1, .primary-editor h2, .primary-editor h3, .primary-editor h4, .primary-editor h5, .primary-editor h6, .primary-editor pre, .primary-editor blockquote, .primary-editor table, .primary-editor ul, .primary-editor ol, .primary-editor hr{
- max-width: unset
- }
- `,
+ innerHTML: await getFileContent(rootURI + "chrome/content/styles/editor.css"),
},
ignoreIfExists: true,
},
diff --git a/src/modules/editor/toolbar.ts b/src/modules/editor/toolbar.ts
index 373f959..a680b62 100644
--- a/src/modules/editor/toolbar.ts
+++ b/src/modules/editor/toolbar.ts
@@ -4,17 +4,12 @@ import { getLineAtCursor, getSectionAtCursor } from "../../utils/editor";
import { showHint } from "../../utils/hint";
import { getNoteLink, getNoteLinkParams } from "../../utils/link";
import { getString } from "../../utils/locale";
-import {
- addLineToNote,
- getNoteTreeFlattened,
- getNoteType,
-} from "../../utils/note";
+import { addLineToNote, getNoteTreeFlattened } from "../../utils/note";
import { getPref } from "../../utils/prefs";
import { slice } from "../../utils/str";
export async function initEditorToolbar(editor: Zotero.EditorInstance) {
const noteItem = editor._item;
- const noteType = getNoteType(noteItem.id);
const toolbar = await registerEditorToolbar(editor, makeId("toolbar"));
// Settings
@@ -56,22 +51,7 @@ export async function initEditorToolbar(editor: Zotero.EditorInstance) {
id: makeId("settings-openWorkspace"),
text: getString("editor.toolbar.settings.openWorkspace"),
callback: (e) => {
- addon.hooks.onOpenWorkspace("tab");
- },
- },
- {
- id: makeId("settings-setWorkspace"),
- text: getString("editor.toolbar.settings.setWorkspace"),
- callback: (e) => {
- addon.hooks.onSetWorkspaceNote(e.editor._item.id, "main");
- },
- },
- {
- id: makeId("settings-previewInWorkspace"),
- text: getString("editor.toolbar.settings.previewInWorkspace"),
- callback: (e) => {
- addon.hooks.onOpenWorkspace("tab");
- addon.hooks.onSetWorkspaceNote(e.editor._item.id, "preview");
+ addon.hooks.onOpenWorkspace(noteItem, "tab");
},
},
{
@@ -221,103 +201,95 @@ export async function initEditorToolbar(editor: Zotero.EditorInstance) {
});
// Center button
- if (noteType === "main") {
- registerEditorToolbarElement(
+
+ const onTriggerMenu = (ev: MouseEvent) => {
+ editor._iframeWindow.focus();
+ const linkMenu: PopupData[] = getLinkMenuData(editor);
+ editor._iframeWindow.document
+ .querySelector(`#${makeId("link")}`)!
+ .querySelector(".toolbar-button")!.innerHTML = ICONS.linkAfter;
+
+ const popup = registerEditorToolbarPopup(
editor,
- toolbar,
+ linkButton,
+ `${config.addonRef}-link-popup`,
"middle",
- ztoolkit.UI.createElement(editor._iframeWindow.document, "div", {
- properties: { innerHTML: getString("editor.toolbar.main") },
- }),
+ linkMenu,
);
- } else {
- const onTriggerMenu = (ev: MouseEvent) => {
- editor._iframeWindow.focus();
- const linkMenu: PopupData[] = getLinkMenuData(editor);
- editor._iframeWindow.document
- .querySelector(`#${makeId("link")}`)!
- .querySelector(".toolbar-button")!.innerHTML = ICONS.linkAfter;
+ };
- const popup = registerEditorToolbarPopup(
- editor,
- linkButton,
- `${config.addonRef}-link-popup`,
- "middle",
- linkMenu,
- );
- };
+ const onExitMenu = (ev: MouseEvent) => {
+ editor._iframeWindow.document
+ .querySelector(`#${makeId("link-popup")}`)
+ ?.remove();
+ editor._iframeWindow.document
+ .querySelector(`#${makeId("link")}`)!
+ .querySelector(".toolbar-button")!.innerHTML = ICONS.addon;
+ };
- const onExitMenu = (ev: MouseEvent) => {
- editor._iframeWindow.document
- .querySelector(`#${makeId("link-popup")}`)
- ?.remove();
- editor._iframeWindow.document
- .querySelector(`#${makeId("link")}`)!
- .querySelector(".toolbar-button")!.innerHTML = ICONS.addon;
- };
-
- const onClickMenu = async (ev: MouseEvent) => {
- const mainNote = Zotero.Items.get(addon.data.workspace.mainId) || null;
- if (!mainNote?.isNote()) {
- return;
- }
- const lineIndex = parseInt(
- (ev.target as HTMLDivElement).id.split("-").pop() || "-1",
- );
- const forwardLink = getNoteLink(noteItem);
- const backLink = getNoteLink(mainNote, { ignore: true, lineIndex });
- addLineToNote(
- mainNote,
- await addon.api.template.runTemplate(
- "[QuickInsertV2]",
- "link, linkText, subNoteItem, noteItem",
- [
- forwardLink,
- noteItem.getNoteTitle().trim() || forwardLink,
- noteItem,
- mainNote,
- ],
- ),
- lineIndex,
- );
- addLineToNote(
- noteItem,
- await addon.api.template.runTemplate(
- "[QuickBackLinkV2]",
- "link, linkText, subNoteItem, noteItem",
- [
- backLink,
- mainNote.getNoteTitle().trim() || "Workspace Note",
- noteItem,
- mainNote,
- "",
- ],
- ),
- );
- onExitMenu(ev);
- ev.stopPropagation();
- };
-
- const linkButton = await registerEditorToolbarDropdown(
- editor,
- toolbar,
- makeId("link"),
- ICONS.addon,
- getString("editor.toolbar.link.title"),
- "middle",
- onClickMenu,
+ const onClickMenu = async (ev: MouseEvent) => {
+ // TODO: fix link
+ return;
+ const mainNote = Zotero.Items.get(addon.data.workspace.mainId) || null;
+ if (!mainNote?.isNote()) {
+ return;
+ }
+ const lineIndex = parseInt(
+ (ev.target as HTMLDivElement).id.split("-").pop() || "-1",
);
+ const forwardLink = getNoteLink(noteItem);
+ const backLink = getNoteLink(mainNote, { ignore: true, lineIndex });
+ addLineToNote(
+ mainNote,
+ await addon.api.template.runTemplate(
+ "[QuickInsertV2]",
+ "link, linkText, subNoteItem, noteItem",
+ [
+ forwardLink,
+ noteItem.getNoteTitle().trim() || forwardLink,
+ noteItem,
+ mainNote,
+ ],
+ ),
+ lineIndex,
+ );
+ addLineToNote(
+ noteItem,
+ await addon.api.template.runTemplate(
+ "[QuickBackLinkV2]",
+ "link, linkText, subNoteItem, noteItem",
+ [
+ backLink,
+ mainNote.getNoteTitle().trim() || "Workspace Note",
+ noteItem,
+ mainNote,
+ "",
+ ],
+ ),
+ );
+ onExitMenu(ev);
+ ev.stopPropagation();
+ };
- linkButton.addEventListener("mouseenter", onTriggerMenu);
- linkButton.addEventListener("mouseleave", onExitMenu);
- linkButton.addEventListener("mouseleave", onExitMenu);
- linkButton.addEventListener("click", (ev) => {
- if ((ev.target as HTMLElement).classList.contains("option")) {
- onClickMenu(ev);
- }
- });
- editor._iframeWindow.document.addEventListener("click", onExitMenu);
- }
+ const linkButton = await registerEditorToolbarDropdown(
+ editor,
+ toolbar,
+ makeId("link"),
+ ICONS.addon,
+ getString("editor.toolbar.link.title"),
+ "middle",
+ onClickMenu,
+ );
+
+ linkButton.addEventListener("mouseenter", onTriggerMenu);
+ linkButton.addEventListener("mouseleave", onExitMenu);
+ linkButton.addEventListener("mouseleave", onExitMenu);
+ linkButton.addEventListener("click", (ev) => {
+ if ((ev.target as HTMLElement).classList.contains("option")) {
+ onClickMenu(ev);
+ }
+ });
+ editor._iframeWindow.document.addEventListener("click", onExitMenu);
// Export
// const exportButton = await registerEditorToolbarDropdown(
diff --git a/src/modules/export/api.ts b/src/modules/export/api.ts
index 19455c8..91c4c18 100644
--- a/src/modules/export/api.ts
+++ b/src/modules/export/api.ts
@@ -212,7 +212,7 @@ async function toFreeMind(noteItem: Zotero.Item) {
}
async function embedLinkedNotes(noteItem: Zotero.Item): Promise {
- const parser = ztoolkit.getDOMParser();
+ const parser = new DOMParser();
const globalCitationData = getNoteCitationData(noteItem as Zotero.Item);
diff --git a/src/modules/menu.ts b/src/modules/menu.ts
index aa8d78d..1c688a5 100644
--- a/src/modules/menu.ts
+++ b/src/modules/menu.ts
@@ -17,22 +17,6 @@ export function registerMenus() {
);
},
});
- ztoolkit.Menu.register("item", {
- tag: "menuitem",
- label: getString("menuItem.setMainNote"),
- icon: `chrome://${config.addonRef}/content/icons/favicon.png`,
- commandListener: (ev) => {
- addon.hooks.onSetWorkspaceNote(ZoteroPane.getSelectedItems()[0].id);
- },
- getVisibility: (elem, ev) => {
- const items = ZoteroPane.getSelectedItems();
- return (
- items.length == 1 &&
- items[0].isNote() &&
- items[0].id !== addon.data.workspace.mainId
- );
- },
- });
// menuEdit
const menuEditAnchor = document.querySelector(
@@ -133,88 +117,66 @@ export function registerMenus() {
menuFileAnchor,
);
- document
- .querySelector(`#${recentMainNotesMenuId}`)
- ?.addEventListener("popupshowing", () => {
- const popup = document.querySelector(`#${recentMainNotesMenuPopupId}`)!;
- removeChildren(popup);
- const children = Zotero.Items.get(
- ((getPref("recentMainNoteIds") as string) || "")
- .split(",")
- .map((id) => parseInt(id))
- .filter((id) => id !== addon.data.workspace.mainId),
- )
- .filter((item) => item.isNote())
- .map((item) => ({
- tag: "menuitem",
- attributes: {
- label: slice(
- `${slice(item.getNoteTitle().trim() || item.key, 30)} - ${
- item.parentItem
- ? "📄" + item.parentItem.getField("title")
- : "📁" +
- Zotero.Collections.get(item.getCollections())
- .map(
- (collection) => (collection as Zotero.Collection).name,
- )
- .join(", ")
- }`,
- 200,
- ),
- },
- listeners: [
- {
- type: "command",
- listener: () => {
- addon.hooks.onSetWorkspaceNote(item.id, "main");
- addon.hooks.onOpenWorkspace();
- },
- },
- ],
- }));
- const defaultChildren = [
- {
- tag: "menuitem",
- attributes: {
- label: getString("menuFile-openRecent-empty"),
- disabled: true,
- },
- },
- ];
+ // document
+ // .querySelector(`#${recentMainNotesMenuId}`)
+ // ?.addEventListener("popupshowing", () => {
+ // const popup = document.querySelector(`#${recentMainNotesMenuPopupId}`)!;
+ // removeChildren(popup);
+ // const children = Zotero.Items.get(
+ // ((getPref("recentMainNoteIds") as string) || "")
+ // .split(",")
+ // .map((id) => parseInt(id))
+ // .filter((id) => id !== addon.data.workspace.mainId),
+ // )
+ // .filter((item) => item.isNote())
+ // .map((item) => ({
+ // tag: "menuitem",
+ // attributes: {
+ // label: slice(
+ // `${slice(item.getNoteTitle().trim() || item.key, 30)} - ${
+ // item.parentItem
+ // ? "📄" + item.parentItem.getField("title")
+ // : "📁" +
+ // Zotero.Collections.get(item.getCollections())
+ // .map(
+ // (collection) => (collection as Zotero.Collection).name,
+ // )
+ // .join(", ")
+ // }`,
+ // 200,
+ // ),
+ // },
+ // listeners: [
+ // {
+ // type: "command",
+ // listener: () => {
+ // addon.hooks.onSetWorkspaceNote(item.id, "main");
+ // addon.hooks.onOpenWorkspace();
+ // },
+ // },
+ // ],
+ // }));
+ // const defaultChildren = [
+ // {
+ // tag: "menuitem",
+ // attributes: {
+ // label: getString("menuFile-openRecent-empty"),
+ // disabled: true,
+ // },
+ // },
+ // ];
- ztoolkit.UI.appendElement(
- {
- tag: "fragment",
- children: children.length === 0 ? defaultChildren : children,
- enableElementRecord: false,
- },
- popup,
- );
- return true;
- });
+ // ztoolkit.UI.appendElement(
+ // {
+ // tag: "fragment",
+ // children: children.length === 0 ? defaultChildren : children,
+ // enableElementRecord: false,
+ // },
+ // popup,
+ // );
+ // return true;
+ // });
- ztoolkit.Menu.register(
- "menuFile",
- {
- tag: "menuitem",
- label: getString("menuFile-openMainNote"),
- icon: `chrome://${config.addonRef}/content/icons/favicon.png`,
- commandListener: async (ev) => {
- const selectedIds = await itemPicker();
- if (
- selectedIds?.length === 1 &&
- Zotero.Items.get(selectedIds[0]).isNote()
- ) {
- addon.hooks.onSetWorkspaceNote(selectedIds[0], "main");
- addon.hooks.onOpenWorkspace();
- } else {
- window.alert(getString("menuFile-openMainNote-error"));
- }
- },
- },
- "after",
- menuFileAnchor,
- );
ztoolkit.Menu.register(
"menuFile",
{ tag: "menuseparator" },
@@ -256,28 +218,11 @@ export function registerMenus() {
"after",
menuFileAnchor,
);
- ztoolkit.Menu.register(
- "menuFile",
- {
- tag: "menuitem",
- label: getString("menuAddNote.newMainNote"),
- icon: `chrome://${config.addonRef}/content/icons/favicon.png`,
- commandListener: addon.hooks.onCreateWorkspaceNote,
- },
- "after",
- menuFileAnchor,
- );
// create note menu in library
const newNoteMenu = document
.querySelector("#zotero-tb-note-add")
?.querySelector("menupopup") as XUL.MenuPopup;
- ztoolkit.Menu.register(newNoteMenu, {
- tag: "menuitem",
- label: getString("menuAddNote.newMainNote"),
- icon: `chrome://${config.addonRef}/content/icons/favicon.png`,
- commandListener: addon.hooks.onCreateWorkspaceNote,
- });
ztoolkit.Menu.register(newNoteMenu, {
tag: "menuitem",
label: getString("menuAddNote.newTemplateStandaloneNote"),
diff --git a/src/modules/viewItems.ts b/src/modules/viewItems.ts
new file mode 100644
index 0000000..43e76f1
--- /dev/null
+++ b/src/modules/viewItems.ts
@@ -0,0 +1,28 @@
+import { PatchHelper } from "zotero-plugin-toolkit/dist/helpers/patch";
+
+export function patchViewItems(win: Window) {
+ // @ts-ignore
+ const ZoteroPane = win.ZoteroPane;
+ new PatchHelper().setData({
+ target: ZoteroPane,
+ funcSign: "viewItems",
+ patcher: (origin) =>
+ function (items: Zotero.Item[], event?: KeyboardEvent) {
+ if (!addon.data.alive || event?.shiftKey) {
+ // @ts-ignore
+ return origin.apply(this, [items, event]);
+ }
+ const otherItems = [];
+ for (const item of items) {
+ if (item.isNote()) {
+ addon.hooks.onOpenWorkspace(item, "tab");
+ continue;
+ }
+ otherItems.push(item);
+ }
+ // @ts-ignore
+ return origin.apply(this, [otherItems, event]);
+ },
+ enabled: true,
+ });
+}
diff --git a/src/modules/workspace/content.ts b/src/modules/workspace/content.ts
index 9ca1ec9..97fb6f2 100644
--- a/src/modules/workspace/content.ts
+++ b/src/modules/workspace/content.ts
@@ -1,489 +1,20 @@
-import { config } from "../../../package.json";
-import { ICONS } from "../../utils/config";
-import { itemPicker } from "../../utils/itemPicker";
-import { getString } from "../../utils/locale";
-import { getNoteTreeFlattened } from "../../utils/note";
-import { getPref } from "../../utils/prefs";
-import { waitUtilAsync } from "../../utils/wait";
-import { OutlineType } from "../../utils/workspace";
import { saveFreeMind as _saveFreeMind } from "../export/freemind";
-function makeId(key: string) {
- return `betternotes-workspace-${key}`;
-}
-
-export function initWorkspace(container: XUL.Box | undefined) {
+export function initWorkspace(container: XUL.Box, item: Zotero.Item) {
if (!container) {
return;
}
- function makeTooltipProp(
- id: string,
- content: string,
- title: string,
- callback: (ev: Event) => void,
- ) {
- return {
- id,
- tag: "button",
- namespace: "html",
- classList: ["tool-button"],
- properties: {
- innerHTML: content,
- title,
- },
- listeners: [
- {
- type: "click",
- listener: callback,
- },
- ],
- };
- }
- ztoolkit.UI.appendElement(
- {
- tag: "hbox",
- id: makeId("top-container"),
- styles: { width: "100%", height: "100%" },
- properties: {},
- attributes: {
- flex: "1",
- },
- children: [
- {
- tag: "vbox",
- id: makeId("outline-container"),
- styles: {
- overflow: "hidden",
- display: "flex",
- flexDirection: "column",
- },
- attributes: {
- width: "330",
- minwidth: "300",
- flex: "1",
- },
- children: [
- {
- tag: "link",
- properties: {
- rel: "stylesheet",
- href: `chrome://${config.addonRef}/content/toolbutton.css`,
- },
- },
- {
- tag: "div",
- id: makeId("outline-content"),
- styles: {
- height: "100%",
- },
- children: [
- {
- tag: "iframe",
- id: makeId("outline-iframe"),
- properties: {
- width: "100%",
- },
- styles: {
- border: "0",
- height: "100%",
- },
- },
- ],
- },
- {
- tag: "div",
- id: makeId("outline-buttons"),
- styles: {
- height: "50px",
- display: "flex",
- flexDirection: "row",
- justifyContent: "space-between",
- margin: "10px 20px 10px 20px",
- },
- children: [
- makeTooltipProp(
- makeId("switchOutline"),
- ICONS.switchOutline,
- getString("workspace.switchOutline"),
- (ev) => {
- setOutline(container);
- },
- ),
- makeTooltipProp(
- makeId("saveOutlineImage"),
- ICONS.saveOutlineImage,
- getString("workspace.saveOutlineImage"),
- (ev) => {
- saveImage(container);
- },
- ),
- makeTooltipProp(
- makeId("saveOutlineFreeMind"),
- ICONS.saveOutlineFreeMind,
- getString("workspace.saveOutlineFreeMind"),
- (ev) => {
- saveFreeMind();
- },
- ),
- ],
- },
- ],
- },
- {
- tag: "splitter",
- id: makeId("outline-splitter"),
- attributes: { collapse: "before" },
- children: [
- {
- tag: "grippy",
- },
- ],
- },
- {
- tag: "vbox",
- id: makeId("editor-main-container"),
- attributes: {
- flex: "1",
- width: "700",
- },
- styles: {
- background: "var(--material-background50)",
- },
- },
- {
- tag: "splitter",
- id: makeId("preview-splitter"),
- attributes: { collapse: "after", state: "collapsed" },
- children: [
- {
- tag: "grippy",
- },
- ],
- },
- {
- tag: "vbox",
- id: makeId("editor-preview-container"),
- attributes: {
- flex: "1",
- width: "500",
- },
- },
- ],
- },
- container,
- );
- // Manually add custom editor items in Zotero 7
+
+ container.style.minWidth = "0px";
+ container.style.minHeight = "0px";
+
// @ts-ignore
const customElements = container.ownerGlobal
.customElements as CustomElementRegistry;
- const mainEditorContainer = container.querySelector(
- `#${makeId("editor-main-container")}`,
- );
- const previewEditorContainer = container.querySelector(
- `#${makeId("editor-preview-container")}`,
- );
- const mainEditor = new (customElements.get("note-editor")!)();
- mainEditor.id = makeId("editor-main");
- mainEditor.setAttribute("flex", "1");
- const previewEditor = new (customElements.get("note-editor")!)();
- previewEditor.id = makeId("editor-preview");
- previewEditor.setAttribute("flex", "1");
- mainEditorContainer?.append(mainEditor);
- previewEditorContainer?.append(previewEditor);
- const outlineContainer = container.querySelector(
- `#${makeId("outline-container")}`,
- ) as XUL.Box;
- outlineContainer.style.background = "var(--material-sidepane)";
- const outlineMut = new (ztoolkit.getGlobal("MutationObserver"))(
- (mutations) => {
- if (outlineContainer.getAttribute("collapsed") === "true") {
- outlineContainer.style.removeProperty("display");
- } else {
- outlineContainer.style.display = "flex";
- }
- },
- );
- outlineMut.observe(outlineContainer, {
- attributes: true,
- attributeFilter: ["collapsed"],
- });
-
- setOutline(container, OutlineType.treeView);
- initWorkspaceEditor(container, "main", addon.data.workspace.mainId);
-}
-
-export async function initWorkspaceEditor(
- container: XUL.Box,
- type: "main" | "preview",
- noteId: number,
- options: {
- lineIndex?: number;
- sectionName?: string;
- } = {},
-) {
- const noteItem = Zotero.Items.get(noteId);
- if (!noteItem || !noteItem.isNote()) {
- ztoolkit.UI.appendElement(
- {
- tag: "div",
- id: makeId("emptyWorkspaceGuide"),
- styles: {
- position: "absolute",
- top: "0",
- left: "0",
- width: "100%",
- height: "100%",
- display: "flex",
- flexDirection: "column",
- justifyContent: "center",
- alignItems: "center",
- textAlign: "center",
- backgroundColor: "rgba(255,255,255,0.8)",
- zIndex: "100",
- },
- children: [
- {
- tag: "div",
- properties: {
- innerHTML: getString("workspace-emptyWorkspaceGuideInfo"),
- },
- styles: {
- fontSize: "20px",
- fontWeight: "bold",
- color: "gray",
- },
- },
- {
- tag: "button",
- namespace: "xul",
- properties: {
- label: getString("workspace-emptyWorkspaceGuideOpen"),
- },
- styles: {
- fontSize: "16px",
- },
- listeners: [
- {
- type: "command",
- listener: async (ev) => {
- const selectedIds = await itemPicker();
- if (
- selectedIds?.length === 1 &&
- Zotero.Items.get(selectedIds[0]).isNote()
- ) {
- addon.hooks.onSetWorkspaceNote(selectedIds[0], "main");
- addon.hooks.onOpenWorkspace();
- } else {
- window.alert(getString("menuFile-openMainNote-error"));
- }
- },
- },
- ],
- },
- {
- tag: "div",
- properties: {
- innerHTML: getString("workspace-emptyWorkspaceGuideOr"),
- },
- styles: {
- fontSize: "16px",
- color: "gray",
- },
- },
- {
- tag: "button",
- namespace: "xul",
- properties: {
- label: getString("workspace-emptyWorkspaceGuideCreate"),
- },
- styles: {
- fontSize: "16px",
- },
- listeners: [
- {
- type: "command",
- listener: () => {
- addon.hooks.onCreateWorkspaceNote();
- },
- },
- ],
- },
- ],
- },
- container,
- );
- return;
- } else {
- container.querySelector(`#${makeId("emptyWorkspaceGuide")}`)?.remove();
- }
- const editorElem = container.querySelector(
- `#${makeId("editor-" + type)}`,
- ) as EditorElement;
- await waitUtilAsync(() => Boolean(editorElem._initialized))
- .then(() => ztoolkit.log("ok"))
- .catch(() => ztoolkit.log("fail"));
- if (!editorElem._initialized) {
- throw new Error("initNoteEditor: waiting initialization failed");
- }
- editorElem.mode = "edit";
- editorElem.viewMode = "library";
- editorElem.parent = undefined;
- editorElem.item = noteItem;
- await waitUtilAsync(() => Boolean(editorElem._editorInstance));
- await editorElem._editorInstance._initPromise;
- if (typeof options.lineIndex === "number") {
- addon.api.editor.scroll(editorElem._editorInstance, options.lineIndex);
- }
- if (typeof options.sectionName === "string") {
- addon.api.editor.scrollToSection(
- editorElem._editorInstance,
- options.sectionName,
- );
- }
- return;
-}
-
-function getContainerType(
- container: XUL.Box | undefined,
-): "tab" | "window" | "unknown" {
- if (!container) {
- return "unknown";
- }
- return (container.getAttribute("workspace-type") || "unknown") as
- | "tab"
- | "window"
- | "unknown";
-}
-
-export function toggleOutlinePane(visibility?: boolean, container?: XUL.Box) {
- const splitter = container?.querySelector(`#${makeId("outline-splitter")}`);
- if (typeof visibility === "undefined") {
- visibility = splitter?.getAttribute("state") === "collapsed";
- }
- splitter?.setAttribute("state", visibility ? "open" : "collapsed");
-}
-
-export function togglePreviewPane(visibility?: boolean, container?: XUL.Box) {
- const splitter = container?.querySelector(`#${makeId("preview-splitter")}`);
- if (typeof visibility === "undefined") {
- visibility = splitter?.getAttribute("state") === "collapsed";
- }
- splitter?.setAttribute("state", visibility ? "open" : "collapsed");
-}
-
-export function toggleNotesPane(visibility?: boolean) {
- const splitter = document?.querySelector("#zotero-context-splitter");
- if (typeof visibility === "undefined") {
- visibility = splitter?.getAttribute("state") === "collapsed";
- }
- splitter?.setAttribute("state", visibility ? "open" : "collapsed");
-}
-
-export function getWorkspaceEditor(
- workspaceType: "tab" | "window",
- editorType: "main" | "preview" = "main",
-) {
- const container =
- workspaceType === "tab"
- ? addon.data.workspace.tab.container
- : addon.data.workspace.window.container;
- return (
- container?.querySelector(`#${makeId(`editor-${editorType}`)}`) as
- | EditorElement
- | undefined
- )?._editorInstance;
-}
-
-const SRC_LIST = [
- "",
- `chrome://${config.addonRef}/content/treeView.html`,
- `chrome://${config.addonRef}/content/mindMap.html`,
- `chrome://${config.addonRef}/content/bubbleMap.html`,
-];
-
-function setOutline(
- container: XUL.Box,
- newType: OutlineType = OutlineType.empty,
-) {
- if (newType === OutlineType.empty) {
- newType = addon.data.workspace.outline + 1;
- }
- if (newType > OutlineType.bubbleMap) {
- newType = OutlineType.treeView;
- }
- addon.data.workspace.outline = newType;
- (
- container.querySelector(`#${makeId("saveOutlineImage")}`) as HTMLDivElement
- ).hidden = newType === OutlineType.treeView;
- (
- container.querySelector(
- `#${makeId("saveOutlineFreeMind")}`,
- ) as HTMLDivElement
- ).hidden = newType === OutlineType.treeView;
- const iframe = container.querySelector(
- `#${makeId("outline-iframe")}`,
- ) as HTMLIFrameElement;
- iframe.setAttribute("src", SRC_LIST[addon.data.workspace.outline]);
- updateOutline(container);
- updateOutlineButtons(container);
-}
-
-export async function updateOutline(container: XUL.Box) {
- const iframe = container.querySelector(
- `#${makeId("outline-iframe")}`,
- ) as HTMLIFrameElement;
- await waitUtilAsync(
- () => iframe.contentWindow?.document.readyState === "complete",
- );
- iframe.contentWindow?.postMessage(
- {
- type: "setMindMapData",
- nodes: getNoteTreeFlattened(
- Zotero.Items.get(addon.data.workspace.mainId),
- { keepLink: !!getPref("workspace.outline.keepLinks") },
- ),
- workspaceType: getContainerType(container),
- expandLevel: getPref("workspace.outline.expandLevel"),
- },
- "*",
- );
-}
-
-function updateOutlineButtons(container: XUL.Box) {
- const outlineType = addon.data.workspace.outline;
- const isTreeView = outlineType === OutlineType.treeView;
- (
- container.querySelector(`#${makeId("saveOutlineImage")}`) as HTMLDivElement
- ).style.visibility = isTreeView ? "hidden" : "visible";
- (
- container.querySelector(
- `#${makeId("saveOutlineFreeMind")}`,
- ) as HTMLDivElement
- ).style.visibility = isTreeView ? "hidden" : "visible";
-}
-
-function saveImage(container: XUL.Box) {
- const iframe = container.querySelector(
- `#${makeId("outline-iframe")}`,
- ) as HTMLIFrameElement;
- iframe.contentWindow?.postMessage(
- {
- type: "saveSVG",
- },
- "*",
- );
-}
-
-async function saveFreeMind() {
- // TODO: uncouple this part
- const filename = await new ztoolkit.FilePicker(
- `${Zotero.getString("fileInterface.export")} FreeMind XML`,
- "save",
- [["FreeMind XML File(*.mm)", "*.mm"]],
- `${Zotero.Items.get(addon.data.workspace.mainId).getNoteTitle()}.mm`,
- ).open();
- if (filename) {
- await _saveFreeMind(filename, addon.data.workspace.mainId);
- }
+ const workspace = new (customElements.get("bn-workspace")!)() as any;
+ container.append(workspace);
+ workspace.item = item;
+ workspace.containerType = "tab";
+ workspace.render();
}
diff --git a/src/modules/workspace/message.ts b/src/modules/workspace/message.ts
deleted file mode 100644
index 61ae324..0000000
--- a/src/modules/workspace/message.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-import {
- getEditorInstance,
- moveHeading,
- updateHeadingTextAtLine,
-} from "../../utils/editor";
-import { showHintWithLink } from "../../utils/hint";
-import { getNoteLinkParams } from "../../utils/link";
-import { getNoteTree, getNoteTreeNodeById } from "../../utils/note";
-import { formatPath } from "../../utils/str";
-
-export async function messageHandler(ev: MessageEvent) {
- switch (ev.data.type) {
- case "jumpNode": {
- const editor = addon.api.workspace.getWorkspaceEditor(
- ev.data.workspaceType,
- "main",
- );
- if (!editor) {
- return;
- }
- addon.api.editor.scroll(editor, ev.data.lineIndex);
- return;
- }
- case "openNote": {
- const linkParams = getNoteLinkParams(ev.data.link);
- if (!linkParams.noteItem) {
- return;
- }
- addon.hooks.onOpenNote(linkParams.noteItem.id, "preview", {
- lineIndex: linkParams.lineIndex || undefined,
- });
- return;
- }
- case "moveNode": {
- const noteItem = Zotero.Items.get(addon.data.workspace.mainId);
- const tree = getNoteTree(noteItem);
- const fromNode = getNoteTreeNodeById(noteItem, ev.data.fromID, tree);
- const toNode = getNoteTreeNodeById(noteItem, ev.data.toID, tree);
- moveHeading(
- getEditorInstance(noteItem.id),
- fromNode!,
- toNode!,
- ev.data.moveType,
- );
- return;
- }
- case "editNode": {
- const editor = addon.api.workspace.getWorkspaceEditor(
- ev.data.workspaceType,
- "main",
- );
- if (!editor) {
- return;
- }
- updateHeadingTextAtLine(
- editor,
- ev.data.lineIndex,
- ev.data.text.replace(/[\r\n]/g, ""),
- );
- return;
- }
- case "saveSVGReturn": {
- const filename = await new ztoolkit.FilePicker(
- `${Zotero.getString("fileInterface.export")} SVG Image`,
- "save",
- [["SVG File(*.svg)", "*.svg"]],
- `${Zotero.Items.get(addon.data.workspace.mainId).getNoteTitle()}.svg`,
- ).open();
- if (filename) {
- await Zotero.File.putContentsAsync(formatPath(filename), ev.data.image);
- showHintWithLink(
- `Image Saved to ${filename}`,
- "Show in Folder",
- (ev) => {
- Zotero.File.reveal(filename);
- },
- );
- }
- return;
- }
- default:
- return;
- }
-}
diff --git a/src/modules/workspace/tab.ts b/src/modules/workspace/tab.ts
index 72e3004..d443f83 100644
--- a/src/modules/workspace/tab.ts
+++ b/src/modules/workspace/tab.ts
@@ -1,13 +1,7 @@
import { config } from "../../../package.json";
-import { ICONS } from "../../utils/config";
-import { showHint } from "../../utils/hint";
-import { getString } from "../../utils/locale";
-import { getPref, setPref } from "../../utils/prefs";
-import { waitUtilAsync } from "../../utils/wait";
-// TODO: uncouple these imports
-import { messageHandler } from "./message";
+import { initWorkspace } from "./content";
-export const TAB_TYPE = "betternotes";
+export const TAB_TYPE = "note";
export function registerWorkspaceTab(win: Window) {
const doc = win.document;
@@ -29,18 +23,17 @@ export function registerWorkspaceTab(win: Window) {
{
type: "command",
listener: (ev) => {
- if ((ev as MouseEvent).shiftKey) {
- addon.hooks.onOpenWorkspace("window");
- } else {
- addon.hooks.onOpenWorkspace("tab");
- }
+ // if ((ev as MouseEvent).shiftKey) {
+ // addon.hooks.onOpenWorkspace("window");
+ // } else {
+ // addon.hooks.onOpenWorkspace("tab");
+ // }
},
},
],
},
spacer,
) as XUL.ToolBarButton;
- win.addEventListener("message", messageHandler, false);
const collectionSearch = doc.querySelector("#zotero-collections-search")!;
const ob = new (ztoolkit.getGlobal("MutationObserver"))((muts) => {
tabButton.hidden = !!collectionSearch?.classList.contains("visible");
@@ -54,347 +47,47 @@ export function registerWorkspaceTab(win: Window) {
"unload",
() => {
ob.disconnect();
- win.removeEventListener("message", messageHandler);
},
{ once: true },
);
}
-export async function openWorkspaceTab() {
- if (addon.data.workspace.tab.active) {
- Zotero_Tabs.select(addon.data.workspace.tab.id!);
+export async function openWorkspaceTab(item: Zotero.Item) {
+ const currentTab = Zotero_Tabs._tabs.find(
+ (tab) => tab.data?.itemID == item.id,
+ );
+ if (currentTab) {
+ Zotero_Tabs.select(currentTab.id);
return;
}
const { id, container } = Zotero_Tabs.add({
type: TAB_TYPE,
- title: getString("tab.name"),
+ title: item.getNoteTitle(),
index: 1,
data: {
- itemID: addon.data.workspace.mainId,
+ itemID: item.id,
},
select: false,
- onClose: () => {
- deActivateWorkspaceTab();
- },
+ onClose: () => {},
});
- addon.data.workspace.tab.id = id;
- container.setAttribute("workspace-type", "tab");
- addon.data.workspace.tab.container = container;
-
- await activateWorkspaceTab();
+ initWorkspace(container, item);
Zotero_Tabs.select(id);
}
-function hoverWorkspaceTab(hovered: boolean) {
- Array.from(document.querySelectorAll(".tab-toggle")).forEach((elem) => {
- (elem as HTMLDivElement).style.visibility = hovered ? "visible" : "hidden";
- });
- const tabElem = document.querySelector(
- `.tabs-wrapper .tab[data-id=${addon.data.workspace.tab.id}]`,
- ) as HTMLDivElement;
- const content = tabElem.querySelector(".tab-name") as HTMLDivElement;
- content.removeAttribute("style");
- if (hovered) {
- content.style["-moz-box-pack" as any] = "start";
- }
-}
+let contextPaneOpen: boolean | undefined = undefined;
-function updateWorkspaceTabToggleButton(
- type: "outline" | "preview" | "notes",
- state: "open" | "collapsed",
-) {
- const elem = document.querySelector(
- `#betternotes-tab-toggle-${type}`,
- ) as HTMLDivElement;
- if (!elem) {
+export function onTabSelect(tabType: string) {
+ const ZoteroContextPane = ztoolkit.getGlobal("ZoteroContextPane");
+ const splitter = ZoteroContextPane.getSplitter();
+
+ if (tabType === TAB_TYPE) {
+ contextPaneOpen = splitter.getAttribute("state") != "collapsed";
+ splitter.setAttribute("state", "collapsed");
+ } else if (typeof contextPaneOpen !== "undefined") {
+ splitter.setAttribute("state", contextPaneOpen ? "open" : "collapsed");
+ contextPaneOpen = undefined;
+ } else {
return;
}
- if (state !== "collapsed") {
- state = "open";
- }
- elem.innerHTML = ICONS[`workspace_${type}_${state}`];
-}
-
-function registerWorkspaceTabPaneObserver() {
- const outlineSplitter = document.querySelector(
- "#betternotes-workspace-outline-splitter",
- );
- const outlineMut = new (ztoolkit.getGlobal("MutationObserver"))((muts) => {
- updateWorkspaceTabToggleButton(
- "outline",
- outlineSplitter!.getAttribute("state")! as "open" | "collapsed",
- );
- });
- outlineMut.observe(outlineSplitter!, {
- attributes: true,
- attributeFilter: ["state"],
- });
- const previewSplitter = document.querySelector(
- "#betternotes-workspace-preview-splitter",
- );
- const previeweMut = new (ztoolkit.getGlobal("MutationObserver"))((muts) => {
- updateWorkspaceTabToggleButton(
- "preview",
- previewSplitter!.getAttribute("state")! as "open" | "collapsed",
- );
- });
- previeweMut.observe(previewSplitter!, {
- attributes: true,
- attributeFilter: ["state"],
- });
- const notesSplitter = document.querySelector("#zotero-context-splitter");
- const notesMut = new (ztoolkit.getGlobal("MutationObserver"))((muts) => {
- updateWorkspaceTabToggleButton(
- "notes",
- notesSplitter!.getAttribute("state")! as "open" | "collapsed",
- );
- });
- notesMut.observe(notesSplitter!, {
- attributes: true,
- attributeFilter: ["state"],
- });
-}
-
-function isContextPaneInitialized() {
- return (
- (document.querySelector(".notes-pane-deck")?.childElementCount || 0) > 0
- );
-}
-
-export async function activateWorkspaceTab() {
- if (Zotero_Tabs.selectedType === TAB_TYPE && isContextPaneInitialized()) {
- const tabToolbar = document.querySelector("#zotero-tab-toolbar") as XUL.Box;
- tabToolbar && (tabToolbar.style.visibility = "collapse");
- const toolbar = document.querySelector(
- "#zotero-context-toolbar-extension",
- ) as XUL.Box;
- if (toolbar) {
- toolbar.style.visibility = "collapse";
- toolbar.nextElementSibling?.setAttribute("selectedIndex", "1");
- }
- }
-
- if (addon.data.workspace.tab.active) {
- ztoolkit.log("workspace tab is already active");
- return;
- }
- setWorkspaceTabStatus(true);
- // reset tab style
- await waitUtilAsync(() =>
- Boolean(
- document.querySelector(
- `.tabs-wrapper .tab[data-id=${addon.data.workspace.tab.id}]`,
- ),
- ),
- );
- const tabElem = document.querySelector(
- `.tabs-wrapper .tab[data-id=${addon.data.workspace.tab.id}]`,
- ) as HTMLDivElement;
- tabElem.removeAttribute("style");
- const content = tabElem.querySelector(".tab-name") as HTMLDivElement;
- const close = tabElem.querySelector(".tab-close") as HTMLDivElement;
- content.removeAttribute("style");
- content.append(document.createTextNode(getString("tab.name")));
- close.style.removeProperty("visibility");
- ztoolkit.UI.insertElementBefore(
- {
- tag: "fragment",
- children: [
- {
- tag: "div",
- id: "betternotes-tab-toggle-outline",
- classList: ["tab-close", "tab-toggle"],
- styles: {
- right: "56px",
- },
- properties: {
- innerHTML: ICONS.workspace_outline_open,
- },
- listeners: [
- {
- type: "click",
- listener: (ev) => {
- addon.hooks.onToggleWorkspacePane(
- "outline",
- undefined,
- addon.data.workspace.tab.container,
- );
- },
- },
- ],
- },
- {
- tag: "div",
- id: "betternotes-tab-toggle-preview",
- classList: ["tab-close", "tab-toggle"],
- styles: {
- right: "40px",
- },
- properties: {
- innerHTML: ICONS.workspace_preview_collapsed,
- },
- listeners: [
- {
- type: "click",
- listener: (ev) => {
- addon.hooks.onToggleWorkspacePane(
- "preview",
- undefined,
- addon.data.workspace.tab.container,
- );
- },
- },
- ],
- },
- {
- tag: "div",
- id: "betternotes-tab-toggle-notes",
- classList: ["tab-close", "tab-toggle"],
- styles: {
- right: "24px",
- },
- properties: {
- innerHTML:
- document
- .querySelector("#zotero-context-splitter")
- ?.getAttribute("state") === "open"
- ? ICONS.workspace_notes_open
- : ICONS.workspace_notes_collapsed,
- },
- listeners: [
- {
- type: "click",
- listener: (ev) => {
- if (isContextPaneInitialized()) {
- addon.hooks.onToggleWorkspacePane("notes");
- return;
- }
- showHint(getString("workspace.notesPane.hint"));
- },
- },
- ],
- },
- ],
- },
- close,
- );
- hoverWorkspaceTab(false);
- tabElem.addEventListener("mouseenter", () => {
- if (Zotero_Tabs.selectedType !== "betternotes") {
- return;
- }
- hoverWorkspaceTab(true);
- });
- tabElem.addEventListener("mousedown", () => hoverWorkspaceTab(true));
- tabElem.addEventListener("mouseleave", () => hoverWorkspaceTab(false));
- tabElem.addEventListener("mousedown", async (ev) => {
- if (ev.button !== 2) {
- return;
- }
- await Zotero.Promise.delay(300);
- const menu = document
- .querySelector("#zotero-itemmenu")
- ?.parentElement?.lastElementChild?.querySelector("menu")
- ?.querySelector("menupopup")?.lastElementChild;
- menu?.addEventListener("click", () => {
- addon.hooks.onOpenWorkspace("window");
- });
- });
- // load workspace content
- const container = addon.data.workspace.tab.container;
- initWorkspaceTabDragDrop(container, tabElem);
- addon.hooks.onInitWorkspace(container);
- registerWorkspaceTabPaneObserver();
- setWorkspaceTabStatus(true);
-}
-
-export function deActivateWorkspaceTab() {
- const tabToolbar = document.querySelector("#zotero-tab-toolbar") as XUL.Box;
- tabToolbar && tabToolbar.style.removeProperty("visibility");
- const toolbar = document.querySelector(
- "#zotero-context-toolbar-extension",
- ) as XUL.Box;
- toolbar?.style.removeProperty("visibility");
- setWorkspaceTabStatus(false);
-}
-
-function setWorkspaceTabStatus(status: boolean) {
- addon.data.workspace.tab.active = status;
- setPref("workspace.tab.active", status);
-}
-
-function initWorkspaceTabDragDrop(
- container?: XUL.Box,
- tabElem?: HTMLDivElement,
-) {
- if (!container) {
- return;
- }
- const rect = tabElem?.getBoundingClientRect();
- ztoolkit.UI.appendElement(
- {
- tag: "div",
- id: "bn-workspace-tab-drop",
- styles: {
- background: "#252526",
- opacity: "0.6",
- width: "100%",
- height: "100px",
- position: "fixed",
- left: "0px",
- top: `${rect?.bottom}px`,
- textAlign: "center",
- display: "flex",
- visibility: "hidden",
- zIndex: "65535",
- },
- properties: {
- hidden: true,
- ondrop: (ev: DragEvent) => {
- addon.hooks.onOpenWorkspace("window");
- },
- ondragenter: (ev: DragEvent) => {
- ev.preventDefault();
- ev.stopPropagation();
- dropElem.style.opacity = "0.9";
- if (ev.dataTransfer) {
- ev.dataTransfer.dropEffect = "move";
- }
- },
- ondragover: (ev: DragEvent) => {
- ev.preventDefault();
- ev.stopPropagation();
- },
- ondragleave: (ev: DragEvent) => {
- ev.preventDefault();
- ev.stopPropagation();
- dropElem.style.opacity = "0.6";
- },
- },
- children: [
- {
- tag: "div",
- styles: {
- margin: "auto",
- textAlign: "center",
- color: "#fff",
- },
- properties: {
- innerHTML: getString("tab.openInWindow"),
- },
- },
- ],
- enableElementRecord: false,
- },
- container,
- );
- const dropElem = container.querySelector(
- "#bn-workspace-tab-drop",
- ) as HTMLDivElement;
- tabElem?.addEventListener("dragstart", (ev) => {
- dropElem.style.visibility = "visible";
- });
- tabElem?.addEventListener("dragend", (ev) => {
- dropElem.style.visibility = "hidden";
- });
+ ZoteroContextPane.update();
}
diff --git a/src/modules/workspace/window.ts b/src/modules/workspace/window.ts
index 855702b..382c83a 100644
--- a/src/modules/workspace/window.ts
+++ b/src/modules/workspace/window.ts
@@ -1,12 +1,6 @@
import { config } from "../../../package.json";
-import { isWindowAlive } from "../../utils/window";
-import { messageHandler } from "./message";
-export async function openWorkspaceWindow() {
- if (isWindowAlive(addon.data.workspace.window.window)) {
- addon.data.workspace.window.window?.focus();
- return;
- }
+export async function openWorkspaceWindow(item: Zotero.Item) {
const windowArgs = {
_initPromise: Zotero.Promise.defer(),
};
@@ -17,16 +11,9 @@ export async function openWorkspaceWindow() {
windowArgs,
)!;
await windowArgs._initPromise.promise;
- addon.data.workspace.window.active = true;
- addon.data.workspace.window.window = win;
- addon.data.workspace.window.container = win.document.querySelector(
+
+ const container = win.document.querySelector(
"#workspace-container",
) as XUL.Box;
- addon.hooks.onInitWorkspace(addon.data.workspace.window.container);
- win.addEventListener("message", messageHandler, false);
- win.addEventListener("unload", function onWindowUnload(ev) {
- addon.data.workspace.window.active = false;
- this.window.removeEventListener("unload", onWindowUnload, false);
- this.window.removeEventListener("message", messageHandler, false);
- });
+ addon.hooks.onInitWorkspace(container, item);
}
diff --git a/src/utils/note.ts b/src/utils/note.ts
index 4a7ad7c..b15a549 100644
--- a/src/utils/note.ts
+++ b/src/utils/note.ts
@@ -11,7 +11,6 @@ export {
parseHTMLLines,
getLinesInNote,
addLineToNote,
- getNoteType,
getNoteTree,
getNoteTreeFlattened,
getNoteTreeNodeById,
@@ -197,7 +196,7 @@ async function renderNoteHTML(
refNotes = [noteItem];
}
- const parser = ztoolkit.getDOMParser();
+ const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
const imageAttachments = refNotes.reduce((acc, note) => {
acc.push(...Zotero.Items.get(note.getAttachments()));
@@ -269,22 +268,12 @@ async function renderNoteHTML(
return doc.body.innerHTML;
}
-function getNoteType(id: number) {
- if (id === addon.data.workspace.mainId) {
- return "main";
- } else if (id === addon.data.workspace.previewId) {
- return "preview";
- } else {
- return "default";
- }
-}
-
function getNoteTree(
note: Zotero.Item,
parseLink: boolean = true,
): TreeModel.Node {
const noteLines = getLinesInNote(note);
- const parser = ztoolkit.getDOMParser();
+ const parser = new DOMParser();
const tree = new TreeModel();
const root = tree.parse({
id: -1,
@@ -422,7 +411,7 @@ async function copyEmbeddedImagesInHTML(
ztoolkit.log(attachments);
- const doc = ztoolkit.getDOMParser().parseFromString(html, "text/html");
+ const doc = new DOMParser().parseFromString(html, "text/html");
// Copy note image attachments and replace keys in the new note
for (const attachment of attachments) {
diff --git a/src/utils/wait.ts b/src/utils/wait.ts
index 4183d14..f03b3f6 100644
--- a/src/utils/wait.ts
+++ b/src/utils/wait.ts
@@ -5,12 +5,12 @@ export function waitUntil(
timeout = 10000,
) {
const start = Date.now();
- const intervalId = ztoolkit.getGlobal("setInterval")(() => {
+ const intervalId = setInterval(() => {
if (condition()) {
- ztoolkit.getGlobal("clearInterval")(intervalId);
+ clearInterval(intervalId);
callback();
} else if (Date.now() - start > timeout) {
- ztoolkit.getGlobal("clearInterval")(intervalId);
+ clearInterval(intervalId);
}
}, interval);
}
@@ -22,12 +22,12 @@ export function waitUtilAsync(
) {
return new Promise((resolve, reject) => {
const start = Date.now();
- const intervalId = ztoolkit.getGlobal("setInterval")(() => {
+ const intervalId = setInterval(() => {
if (condition()) {
- ztoolkit.getGlobal("clearInterval")(intervalId);
+ clearInterval(intervalId);
resolve();
} else if (Date.now() - start > timeout) {
- ztoolkit.getGlobal("clearInterval")(intervalId);
+ clearInterval(intervalId);
reject();
}
}, interval);
diff --git a/typings/global.d.ts b/typings/global.d.ts
index 0cae16c..a659262 100644
--- a/typings/global.d.ts
+++ b/typings/global.d.ts
@@ -33,3 +33,22 @@ declare const addon: import("../src/addon").default;
declare const __env__: "production" | "development";
declare class Localization {}
+
+declare class XULElementBase extends HTMLElement {
+ get content(): DocumentFragment;
+ init(): void;
+ destroy(): void;
+ connectedCallback(): void;
+ disconnectedCallback(): void;
+ attributeChangedCallback(
+ name: string,
+ oldValue: string,
+ newValue: string,
+ ): void;
+ static get observedAttributes(): string[];
+}
+
+declare class MozXULElement {
+ static parseXULToFragment(xul: string): Fragment;
+ static insertFTLIfNeeded(ftl: string): void;
+}
From 33dda555b61fc1771a9873e21f8131548b2d6e07 Mon Sep 17 00:00:00 2001
From: windingwind <33902321+windingwind@users.noreply.github.com>
Date: Fri, 15 Mar 2024 11:37:38 +0800
Subject: [PATCH 02/23] fix: note tab button style
---
addon/chrome/content/styles/outline.css | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/addon/chrome/content/styles/outline.css b/addon/chrome/content/styles/outline.css
index 4598945..8e0b09a 100644
--- a/addon/chrome/content/styles/outline.css
+++ b/addon/chrome/content/styles/outline.css
@@ -26,10 +26,15 @@ 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");
}
From 86c77b095912eb1e16cbd1875852b41a0e520554 Mon Sep 17 00:00:00 2001
From: windingwind <33902321+windingwind@users.noreply.github.com>
Date: Fri, 15 Mar 2024 11:38:29 +0800
Subject: [PATCH 03/23] fix: disable workspace related code
---
src/extras/workspace.ts | 2 +-
src/modules/editor/toolbar.ts | 153 +++++++++++++++---------------
src/modules/sync/managerWindow.ts | 2 +-
src/modules/template/picker.ts | 2 +-
src/modules/template/preview.ts | 65 +++++++------
5 files changed, 114 insertions(+), 110 deletions(-)
diff --git a/src/extras/workspace.ts b/src/extras/workspace.ts
index 2b134de..d629838 100644
--- a/src/extras/workspace.ts
+++ b/src/extras/workspace.ts
@@ -6,7 +6,7 @@ import { Workspace } from "../elements/workspace";
const elements = {
"bn-context": ContextPane,
"bn-outline": OutlinePane,
- "bn-details": NoteDetails,
+ "bn-details": NoteDetails as unknown as CustomElementConstructor,
"bn-workspace": Workspace,
};
diff --git a/src/modules/editor/toolbar.ts b/src/modules/editor/toolbar.ts
index a680b62..89c0bec 100644
--- a/src/modules/editor/toolbar.ts
+++ b/src/modules/editor/toolbar.ts
@@ -230,45 +230,45 @@ export async function initEditorToolbar(editor: Zotero.EditorInstance) {
const onClickMenu = async (ev: MouseEvent) => {
// TODO: fix link
return;
- const mainNote = Zotero.Items.get(addon.data.workspace.mainId) || null;
- if (!mainNote?.isNote()) {
- return;
- }
- const lineIndex = parseInt(
- (ev.target as HTMLDivElement).id.split("-").pop() || "-1",
- );
- const forwardLink = getNoteLink(noteItem);
- const backLink = getNoteLink(mainNote, { ignore: true, lineIndex });
- addLineToNote(
- mainNote,
- await addon.api.template.runTemplate(
- "[QuickInsertV2]",
- "link, linkText, subNoteItem, noteItem",
- [
- forwardLink,
- noteItem.getNoteTitle().trim() || forwardLink,
- noteItem,
- mainNote,
- ],
- ),
- lineIndex,
- );
- addLineToNote(
- noteItem,
- await addon.api.template.runTemplate(
- "[QuickBackLinkV2]",
- "link, linkText, subNoteItem, noteItem",
- [
- backLink,
- mainNote.getNoteTitle().trim() || "Workspace Note",
- noteItem,
- mainNote,
- "",
- ],
- ),
- );
- onExitMenu(ev);
- ev.stopPropagation();
+ // const mainNote = Zotero.Items.get(addon.data.workspace.mainId) || null;
+ // if (!mainNote?.isNote()) {
+ // return;
+ // }
+ // const lineIndex = parseInt(
+ // (ev.target as HTMLDivElement).id.split("-").pop() || "-1",
+ // );
+ // const forwardLink = getNoteLink(noteItem);
+ // const backLink = getNoteLink(mainNote, { ignore: true, lineIndex });
+ // addLineToNote(
+ // mainNote,
+ // await addon.api.template.runTemplate(
+ // "[QuickInsertV2]",
+ // "link, linkText, subNoteItem, noteItem",
+ // [
+ // forwardLink,
+ // noteItem.getNoteTitle().trim() || forwardLink,
+ // noteItem,
+ // mainNote,
+ // ],
+ // ),
+ // lineIndex,
+ // );
+ // addLineToNote(
+ // noteItem,
+ // await addon.api.template.runTemplate(
+ // "[QuickBackLinkV2]",
+ // "link, linkText, subNoteItem, noteItem",
+ // [
+ // backLink,
+ // mainNote.getNoteTitle().trim() || "Workspace Note",
+ // noteItem,
+ // mainNote,
+ // "",
+ // ],
+ // ),
+ // );
+ // onExitMenu(ev);
+ // ev.stopPropagation();
};
const linkButton = await registerEditorToolbarDropdown(
@@ -310,43 +310,44 @@ export async function initEditorToolbar(editor: Zotero.EditorInstance) {
}
function getLinkMenuData(editor: Zotero.EditorInstance): PopupData[] {
- const workspaceNote = Zotero.Items.get(addon.data.workspace.mainId) || null;
- const currentNote = editor._item;
- if (!workspaceNote?.isNote()) {
- return [
- {
- id: makeId("link-popup-nodata"),
- text: getString("editor.toolbar.link.popup.nodata"),
- },
- ];
- }
- const nodes = getNoteTreeFlattened(workspaceNote, {
- keepLink: true,
- });
- const menuData: PopupData[] = [];
- for (const node of nodes) {
- if (node.model.level === 7) {
- const lastMenu =
- menuData.length > 0 ? menuData[menuData.length - 1] : null;
- const linkNote = getNoteLinkParams(node.model.link).noteItem;
- if (linkNote && linkNote.id === currentNote.id && lastMenu) {
- lastMenu.suffix = "🔗";
- }
- continue;
- }
- menuData.push({
- id: makeId(
- `link-popup-${
- getPref("editor.link.insertPosition")
- ? node.model.lineIndex - 1
- : node.model.endIndex
- }`,
- ),
- text: node.model.name,
- prefix: "·".repeat(node.model.level - 1),
- });
- }
- return menuData;
+ return [];
+ // const workspaceNote = Zotero.Items.get(addon.data.workspace.mainId) || null;
+ // const currentNote = editor._item;
+ // if (!workspaceNote?.isNote()) {
+ // return [
+ // {
+ // id: makeId("link-popup-nodata"),
+ // text: getString("editor.toolbar.link.popup.nodata"),
+ // },
+ // ];
+ // }
+ // const nodes = getNoteTreeFlattened(workspaceNote, {
+ // keepLink: true,
+ // });
+ // const menuData: PopupData[] = [];
+ // for (const node of nodes) {
+ // if (node.model.level === 7) {
+ // const lastMenu =
+ // menuData.length > 0 ? menuData[menuData.length - 1] : null;
+ // const linkNote = getNoteLinkParams(node.model.link).noteItem;
+ // if (linkNote && linkNote.id === currentNote.id && lastMenu) {
+ // lastMenu.suffix = "🔗";
+ // }
+ // continue;
+ // }
+ // menuData.push({
+ // id: makeId(
+ // `link-popup-${
+ // getPref("editor.link.insertPosition")
+ // ? node.model.lineIndex - 1
+ // : node.model.endIndex
+ // }`,
+ // ),
+ // text: node.model.name,
+ // prefix: "·".repeat(node.model.level - 1),
+ // });
+ // }
+ // return menuData;
}
async function registerEditorToolbar(
diff --git a/src/modules/sync/managerWindow.ts b/src/modules/sync/managerWindow.ts
index 493b783..ee17f5f 100644
--- a/src/modules/sync/managerWindow.ts
+++ b/src/modules/sync/managerWindow.ts
@@ -102,7 +102,7 @@ export async function showSyncManager() {
// @ts-ignore TODO: Fix type in zotero-plugin-toolkit
.setProp("onColumnSort", (columnIndex, ascending) => {
addon.data.sync.manager.columnIndex = columnIndex;
- addon.data.sync.manager.columnAscending = ascending > 0;
+ addon.data.sync.manager.columnAscending = ascending;
refresh();
})
.render();
diff --git a/src/modules/template/picker.ts b/src/modules/template/picker.ts
index 063b031..ccb2383 100644
--- a/src/modules/template/picker.ts
+++ b/src/modules/template/picker.ts
@@ -77,7 +77,7 @@ function getTemplatePromptHandler(name: string) {
async function insertTemplateCallback(name: string) {
const targetNoteItem = Zotero.Items.get(
- addon.data.template.picker.data.noteId || addon.data.workspace.mainId,
+ addon.data.template.picker.data.noteId,
);
let html = "";
if (name.toLowerCase().startsWith("[item]")) {
diff --git a/src/modules/template/preview.ts b/src/modules/template/preview.ts
index b9631b5..8f76010 100644
--- a/src/modules/template/preview.ts
+++ b/src/modules/template/preview.ts
@@ -78,15 +78,16 @@ async function renderTemplatePreview(
const link = getNoteLink(data);
const linkText = data.getNoteTitle().trim() || link;
const subNoteItem = data;
- const noteItem = Zotero.Items.get(addon.data.workspace.mainId);
- html = await addon.api.template.runTemplate(
- templateName,
- "link, linkText, subNoteItem, noteItem",
- [link, linkText, subNoteItem, noteItem],
- {
- dryRun: true,
- },
- );
+ return "";
+ // const noteItem = Zotero.Items.get(addon.data.workspace.mainId);
+ // html = await addon.api.template.runTemplate(
+ // templateName,
+ // "link, linkText, subNoteItem, noteItem",
+ // [link, linkText, subNoteItem, noteItem],
+ // {
+ // dryRun: true,
+ // },
+ // );
}
} else if (templateName.includes("QuickBackLink")) {
// link, linkText, subNoteItem, noteItem
@@ -94,18 +95,19 @@ async function renderTemplatePreview(
if (!data) {
html = "No note item selected
";
} else {
- const link = getNoteLink(data);
- const noteItem = Zotero.Items.get(addon.data.workspace.mainId);
- const linkText = noteItem.getNoteTitle().trim() || "Workspace Note";
- const subNoteItem = data;
- html = await addon.api.template.runTemplate(
- templateName,
- "link, linkText, subNoteItem, noteItem",
- [link, linkText, subNoteItem, noteItem],
- {
- dryRun: true,
- },
- );
+ return "";
+ // const link = getNoteLink(data);
+ // const noteItem = Zotero.Items.get(addon.data.workspace.mainId);
+ // const linkText = noteItem.getNoteTitle().trim() || "Workspace Note";
+ // const subNoteItem = data;
+ // html = await addon.api.template.runTemplate(
+ // templateName,
+ // "link, linkText, subNoteItem, noteItem",
+ // [link, linkText, subNoteItem, noteItem],
+ // {
+ // dryRun: true,
+ // },
+ // );
}
} else if (templateName.includes("QuickImport")) {
// link, noteItem
@@ -113,16 +115,17 @@ async function renderTemplatePreview(
if (!data) {
html = "No note item selected
";
} else {
- const link = getNoteLink(data);
- const noteItem = Zotero.Items.get(addon.data.workspace.mainId);
- html = await addon.api.template.runTemplate(
- templateName,
- "link, noteItem",
- [link, noteItem],
- {
- dryRun: true,
- },
- );
+ return "";
+ // const link = getNoteLink(data);
+ // const noteItem = Zotero.Items.get(addon.data.workspace.mainId);
+ // html = await addon.api.template.runTemplate(
+ // templateName,
+ // "link, noteItem",
+ // [link, noteItem],
+ // {
+ // dryRun: true,
+ // },
+ // );
}
} else if (templateName.includes("QuickNote")) {
// annotationItem, topItem, noteItem
From 75ca707edcf6245ecf3726d8eccf9cf6ef64f6b4 Mon Sep 17 00:00:00 2001
From: windingwind <33902321+windingwind@users.noreply.github.com>
Date: Fri, 15 Mar 2024 11:38:43 +0800
Subject: [PATCH 04/23] add: restore note tabs
---
src/hooks.ts | 4 ++++
src/modules/workspace/tab.ts | 29 ++++++++++++++++++++++++-----
2 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/src/hooks.ts b/src/hooks.ts
index 88fdcb7..7c52a59 100644
--- a/src/hooks.ts
+++ b/src/hooks.ts
@@ -12,6 +12,7 @@ import {
registerWorkspaceTab,
openWorkspaceTab,
onTabSelect,
+ restoreNoteTabs,
} from "./modules/workspace/tab";
import { initWorkspace } from "./modules/workspace/content";
import { registerNotify } from "./modules/notify";
@@ -81,6 +82,8 @@ async function onMainWindowLoad(win: Window): Promise {
initTemplates();
patchViewItems(win);
+
+ restoreNoteTabs();
}
async function onMainWindowUnload(win: Window): Promise {
@@ -255,4 +258,5 @@ export default {
onShowTemplateEditor,
onCreateNoteFromTemplate,
onCreateNoteFromMD,
+ restoreNoteTabs,
};
diff --git a/src/modules/workspace/tab.ts b/src/modules/workspace/tab.ts
index d443f83..214be70 100644
--- a/src/modules/workspace/tab.ts
+++ b/src/modules/workspace/tab.ts
@@ -52,26 +52,32 @@ export function registerWorkspaceTab(win: Window) {
);
}
-export async function openWorkspaceTab(item: Zotero.Item) {
+export async function openWorkspaceTab(
+ item: Zotero.Item,
+ options: { select?: boolean; index?: number } = {
+ select: true,
+ },
+) {
+ const { select, index } = options;
+ if (!item) return;
const currentTab = Zotero_Tabs._tabs.find(
(tab) => tab.data?.itemID == item.id,
);
if (currentTab) {
- Zotero_Tabs.select(currentTab.id);
+ if (select) Zotero_Tabs.select(currentTab.id);
return;
}
const { id, container } = Zotero_Tabs.add({
type: TAB_TYPE,
title: item.getNoteTitle(),
- index: 1,
+ index,
data: {
itemID: item.id,
},
- select: false,
+ select,
onClose: () => {},
});
initWorkspace(container, item);
- Zotero_Tabs.select(id);
}
let contextPaneOpen: boolean | undefined = undefined;
@@ -91,3 +97,16 @@ export function onTabSelect(tabType: string) {
}
ZoteroContextPane.update();
}
+
+export function restoreNoteTabs() {
+ const tabsCache: _ZoteroTypes.TabInstance[] =
+ Zotero.Session.state.windows.find((x: any) => x.type == "pane")?.tabs;
+ for (const i in tabsCache) {
+ const tab = tabsCache[i];
+ if (tab.type !== TAB_TYPE) continue;
+ openWorkspaceTab(Zotero.Items.get(tab.data.itemID), {
+ select: tab.selected,
+ index: Number(i),
+ });
+ }
+}
From acaef3d50731dd29772484f22425dbf9ce1f16e5 Mon Sep 17 00:00:00 2001
From: windingwind <33902321+windingwind@users.noreply.github.com>
Date: Fri, 22 Mar 2024 11:58:09 +0800
Subject: [PATCH 05/23] fix: sync manager list sorting
---
package.json | 2 +-
src/modules/sync/managerWindow.ts | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/package.json b/package.json
index d99b862..5639361 100644
--- a/package.json
+++ b/package.json
@@ -58,7 +58,7 @@
"unist-util-visit": "^5.0.0",
"unist-util-visit-parents": "^6.0.1",
"yamljs": "^0.3.0",
- "zotero-plugin-toolkit": "^2.3.26"
+ "zotero-plugin-toolkit": "^2.3.28"
},
"devDependencies": {
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
diff --git a/src/modules/sync/managerWindow.ts b/src/modules/sync/managerWindow.ts
index ee17f5f..493b783 100644
--- a/src/modules/sync/managerWindow.ts
+++ b/src/modules/sync/managerWindow.ts
@@ -102,7 +102,7 @@ export async function showSyncManager() {
// @ts-ignore TODO: Fix type in zotero-plugin-toolkit
.setProp("onColumnSort", (columnIndex, ascending) => {
addon.data.sync.manager.columnIndex = columnIndex;
- addon.data.sync.manager.columnAscending = ascending;
+ addon.data.sync.manager.columnAscending = ascending > 0;
refresh();
})
.render();
From 3579a5b583e3d6bb69104c30a6f8d24f7f8b7bc2 Mon Sep 17 00:00:00 2001
From: windingwind <33902321+windingwind@users.noreply.github.com>
Date: Thu, 28 Mar 2024 20:55:36 +0800
Subject: [PATCH 06/23] fix: workspace context pane editable
---
src/elements/context.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/elements/context.ts b/src/elements/context.ts
index febba36..dee1d8d 100644
--- a/src/elements/context.ts
+++ b/src/elements/context.ts
@@ -37,7 +37,7 @@ export class ContextPane extends PluginCEBase {
render() {
if (!this.item) return;
- this._details.mode = this.item.isEditable() ? null : "view";
+ this._details.editable = this.item.isEditable();
this._details.item = this.item;
this._details.parentID = this.item.parentID;
this._details.sidenav = this._sidenav;
From 1287ccc9002b468edd7089a7f20ffb0a0d826503 Mon Sep 17 00:00:00 2001
From: windingwind <33902321+windingwind@users.noreply.github.com>
Date: Thu, 28 Mar 2024 21:01:11 +0800
Subject: [PATCH 07/23] fix: fx115 context pane
---
src/modules/workspace/tab.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/modules/workspace/tab.ts b/src/modules/workspace/tab.ts
index 214be70..0c53408 100644
--- a/src/modules/workspace/tab.ts
+++ b/src/modules/workspace/tab.ts
@@ -84,7 +84,7 @@ let contextPaneOpen: boolean | undefined = undefined;
export function onTabSelect(tabType: string) {
const ZoteroContextPane = ztoolkit.getGlobal("ZoteroContextPane");
- const splitter = ZoteroContextPane.getSplitter();
+ const splitter = ZoteroContextPane.splitter;
if (tabType === TAB_TYPE) {
contextPaneOpen = splitter.getAttribute("state") != "collapsed";
From d8e29cedc8deffa5649db9cee11483601c895659 Mon Sep 17 00:00:00 2001
From: windingwind <33902321+windingwind@users.noreply.github.com>
Date: Thu, 28 Mar 2024 21:01:22 +0800
Subject: [PATCH 08/23] fix: template preview
---
src/modules/template/preview.ts | 65 ++++++++++++++++-----------------
1 file changed, 31 insertions(+), 34 deletions(-)
diff --git a/src/modules/template/preview.ts b/src/modules/template/preview.ts
index 8f76010..d5a0440 100644
--- a/src/modules/template/preview.ts
+++ b/src/modules/template/preview.ts
@@ -78,16 +78,15 @@ async function renderTemplatePreview(
const link = getNoteLink(data);
const linkText = data.getNoteTitle().trim() || link;
const subNoteItem = data;
- return "";
- // const noteItem = Zotero.Items.get(addon.data.workspace.mainId);
- // html = await addon.api.template.runTemplate(
- // templateName,
- // "link, linkText, subNoteItem, noteItem",
- // [link, linkText, subNoteItem, noteItem],
- // {
- // dryRun: true,
- // },
- // );
+ const noteItem = new Zotero.Item("note");
+ html = await addon.api.template.runTemplate(
+ templateName,
+ "link, linkText, subNoteItem, noteItem",
+ [link, linkText, subNoteItem, noteItem],
+ {
+ dryRun: true,
+ },
+ );
}
} else if (templateName.includes("QuickBackLink")) {
// link, linkText, subNoteItem, noteItem
@@ -95,19 +94,18 @@ async function renderTemplatePreview(
if (!data) {
html = "No note item selected
";
} else {
- return "";
- // const link = getNoteLink(data);
- // const noteItem = Zotero.Items.get(addon.data.workspace.mainId);
- // const linkText = noteItem.getNoteTitle().trim() || "Workspace Note";
- // const subNoteItem = data;
- // html = await addon.api.template.runTemplate(
- // templateName,
- // "link, linkText, subNoteItem, noteItem",
- // [link, linkText, subNoteItem, noteItem],
- // {
- // dryRun: true,
- // },
- // );
+ const link = getNoteLink(data);
+ const noteItem = new Zotero.Item("note");
+ const linkText = noteItem.getNoteTitle().trim() || "Workspace Note";
+ const subNoteItem = data;
+ html = await addon.api.template.runTemplate(
+ templateName,
+ "link, linkText, subNoteItem, noteItem",
+ [link, linkText, subNoteItem, noteItem],
+ {
+ dryRun: true,
+ },
+ );
}
} else if (templateName.includes("QuickImport")) {
// link, noteItem
@@ -115,17 +113,16 @@ async function renderTemplatePreview(
if (!data) {
html = "No note item selected
";
} else {
- return "";
- // const link = getNoteLink(data);
- // const noteItem = Zotero.Items.get(addon.data.workspace.mainId);
- // html = await addon.api.template.runTemplate(
- // templateName,
- // "link, noteItem",
- // [link, noteItem],
- // {
- // dryRun: true,
- // },
- // );
+ const link = getNoteLink(data);
+ const noteItem = new Zotero.Item("note");
+ html = await addon.api.template.runTemplate(
+ templateName,
+ "link, noteItem",
+ [link, noteItem],
+ {
+ dryRun: true,
+ },
+ );
}
} else if (templateName.includes("QuickNote")) {
// annotationItem, topItem, noteItem
From 9e2b4c583dbce6fffe3548a0d0004ceb715170dc Mon Sep 17 00:00:00 2001
From: windingwind <33902321+windingwind@users.noreply.github.com>
Date: Sat, 6 Apr 2024 11:23:24 +0800
Subject: [PATCH 09/23] add: linkNote dialog
---
addon/chrome/content/linkNote.xhtml | 98 +++
addon/chrome/content/styles/linkNote.css | 33 +
addon/chrome/content/styles/notePicker.css | 40 ++
addon/chrome/content/styles/toolbar.css | 48 ++
addon/locale/en-US/addon.ftl | 6 +-
addon/locale/it-IT/addon.ftl | 6 +-
addon/locale/ru-RU/addon.ftl | 6 +-
addon/locale/tr-TR/addon.ftl | 6 +-
addon/prefs.js | 2 +
src/api.ts | 2 +
src/elements/base.ts | 10 +-
src/elements/notePicker.ts | 304 +++++++++
src/elements/outlinePane.ts | 14 +-
.../{workspace.ts => customElements.ts} | 2 +
src/extras/linkNote.ts | 325 ++++++++++
src/hooks.ts | 7 +-
src/modules/convert/api.ts | 4 +-
src/modules/editor/toolbar.ts | 613 ++++++------------
src/modules/export/markdown.ts | 2 +-
src/modules/export/pdf.ts | 1 -
src/modules/relatedNotes.ts | 31 +
src/modules/sync/managerWindow.ts | 1 -
src/modules/template/api.ts | 4 +-
src/utils/config.ts | 20 +-
src/utils/linkNote.ts | 42 ++
src/utils/note.ts | 111 ++--
26 files changed, 1237 insertions(+), 501 deletions(-)
create mode 100644 addon/chrome/content/linkNote.xhtml
create mode 100644 addon/chrome/content/styles/linkNote.css
create mode 100644 addon/chrome/content/styles/notePicker.css
create mode 100644 addon/chrome/content/styles/toolbar.css
create mode 100644 src/elements/notePicker.ts
rename src/extras/{workspace.ts => customElements.ts} (86%)
create mode 100644 src/extras/linkNote.ts
create mode 100644 src/modules/relatedNotes.ts
create mode 100644 src/utils/linkNote.ts
diff --git a/addon/chrome/content/linkNote.xhtml b/addon/chrome/content/linkNote.xhtml
new file mode 100644
index 0000000..034ecb7
--- /dev/null
+++ b/addon/chrome/content/linkNote.xhtml
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addon/chrome/content/styles/linkNote.css b/addon/chrome/content/styles/linkNote.css
new file mode 100644
index 0000000..1403699
--- /dev/null
+++ b/addon/chrome/content/styles/linkNote.css
@@ -0,0 +1,33 @@
+.container {
+ height: 100%;
+ margin: 0;
+}
+
+#top-container {
+ gap: 16px;
+ overflow: auto;
+ width: 800px;
+ padding: 2em;
+}
+
+bn-note-picker {
+ border: var(--material-border);
+ min-width: 600px;
+ height: 500px;
+}
+
+#bn-select-note-outline-container {
+ border: var(--material-border);
+ min-width: 300px;
+ height: 500px;
+}
+
+#bn-note-preview-container {
+ border: var(--material-border);
+ min-width: 450px;
+ height: 500px;
+}
+
+#bn-link-insert-position-container {
+ align-items: center;
+}
diff --git a/addon/chrome/content/styles/notePicker.css b/addon/chrome/content/styles/notePicker.css
new file mode 100644
index 0000000..e3347e1
--- /dev/null
+++ b/addon/chrome/content/styles/notePicker.css
@@ -0,0 +1,40 @@
+bn-note-picker {
+ flex-direction: column;
+}
+
+.container {
+ height: 100%;
+ margin: 0;
+}
+
+#select-items-dialog #zotero-select-items-container {
+ gap: 0;
+}
+
+#collections-items-container {
+ display: flex;
+ height: 100%;
+ 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;
+}
diff --git a/addon/chrome/content/styles/toolbar.css b/addon/chrome/content/styles/toolbar.css
new file mode 100644
index 0000000..aa3f5af
--- /dev/null
+++ b/addon/chrome/content/styles/toolbar.css
@@ -0,0 +1,48 @@
+.toolbar {
+ background: var(--material-toolbar);
+ border-bottom: var(--material-border);
+ padding: 6px;
+ align-items: center;
+ justify-content: space-between !important;
+}
+
+.toolbar-start,
+.toolbar-middle,
+.toolbar-end {
+ align-items: center;
+}
+
+.toolbar-header {
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 1;
+ overflow: hidden;
+ color: var(--fill-secondary);
+ text-overflow: ellipsis;
+ overflow-wrap: anywhere;
+ line-break: anywhere;
+ font-size: calc(var(--zotero-font-size) * 1.2);
+}
+
+.toolbar-header.content {
+ flex-shrink: 0;
+}
+
+.toolbar-header.highlight {
+ font-size: var(--zotero-font-size);
+ padding: 4px;
+ 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);
+ transition: all 0.3s ease;
+}
+.toolbar-header.highlight:hover {
+ box-shadow: 0 5px 15px
+ color-mix(in srgb, var(--material-background) 20%, transparent);
+ background: var(--color-background50);
+}
+.toolbar-header.highlight:empty {
+ display: none;
+}
diff --git a/addon/locale/en-US/addon.ftl b/addon/locale/en-US/addon.ftl
index d532e07..663c97d 100644
--- a/addon/locale/en-US/addon.ftl
+++ b/addon/locale/en-US/addon.ftl
@@ -78,9 +78,9 @@ editor-toolbar-settings-openWorkspace = Open Note Workspace
editor-toolbar-settings-setWorkspace = Set as Workspace Note
editor-toolbar-settings-previewInWorkspace = Preview in Workspace
editor-toolbar-settings-showInLibrary = Show in Library
-editor-toolbar-settings-insertTemplate = Insert Template to Cursor Line
-editor-toolbar-settings-copyLink = Copy Note Link at Line ({ $line })
-editor-toolbar-settings-copyLinkAtSection = Copy Note Link at Section ({ $section })
+editor-toolbar-settings-insertTemplate = Insert template
+editor-toolbar-settings-copyLink = Copy link (L{ $line })
+editor-toolbar-settings-copyLinkAtSection = Copy link (Sec. { $section })
editor-toolbar-settings-openParent = Open Attachment
editor-toolbar-settings-export = Export Current Note...
editor-toolbar-settings-refreshSyncing = Sync Now
diff --git a/addon/locale/it-IT/addon.ftl b/addon/locale/it-IT/addon.ftl
index 8fb74dc..f8b9091 100644
--- a/addon/locale/it-IT/addon.ftl
+++ b/addon/locale/it-IT/addon.ftl
@@ -74,9 +74,9 @@ editor-toolbar-settings-openWorkspace = Apri nota di lavoro
editor-toolbar-settings-setWorkspace = Imposta come nota di lavoro
editor-toolbar-settings-previewInWorkspace = Anteprima nello spazio di lavoro
editor-toolbar-settings-showInLibrary = Show in Library
-editor-toolbar-settings-insertTemplate = Inserisci template nella posizione del cursore
-editor-toolbar-settings-copyLink = Copia link della nota alla riga ({ $line })
-editor-toolbar-settings-copyLinkAtSection = Copia link della nota alla sezione ({ $section })
+editor-toolbar-settings-insertTemplate = Inserisci template
+editor-toolbar-settings-copyLink = Copia link (L{ $line })
+editor-toolbar-settings-copyLinkAtSection = Copia link (Sec. { $section })
editor-toolbar-settings-openParent = Apri allegato
editor-toolbar-settings-export = Esporta nota corrente...
editor-toolbar-settings-refreshSyncing = Sincronizza ora
diff --git a/addon/locale/ru-RU/addon.ftl b/addon/locale/ru-RU/addon.ftl
index 695aab1..7f509c6 100644
--- a/addon/locale/ru-RU/addon.ftl
+++ b/addon/locale/ru-RU/addon.ftl
@@ -78,9 +78,9 @@ editor-toolbar-settings-openWorkspace=Открыть пространство з
editor-toolbar-settings-setWorkspace=Установить как Заметку раб. пространства
editor-toolbar-settings-previewInWorkspace=Предпросмотр в рабочем пространстве
editor-toolbar-settings-showInLibrary = Show in Library
-editor-toolbar-settings-insertTemplate=Вставить шаблон в строку курсора
-editor-toolbar-settings-copyLink = Копировать Ссылку на Заметку на строке ({ $line })
-editor-toolbar-settings-copyLinkAtSection = Копировать Ссылку на Заметку в секции ({ $section })
+editor-toolbar-settings-insertTemplate=Вставить шаблон
+editor-toolbar-settings-copyLink = Копировать Ссылку (L{ $line })
+editor-toolbar-settings-copyLinkAtSection = Копировать Ссылку (Sec. { $section })
editor-toolbar-settings-openParent=Открыть вложение
editor-toolbar-settings-export=Экспортировать текущую заметку...
editor-toolbar-settings-refreshSyncing=Синхронизировать сейчас
diff --git a/addon/locale/tr-TR/addon.ftl b/addon/locale/tr-TR/addon.ftl
index 9764fa2..0bd255b 100644
--- a/addon/locale/tr-TR/addon.ftl
+++ b/addon/locale/tr-TR/addon.ftl
@@ -78,9 +78,9 @@ editor-toolbar-settings-openWorkspace = Not Çalışma Alanı Açın
editor-toolbar-settings-setWorkspace = Çalışma Alanı Notu Olarak Ayarla
editor-toolbar-settings-previewInWorkspace = Çalışma Alanında Ön İzle Preview in Workspace
editor-toolbar-settings-showInLibrary = Show in Library
-editor-toolbar-settings-insertTemplate = İşaretçi Satırına Şablon Ekle
-editor-toolbar-settings-copyLink = Satıra Not Linki Kopyala ({ $line })
-editor-toolbar-settings-copyLinkAtSection = Bölüme Not Linki Kopyala ({ $section })
+editor-toolbar-settings-insertTemplate = Insert template
+editor-toolbar-settings-copyLink = Copy link (L{ $line })
+editor-toolbar-settings-copyLinkAtSection =Copy link (Sec. { $section })
editor-toolbar-settings-openParent = Eki Aç
editor-toolbar-settings-export = Bu Notu Dışa Aktar...
editor-toolbar-settings-refreshSyncing = Senkronize Et
diff --git a/addon/prefs.js b/addon/prefs.js
index 95b1609..34e1fdb 100644
--- a/addon/prefs.js
+++ b/addon/prefs.js
@@ -6,6 +6,8 @@ pref("__prefsPrefix__.syncAttachmentFolder", "attachments");
pref("__prefsPrefix__.autoAnnotation", false);
+pref("__prefsPrefix__.insertLinkPosition", "end");
+
pref("__prefsPrefix__.embedLink", true);
pref("__prefsPrefix__.standaloneLink", false);
pref("__prefsPrefix__.keepLink", true);
diff --git a/src/api.ts b/src/api.ts
index 5bbabaf..9feb17b 100644
--- a/src/api.ts
+++ b/src/api.ts
@@ -65,6 +65,7 @@ import {
updateRelatedNotes,
getRelatedNoteIds,
getNoteTreeFlattened,
+ getLinesInNote,
} from "./utils/note";
const workspace = {};
@@ -144,6 +145,7 @@ const editor = {
const note = {
insert: addLineToNote,
+ getLinesInNote,
updateRelatedNotes,
getRelatedNoteIds,
getNoteTreeFlattened,
diff --git a/src/elements/base.ts b/src/elements/base.ts
index 76e10b7..65627d8 100644
--- a/src/elements/base.ts
+++ b/src/elements/base.ts
@@ -11,14 +11,14 @@ export class PluginCEBase extends XULElementBase {
_wrapID(key: string) {
if (key.startsWith(config.addonRef)) {
- return key;
+ return key;
}
return `${config.addonRef}-${key}`;
}
_unwrapID(id: string) {
if (id.startsWith(config.addonRef)) {
- return id.slice(config.addonRef.length + 1);
+ return id.slice(config.addonRef.length + 1);
}
return id;
}
@@ -28,9 +28,9 @@ export class PluginCEBase extends XULElementBase {
}
_parseContentID(dom: DocumentFragment) {
- dom.querySelectorAll("*[id]").forEach(elem => {
- elem.id = this._wrapID(elem.id);
- })
+ dom.querySelectorAll("*[id]").forEach((elem) => {
+ elem.id = this._wrapID(elem.id);
+ });
return dom;
}
diff --git a/src/elements/notePicker.ts b/src/elements/notePicker.ts
new file mode 100644
index 0000000..bd092c6d
--- /dev/null
+++ b/src/elements/notePicker.ts
@@ -0,0 +1,304 @@
+import { config } from "../../package.json";
+import { VirtualizedTableHelper } from "zotero-plugin-toolkit/dist/helpers/virtualizedTable";
+import { PluginCEBase } from "./base";
+
+const _require = window.require;
+const CollectionTree = _require("chrome://zotero/content/collectionTree.js");
+const ItemTree = _require("chrome://zotero/content/itemTree.js");
+const { getCSSItemTypeIcon } = _require("components/icons");
+
+export class NotePicker extends PluginCEBase {
+ itemsView!: _ZoteroTypes.ItemTree;
+ collectionsView!: _ZoteroTypes.CollectionTree;
+ openedNotesView!: VirtualizedTableHelper;
+
+ openedNotes: Zotero.Item[] = [];
+
+ activeSelectionType: "library" | "tabs" | "none" = "none";
+
+ get content() {
+ return MozXULElement.parseXULToFragment(`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`);
+ }
+
+ set openedNoteIDs(ids: number[]) {
+ this.openedNotes = Zotero.Items.get(ids).filter((item) => item.isNote());
+ if (this.openedNotesView) {
+ this.openedNotesView.render();
+ return;
+ }
+ this.loadOpenedNotes();
+ }
+
+ async init() {
+ await this.loadLibraryNotes();
+ this.loadQuickSearch();
+ await this.loadOpenedNotes();
+
+ window.addEventListener("unload", () => {
+ this.destroy();
+ });
+ }
+
+ destroy(): void {
+ this.collectionsView.unregister();
+ if (this.itemsView) this.itemsView.unregister();
+ }
+
+ async loadLibraryNotes() {
+ this.itemsView = await ItemTree.init(
+ this.querySelector("#zotero-items-tree"),
+ {
+ onSelectionChange: () => {
+ this.onItemSelected();
+ },
+ id: "select-items-dialog",
+ dragAndDrop: false,
+ persistColumns: true,
+ columnPicker: true,
+ emptyMessage: Zotero.getString("pane.items.loading"),
+ },
+ );
+ this.itemsView.isSelectable = (index: number, selectAll = false) => {
+ const row = this.itemsView.getRow(index);
+ if (!row) {
+ return false;
+ }
+ // @ts-ignore
+ if (!row.ref.isNote()) return false;
+ if (this.itemsView.collectionTreeRow.isTrash()) {
+ // @ts-ignore
+ return row.ref.deleted;
+ } else {
+ // @ts-ignore
+ return this.itemsView._searchItemIDs.has(row.id);
+ }
+ };
+ this.itemsView.setItemsPaneMessage(Zotero.getString("pane.items.loading"));
+
+ // Wait otherwise the collection tree will not be initialized
+ await Zotero.Promise.delay(10);
+ this.collectionsView = await CollectionTree.init(
+ this.querySelector("#zotero-collections-tree"),
+ {
+ onSelectionChange: Zotero.Utilities.debounce(
+ () => this.onCollectionSelected(),
+ 100,
+ ),
+ },
+ );
+ this.collectionsView.hideSources = ["duplicates", "trash", "feeds"];
+
+ await this.collectionsView.makeVisible();
+ }
+
+ loadQuickSearch() {
+ // @ts-ignore
+ const searchBox = document.createXULElement("quick-search-textbox");
+ searchBox.id = "zotero-tb-search";
+ searchBox.setAttribute("timeout", "250");
+ searchBox.setAttribute("dir", "reverse");
+ searchBox.addEventListener("command", this.onSearch);
+ this.querySelector("#search-toolbar > .toolbar-end")?.appendChild(
+ searchBox,
+ );
+
+ Zotero.updateQuickSearchBox(document);
+ }
+
+ async loadOpenedNotes() {
+ const renderLock = Zotero.Promise.defer();
+ this.openedNotesView = new VirtualizedTableHelper(window)
+ .setContainerId("bn-select-opened-notes-tree")
+ .setProp({
+ id: `bn-select-opened-notes-table`,
+ columns: [
+ {
+ dataKey: "title",
+ label: "Opened Notes",
+ flex: 1,
+ },
+ ],
+ showHeader: true,
+ multiSelect: false,
+ staticColumns: true,
+ disableFontSizeScaling: true,
+ })
+ .setProp("getRowCount", () => this.openedNotes.length || 0)
+ .setProp("getRowData", (index) => {
+ const note = this.openedNotes[index];
+ return {
+ title: note.getNoteTitle(),
+ };
+ })
+ .setProp("onSelectionChange", (selection) => {
+ this.onOpenedNoteSelected();
+ })
+ // For find-as-you-type
+ .setProp(
+ "getRowString",
+ (index) => this.openedNotes[index].getNoteTitle() || "",
+ )
+ .setProp("renderItem", (index, selection, oldElem, columns) => {
+ let div;
+ if (oldElem) {
+ div = oldElem;
+ div.innerHTML = "";
+ } else {
+ div = document.createElement("div");
+ div.className = "row";
+ }
+
+ div.classList.toggle("selected", selection.isSelected(index));
+ div.classList.toggle("focused", selection.focused == index);
+ const rowData = this.openedNotes[index];
+
+ for (const column of columns) {
+ const span = document.createElement("span");
+ // @ts-ignore
+ span.className = `cell ${column?.className}`;
+ span.textContent = rowData.getNoteTitle();
+ const icon = getCSSItemTypeIcon("note");
+ icon.classList.add("cell-icon");
+ span.prepend(icon);
+ div.append(span);
+ }
+ return div;
+ })
+ .render(-1, () => {
+ renderLock.resolve();
+ });
+ await renderLock.promise;
+
+ if (this.openedNotes.length === 1) {
+ this.openedNotesView.treeInstance.selection.select(0);
+ }
+ }
+
+ onSearch() {
+ if (this.itemsView) {
+ const searchVal = (
+ this.querySelector("#zotero-tb-search-textbox") as HTMLInputElement
+ )?.value;
+ this.itemsView.setFilter("search", searchVal);
+ }
+ }
+
+ async onCollectionSelected() {
+ const collectionTreeRow = this.collectionsView.getRow(
+ this.collectionsView.selection.focused,
+ );
+ if (!this.collectionsView.selection.count) return;
+ // Collection not changed
+ if (
+ this.itemsView &&
+ this.itemsView.collectionTreeRow &&
+ this.itemsView.collectionTreeRow.id == collectionTreeRow.id
+ ) {
+ return;
+ }
+ // @ts-ignore
+ if (!collectionTreeRow._bnPatched) {
+ // @ts-ignore
+ collectionTreeRow._bnPatched = true;
+ const getItems = collectionTreeRow.getItems.bind(collectionTreeRow);
+ // @ts-ignore
+ collectionTreeRow.getItems = async function () {
+ const items = (await getItems()) as Zotero.Item[];
+ return items.filter((item) => item.isNote()) as unknown[];
+ };
+ }
+ collectionTreeRow.setSearch("");
+ Zotero.Prefs.set("lastViewedFolder", collectionTreeRow.id);
+
+ this.itemsView.setItemsPaneMessage(Zotero.getString("pane.items.loading"));
+
+ // Load library data if necessary
+ const library = Zotero.Libraries.get(collectionTreeRow.ref.libraryID);
+ if (library) {
+ if (!library.getDataLoaded("item")) {
+ Zotero.debug(
+ "Waiting for items to load for library " + library.libraryID,
+ );
+ await library.waitForDataLoad("item");
+ }
+ }
+
+ await this.itemsView.changeCollectionTreeRow(collectionTreeRow);
+
+ this.itemsView.clearItemsPaneMessage();
+
+ this.collectionsView.runListeners("select");
+ }
+
+ onItemSelected() {
+ this.activeSelectionType = "library";
+ this.dispatchSelectionChange();
+ }
+
+ onOpenedNoteSelected() {
+ this.activeSelectionType = "tabs";
+ this.dispatchSelectionChange();
+ }
+
+ dispatchSelectionChange() {
+ this.dispatchEvent(
+ new CustomEvent("selectionChange", {
+ detail: {
+ selectedNote: this.getSelectedNotes()[0],
+ },
+ }),
+ );
+ }
+
+ getSelectedNotes(): 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],
+ );
+ }
+}
diff --git a/src/elements/outlinePane.ts b/src/elements/outlinePane.ts
index 81d816e..43f6785 100644
--- a/src/elements/outlinePane.ts
+++ b/src/elements/outlinePane.ts
@@ -132,7 +132,9 @@ export class OutlinePane extends PluginCEBase {
init(): void {
MozXULElement.insertFTLIfNeeded(`${config.addonRef}-outline.ftl`);
- this._outlineContainer = this._queryID("outline") as unknown as HTMLIFrameElement;
+ this._outlineContainer = this._queryID(
+ "outline",
+ ) as unknown as HTMLIFrameElement;
this._queryID("left-toolbar")?.addEventListener(
"command",
@@ -163,6 +165,7 @@ export class OutlinePane extends PluginCEBase {
extraData: { [key: string]: any },
) {
if (!this.item) return;
+ if (extraData.skipBN) return;
if (event === "modify" && type === "item") {
if ((ids as number[]).includes(this.item.id)) {
this.updateOutline();
@@ -188,10 +191,15 @@ export class OutlinePane extends PluginCEBase {
this.messageHandler,
);
- this._outlineContainer.setAttribute("src", OutlinePane.outlineSources[this.outlineType]);
+ this._outlineContainer.setAttribute(
+ "src",
+ OutlinePane.outlineSources[this.outlineType],
+ );
await waitUtilAsync(
- () => this._outlineContainer.contentWindow?.document.readyState === "complete",
+ () =>
+ this._outlineContainer.contentWindow?.document.readyState ===
+ "complete",
);
this._outlineContainer.contentWindow?.addEventListener(
"message",
diff --git a/src/extras/workspace.ts b/src/extras/customElements.ts
similarity index 86%
rename from src/extras/workspace.ts
rename to src/extras/customElements.ts
index d629838..69e9fa4 100644
--- a/src/extras/workspace.ts
+++ b/src/extras/customElements.ts
@@ -1,5 +1,6 @@
import { ContextPane } from "../elements/context";
import { NoteDetails } from "../elements/detailsPane";
+import { NotePicker } from "../elements/notePicker";
import { OutlinePane } from "../elements/outlinePane";
import { Workspace } from "../elements/workspace";
@@ -8,6 +9,7 @@ const elements = {
"bn-outline": OutlinePane,
"bn-details": NoteDetails as unknown as CustomElementConstructor,
"bn-workspace": Workspace,
+ "bn-note-picker": NotePicker,
};
for (const [key, constructor] of Object.entries(elements)) {
diff --git a/src/extras/linkNote.ts b/src/extras/linkNote.ts
new file mode 100644
index 0000000..93c1615
--- /dev/null
+++ b/src/extras/linkNote.ts
@@ -0,0 +1,325 @@
+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);
+
+ // @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();
+};
+
+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/hooks.ts b/src/hooks.ts
index 7c52a59..493bccc 100644
--- a/src/hooks.ts
+++ b/src/hooks.ts
@@ -35,6 +35,7 @@ import { waitUtilAsync } from "./utils/wait";
import { initSyncList } from "./modules/sync/api";
import { getPref } from "./utils/prefs";
import { patchViewItems } from "./modules/viewItems";
+import { onUpdateRelated } from "./modules/relatedNotes";
async function onStartup() {
await Promise.all([
@@ -67,7 +68,7 @@ async function onMainWindowLoad(win: Window): Promise {
await waitUtilAsync(() => document.readyState === "complete");
Services.scriptloader.loadSubScript(
- `chrome://${config.addonRef}/content/scripts/workspace.js`,
+ `chrome://${config.addonRef}/content/scripts/customElements.js`,
win,
);
// Create ztoolkit for every window
@@ -121,6 +122,7 @@ function onNotify(
skipActive: true,
reason: "item-modify",
});
+ addon.hooks.onUpdateRelated(modifiedNotes, { skipActive: true });
}
} else {
return;
@@ -164,7 +166,7 @@ function onOpenNote(
// addon.hooks.onSetWorkspaceNote(noteId, "preview", options);
break;
case "workspace":
- // addon.hooks.onSetWorkspaceNote(noteId, "main", options);
+ addon.hooks.onOpenWorkspace(noteItem, "tab");
break;
case "standalone":
ZoteroPane.openNoteWindow(noteId);
@@ -247,6 +249,7 @@ export default {
onOpenWorkspace,
onToggleWorkspacePane,
onSyncing,
+ onUpdateRelated,
onShowTemplatePicker,
onUpdateTemplatePicker,
onImportTemplateFromClipboard,
diff --git a/src/modules/convert/api.ts b/src/modules/convert/api.ts
index 013e26e..39b8792 100644
--- a/src/modules/convert/api.ts
+++ b/src/modules/convert/api.ts
@@ -175,7 +175,7 @@ async function note2noteDiff(noteItem: Zotero.Item) {
function note2link(
noteItem: Zotero.Item,
- options: Parameters[1],
+ options: Parameters[1] = {},
) {
return getNoteLink(noteItem, options);
}
@@ -236,7 +236,7 @@ function annotations2html(
async function note2html(
noteItems: Zotero.Item | Zotero.Item[],
- options: { targetNoteItem?: Zotero.Item; html?: string },
+ options: { targetNoteItem?: Zotero.Item; html?: string } = {},
) {
if (!Array.isArray(noteItems)) {
noteItems = [noteItems];
diff --git a/src/modules/editor/toolbar.ts b/src/modules/editor/toolbar.ts
index 89c0bec..525ab5e 100644
--- a/src/modules/editor/toolbar.ts
+++ b/src/modules/editor/toolbar.ts
@@ -2,431 +2,212 @@ import { config } from "../../../package.json";
import { ICONS } from "../../utils/config";
import { getLineAtCursor, getSectionAtCursor } from "../../utils/editor";
import { showHint } from "../../utils/hint";
-import { getNoteLink, getNoteLinkParams } from "../../utils/link";
+import { getNoteLink } from "../../utils/link";
import { getString } from "../../utils/locale";
-import { addLineToNote, getNoteTreeFlattened } from "../../utils/note";
-import { getPref } from "../../utils/prefs";
+import { openLinkNoteDialog } from "../../utils/linkNote";
import { slice } from "../../utils/str";
export async function initEditorToolbar(editor: Zotero.EditorInstance) {
const noteItem = editor._item;
- const toolbar = await registerEditorToolbar(editor, makeId("toolbar"));
- // Settings
- const settingsButton = await registerEditorToolbarDropdown(
+ const _document = editor._iframeWindow.document;
+ registerEditorToolbarElement(
editor,
- toolbar,
- makeId("settings"),
- ICONS.settings,
- getString("editor.toolbar.settings.title"),
- "end",
- (e) => {},
+ _document.querySelector(".toolbar") as HTMLDivElement,
+ "start",
+ ztoolkit.UI.createElement(_document, "button", {
+ classList: ["toolbar-button"],
+ properties: {
+ innerHTML: ICONS.addon,
+ title: "Link current note to another note",
+ },
+ listeners: [
+ {
+ type: "click",
+ listener: (e) => {
+ openLinkNoteDialog(noteItem);
+ },
+ },
+ ],
+ }) as HTMLButtonElement,
);
- settingsButton.addEventListener("click", async (ev) => {
- ev.stopPropagation();
- function removePopup() {
- const popup = editor._iframeWindow.document.querySelector(
- `#${makeId("settings-popup")}`,
- );
- if (popup) {
- popup.remove();
- settingsButton
- .querySelector(".toolbar-button")
- ?.classList.remove("active");
- editor._iframeWindow.document.removeEventListener("click", removePopup);
- return true;
+ const settingsButton = editor._iframeWindow.document.querySelector(
+ ".toolbar .end .dropdown .toolbar-button",
+ ) as HTMLDivElement;
+
+ const MutationObserver = // @ts-ignore
+ editor._iframeWindow.MutationObserver as typeof window.MutationObserver;
+ const observer = new MutationObserver((mutations) => {
+ mutations.forEach(async (mutation) => {
+ if (
+ mutation.type === "attributes" &&
+ mutation.attributeName === "class" &&
+ mutation.target === settingsButton
+ ) {
+ if (settingsButton.classList.contains("active")) {
+ const dropdown = settingsButton.parentElement!;
+ const popup = dropdown.querySelector(".popup") as HTMLDivElement;
+ ztoolkit.log(popup);
+ registerEditorToolbarPopup(editor, popup, await getMenuData(editor));
+ }
}
- return false;
- }
-
- if (removePopup()) {
- return;
- }
-
- const currentLine = getLineAtCursor(editor);
- const currentSection = getSectionAtCursor(editor);
- const settingsMenuData: PopupData[] = [
- {
- id: makeId("settings-openWorkspace"),
- text: getString("editor.toolbar.settings.openWorkspace"),
- callback: (e) => {
- addon.hooks.onOpenWorkspace(noteItem, "tab");
- },
- },
- {
- id: makeId("settings-showInLibrary"),
- text: getString("editor.toolbar.settings.showInLibrary"),
- callback: (e) => {
- ZoteroPane.selectItems([e.editor._item.id]);
- },
- },
- ];
-
- if (currentLine >= 0) {
- settingsMenuData.push(
- ...([
- {
- type: "splitter",
- },
- {
- id: makeId("settings-export"),
- text: getString("editor.toolbar.settings.export"),
- callback: (e) => {
- if (addon.api.sync.isSyncNote(noteItem.id)) {
- addon.hooks.onShowSyncInfo(noteItem.id);
- } else {
- addon.hooks.onShowExportNoteOptions([noteItem.id]);
- }
- },
- },
- {
- type: "splitter",
- },
- {
- id: makeId("settings-insertTemplate"),
- text: getString("editor.toolbar.settings.insertTemplate"),
- callback: (e) => {
- addon.hooks.onShowTemplatePicker("insert", {
- noteId: e.editor._item.id,
- lineIndex: currentLine,
- });
- },
- },
- {
- type: "splitter",
- },
- {
- id: makeId("settings-copyLink"),
- text: getString("editor.toolbar.settings.copyLink", {
- args: {
- line: currentLine,
- },
- }),
- callback: (e) => {
- const link =
- getNoteLink(e.editor._item, {
- lineIndex: currentLine,
- }) || "";
- new ztoolkit.Clipboard()
- .addText(link, "text/unicode")
- .addText(
- `${
- e.editor._item.getNoteTitle().trim() || link
- }`,
- "text/html",
- )
- .copy();
- showHint(`Link ${link} copied`);
- },
- },
- {
- id: makeId("settings-copyLinkAtSection"),
- text: getString("editor.toolbar.settings.copyLinkAtSection", {
- args: {
- section: currentSection,
- },
- }),
- callback: (e) => {
- const link =
- getNoteLink(e.editor._item, {
- sectionName: currentSection,
- }) || "";
- new ztoolkit.Clipboard()
- .addText(link, "text/unicode")
- .addText(
- `${
- e.editor._item.getNoteTitle().trim() || link
- }`,
- "text/html",
- )
- .copy();
- showHint(`Link ${link} copied`);
- },
- },
- {
- id: makeId("settings-updateRelatedNotes"),
- text: getString("editor-toolbar-settings-updateRelatedNotes"),
- callback: (e) => {
- addon.api.note.updateRelatedNotes(e.editor._item.id);
- },
- },
- ]),
- );
- }
-
- const parentAttachment = await noteItem.parentItem?.getBestAttachment();
- if (parentAttachment) {
- settingsMenuData.push(
- ...([
- {
- type: "splitter",
- },
- {
- id: makeId("settings-openParent"),
- text: getString("editor.toolbar.settings.openParent"),
- callback: (e) => {
- ZoteroPane.viewAttachment([parentAttachment.id]);
- Zotero.Notifier.trigger("open", "file", parentAttachment.id);
- },
- },
- ]),
- );
- }
-
- if (addon.api.sync.isSyncNote(noteItem.id)) {
- settingsMenuData.splice(5, 0, {
- id: makeId("settings-refreshSyncing"),
- text: getString("editor.toolbar.settings.refreshSyncing"),
- callback: (e) => {
- addon.hooks.onSyncing(undefined, {
- quiet: false,
- skipActive: false,
- reason: "manual-editor",
- });
- },
- });
- }
-
- registerEditorToolbarPopup(
- editor,
- settingsButton,
- `${config.addonRef}-settings-popup`,
- "right",
- settingsMenuData,
- ).then((popup) => {
- settingsButton.querySelector(".toolbar-button")?.classList.add("active");
- editor._iframeWindow.document.addEventListener("click", removePopup);
});
});
-
- // Center button
-
- const onTriggerMenu = (ev: MouseEvent) => {
- editor._iframeWindow.focus();
- const linkMenu: PopupData[] = getLinkMenuData(editor);
- editor._iframeWindow.document
- .querySelector(`#${makeId("link")}`)!
- .querySelector(".toolbar-button")!.innerHTML = ICONS.linkAfter;
-
- const popup = registerEditorToolbarPopup(
- editor,
- linkButton,
- `${config.addonRef}-link-popup`,
- "middle",
- linkMenu,
- );
- };
-
- const onExitMenu = (ev: MouseEvent) => {
- editor._iframeWindow.document
- .querySelector(`#${makeId("link-popup")}`)
- ?.remove();
- editor._iframeWindow.document
- .querySelector(`#${makeId("link")}`)!
- .querySelector(".toolbar-button")!.innerHTML = ICONS.addon;
- };
-
- const onClickMenu = async (ev: MouseEvent) => {
- // TODO: fix link
- return;
- // const mainNote = Zotero.Items.get(addon.data.workspace.mainId) || null;
- // if (!mainNote?.isNote()) {
- // return;
- // }
- // const lineIndex = parseInt(
- // (ev.target as HTMLDivElement).id.split("-").pop() || "-1",
- // );
- // const forwardLink = getNoteLink(noteItem);
- // const backLink = getNoteLink(mainNote, { ignore: true, lineIndex });
- // addLineToNote(
- // mainNote,
- // await addon.api.template.runTemplate(
- // "[QuickInsertV2]",
- // "link, linkText, subNoteItem, noteItem",
- // [
- // forwardLink,
- // noteItem.getNoteTitle().trim() || forwardLink,
- // noteItem,
- // mainNote,
- // ],
- // ),
- // lineIndex,
- // );
- // addLineToNote(
- // noteItem,
- // await addon.api.template.runTemplate(
- // "[QuickBackLinkV2]",
- // "link, linkText, subNoteItem, noteItem",
- // [
- // backLink,
- // mainNote.getNoteTitle().trim() || "Workspace Note",
- // noteItem,
- // mainNote,
- // "",
- // ],
- // ),
- // );
- // onExitMenu(ev);
- // ev.stopPropagation();
- };
-
- const linkButton = await registerEditorToolbarDropdown(
- editor,
- toolbar,
- makeId("link"),
- ICONS.addon,
- getString("editor.toolbar.link.title"),
- "middle",
- onClickMenu,
- );
-
- linkButton.addEventListener("mouseenter", onTriggerMenu);
- linkButton.addEventListener("mouseleave", onExitMenu);
- linkButton.addEventListener("mouseleave", onExitMenu);
- linkButton.addEventListener("click", (ev) => {
- if ((ev.target as HTMLElement).classList.contains("option")) {
- onClickMenu(ev);
- }
+ observer.observe(settingsButton, {
+ attributes: true,
+ attributeFilter: ["class"],
});
- editor._iframeWindow.document.addEventListener("click", onExitMenu);
-
- // Export
- // const exportButton = await registerEditorToolbarDropdown(
- // editor,
- // toolbar,
- // makeId("export"),
- // ICONS.export,
- // getString("editor.toolbar.export.title"),
- // "end",
- // (e) => {
- // if (addon.api.sync.isSyncNote(noteItem.id)) {
- // addon.hooks.onShowSyncInfo(noteItem.id);
- // } else {
- // addon.hooks.onShowExportNoteOptions([noteItem.id]);
- // }
- // }
- // );
}
-function getLinkMenuData(editor: Zotero.EditorInstance): PopupData[] {
- return [];
- // const workspaceNote = Zotero.Items.get(addon.data.workspace.mainId) || null;
- // const currentNote = editor._item;
- // if (!workspaceNote?.isNote()) {
- // return [
- // {
- // id: makeId("link-popup-nodata"),
- // text: getString("editor.toolbar.link.popup.nodata"),
- // },
- // ];
- // }
- // const nodes = getNoteTreeFlattened(workspaceNote, {
- // keepLink: true,
- // });
- // const menuData: PopupData[] = [];
- // for (const node of nodes) {
- // if (node.model.level === 7) {
- // const lastMenu =
- // menuData.length > 0 ? menuData[menuData.length - 1] : null;
- // const linkNote = getNoteLinkParams(node.model.link).noteItem;
- // if (linkNote && linkNote.id === currentNote.id && lastMenu) {
- // lastMenu.suffix = "🔗";
- // }
- // continue;
- // }
- // menuData.push({
- // id: makeId(
- // `link-popup-${
- // getPref("editor.link.insertPosition")
- // ? node.model.lineIndex - 1
- // : node.model.endIndex
- // }`,
- // ),
- // text: node.model.name,
- // prefix: "·".repeat(node.model.level - 1),
- // });
- // }
- // return menuData;
-}
+async function getMenuData(editor: Zotero.EditorInstance) {
+ const noteItem = editor._item;
-async function registerEditorToolbar(
- editor: Zotero.EditorInstance,
- id: string,
-) {
- await editor._initPromise;
- const _document = editor._iframeWindow.document;
- const toolbar = ztoolkit.UI.createElement(_document, "div", {
- attributes: {
- id,
+ const currentLine = getLineAtCursor(editor);
+ const currentSection = slice(getSectionAtCursor(editor) || "", 10);
+ const settingsMenuData: PopupData[] = [
+ {
+ id: makeId("settings-openWorkspace"),
+ text: getString("editor.toolbar.settings.openWorkspace"),
+ callback: (e) => {
+ addon.hooks.onOpenWorkspace(noteItem, "tab");
+ },
},
- classList: ["toolbar"],
- children: [
- {
- tag: "div",
- classList: ["start"],
+ {
+ id: makeId("settings-showInLibrary"),
+ text: getString("editor.toolbar.settings.showInLibrary"),
+ callback: (e) => {
+ ZoteroPane.selectItems([e.editor._item.id]);
},
- {
- tag: "div",
- classList: ["middle"],
- },
- {
- tag: "div",
- classList: ["end"],
- },
- ],
- ignoreIfExists: true,
- }) as HTMLDivElement;
- _document.querySelector(".editor")?.childNodes[0].before(toolbar);
- return toolbar;
-}
-
-async function registerEditorToolbarDropdown(
- editor: Zotero.EditorInstance,
- toolbar: HTMLDivElement,
- id: string,
- icon: string,
- title: string,
- position: "start" | "middle" | "end",
- callback: (e: MouseEvent & { editor: Zotero.EditorInstance }) => any,
-) {
- await editor._initPromise;
- const _document = editor._iframeWindow.document;
- const dropdown = ztoolkit.UI.createElement(_document, "div", {
- attributes: {
- id,
- title,
},
- classList: ["dropdown", "more-dropdown"],
- children: [
- {
- tag: "button",
- attributes: {
- title,
+ ];
+
+ if (currentLine >= 0) {
+ settingsMenuData.push(
+ ...([
+ {
+ type: "splitter",
},
- properties: {
- innerHTML: icon,
- },
- classList: ["toolbar-button"],
- listeners: [
- {
- type: "click",
- listener: (e) => {
- Object.assign(e, { editor });
- if (callback) {
- callback(
- e as any as MouseEvent & { editor: Zotero.EditorInstance },
- );
- }
- },
+ {
+ id: makeId("settings-export"),
+ text: getString("editor.toolbar.settings.export"),
+ callback: (e) => {
+ if (addon.api.sync.isSyncNote(noteItem.id)) {
+ addon.hooks.onShowSyncInfo(noteItem.id);
+ } else {
+ addon.hooks.onShowExportNoteOptions([noteItem.id]);
+ }
},
- ],
+ },
+ {
+ type: "splitter",
+ },
+ {
+ id: makeId("settings-insertTemplate"),
+ text: getString("editor.toolbar.settings.insertTemplate"),
+ callback: (e) => {
+ addon.hooks.onShowTemplatePicker("insert", {
+ noteId: e.editor._item.id,
+ lineIndex: currentLine,
+ });
+ },
+ },
+ {
+ type: "splitter",
+ },
+ {
+ id: makeId("settings-copyLink"),
+ text: getString("editor.toolbar.settings.copyLink", {
+ args: {
+ line: currentLine,
+ },
+ }),
+ callback: (e) => {
+ const link =
+ getNoteLink(e.editor._item, {
+ lineIndex: currentLine,
+ }) || "";
+ new ztoolkit.Clipboard()
+ .addText(link, "text/unicode")
+ .addText(
+ `${
+ e.editor._item.getNoteTitle().trim() || link
+ }`,
+ "text/html",
+ )
+ .copy();
+ showHint(`Link ${link} copied`);
+ },
+ },
+ {
+ id: makeId("settings-copyLinkAtSection"),
+ text: getString("editor.toolbar.settings.copyLinkAtSection", {
+ args: {
+ section: currentSection,
+ },
+ }),
+ callback: (e) => {
+ const link =
+ getNoteLink(e.editor._item, {
+ sectionName: currentSection,
+ }) || "";
+ new ztoolkit.Clipboard()
+ .addText(link, "text/unicode")
+ .addText(
+ `${
+ e.editor._item.getNoteTitle().trim() || link
+ }`,
+ "text/html",
+ )
+ .copy();
+ showHint(`Link ${link} copied`);
+ },
+ },
+ {
+ id: makeId("settings-updateRelatedNotes"),
+ text: getString("editor-toolbar-settings-updateRelatedNotes"),
+ callback: (e) => {
+ addon.api.note.updateRelatedNotes(e.editor._item.id);
+ },
+ },
+ ]),
+ );
+ }
+
+ const parentAttachment = await noteItem.parentItem?.getBestAttachment();
+ if (parentAttachment) {
+ settingsMenuData.push(
+ ...([
+ {
+ type: "splitter",
+ },
+ {
+ id: makeId("settings-openParent"),
+ text: getString("editor.toolbar.settings.openParent"),
+ callback: (e) => {
+ ZoteroPane.viewAttachment([parentAttachment.id]);
+ Zotero.Notifier.trigger("open", "file", parentAttachment.id);
+ },
+ },
+ ]),
+ );
+ }
+
+ if (addon.api.sync.isSyncNote(noteItem.id)) {
+ settingsMenuData.splice(5, 0, {
+ id: makeId("settings-refreshSyncing"),
+ text: getString("editor.toolbar.settings.refreshSyncing"),
+ callback: (e) => {
+ addon.hooks.onSyncing(undefined, {
+ quiet: false,
+ skipActive: false,
+ reason: "manual-editor",
+ });
},
- ],
- skipIfExists: true,
- });
- toolbar.querySelector(`.${position}`)?.append(dropdown);
- return dropdown;
+ });
+ }
+
+ return settingsMenuData;
}
declare interface PopupData {
@@ -440,21 +221,18 @@ declare interface PopupData {
async function registerEditorToolbarPopup(
editor: Zotero.EditorInstance,
- dropdown: HTMLDivElement,
- id: string,
- align: "middle" | "left" | "right",
+ popup: HTMLDivElement,
popupLines: PopupData[],
) {
await editor._initPromise;
- const popup = ztoolkit.UI.appendElement(
+ ztoolkit.UI.appendElement(
{
- tag: "div",
- classList: ["popup"],
- id,
+ tag: "fragment",
children: popupLines.map((props) => {
return props.type === "splitter"
? {
- tag: "hr",
+ tag: "div",
+ classList: ["separator"],
properties: {
id: props.id,
},
@@ -485,20 +263,9 @@ async function registerEditorToolbarPopup(
],
};
}),
- removeIfExists: true,
},
- dropdown,
+ popup,
) as HTMLDivElement;
- let style: string = "";
- if (align === "middle") {
- style = `right: -${popup.offsetWidth / 2 - 15}px;`;
- } else if (align === "left") {
- style = "left: 0; right: auto;";
- } else if (align === "right") {
- style = "right: 0;";
- }
- popup.setAttribute("style", style);
- return popup;
}
async function registerEditorToolbarElement(
diff --git a/src/modules/export/markdown.ts b/src/modules/export/markdown.ts
index 3b33186..7f5a1da 100644
--- a/src/modules/export/markdown.ts
+++ b/src/modules/export/markdown.ts
@@ -8,7 +8,7 @@ export async function saveMD(
options: {
keepNoteLink?: boolean;
withYAMLHeader?: boolean;
- },
+ } = {},
) {
const noteItem = Zotero.Items.get(noteId);
const dir = jointPath(...PathUtils.split(formatPath(filename)).slice(0, -1));
diff --git a/src/modules/export/pdf.ts b/src/modules/export/pdf.ts
index 23962c4..4f49e16 100644
--- a/src/modules/export/pdf.ts
+++ b/src/modules/export/pdf.ts
@@ -23,7 +23,6 @@ export async function savePDF(noteId: number) {
}
function disablePrintFooterHeader() {
- // @ts-ignore
Zotero.Prefs.resetBranch([], "print");
Zotero.Prefs.set("print.print_footercenter", "", true);
Zotero.Prefs.set("print.print_footerleft", "", true);
diff --git a/src/modules/relatedNotes.ts b/src/modules/relatedNotes.ts
new file mode 100644
index 0000000..8777fa2
--- /dev/null
+++ b/src/modules/relatedNotes.ts
@@ -0,0 +1,31 @@
+import { getPref } from "../utils/prefs";
+
+export { onUpdateRelated };
+
+function onUpdateRelated(
+ items: Zotero.Item[] = [],
+ { skipActive } = {
+ skipActive: true,
+ },
+) {
+ if (!getPref("workspace.autoUpdateRelatedNotes")) {
+ return;
+ }
+ if (skipActive) {
+ // Skip active note editors' targets
+ const activeNoteIds = Zotero.Notes._editorInstances
+ .filter(
+ (editor) =>
+ !Components.utils.isDeadWrapper(editor._iframeWindow) &&
+ editor._iframeWindow.document.hasFocus(),
+ )
+ .map((editor) => editor._item.id);
+ const filteredItems = items.filter(
+ (item) => !activeNoteIds.includes(item.id),
+ );
+ items = filteredItems;
+ }
+ for (const item of items) {
+ addon.api.note.updateRelatedNotes(item.id);
+ }
+}
diff --git a/src/modules/sync/managerWindow.ts b/src/modules/sync/managerWindow.ts
index 493b783..4bfa6c2 100644
--- a/src/modules/sync/managerWindow.ts
+++ b/src/modules/sync/managerWindow.ts
@@ -99,7 +99,6 @@ export async function showSyncManager() {
"getRowString",
(index) => addon.data.prefs?.rows[index].title || "",
)
- // @ts-ignore TODO: Fix type in zotero-plugin-toolkit
.setProp("onColumnSort", (columnIndex, ascending) => {
addon.data.sync.manager.columnIndex = columnIndex;
addon.data.sync.manager.columnAscending = ascending > 0;
diff --git a/src/modules/template/api.ts b/src/modules/template/api.ts
index 98fdd40..4b48f30 100644
--- a/src/modules/template/api.ts
+++ b/src/modules/template/api.ts
@@ -109,7 +109,7 @@ async function runTextTemplate(
options: {
targetNoteId?: number;
dryRun?: boolean;
- },
+ } = {},
) {
const { targetNoteId, dryRun } = options;
const targetNoteItem = Zotero.Items.get(targetNoteId || -1);
@@ -130,7 +130,7 @@ async function runItemTemplate(
itemIds?: number[];
targetNoteId?: number;
dryRun?: boolean;
- },
+ } = {},
): Promise {
/**
* args:
diff --git a/src/utils/config.ts b/src/utils/config.ts
index 9d230b3..5bd67ff 100644
--- a/src/utils/config.ts
+++ b/src/utils/config.ts
@@ -1,6 +1,6 @@
export const ICONS = {
- settings: ``,
- addon: ``,
+ settings: ``,
+ addon: ``,
linkAfter: `
`,
- previewImage: ``,
- resizeImage: ``,
- imageViewerPin: ``,
- imageViewerPined: ``,
+ previewImage: ``,
+ resizeImage: ``,
+ imageViewerPin: ``,
+ imageViewerPined: ``,
switchOutline: ``,
workspace_notes_collapsed: ``,
workspace_notes_open: ``,
- readerQuickNote: ``,
- embedLinkContent: ``,
- updateLinkText: ``,
- openInNewWindow: ``,
+ readerQuickNote: ``,
+ embedLinkContent: ``,
+ updateLinkText: ``,
+ openInNewWindow: ``,
};
export const PROGRESS_TITLE = "Better Notes";
diff --git a/src/utils/linkNote.ts b/src/utils/linkNote.ts
new file mode 100644
index 0000000..0a64da3
--- /dev/null
+++ b/src/utils/linkNote.ts
@@ -0,0 +1,42 @@
+import { config } from "../../package.json";
+
+export { openLinkNoteDialog };
+
+async function openLinkNoteDialog(currentNote: Zotero.Item) {
+ const io = {
+ openedNoteIDs: Zotero_Tabs._tabs
+ .map((tab) => tab.data?.itemID)
+ .filter((id) => id),
+ currentNoteID: currentNote.id,
+ deferred: Zotero.Promise.defer(),
+ } as any;
+ window.openDialog(
+ `chrome://${config.addonRef}/content/linkNote.xhtml`,
+ "_blank",
+ "chrome,modal,centerscreen,resizable=no",
+ io,
+ );
+ await io.deferred.promise;
+
+ const targetNote = Zotero.Items.get(io.targetNoteID);
+ const content = io.content;
+ const lineIndex = io.lineIndex;
+
+ if (!targetNote || !content) return;
+
+ await addon.api.note.insert(targetNote, content, lineIndex);
+
+ await Zotero.DB.executeTransaction(async () => {
+ const saveParams = {
+ skipDateModifiedUpdate: true,
+ skipSelect: true,
+ notifierData: {
+ skipBN: true,
+ },
+ };
+ targetNote.addRelatedItem(currentNote);
+ currentNote.addRelatedItem(targetNote);
+ targetNote.save(saveParams);
+ currentNote.save(saveParams);
+ });
+}
diff --git a/src/utils/note.ts b/src/utils/note.ts
index b15a549..ac85f97 100644
--- a/src/utils/note.ts
+++ b/src/utils/note.ts
@@ -4,7 +4,7 @@ import { getEditorInstance, getPositionAtLine, insert } from "./editor";
import { formatPath, getItemDataURL } from "./str";
import { showHint } from "./hint";
import { config } from "../../package.json";
-import { getNoteLinkParams } from "./link";
+import { getNoteLink, getNoteLinkParams } from "./link";
export {
renderNoteHTML,
@@ -111,11 +111,32 @@ function parseHTMLLines(html: string): string[] {
return parsedLines;
}
-function getLinesInNote(note: Zotero.Item): string[] {
+function getLinesInNote(note: Zotero.Item): string[];
+
+async function getLinesInNote(
+ note: Zotero.Item,
+ options: {
+ convertToHTML?: true;
+ },
+): Promise;
+
+function getLinesInNote(
+ note: Zotero.Item,
+ options?: {
+ convertToHTML?: boolean;
+ },
+): string[] | Promise {
if (!note) {
return [];
}
const noteText: string = note.getNote();
+ if (options?.convertToHTML) {
+ return new Promise((resolve) => {
+ addon.api.convert.note2html(note).then((html) => {
+ resolve(parseHTMLLines(html));
+ });
+ });
+ }
return parseHTMLLines(noteText);
}
@@ -162,7 +183,7 @@ async function addLineToNote(
const editor = getEditorInstance(note.id);
if (editor && !forceMetadata) {
// The note is opened. Add line via note editor
- const pos = getPositionAtLine(editor, lineIndex, "end");
+ const pos = getPositionAtLine(editor, lineIndex, "start");
ztoolkit.log("Add note line via note editor", pos);
insert(editor, html, pos);
// The selection is automatically moved to the next line
@@ -524,24 +545,8 @@ async function updateRelatedNotes(noteID: number) {
ztoolkit.log(`updateRelatedNotes: ${noteID} is not a note.`);
return;
}
- const relatedNoteIDs = await getRelatedNoteIds(noteID);
- const relatedNotes = Zotero.Items.get(relatedNoteIDs);
- const currentRelatedNotes = {} as Record;
+ const { detectedIDSet, currentIDSet } = await getRelatedNoteIds(noteID);
- // Get current related items
- for (const relItemKey of noteItem.relatedItems) {
- try {
- const relItem = (await Zotero.Items.getByLibraryAndKeyAsync(
- noteItem.libraryID,
- relItemKey,
- )) as Zotero.Item;
- if (relItem.isNote()) {
- currentRelatedNotes[relItem.id] = relItem;
- }
- } catch (e) {
- ztoolkit.log(e);
- }
- }
await Zotero.DB.executeTransaction(async () => {
const saveParams = {
skipDateModifiedUpdate: true,
@@ -550,18 +555,18 @@ async function updateRelatedNotes(noteID: number) {
skipBN: true,
},
};
- for (const toAddNote of relatedNotes) {
- if (toAddNote.id in currentRelatedNotes) {
+ for (const toAddNote of Zotero.Items.get(Array.from(detectedIDSet))) {
+ if (currentIDSet.has(toAddNote.id)) {
// Remove existing notes from current dict for later process
- delete currentRelatedNotes[toAddNote.id];
+ currentIDSet.delete(toAddNote.id);
continue;
}
toAddNote.addRelatedItem(noteItem);
noteItem.addRelatedItem(toAddNote);
toAddNote.save(saveParams);
- delete currentRelatedNotes[toAddNote.id];
+ currentIDSet.delete(toAddNote.id);
}
- for (const toRemoveNote of Object.values(currentRelatedNotes)) {
+ for (const toRemoveNote of Zotero.Items.get(Array.from(currentIDSet))) {
// Remove related notes that are not in the new list
toRemoveNote.removeRelatedItem(noteItem);
noteItem.removeRelatedItem(toRemoveNote);
@@ -571,21 +576,49 @@ async function updateRelatedNotes(noteID: number) {
});
}
-async function getRelatedNoteIds(noteId: number): Promise {
- let allNoteIds: number[] = [noteId];
+async function getRelatedNoteIds(noteId: number) {
+ let detectedIDs: number[] = [];
const note = Zotero.Items.get(noteId);
const linkMatches = note.getNote().match(/zotero:\/\/note\/\w+\/\w+\//g);
- if (!linkMatches) {
- return allNoteIds;
- }
- const subNoteIds = (
- await Promise.all(
- linkMatches.map(async (link) => getNoteLinkParams(link).noteItem),
+ const currentIDs: number[] = [];
+
+ if (linkMatches) {
+ const subNoteIds = (
+ await Promise.all(
+ linkMatches.map(async (link) => getNoteLinkParams(link).noteItem),
+ )
)
- )
- .filter((item) => item && item.isNote())
- .map((item) => (item as Zotero.Item).id);
- allNoteIds = allNoteIds.concat(subNoteIds);
- allNoteIds = new Array(...new Set(allNoteIds));
- return allNoteIds;
+ .filter((item) => item && item.isNote())
+ .map((item) => (item as Zotero.Item).id);
+ detectedIDs = detectedIDs.concat(subNoteIds);
+ }
+
+ const currentNoteLink = getNoteLink(note);
+ if (currentNoteLink) {
+ // Get current related items
+ for (const relItemKey of note.relatedItems) {
+ try {
+ const relItem = (await Zotero.Items.getByLibraryAndKeyAsync(
+ note.libraryID,
+ relItemKey,
+ )) as Zotero.Item;
+
+ // If the related item is a note and contains the current note link
+ // Add it to the related note list
+ if (relItem.isNote()) {
+ if (relItem.getNote().includes(currentNoteLink)) {
+ detectedIDs.push(relItem.id);
+ }
+ currentIDs.push(relItem.id);
+ }
+ } catch (e) {
+ ztoolkit.log(e);
+ }
+ }
+ }
+
+ const detectedIDSet = new Set(detectedIDs);
+ detectedIDSet.delete(noteId);
+ const currentIDSet = new Set(currentIDs);
+ return { detectedIDSet, currentIDSet };
}
From 1977c16a09852455df9b5a4f76463d064999e683 Mon Sep 17 00:00:00 2001
From: windingwind <33902321+windingwind@users.noreply.github.com>
Date: Sat, 6 Apr 2024 11:43:45 +0800
Subject: [PATCH 10/23] fix: tab title update
---
src/hooks.ts | 5 ++-
src/modules/workspace/tab.ts | 63 ++++++++----------------------------
2 files changed, 16 insertions(+), 52 deletions(-)
diff --git a/src/hooks.ts b/src/hooks.ts
index 493bccc..8bb3262 100644
--- a/src/hooks.ts
+++ b/src/hooks.ts
@@ -9,10 +9,10 @@ import {
} from "./modules/template/controller";
import { registerMenus } from "./modules/menu";
import {
- registerWorkspaceTab,
openWorkspaceTab,
onTabSelect,
restoreNoteTabs,
+ onUpdateNoteTabsTitle,
} from "./modules/workspace/tab";
import { initWorkspace } from "./modules/workspace/content";
import { registerNotify } from "./modules/notify";
@@ -78,8 +78,6 @@ async function onMainWindowLoad(win: Window): Promise {
registerMenus();
- registerWorkspaceTab(win);
-
initTemplates();
patchViewItems(win);
@@ -123,6 +121,7 @@ function onNotify(
reason: "item-modify",
});
addon.hooks.onUpdateRelated(modifiedNotes, { skipActive: true });
+ onUpdateNoteTabsTitle(modifiedNotes);
}
} else {
return;
diff --git a/src/modules/workspace/tab.ts b/src/modules/workspace/tab.ts
index 0c53408..5f7f66f 100644
--- a/src/modules/workspace/tab.ts
+++ b/src/modules/workspace/tab.ts
@@ -3,55 +3,6 @@ import { initWorkspace } from "./content";
export const TAB_TYPE = "note";
-export function registerWorkspaceTab(win: Window) {
- const doc = win.document;
- const spacer = doc.querySelector("#zotero-collections-toolbar > spacer");
- if (!spacer) {
- return;
- }
- const tabButton = ztoolkit.UI.insertElementBefore(
- {
- tag: "toolbarbutton",
- classList: ["zotero-tb-button"],
- styles: {
- listStyleImage: `url("chrome://${config.addonRef}/content/icons/icon-linear-20.svg")`,
- },
- attributes: {
- tooltiptext: "Open workspace",
- },
- listeners: [
- {
- type: "command",
- listener: (ev) => {
- // if ((ev as MouseEvent).shiftKey) {
- // addon.hooks.onOpenWorkspace("window");
- // } else {
- // addon.hooks.onOpenWorkspace("tab");
- // }
- },
- },
- ],
- },
- spacer,
- ) as XUL.ToolBarButton;
- const collectionSearch = doc.querySelector("#zotero-collections-search")!;
- const ob = new (ztoolkit.getGlobal("MutationObserver"))((muts) => {
- tabButton.hidden = !!collectionSearch?.classList.contains("visible");
- });
- ob.observe(collectionSearch, {
- attributes: true,
- attributeFilter: ["class"],
- });
-
- win.addEventListener(
- "unload",
- () => {
- ob.disconnect();
- },
- { once: true },
- );
-}
-
export async function openWorkspaceTab(
item: Zotero.Item,
options: { select?: boolean; index?: number } = {
@@ -110,3 +61,17 @@ export function restoreNoteTabs() {
});
}
}
+
+export function onUpdateNoteTabsTitle(noteItems: Zotero.Item[]) {
+ const ids = noteItems.map((item) => item.id);
+ for (const tab of Zotero_Tabs._tabs) {
+ if (tab.type !== TAB_TYPE) continue;
+ if (ids.includes(tab.data.itemID)) {
+ const newTitle = Zotero.Items.get(tab.data.itemID).getNoteTitle();
+ if (tab.title === newTitle) {
+ continue;
+ }
+ Zotero_Tabs.rename(tab.id, newTitle);
+ }
+ }
+}
From 30fc3c8e86770200db36d1cae244bd6d998dd678 Mon Sep 17 00:00:00 2001
From: windingwind <33902321+windingwind@users.noreply.github.com>
Date: Sat, 6 Apr 2024 11:52:56 +0800
Subject: [PATCH 11/23] fix: linkNote layout
---
addon/chrome/content/styles/linkNote.css | 1 +
addon/chrome/content/styles/notePicker.css | 1 +
2 files changed, 2 insertions(+)
diff --git a/addon/chrome/content/styles/linkNote.css b/addon/chrome/content/styles/linkNote.css
index 1403699..61204aa 100644
--- a/addon/chrome/content/styles/linkNote.css
+++ b/addon/chrome/content/styles/linkNote.css
@@ -1,4 +1,5 @@
.container {
+ min-height: 0;
height: 100%;
margin: 0;
}
diff --git a/addon/chrome/content/styles/notePicker.css b/addon/chrome/content/styles/notePicker.css
index e3347e1..8f68793 100644
--- a/addon/chrome/content/styles/notePicker.css
+++ b/addon/chrome/content/styles/notePicker.css
@@ -3,6 +3,7 @@ bn-note-picker {
}
.container {
+ min-height: 0;
height: 100%;
margin: 0;
}
From 3a42791729c55e3368d4160e34adaa2404b80e23 Mon Sep 17 00:00:00 2001
From: windingwind <33902321+windingwind@users.noreply.github.com>
Date: Sat, 6 Apr 2024 21:42:09 +0800
Subject: [PATCH 12/23] fix: workspace window
---
addon/chrome/content/styles/related.css | 61 ++++++++++++
addon/chrome/content/workspaceWindow.xhtml | 59 +++++-------
addon/locale/en-US/addon.ftl | 3 +-
addon/locale/it-IT/addon.ftl | 3 +-
addon/locale/ru-RU/addon.ftl | 3 +-
addon/locale/tr-TR/addon.ftl | 3 +-
addon/locale/zh-CN/addon.ftl | 3 +-
src/elements/detailsPane.ts | 2 +-
src/elements/related.ts | 103 +++++++++++++++++++++
src/extras/customElements.ts | 2 +
src/extras/workspaceWindow.ts | 37 ++++++++
src/hooks.ts | 5 +-
src/modules/editor/toolbar.ts | 11 ++-
src/modules/workspace/content.ts | 2 -
src/modules/workspace/window.ts | 4 +
15 files changed, 254 insertions(+), 47 deletions(-)
create mode 100644 addon/chrome/content/styles/related.css
create mode 100644 src/elements/related.ts
create mode 100644 src/extras/workspaceWindow.ts
diff --git a/addon/chrome/content/styles/related.css b/addon/chrome/content/styles/related.css
new file mode 100644
index 0000000..ee3abdf
--- /dev/null
+++ b/addon/chrome/content/styles/related.css
@@ -0,0 +1,61 @@
+bn-related-box {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+bn-related-box[hidden] {
+ display: none;
+}
+bn-related-box[readonly] .add {
+ display: none;
+}
+bn-related-box .body {
+ display: flex;
+ flex-direction: column;
+ padding-inline-start: 12px;
+}
+bn-related-box .body .row {
+ display: flex;
+ gap: 4px;
+ align-items: flex-start;
+}
+[zoteroUIDensity="comfortable"] bn-related-box .body .row {
+ padding-block: 2px;
+}
+bn-related-box .body .row .box {
+ display: flex;
+ align-items: flex-start;
+ gap: 4px;
+ padding-inline-start: 4px;
+ overflow: hidden;
+ border-radius: 5px;
+ flex: 1;
+}
+bn-related-box .body .row .box:not([disabled]):hover {
+ background-color: var(--fill-quinary);
+}
+bn-related-box .body .row .box:not([disabled]):active {
+ background-color: var(--fill-quarternary);
+}
+bn-related-box .body .row .box .icon {
+ height: calc(1.3333333333 * var(--zotero-font-size));
+}
+bn-related-box .body .row .box .label {
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 10;
+ width: 0;
+ flex: 1;
+ overflow: hidden;
+}
+bn-related-box .body .row .box .icon,
+bn-related-box .body .row .box .label {
+ padding-block: 2px;
+}
+bn-related-box .body .row toolbarbutton {
+ margin-inline-start: auto;
+ visibility: hidden;
+}
+bn-related-box .body .row:is(:hover, :focus-within) toolbarbutton {
+ visibility: visible;
+}
diff --git a/addon/chrome/content/workspaceWindow.xhtml b/addon/chrome/content/workspaceWindow.xhtml
index 8b9909d..c856b79 100644
--- a/addon/chrome/content/workspaceWindow.xhtml
+++ b/addon/chrome/content/workspaceWindow.xhtml
@@ -4,7 +4,7 @@
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+