diff --git a/.gitignore b/.gitignore
index 40aa3c7..0be9b7b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
**/builds
node_modules
.vscode
-package-lock.json
\ No newline at end of file
+package-lock.json
+zotero-cmd.json
\ No newline at end of file
diff --git a/README.md b/README.md
index e240eff..2b1e27e 100644
--- a/README.md
+++ b/README.md
@@ -34,6 +34,11 @@ https://user-images.githubusercontent.com/33902321/167992626-34adfd97-c2df-48b0-
For new users, a **User Guide** will help you get started quickly and create a user guide note for you. Use it as a playground and explore your own workflow!
+### Important Changes
+
+- Since v0.2.0, most of the workspace bottom-left buttons are moved to menu bar.
+- Since v0.5.0, workspace will open as a Zotero tab by default. `Menu -> File -> Open in New Window` or press `shift` while clicking the Open Workspace button to use the standalone mode.
+
Documentation:
[User Guide(EN)](./UserGuide.md) | [用户指引(中文)](./UserGuideCN.md)
diff --git a/addon/chrome/content/overlay.xul b/addon/chrome/content/overlay.xul
index e54f6c3..005faca 100644
--- a/addon/chrome/content/overlay.xul
+++ b/addon/chrome/content/overlay.xul
@@ -1,11 +1,105 @@
-
+
+
+ %globalDTD;
+
+ %standaloneDTD;
+
+ %editMenuOverlayDTD;
+
+ %zoteroDTD;
+
+ %knowledgeDTD;
+]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/addon/chrome/content/workspace.xul b/addon/chrome/content/workspace.xul
index 6d980b0..2a77dc4 100644
--- a/addon/chrome/content/workspace.xul
+++ b/addon/chrome/content/workspace.xul
@@ -50,77 +50,72 @@
-
-
-
-
diff --git a/package.json b/package.json
index 62fd50f..d780094 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,10 @@
"main": "src/index.js",
"scripts": {
"build": "node build.js",
+ "start": "node start.js",
+ "stop": "node stop.js",
+ "prerestart": "npm run build",
+ "restart": "node restart.js",
"release": "release-it",
"test": "echo \"Error: no test specified\" && exit 1"
},
diff --git a/restart.js b/restart.js
new file mode 100644
index 0000000..be1fe1e
--- /dev/null
+++ b/restart.js
@@ -0,0 +1,8 @@
+const { execSync } = require("child_process");
+const { killZotero, startZotero } = require("./zotero-cmd.json");
+
+try {
+ execSync(killZotero);
+} catch (e) {}
+
+execSync(startZotero);
diff --git a/src/events.ts b/src/events.ts
index 8f0c821..c71af97 100644
--- a/src/events.ts
+++ b/src/events.ts
@@ -37,18 +37,55 @@ class AddonEvents extends AddonBase {
new EditorMessage("buildReaderAnnotationButton", {})
);
}
+ if (event == "add" && type == "tab") {
+ if (ids[0] === this._Addon.knowledge.workspaceTabId) {
+ const tabTitle = document
+ .querySelector(`.tab[data-id=${ids[0]}]`)
+ .querySelector(".tab-name");
+ tabTitle.innerHTML = `${this._Addon.views.editorIcon.tabIcon}${tabTitle.innerHTML}`;
+ }
+ }
+ if (event == "select" && type == "tab") {
+ if (extraData[ids[0]].type == "betternotes") {
+ let t = 0;
+ await this._Addon.knowledge.waitWorkspaceReady();
+ while (
+ !(await this._Addon.knowledge.getWorkspaceEditorInstance(
+ "main",
+ false
+ )) &&
+ t < 100
+ ) {
+ t += 1;
+ this._Addon.knowledge.setWorkspaceNote();
+ await Zotero.Promise.delay(100);
+ }
+
+ const _tabCover = document.getElementById("zotero-tab-cover");
+ const _contextPane = document.getElementById(
+ "zotero-context-pane"
+ ) as XUL.Element;
+ const _contextPaneSplitter = document.getElementById(
+ "zotero-context-splitter"
+ ) as XUL.Element;
+ const _tabToolbar = document.getElementById("zotero-tab-toolbar");
+ _contextPaneSplitter.setAttribute("hidden", true);
+ _contextPane.setAttribute("collapsed", true);
+ _tabToolbar.hidden = true;
+ _tabCover.hidden = true;
+ this._Addon.views.switchKey(false);
+ } else {
+ this._Addon.views.switchRealMenuBar(true);
+ this._Addon.views.switchKey(true);
+ }
+ }
},
};
}
public async onInit() {
Zotero.debug("Knowledge4Zotero: init called");
- await Zotero.uiReadyPromise;
- // Init translator
- // await loadTranslator(TRANSLATOR_ID_BETTER_MARKDOWN);
- // Init UI
- this._Addon.views.addOpenWorkspaceButton();
- this._Addon.views.addNewKnowledgeButton();
+
this.addEditorInstanceListener();
// Register the callback in Zotero as an item observer
let notifierID = Zotero.Notifier.registerObserver(this.notifierCallback, [
@@ -60,15 +97,54 @@ class AddonEvents extends AddonBase {
// Unregister callback when the window closes (important to avoid a memory leak)
window.addEventListener(
"unload",
- function (e) {
+ (e) => {
Zotero.Notifier.unregisterObserver(notifierID);
},
false
);
+
+ await Zotero.uiReadyPromise;
+ this._Addon.views.addOpenWorkspaceButton();
+ this._Addon.views.addNewKnowledgeButton();
+
if (!Zotero.Prefs.get("Knowledge4Zotero.mainKnowledgeID")) {
this.onEditorEvent(new EditorMessage("openUserGuide", {}));
}
this.resetState();
+
+ this.initWorkspaceTab();
+ this._Addon.views.switchRealMenuBar(true);
+ this._Addon.views.switchKey(true);
+ }
+
+ private async initWorkspaceTab() {
+ let state = Zotero.Session.state.windows.find((x) => x.type === "pane");
+ Zotero.debug("initWorkspaceTab");
+ Zotero.debug(state);
+ if (state) {
+ const noteTab = state.tabs.find((t) => t.type === "betternotes");
+ Zotero.debug(noteTab);
+ if (noteTab) {
+ let t = 0;
+ while (t < 5) {
+ t += 1;
+ try {
+ await this._Addon.knowledge.openWorkspaceWindow(
+ "tab",
+ false,
+ noteTab.selected
+ );
+ break;
+ } catch (e) {
+ this._Addon.views.showProgressWindow(
+ "Recovering Note Workspace Failed",
+ e
+ );
+ }
+ await Zotero.Promise.delay(1000);
+ }
+ }
+ }
}
public addEditorInstanceListener() {
@@ -142,9 +218,16 @@ class AddonEvents extends AddonBase {
);
} else if (message.type === "openWorkspace") {
/*
- message.content = {}
+ message.content = {event?}
*/
- await this._Addon.knowledge.openWorkspaceWindow();
+ if (
+ message.content.event &&
+ (message.content.event as unknown as MouseEvent).shiftKey
+ ) {
+ await this._Addon.knowledge.openWorkspaceWindow("window", true);
+ } else {
+ await this._Addon.knowledge.openWorkspaceWindow();
+ }
} else if (message.type === "openWorkspaceInWindow") {
/*
message.content = {}
@@ -159,8 +242,16 @@ class AddonEvents extends AddonBase {
/*
message.content = {}
*/
+ const currentCollection = ZoteroPane_Local.getSelectedCollection();
+ if (!currentCollection) {
+ this._Addon.views.showProgressWindow(
+ "Better Notes",
+ "Please select a collection before creating a new main note."
+ );
+ return;
+ }
const res = confirm(
- `Will create a new note under collection '${ZoteroPane_Local.getSelectedCollection().getName()}' and set it the main note. Continue?`
+ `Will create a new note under collection '${currentCollection.getName()}' and set it the main note. Continue?`
);
if (!res) {
return;
diff --git a/src/knowledge.ts b/src/knowledge.ts
index 80a9352..5af1f0a 100644
--- a/src/knowledge.ts
+++ b/src/knowledge.ts
@@ -32,10 +32,13 @@ class Knowledge extends AddonBase {
async openWorkspaceWindow(
type: "window" | "tab" = "tab",
- reopen: boolean = false
+ reopen: boolean = false,
+ select: boolean = true
) {
+ // this._Addon.views.showProgressWindow("Recovering Note", "4");
if (this.getWorkspaceWindow()) {
if (!reopen) {
+ Zotero.debug("openWorkspaceWindow: reopen");
if (this.workspaceTabId) {
Zotero_Tabs.select(this.workspaceTabId);
} else {
@@ -47,6 +50,7 @@ class Knowledge extends AddonBase {
}
}
if (type === "window") {
+ Zotero.debug("openWorkspaceWindow: as window");
this._Addon.views._initIframe = Zotero.Promise.defer();
let win = window.open(
"chrome://Knowledge4Zotero/content/workspace.xul",
@@ -62,17 +66,17 @@ class Knowledge extends AddonBase {
this._Addon.views.switchView(OutlineType.treeView);
this._Addon.views.updateOutline();
} else {
+ Zotero.debug("openWorkspaceWindow: as tab");
this._Addon.views._initIframe = Zotero.Promise.defer();
// Avoid sidebar show up
Zotero_Tabs.jump(0);
let { id, container } = Zotero_Tabs.add({
- type: "library",
- title: "Better Notes Workspace",
+ type: "betternotes",
+ title: Zotero.locale.includes("zh") ? "工作区" : "Workspace",
index: 1,
- data: {
- _item: this.getWorkspaceNote(),
- },
- select: true,
+ data: {},
+ select: select,
+ onClose: undefined,
});
this.workspaceTabId = id;
const _iframe = window.document.createElement("browser");
@@ -84,27 +88,17 @@ class Knowledge extends AddonBase {
"chrome://Knowledge4Zotero/content/workspace.xul"
);
container.appendChild(_iframe);
- // @ts-ignore
- window.addEventListener("DOMContentLoaded", async (event) => {
- if (
- _iframe &&
- // @ts-ignore
- _iframe.contentWindow &&
- // @ts-ignore
- _iframe.contentWindow.document === event.target
- ) {
- // @ts-ignore
- this.workspaceWindow = _iframe.contentWindow;
- await this.waitWorkspaceReady();
- // Need reinit
- this.setWorkspaceNote("main");
- this.currentLine = -1;
- this._Addon.views.initKnowledgeWindow(this.workspaceWindow);
- this._Addon.views.switchView(OutlineType.treeView);
- this._Addon.views.updateOutline();
- }
- });
+ // @ts-ignore
+ this.workspaceWindow = _iframe.contentWindow;
+ await this.waitWorkspaceReady();
+
+ this._Addon.views.hideMenuBar(this.workspaceWindow.document);
+
+ this.currentLine = -1;
+ this._Addon.views.initKnowledgeWindow(this.workspaceWindow);
+ this._Addon.views.switchView(OutlineType.treeView);
+ this._Addon.views.updateOutline();
}
}
@@ -141,11 +135,12 @@ class Knowledge extends AddonBase {
}
async getWorkspaceEditorInstance(
- type: "main" | "preview" = "main"
+ type: "main" | "preview" = "main",
+ wait: boolean = true
): Promise {
let noteEditor = (await this.getWorkspaceEditor(type)) as any;
let t = 0;
- while (!noteEditor.getCurrentInstance() && t < 500) {
+ while (wait && !noteEditor.getCurrentInstance() && t < 500) {
t += 1;
await Zotero.Promise.delay(10);
}
@@ -749,34 +744,32 @@ class Knowledge extends AddonBase {
[["MarkDown File(*.md)", "*.md"]],
`${newNote.getNoteTitle()}.md`
);
- if (!filename) {
- return;
- }
+ if (filename) {
+ this._exportNote = newNote;
+ this._exportPath =
+ Zotero.File.pathToFile(filename).parent.path + "/attachments";
+ // Convert to unix format
+ this._exportPath = this._exportPath.replace(/\\/g, "/");
- this._exportNote = newNote;
- this._exportPath =
- Zotero.File.pathToFile(filename).parent.path + "/attachments";
- // Convert to unix format
- this._exportPath = this._exportPath.replace(/\\/g, "/");
+ Components.utils.import("resource://gre/modules/osfile.jsm");
- Components.utils.import("resource://gre/modules/osfile.jsm");
+ const hasImage = newNote.getNote().includes("
`,
close: ``,
openWorkspaceCollectionView: ``,
+ tabIcon: ``,
};
this.currentOutline = OutlineType.treeView;
this._initIframe = Zotero.Promise.defer();
@@ -37,6 +38,23 @@ class AddonViews extends AddonBase {
return editor;
}
+ hideMenuBar(_document: Document) {
+ _document.getElementById("better-notes-menu").hidden = true;
+ }
+
+ switchRealMenuBar(hidden: boolean) {
+ // We only handle hide. The show will be handled by the ZoteroStandalone.switchMenuType
+ document
+ .querySelectorAll(".menu-type-betternotes")
+ .forEach((el) => ((el as HTMLElement).hidden = hidden));
+ }
+
+ switchKey(disabled: boolean) {
+ document
+ .querySelectorAll(".key-type-betternotes")
+ .forEach((el) => (el as XUL.Element).setAttribute("disabled", disabled));
+ }
+
async addEditorKnowledgeToolBar(editorInstances: EditorInstance) {
await editorInstances._initPromise;
@@ -318,11 +336,15 @@ class AddonViews extends AddonBase {
const span4 = document.createElement("span");
span4.setAttribute("class", "cell-text");
span4.setAttribute("style", "margin-left: 6px;");
- span4.innerHTML = "Open Workspace";
+ span4.innerHTML = Zotero.locale.includes("zh")
+ ? "打开工作区"
+ : "Open Workspace";
span1.append(span2, span3, span4);
treeRow.append(span1);
treeRow.addEventListener("click", (e) => {
- this._Addon.events.onEditorEvent(new EditorMessage("openWorkspace", {}));
+ this._Addon.events.onEditorEvent(
+ new EditorMessage("openWorkspace", { event: e })
+ );
});
treeRow.addEventListener("mouseover", (e: XULEvent) => {
treeRow.setAttribute(
@@ -592,6 +614,11 @@ class AddonViews extends AddonBase {
this.switchView();
});
_window.addEventListener("resize", (e) => this.resizeOutline(_window));
+ _window.document
+ .getElementById("outline-splitter")
+ .addEventListener("mouseup", async (e) => {
+ this.resizeOutline(_window);
+ });
}
async messageHandler(e) {
diff --git a/start.js b/start.js
new file mode 100644
index 0000000..08a6b78
--- /dev/null
+++ b/start.js
@@ -0,0 +1,6 @@
+const { execSync } = require("child_process");
+const { exit } = require("process");
+const { startZotero } = require("./zotero-cmd.json");
+
+execSync(startZotero);
+exit(0);
diff --git a/stop.js b/stop.js
new file mode 100644
index 0000000..50d11ab
--- /dev/null
+++ b/stop.js
@@ -0,0 +1,6 @@
+const { execSync } = require("child_process");
+const { killZotero } = require("./zotero-cmd.json");
+
+try {
+ execSync(killZotero);
+} catch (e) {}
diff --git a/typing/global.d.ts b/typing/global.d.ts
index d64d62f..858e728 100644
--- a/typing/global.d.ts
+++ b/typing/global.d.ts
@@ -217,6 +217,7 @@ declare const Zotero_Tabs: {
index: any;
data: object;
select: boolean;
+ onClose: Function;
});
_tabs: Array;
selectedID: string;
diff --git a/zotero-cmd-default.json b/zotero-cmd-default.json
new file mode 100644
index 0000000..ad72ffc
--- /dev/null
+++ b/zotero-cmd-default.json
@@ -0,0 +1,5 @@
+{
+ "usage": "Copy and rename this file to zotero-cmd.json. Edit the cmd.",
+ "killZotero": "taskkill /f /im zotero.exe",
+ "startZotero": "/path/to/zotero.exe --debugger --purgecaches"
+}