diff --git a/src/events.ts b/src/events.ts index 1446550..5a1e4c4 100644 --- a/src/events.ts +++ b/src/events.ts @@ -59,7 +59,17 @@ class AddonEvents extends AddonBase { public async onEditorEvent(message: EditorMessage) { Zotero.debug(`Knowledge4Zotero: onEditorEvent\n${message.type}`); - if (message.type === "addNoteInstance") { + if (message.type === "openWorkspace") { + /* + message.content = {} + */ + await this._Addon.knowledge.openWorkspaceWindow(); + } else if (message.type === "addNoteInstance") { + /* + message.content = { + editorInstance, + } + */ let mainKnowledgeID = parseInt( Zotero.Prefs.get("Knowledge4Zotero.mainKnowledgeID") ); @@ -71,22 +81,34 @@ class AddonEvents extends AddonBase { Zotero.debug(`Knowledge4Zotero: main Knowledge`); this._Addon.views.addEditorButton( message.content.editorInstance, - "mainKnowledge", - isMainKnowledge ? "isMainKnowledge" : "setMainKnowledge", + "workspace", + isMainKnowledge ? "isMainKnowledge" : "notMainKnowledge", isMainKnowledge - ? "This Note is Knowledge Workspace" - : "Use Current Note as Knowledge Workspace", - new EditorMessage("setMainKnowledge", {}) + ? "Edit the main knowledge in Workspace" + : "Open Workspace", + "start", + new EditorMessage("openWorkspace", {}) ); this._Addon.views.addEditorButton( message.content.editorInstance, "addToKnowledge", "addToKnowledge", "Add Note Link to Knowledge Workspace", + "middle", new EditorMessage("addToKnowledge", { itemID: message.content.editorInstance._item.id, }) ); + this._Addon.views.addEditorButton( + message.content.editorInstance, + "export", + "export", + "Export Markdown with linked Notes", + "end", + new EditorMessage("export", { + itemID: message.content.editorInstance._item.id, + }) + ); if (!message.content.editorInstance._knowledgeSelectionInitialized) { this.addEditorDocumentEventListener( message.content.editorInstance, @@ -95,12 +117,36 @@ class AddonEvents extends AddonBase { ); message.content.editorInstance._knowledgeSelectionInitialized = true; } + } else if (message.type === "enterWorkspace") { + /* + message.content = { + editorInstance, + params: "main" | "preview" + } + */ + if (message.content.params === "main") { + // This is a main knowledge, hide all buttons except the export button and add title + } else { + // This is a preview knowledge, hide openWorkspace button add show close botton + } + } else if (message.type === "leaveWorkspace") { + /* + message.content = { + editorInstance, + params: "main" | "preview" + } + */ + if (message.content.params === "main") { + // This is a main knowledge, show all buttons and remove title + } else { + // This is a preview knowledge, show openWorkspace button add hide close botton + } } else if (message.type === "addToKnowledge") { /* message.content = { editorInstance } - */ + */ Zotero.debug("Knowledge4Zotero: addToKnowledge"); this._Addon.knowledge.addLinkToNote( undefined, @@ -112,8 +158,7 @@ class AddonEvents extends AddonBase { message.content = { editorInstance } - */ - // TODO: Complete this part + */ Zotero.debug("Knowledge4Zotero: setMainKnowledge"); let mainKnowledgeID = parseInt( Zotero.Prefs.get("Knowledge4Zotero.mainKnowledgeID") @@ -132,29 +177,36 @@ class AddonEvents extends AddonBase { message.content.editorInstance._item.id ); // Set the button to selected state - this._Addon.views.changeEditorButton( + this._Addon.views.changeEditorButtonView( message.content.event.target, "isMainKnowledge", - "This Note is Knowledge Workspace" + "Edit the main knowledge in Workspace" ); - // TODO: update workspace window here - await this._Addon.knowledge.setWorkspaceMainNote(); + await this._Addon.knowledge.setWorkspaceNote("main"); for (let editor of Zotero.Notes._editorInstances) { await editor._initPromise; if (editor._item.id === mainKnowledgeID) { let button = - editor._iframeWindow.document.getElementById("mainKnowledge"); + editor._iframeWindow.document.getElementById("workspace"); if (button) { - this._Addon.views.changeEditorButton( + this._Addon.views.changeEditorButtonView( button, - "setMainKnowledge", - "Use Current Note as Knowledge Workspace" + "notMainKnowledge", + "Open Workspace" ); } } } } } else if (message.type === "onNoteLink") { + /* + message.content = { + params: { + item: ZoteroItem | boolean, + infoText: string + } + } + */ if (!message.content.params.item) { Zotero.debug(`Knowledge4Zotero: ${message.content.params.infoText}`); } @@ -163,7 +215,8 @@ class AddonEvents extends AddonBase { ); let _window = this._Addon.knowledge.getWorkspaceWindow(); if (_window) { - this._Addon.knowledge.setWorkspacePreviewNote( + this._Addon.knowledge.setWorkspaceNote( + "preview", message.content.params.item ); (_window as Window).focus(); @@ -203,6 +256,15 @@ class AddonEvents extends AddonBase { this._Addon.knowledge.currentLine = currentLineIndex; Zotero.debug(`Knowledge4Zotero: line ${currentLineIndex} selected.`); } + } else if (message.type === "export") { + /* + message.content = { + editorInstance + } + */ + await this._Addon.knowledge.exportNoteToFile( + message.content.editorInstance._item + ); } else { Zotero.debug(`Knowledge4Zotero: message not handled.`); } diff --git a/src/knowledge.ts b/src/knowledge.ts index ad75a92..3549673 100644 --- a/src/knowledge.ts +++ b/src/knowledge.ts @@ -1,4 +1,4 @@ -import { AddonBase } from "./base"; +import { AddonBase, EditorMessage } from "./base"; const TreeModel = require("./treemodel"); @@ -34,7 +34,7 @@ class Knowledge extends AddonBase { ); this.workspaceWindow = win; await this.waitWorkspaceRedy(); - this.setWorkspaceMainNote(); + this.setWorkspaceNote("main"); } } @@ -51,35 +51,46 @@ class Knowledge extends AddonBase { return t < 500; } - async setWorkspaceMainNote(note: ZoteroItem = undefined) { + async getWorkspaceEditor(type: "main" | "preview" = "main") { + let _window = this.getWorkspaceWindow() as Window; + if (!_window) { + return; + } + await this.waitWorkspaceRedy(); + return _window.document.getElementById(`zotero-note-editor-${type}`); + } + + async setWorkspaceNote( + type: "main" | "preview" = "main", + note: ZoteroItem = undefined + ) { let _window = this.getWorkspaceWindow() as Window; note = note || this.getWorkspaceNote(); if (!_window) { return; } await this.waitWorkspaceRedy(); - let noteEditor: any = _window.document.getElementById( - "zotero-note-editor-main" - ); - noteEditor.mode = "edit"; - noteEditor.viewMode = "library"; - noteEditor.parent = null; - noteEditor.item = note; - } - - async setWorkspacePreviewNote(note: ZoteroItem) { - let _window = this.getWorkspaceWindow() as Window; - if (!_window) { - return; + let noteEditor: any = await this.getWorkspaceEditor(type); + let lastEditorInstance = noteEditor._editorInstance as EditorInstance; + if (lastEditorInstance) { + await this._Addon.events.onEditorEvent( + new EditorMessage("leaveWorkspace", { + editorInstance: lastEditorInstance, + params: type, + }) + ); } - await this.waitWorkspaceRedy(); - let noteEditor: any = _window.document.getElementById( - "zotero-note-editor-preview" - ); noteEditor.mode = "edit"; noteEditor.viewMode = "library"; noteEditor.parent = null; noteEditor.item = note; + + await this._Addon.events.onEditorEvent( + new EditorMessage("enterWorkspace", { + editorInstance: noteEditor, + params: type, + }) + ); } getLinesInNote(note: ZoteroItem): string[] { @@ -396,6 +407,81 @@ class Knowledge extends AddonBase { return text; } + async exportNoteToFile(note: ZoteroItem, convertNoteLinks: boolean = true) { + let exporter = new Zotero_File_Exporter(); + + if (convertNoteLinks) { + let item: ZoteroItem = new Zotero.Item("note"); + let noteLines = this.getLinesInNote(note); + let newLines = []; + for (let i in noteLines) { + newLines.push(noteLines[i]); + let linkIndex = noteLines[i].search(/zotero:\/\/note\//g); + while (linkIndex >= 0) { + let link = noteLines[i].substring(linkIndex); + link = link.substring(0, link.search('"')); + let res = await this.getNoteFromLink(link); + if (res.item && res.item.id !== note.id) { + Zotero.debug(`Knowledge4Zotero: Exporting sub-note ${link}`); + newLines.push("
"); + newLines.push( + `

Linked Note: ${res.item.getNoteTitle()}

` + ); + let linkedLines = this.getLinesInNote(res.item); + newLines = newLines.concat(linkedLines); + newLines.push("
"); + } + noteLines[i] = noteLines[i].substring( + noteLines[i].search(/zotero:\/\/note\//g) + ); + noteLines[i] = noteLines[i].substring( + noteLines[i].search(/<\/a>/g) + "".length + ); + linkIndex = noteLines[i].search(/zotero:\/\/note\//g); + } + } + item.setNote(`
${newLines.join("\n")}
`); + item.saveTx(); + exporter.items = [item]; + exporter.save(); + Zotero.Items.erase(item.id); + } else { + exporter.items = [note]; + exporter.save(); + } + } + + async getNoteFromLink(uri: string) { + let [groupID, noteKey] = uri.substring("zotero://note/".length).split("/"); + + // User libraryID by defaultx + let libraryID = 1; + + if (groupID !== "u") { + // Not a user item + let _groupID = parseInt(groupID); + libraryID = Zotero.Groups.getLibraryIDFromGroupID(_groupID); + } + + if (!libraryID) { + return { + item: false, + infoText: "Library does not exist or access denied.", + }; + } + let item = await Zotero.Items.getByLibraryAndKeyAsync(libraryID, noteKey); + if (!item || !item.isNote()) { + return { + item: false, + infoText: "Note does not exist or is not a note.", + }; + } + return { + item: item, + infoText: "OK", + }; + } + parseNoteHTML(note: ZoteroItem): Element { note = note || this.getWorkspaceNote(); if (!note) { diff --git a/src/views.ts b/src/views.ts index f74a2c2..0550c0a 100644 --- a/src/views.ts +++ b/src/views.ts @@ -13,8 +13,9 @@ class AddonViews extends AddonBase { }; this.editorIcon = { addToKnowledge: ``, - setMainKnowledge: ``, + notMainKnowledge: ``, isMainKnowledge: ``, + export: ``, openWorkspaceCollectionView: ``, }; } @@ -24,19 +25,46 @@ class AddonViews extends AddonBase { return editor; } + async addEditorKnowledgeToolBar(editorInstances: EditorInstance) { + await editorInstances._initPromise; + + const _document = editorInstances._iframeWindow.document; + const knowledgeToolBar = _document.createElement("div"); + knowledgeToolBar.setAttribute("id", "knowledge-tools"); + knowledgeToolBar.setAttribute("class", "toolbar"); + const start = _document.createElement("div"); + start.setAttribute("id", "knowledge-tools-start"); + start.setAttribute("class", "start"); + const middle = _document.createElement("div"); + middle.setAttribute("id", "knowledge-tools-middle"); + middle.setAttribute("class", "middle"); + const end = _document.createElement("div"); + end.setAttribute("id", "knowledge-tools-end"); + end.setAttribute("class", "end"); + knowledgeToolBar.append(start, middle, end); + _document + .getElementsByClassName("editor")[0] + .childNodes[0].before(knowledgeToolBar); + } + async addEditorButton( editorInstances: EditorInstance, id: string, icon: string, title: string, + position: "start" | "middle" | "end", message: EditorMessage ) { // Use Zotero.Notes._editorInstances to find current opened note editor await editorInstances._initPromise; - let _document = editorInstances._iframeWindow.document; - let toolbar = _document.getElementsByClassName("middle")[0]; - let button = _document.createElement("button"); + const _document = editorInstances._iframeWindow.document; + let knowledgeToolBar = _document.getElementById("knowledge-tools"); + if (!knowledgeToolBar) { + await this.addEditorKnowledgeToolBar(editorInstances); + } + const toolbar = _document.getElementById(`knowledge-tools-${position}`); + const button = _document.createElement("button"); button.setAttribute("class", "toolbar-button"); button.setAttribute("id", id); button.setAttribute("title", title); @@ -49,9 +77,15 @@ class AddonViews extends AddonBase { }; } - changeEditorButton(button: Element, icon: string, title: string) { + changeEditorButtonView(button: Element, icon: string, title: string = "") { button.innerHTML = this.editorIcon[icon]; - button.setAttribute("title", title); + if (title) { + button.setAttribute("title", title); + } + } + + changeEditorButtonHidden(button: XUL.Element, hidden: boolean) { + button.hidden = hidden; } async scrollToLine(instance: EditorInstance, lineIndex: number) { @@ -90,7 +124,7 @@ class AddonViews extends AddonBase { span1.append(span2, span3, span4); treeRow.append(span1); treeRow.addEventListener("click", (e) => { - this._Addon.knowledge.openWorkspaceWindow(); + this._Addon.events.onEditorEvent(new EditorMessage("openWorkspace", {})); }); treeRow.addEventListener("mouseover", (e: XULEvent) => { treeRow.setAttribute( diff --git a/src/zotero-protocol-handler.js b/src/zotero-protocol-handler.js index 9561e94..13e74fa 100644 --- a/src/zotero-protocol-handler.js +++ b/src/zotero-protocol-handler.js @@ -1283,41 +1283,12 @@ function ZoteroProtocolHandler() { let message = { type: "onNoteLink", content: { - params: { - item: false, - infoText: "", - }, + params: await Zotero.Knowledge4Zotero.knowledge.getNoteFromLink( + uri.spec + ), }, }; - let [groupID, noteKey] = uri.spec - .substring("zotero://note/".length) - .split("/"); - // User libraryID by defaultx - let libraryID = 1; - - if (groupID !== "u") { - // Not a user item - groupID = parseInt(groupID); - libraryID = Zotero.Groups.getLibraryIDFromGroupID(groupID); - } - - if (!libraryID) { - message.content.params.infoText = - "Library does not exist or access denied."; - } else { - let item = await Zotero.Items.getByLibraryAndKeyAsync( - libraryID, - noteKey - ); - if (!item || !item.isNote()) { - message.content.params.infoText = - "Note does not exist or is not a note."; - } else { - message.content.params.item = item; - } - } - Zotero.debug(`Note link ${libraryID} : ${noteKey} called.`); Zotero.Knowledge4Zotero.events.onEditorEvent.bind( Zotero.Knowledge4Zotero.events )(message); diff --git a/typing/global.d.ts b/typing/global.d.ts index 32b669c..e4cdbbe 100644 --- a/typing/global.d.ts +++ b/typing/global.d.ts @@ -149,6 +149,11 @@ declare class ZoteroCollection { getChildItems: (arg1: boolean, arg2: boolean) => Array; } +declare class Zotero_File_Exporter { + items: ZoteroItem[]; + save: () => void; +} + declare const Components: any; declare const Services: any;