refactor: seperate by modules

replace: treemodel.js with tree-model
This commit is contained in:
xiangyu 2022-10-08 17:23:04 +08:00
parent 8ce1452df0
commit 27c9553048
36 changed files with 5121 additions and 5362 deletions

View File

@ -40,7 +40,7 @@ In other type of templates, the default stage is called.
> variables: subNoteLines: string[], subNoteItem, noteItem
### QuickNoteV2
### QuickNoteV3
> variables: annotationItem, topItem

View File

@ -33,7 +33,7 @@ Only the template with specific name will be called.
- QuickInsert: Called when inserting a note link to main note.
- QuickBackLink: Called when inserting a note link to main note. The template will be inserted to the end of the sub-note and point to the main note by default.
- QuickImport: Called when importing a sub-note to main note.
- QuickNoteV2: Called when creating a note from an annotation.
- QuickNoteV3: Called when creating a note from an annotation.
> QuickNote is deprecated since v0.6.25
- ExportMDFileName: Called when exporting notes to markdown in batch/linked notes to markdown mode. The rendered template will be file name.

View File

@ -11,14 +11,14 @@
%knowledgeDTD;
]>
<dialog id="betternotes-export-dialog" windowtype="betternotes-export" title="&zotero.__addonRef__.export.title;" orient="vertical" width="300" height="300" buttons="cancel,accept" ondialogaccept="Zotero.Knowledge4Zotero.export.doAccept();" onload="Zotero.Knowledge4Zotero.export.doLoad(window);" onunload="Zotero.Knowledge4Zotero.export.doUnload();" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" style="padding:2em" persist="screenX screenY width height">
<dialog id="betternotes-export-dialog" windowtype="betternotes-export" title="&zotero.__addonRef__.export.title;" orient="vertical" width="300" height="300" buttons="cancel,accept" ondialogaccept="Zotero.Knowledge4Zotero.NoteExportWindow.doAccept();" onload="Zotero.Knowledge4Zotero.NoteExportWindow.doLoad(window);" onunload="Zotero.Knowledge4Zotero.NoteExportWindow.doUnload();" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" style="padding:2em" persist="screenX screenY width height">
<script src="chrome://zotero/content/include.js" />
<vbox flex="1">
<groupbox flex="1">
<caption label="&zotero.__addonRef__.export.option.label;"></caption>
<rows flex="1">
<row>
<checkbox id="__addonRef__-export-embedLink" tooltiptext="&zotero.__addonRef__.export.cannotworkwith.label;&zotero.__addonRef__.export.singlefile.enable.label;" checked="true" oncommand="Zotero.Knowledge4Zotero.export.doUpdate(event)" />
<checkbox id="__addonRef__-export-embedLink" tooltiptext="&zotero.__addonRef__.export.cannotworkwith.label;&zotero.__addonRef__.export.singlefile.enable.label;" checked="true" oncommand="Zotero.Knowledge4Zotero.NoteExportWindow.doUpdate(event)" />
<label value="&zotero.__addonRef__.export.link.enable.label;" tooltiptext="&zotero.__addonRef__.export.cannotworkwith.label;&zotero.__addonRef__.export.singlefile.enable.label;" />
</row>
<row>
@ -31,15 +31,15 @@
<caption label="&zotero.__addonRef__.export.markdown.label;"></caption>
<rows flex="1">
<row>
<checkbox id="__addonRef__-export-enablefile" checked="true" oncommand="Zotero.Knowledge4Zotero.export.doUpdate(event)" />
<checkbox id="__addonRef__-export-enablefile" checked="true" oncommand="Zotero.Knowledge4Zotero.NoteExportWindow.doUpdate(event)" />
<label value="&zotero.__addonRef__.export.file.enable.label;" />
</row>
<row>
<checkbox id="__addonRef__-export-enablesingle" tooltiptext="&zotero.__addonRef__.export.cannotworkwith.label;&zotero.__addonRef__.export.link.enable.label;" checked="false" oncommand="Zotero.Knowledge4Zotero.export.doUpdate(event)" />
<checkbox id="__addonRef__-export-enablesingle" tooltiptext="&zotero.__addonRef__.export.cannotworkwith.label;&zotero.__addonRef__.export.link.enable.label;" checked="false" oncommand="Zotero.Knowledge4Zotero.NoteExportWindow.doUpdate(event)" />
<label value="&zotero.__addonRef__.export.singlefile.enable.label;" tooltiptext="&zotero.__addonRef__.export.cannotworkwith.label;&zotero.__addonRef__.export.link.enable.label;" />
</row>
<row>
<checkbox id="__addonRef__-export-enableautosync" tooltiptext="&zotero.__addonRef__.export.workwith.label;&zotero.__addonRef__.export.singlefile.enable.label;" checked="false" oncommand="Zotero.Knowledge4Zotero.export.doUpdate(event)" />
<checkbox id="__addonRef__-export-enableautosync" tooltiptext="&zotero.__addonRef__.export.workwith.label;&zotero.__addonRef__.export.singlefile.enable.label;" checked="false" oncommand="Zotero.Knowledge4Zotero.NoteExportWindow.doUpdate(event)" />
<label value="&zotero.__addonRef__.export.enableautosync.enable.label;" tooltiptext="Only work with &zotero.__addonRef__.export.singlefile.enable.label;" />
</row>
<row>

View File

@ -22,36 +22,31 @@
<key id="key_open_betternotes" class="key-type-betternotes" key="O" modifiers="accel" command="cmd_open_betternotes" />
<key id="key_export_betternotes" class="key-type-betternotes" key="E" modifiers="accel" command="cmd_export_betternotes" />
<key id="key_sync_betternotes" class="key-type-betternotes" key="S" modifiers="accel,shift" command="cmd_sync_betternotes" />
<key id="key_indent_betternotes" class="key-type-betternotes" keycode="VK_TAB" command="cmd_indent_betternotes" />
<key id="key_unindent_betternotes" class="key-type-betternotes" keycode="VK_TAB" modifiers="shift" command="cmd_unindent_betternotes" />
</keyset>
<commandset id="mainCommandSet">
<command id="cmd_new_betternotes" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'createWorkspace'});" />
<command id="cmd_open_betternotes" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'selectMainKnowledge'});" />
<command id="cmd_open_betternotes" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'selectMainNote'});" />
<command id="cmd_open_betternotesWindow" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'openWorkspaceInWindow'});" />
<command id="cmd_export_betternotes" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'export', content: {editorInstance: {_item: false}}});" />
<command id="cmd_sync_betternotes" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'sync'});" />
<command id="cmd_sync_manager_betternotes" oncommand="Zotero.Knowledge4Zotero.syncList.openSyncList();" />
<command id="cmd_sync_manager_betternotes" oncommand="Zotero.Knowledge4Zotero.SyncListWindow.openSyncList();" />
<command id="cmd_editTemplate_betternotes" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'editTemplate'});" />
<command id="cmd_addheading_betternotes" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'addHeading'});" />
<command id="cmd_indent_betternotes" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'indentHeading'});" />
<command id="cmd_unindent_betternotes" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'unindentHeading'});" />
<!-- <command id="cmd_importlink_betternotes" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'importLink'});" />
<command id="cmd_updatelink_betternotes" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'updateLink'});" /> -->
<command id="cmd_autoannotation_betternotes" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'updateAutoAnnotation'});" />
<command id="cmd_convertmd_betternotes" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'convertMD'});" />
<command id="cmd_convertasciidoc_betternotes" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'convertAsciiDoc'});" />
<command id="cmd_treeview_betternotes" oncommand="Zotero.Knowledge4Zotero.views.switchView(1);" />
<command id="cmd_mindmap_betternotes" oncommand="Zotero.Knowledge4Zotero.views.switchView(2);" />
<command id="cmd_bubblemap_betternotes" oncommand="Zotero.Knowledge4Zotero.views.switchView(3);" />
<command id="cmd_treeview_betternotes" oncommand="Zotero.Knowledge4Zotero.WorkspaceOutline.switchView(1);" />
<command id="cmd_mindmap_betternotes" oncommand="Zotero.Knowledge4Zotero.WorkspaceOutline.switchView(2);" />
<command id="cmd_bubblemap_betternotes" oncommand="Zotero.Knowledge4Zotero.WorkspaceOutline.switchView(3);" />
<command id="cmd_guide_betternotes" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'openUserGuide'});" />
<command id="cmd_about_betternotes" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'openAbout'});" />
</commandset>
<popup id="zotero-itemmenu">
<menuseparator />
<menuitem id="zotero-itemmenu-__addonRef__-setMainKnowledge" class="menuitem-iconic popup-type-single-note" label="&zotero.__addonRef__.itemmenu.setMainKnowledge.label;" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent.bind(Zotero.Knowledge4Zotero.events)({type: 'setMainKnowledge', content: {params: {itemID: ZoteroPane.getSelectedItems()[0].id}}})" style="list-style-image: url('chrome://__addonRef__/skin/favicon.png');" />
<menuitem id="zotero-itemmenu-__addonRef__-setMainNote" class="menuitem-iconic popup-type-single-note" label="&zotero.__addonRef__.itemmenu.setMainNote.label;" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'setMainNote', content: {params: {itemID: ZoteroPane.getSelectedItems()[0].id}}})" style="list-style-image: url('chrome://__addonRef__/skin/favicon.png');" />
<menuitem id="zotero-itemmenu-__addonRef__-exportNote" class="menuitem-iconic popup-type-single" label="&zotero.__addonRef__.itemmenu.exportNote.label;" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent.bind(Zotero.Knowledge4Zotero.events)({type: 'exportNotes', content: {}})" style="list-style-image: url('chrome://__addonRef__/skin/favicon.png');" />
<menuitem id="zotero-itemmenu-__addonRef__-exportNotes" class="menuitem-iconic popup-type-multiple" label="&zotero.__addonRef__.itemmenu.exportNotes.label;" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent.bind(Zotero.Knowledge4Zotero.events)({type: 'exportNotes', content: {}})" style="list-style-image: url('chrome://__addonRef__/skin/favicon.png');" />
</popup>
@ -68,22 +63,18 @@
<menupopup id="menu_EditPopup">
<menu id="menu_insertTextTemplate_betternotes" class="menu-betternotes" label="&zotero.__addonRef__.workspace.menu.insertTextTemplate;">
<menupopup id="menu_insertTextTemplatePopup" onpopupshowing="Zotero.Knowledge4Zotero.views.updateTemplateMenu('Text');" />
<menupopup id="menu_insertTextTemplatePopup" onpopupshowing="Zotero.Knowledge4Zotero.ZoteroViews.updateTemplateMenu('Text');" />
</menu>
<menu id="menu_insertNoteTemplate_betternotes" class="menu-betternotes" label="&zotero.__addonRef__.workspace.menu.insertNoteTemplate;">
<menupopup id="menu_insertNoteTemplatePopup" onpopupshowing="Zotero.Knowledge4Zotero.views.updateTemplateMenu('Note');" />
<menupopup id="menu_insertNoteTemplatePopup" onpopupshowing="Zotero.Knowledge4Zotero.ZoteroViews.updateTemplateMenu('Note');" />
</menu>
<menu id="menu_insertItemTemplate_betternotes" class="menu-betternotes" label="&zotero.__addonRef__.workspace.menu.insertItemTemplate;">
<menupopup id="menu_insertItemTemplatePopup" onpopupshowing="Zotero.Knowledge4Zotero.views.updateTemplateMenu('Item');" />
<menupopup id="menu_insertItemTemplatePopup" onpopupshowing="Zotero.Knowledge4Zotero.ZoteroViews.updateTemplateMenu('Item');" />
</menu>
<menuitem id="menu_editTemplate_betternotes" class="menu-betternotes" label="&zotero.__addonRef__.workspace.menu.editTemplate;" command="cmd_editTemplate_betternotes" />
<menu id="menu_citeSetting_betternotes" class="menu-type-betternotes menu-betternotes" label="&zotero.__addonRef__.workspace.menu.citeSetting;">
<menupopup id="menu_citeSettingPopup" onpopupshowing="Zotero.Knowledge4Zotero.views.updateCitationStyleMenu();" />
<menupopup id="menu_citeSettingPopup" onpopupshowing="Zotero.Knowledge4Zotero.ZoteroViews.updateCitationStyleMenu();" />
</menu>
<menuseparator class="menu-type-betternotes menu-betternotes" />
<menuitem id="menu_addheading_betternotes" class="menu-type-betternotes menu-betternotes" label="&zotero.__addonRef__.workspace.menu.addheading;" command="cmd_addheading_betternotes" />
<menuitem id="menu_indent_betternotes" class="menu-type-betternotes menu-betternotes" label="&zotero.__addonRef__.workspace.menu.indent;" command="cmd_indent_betternotes" key="key_indent_betternotes" />
<menuitem id="menu_unindent_betternotes" class="menu-type-betternotes menu-betternotes" label="&zotero.__addonRef__.workspace.menu.unindent;" command="cmd_unindent_betternotes" key="key_unindent_betternotes" />
<!-- <menuitem id="menu_importlink_betternotes" class="menu-type-betternotes" label="&zotero.__addonRef__.workspace.menu.importLink;" command="cmd_importlink_betternotes" />
<menuitem id="menu_updatelink_betternotes" class="menu-type-betternotes" label="&zotero.__addonRef__.workspace.menu.updateLink;" command="cmd_updatelink_betternotes" /> -->
<menuseparator class="menu-betternotes" />
@ -107,7 +98,7 @@
<menupopup id="menu_HelpPopup">
<menuseparator class="menu-betternotes" />
<menu id="menu_ocrsetting_betternotes" class="menu-betternotes" label="&zotero.__addonRef__.workspace.menu.OCRSetting;">
<menupopup id="menu_ocrsettingpopup" onpopupshowing="Zotero.Knowledge4Zotero.views.updateOCRStyleMenu();">
<menupopup id="menu_ocrsettingpopup" onpopupshowing="Zotero.Knowledge4Zotero.ZoteroViews.updateOCRStyleMenu();">
<menuitem id="menu_ocr_bing_betternotes" class="menu-betternotes" label="&zotero.__addonRef__.workspace.menu.OCRBing;" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent.bind(Zotero.Knowledge4Zotero.events)({type: 'setOCREngine', content: {params: {engine: 'bing'}}})" type="checkbox" />
<menuitem id="menu_ocr_mathpix_betternotes" class="menu-betternotes" label="&zotero.__addonRef__.workspace.menu.OCRMathpix;" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent.bind(Zotero.Knowledge4Zotero.events)({type: 'setOCREngine', content: {params: {engine: 'mathpix'}}})" type="checkbox" />
<menuitem id="menu_ocr_xunfei_betternotes" class="menu-betternotes" label="&zotero.__addonRef__.workspace.menu.OCRXunfei;" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent.bind(Zotero.Knowledge4Zotero.events)({type: 'setOCREngine', content: {params: {engine: 'xunfei'}}})" type="checkbox" />

View File

@ -11,7 +11,7 @@
%knowledgeDTD;
]>
<dialog id="betternotes-sync-dialog" windowtype="betternotes-sync" title="&zotero.__addonRef__.sync.title;" orient="vertical" width="300" height="300" buttons="cancel,accept,help" ondialogaccept="Zotero.Knowledge4Zotero.sync.doAccept();" ondialoghelp="Zotero.Knowledge4Zotero.sync.doExport();" onload="Zotero.Knowledge4Zotero.sync.doLoad(window);" onunload="Zotero.Knowledge4Zotero.sync.doUnload();" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" style="padding:2em" persist="screenX screenY width height" buttonlabelhelp="&zotero.__addonRef__.sync.export.label;">
<dialog id="betternotes-sync-dialog" windowtype="betternotes-sync" title="&zotero.__addonRef__.sync.title;" orient="vertical" width="300" height="300" buttons="cancel,accept,help" ondialogaccept="Zotero.Knowledge4Zotero.SyncInfoWindow.doAccept();" ondialoghelp="Zotero.Knowledge4Zotero.SyncInfoWindow.doExport();" onload="Zotero.Knowledge4Zotero.SyncInfoWindow.doLoad(window);" onunload="Zotero.Knowledge4Zotero.SyncInfoWindow.doUnload();" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" style="padding:2em" persist="screenX screenY width height" buttonlabelhelp="&zotero.__addonRef__.sync.export.label;">
<script src="chrome://zotero/content/include.js" />
<vbox flex="1">
<label value="&zotero.__addonRef__.sync.path.label;" />
@ -20,8 +20,8 @@
<label id="__addonRef__-sync-lastsync" />
<hbox>
<checkbox id="__addonRef__-sync-enable" label="&zotero.__addonRef__.sync.enable.label;" checked="true" />
<button flex="0" label="&zotero.__addonRef__.sync.dosync.label;" oncommand="Zotero.Knowledge4Zotero.sync.doSync(null, true)"></button>
<button flex="0" label="&zotero.__addonRef__.sync.synclist.label;" oncommand="Zotero.Knowledge4Zotero.syncList.openSyncList()"></button>
<button flex="0" label="&zotero.__addonRef__.sync.dosync.label;" oncommand="Zotero.Knowledge4Zotero.SyncController.doSync(null, true)"></button>
<button flex="0" label="&zotero.__addonRef__.sync.synclist.label;" oncommand="Zotero.Knowledge4Zotero.SyncListWindow.openSyncList()"></button>
</hbox>
</vbox>
</dialog>

View File

@ -16,7 +16,7 @@
%knowledgeDTD;
]>
<window id="zotero-knowledge-sync-list" onload="Zotero.Knowledge4Zotero.syncList.doLoad(window);" onresize="Zotero.Knowledge4Zotero.syncList.doUpdate();" orient="vertical" width="600" height="350" title="&zotero.__addonRef__.syncList.title;" persist="screenX screenY width height" windowtype="zotero:knowledgeSyncList" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
<window id="zotero-knowledge-sync-list" onload="Zotero.Knowledge4Zotero.SyncListWindow.doLoad(window);" onresize="Zotero.Knowledge4Zotero.SyncListWindow.doUpdate();" orient="vertical" width="600" height="350" title="&zotero.__addonRef__.syncList.title;" persist="screenX screenY width height" windowtype="zotero:knowledgeSyncList" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
<script src="chrome://zotero/content/include.js" />
<keyset>
@ -39,11 +39,11 @@
</listcols>
</listbox>
<hbox flex="0">
<button id="doupdate" label="&zotero.__addonRef__.syncList.doupdate.label;" oncommand="Zotero.Knowledge4Zotero.syncList.doUpdate();"></button>
<button id="changesyncperiod" label="&zotero.__addonRef__.syncList.changesyncperiod.label;" oncommand="Zotero.Knowledge4Zotero.syncList.changeSyncPeriod();"></button>
<button id="dosync" label="&zotero.__addonRef__.syncList.dosync.label;" oncommand="Zotero.Knowledge4Zotero.syncList.doSync();"></button>
<button id="changesync" label="&zotero.__addonRef__.syncList.changesync.label;" oncommand="Zotero.Knowledge4Zotero.syncList.changeSync();"></button>
<button id="removesync" label="&zotero.__addonRef__.syncList.removesync.label;" oncommand="Zotero.Knowledge4Zotero.syncList.removeSync();"></button>
<button id="doupdate" label="&zotero.__addonRef__.syncList.doupdate.label;" oncommand="Zotero.Knowledge4Zotero.SyncListWindow.doUpdate();"></button>
<button id="changesyncperiod" label="&zotero.__addonRef__.syncList.changesyncperiod.label;" oncommand="Zotero.Knowledge4Zotero.SyncListWindow.changeSyncPeriod();"></button>
<button id="dosync" label="&zotero.__addonRef__.syncList.dosync.label;" oncommand="Zotero.Knowledge4Zotero.SyncListWindow.doSync();"></button>
<button id="changesync" label="&zotero.__addonRef__.syncList.changesync.label;" oncommand="Zotero.Knowledge4Zotero.SyncListWindow.changeSync();"></button>
<button id="removesync" label="&zotero.__addonRef__.syncList.removesync.label;" oncommand="Zotero.Knowledge4Zotero.SyncListWindow.removeSync();"></button>
<checkbox id="related" label="&zotero.__addonRef__.syncList.related.label;"></checkbox>
</hbox>
</vbox>

View File

@ -19,7 +19,7 @@
%knowledgeDTD;
]>
<window id="zotero-knowledge-workspace" orient="vertical" width="1000" height="350" title="&zotero.__addonRef__.workspace.title;" persist="screenX screenY width height sizemode" windowtype="zotero:knowledgeWorkspace" onload="Zotero.Knowledge4Zotero.knowledge._workspacePromise.resolve()" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
<window id="zotero-knowledge-workspace" orient="vertical" width="1000" height="350" title="&zotero.__addonRef__.workspace.title;" persist="screenX screenY width height sizemode" windowtype="zotero:knowledgeWorkspace" onload="Zotero.Knowledge4Zotero.WorkspaceWindow._workspacePromise.resolve()" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
<script src="chrome://zotero/content/include.js" />
@ -34,25 +34,22 @@
<key id="key_unindent" keycode="VK_TAB" modifiers="shift" command="cmd_unindent_betternotes" />
</keyset>
<command id="cmd_new" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'createWorkspace'});" />
<command id="cmd_open" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'selectMainKnowledge'});" />
<command id="cmd_open" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'selectMainNote'});" />
<command id="cmd_openWindow" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'openWorkspaceInWindow'});" />
<command id="cmd_export" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'export', content: {editorInstance: {_item: false}}});" />
<command id="cmd_sync_betternotes" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'sync'});" />
<command id="cmd_sync_manager_betternotes" oncommand="Zotero.Knowledge4Zotero.syncList.openSyncList();" />
<command id="cmd_sync_manager_betternotes" oncommand="Zotero.Knowledge4Zotero.SyncListWindow.openSyncList();" />
<command id="cmd_close" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'closeWorkspace'});" />
<!-- <command id="cmd_insertNotes" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'insertNotes'});" /> -->
<command id="cmd_editTemplate" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'editTemplate'});" />
<command id="cmd_addheading" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'addHeading'});" />
<command id="cmd_indent_betternotes" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'indentHeading'});" />
<command id="cmd_unindent_betternotes" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'unindentHeading'});" />
<!-- <command id="cmd_importlink_betternotes" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'importLink'});" />
<command id="cmd_updatelink_betternotes" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'updateLink'});" /> -->
<command id="cmd_autoannotation_betternotes" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'updateAutoAnnotation', content: {event: event}});" />
<command id="cmd_convertmd_betternotes" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'convertMD'});" />
<command id="cmd_convertasciidoc_betternotes" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'convertAsciiDoc'});" />
<command id="cmd_treeview" oncommand="Zotero.Knowledge4Zotero.views.switchView(1);" />
<command id="cmd_mindmap" oncommand="Zotero.Knowledge4Zotero.views.switchView(2);" />
<command id="cmd_bubblemap" oncommand="Zotero.Knowledge4Zotero.views.switchView(3);" />
<command id="cmd_treeview" oncommand="Zotero.Knowledge4Zotero.WorkspaceOutline.switchView(1);" />
<command id="cmd_mindmap" oncommand="Zotero.Knowledge4Zotero.WorkspaceOutline.switchView(2);" />
<command id="cmd_bubblemap" oncommand="Zotero.Knowledge4Zotero.WorkspaceOutline.switchView(3);" />
<command id="cmd_guide" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'openUserGuide'});" />
<command id="cmd_about" oncommand="Zotero.Knowledge4Zotero.events.onEditorEvent({type: 'openAbout'});" />
@ -73,22 +70,18 @@
<menupopup id="menu_EditPopup">
<!-- <menuitem id="menu_insertNotes" label="&zotero.__addonRef__.workspace.menu.insertNotes;" key="key_insertNotes" accesskey="I" command="cmd_insertNotes" /> -->
<menu id="menu_insertTextTemplate" label="&zotero.__addonRef__.workspace.menu.insertTextTemplate;">
<menupopup id="menu_insertTextTemplatePopup" onpopupshowing="Zotero.Knowledge4Zotero.views.updateTemplateMenu('Text');" />
<menupopup id="menu_insertTextTemplatePopup" onpopupshowing="Zotero.Knowledge4Zotero.ZoteroViews.updateTemplateMenu('Text');" />
</menu>
<menu id="menu_insertNoteTemplate" label="&zotero.__addonRef__.workspace.menu.insertNoteTemplate;">
<menupopup id="menu_insertNoteTemplatePopup" onpopupshowing="Zotero.Knowledge4Zotero.views.updateTemplateMenu('Note');" />
<menupopup id="menu_insertNoteTemplatePopup" onpopupshowing="Zotero.Knowledge4Zotero.ZoteroViews.updateTemplateMenu('Note');" />
</menu>
<menu id="menu_insertItemTemplate" label="&zotero.__addonRef__.workspace.menu.insertItemTemplate;">
<menupopup id="menu_insertItemTemplatePopup" onpopupshowing="Zotero.Knowledge4Zotero.views.updateTemplateMenu('Item');" />
<menupopup id="menu_insertItemTemplatePopup" onpopupshowing="Zotero.Knowledge4Zotero.ZoteroViews.updateTemplateMenu('Item');" />
</menu>
<menuitem id="menu_editTemplate" label="&zotero.__addonRef__.workspace.menu.editTemplate;" command="cmd_editTemplate" />
<menu id="menu_citeSetting_betternotes" class="menu-type-betternotes" label="&zotero.__addonRef__.workspace.menu.citeSetting;">
<menupopup id="menu_citeSettingPopup" onpopupshowing="Zotero.Knowledge4Zotero.views.updateCitationStyleMenu();" />
<menupopup id="menu_citeSettingPopup" onpopupshowing="Zotero.Knowledge4Zotero.ZoteroViews.updateCitationStyleMenu();" />
</menu>
<menuseparator />
<menuitem id="menu_addheading" label="&zotero.__addonRef__.workspace.menu.addheading;" command="cmd_addheading" />
<menuitem id="menu_indent" label="&zotero.__addonRef__.workspace.menu.indent;" command="cmd_indent_betternotes" key="key_indent" />
<menuitem id="menu_unindent" label="&zotero.__addonRef__.workspace.menu.unindent;" command="cmd_unindent_betternotes" key="key_unindent" />
<!-- <menuitem id="menu_importlink_betternotes" class="menu-type-betternotes" label="&zotero.__addonRef__.workspace.menu.importLink;" command="cmd_importlink_betternotes" />
<menuitem id="menu_updatelink_betternotes" class="menu-type-betternotes" label="&zotero.__addonRef__.workspace.menu.updateLink;" command="cmd_updatelink_betternotes" /> -->
<menuseparator />
@ -99,7 +92,7 @@
</menupopup>
</menu>
<menu id="view-menu" label="&viewMenu.label;" accesskey="&viewMenu.accesskey;" onpopupshowing="Zotero.Knowledge4Zotero.views.updateViewMenu();">
<menu id="view-menu" label="&viewMenu.label;" accesskey="&viewMenu.accesskey;" onpopupshowing="Zotero.Knowledge4Zotero.WorkspaceMenu.updateViewMenu();">
<menupopup id="menu_ViewPopup">
<menuitem id="menu_openWindow" label="&zotero.__addonRef__.workspace.menu.openWindow;" command="cmd_openWindow" />
<menuseparator />

View File

@ -30,7 +30,7 @@
<!ENTITY zotero.__addonRef__.workspace.menu.guide "Better Notes User Guide">
<!ENTITY zotero.__addonRef__.workspace.menu.about "About Better Notes">
<!ENTITY zotero.__addonRef__.itemmenu.setMainKnowledge.label "Set Main Note">
<!ENTITY zotero.__addonRef__.itemmenu.setMainNote.label "Set Main Note">
<!ENTITY zotero.__addonRef__.itemmenu.exportNote.label "Export Note and SubNotes">
<!ENTITY zotero.__addonRef__.itemmenu.exportNotes.label "Export Notes and SubNotes to Markdown">

View File

@ -30,7 +30,7 @@
<!ENTITY zotero.__addonRef__.workspace.menu.guide "Better Notes用户指引">
<!ENTITY zotero.__addonRef__.workspace.menu.about "关于Better Notes">
<!ENTITY zotero.__addonRef__.itemmenu.setMainKnowledge.label "设置为主笔记">
<!ENTITY zotero.__addonRef__.itemmenu.setMainNote.label "设置为主笔记">
<!ENTITY zotero.__addonRef__.itemmenu.exportNote.label "导出笔记及条目子笔记">
<!ENTITY zotero.__addonRef__.itemmenu.exportNotes.label "导出笔记及条目子笔记为Markdown">

View File

@ -1,34 +1,68 @@
import AddonEvents from "./events";
import AddonViews from "./views";
import AddonEvents from "./zotero/events";
import ZoteroViews from "./zotero/views";
import ReaderViews from "./reader/annotationButton";
import WorkspaceOutline from "./workspace/workspaceOutline";
import EditorViews from "./editor/editorUI";
import AddonWizard from "./wizard";
import AddonExport from "./export";
import Knowledge from "./knowledge";
import AddonTemplate from "./template";
import AddonSync from "./sync";
import AddonSyncList from "./syncList";
import AddonParse from "./parse";
import NoteExportWindow from "./editor/noteExportWindow";
import { TemplateController, TemplateAPI } from "./template/templateController";
import SyncInfoWindow from "./sync/syncInfoWindow";
import SyncListWindow from "./sync/syncListWindow";
import NoteParse from "./editor/noteParse";
import WorkspaceWindow from "./workspace/workspaceWindow";
import WorkspaceMenu from "./workspace/workspaceMenu";
import NoteUtils from "./editor/noteUtils";
import NoteExport from "./editor/noteExportController";
import SyncController from "./sync/syncController";
import TemplateWindow from "./template/templateWindow";
class Knowledge4Zotero {
public events: AddonEvents;
public views: AddonViews;
// Zotero UI
public ZoteroViews: ZoteroViews;
// Reader UI
public ReaderViews: ReaderViews;
// Workspace UI
public WorkspaceOutline: WorkspaceOutline;
public WorkspaceWindow: WorkspaceWindow;
public WorkspaceMenu: WorkspaceMenu;
// First-run wizard
public wizard: AddonWizard;
public export: AddonExport;
public parse: AddonParse;
public sync: AddonSync;
public syncList: AddonSyncList;
public template: AddonTemplate;
public knowledge: Knowledge;
// Sync tools
public SyncInfoWindow: SyncInfoWindow;
public SyncListWindow: SyncListWindow;
public SyncController: SyncController;
// Template
public TemplateWindow: TemplateWindow;
public TemplateController: TemplateController;
// Just for template API consistency
public knowledge: TemplateAPI;
// Note tools
public NoteUtils: NoteUtils;
public NoteExport: NoteExport;
public NoteExportWindow: NoteExportWindow;
public NoteParse: NoteParse;
public EditorViews: EditorViews;
constructor() {
this.events = new AddonEvents(this);
this.views = new AddonViews(this);
this.ZoteroViews = new ZoteroViews(this);
this.ReaderViews = new ReaderViews(this);
this.WorkspaceOutline = new WorkspaceOutline(this);
this.WorkspaceWindow = new WorkspaceWindow(this);
this.WorkspaceMenu = new WorkspaceMenu(this);
this.EditorViews = new EditorViews(this);
this.wizard = new AddonWizard(this);
this.export = new AddonExport(this);
this.parse = new AddonParse(this);
this.sync = new AddonSync(this);
this.syncList = new AddonSyncList(this);
this.template = new AddonTemplate(this);
this.knowledge = new Knowledge(this);
this.SyncInfoWindow = new SyncInfoWindow(this);
this.SyncListWindow = new SyncListWindow(this);
this.SyncController = new SyncController(this);
this.TemplateWindow = new TemplateWindow(this);
this.TemplateController = new TemplateController(this);
this.NoteUtils = new NoteUtils(this);
this.NoteExport = new NoteExport(this);
this.NoteExportWindow = new NoteExportWindow(this);
this.NoteParse = new NoteParse(this);
this.knowledge = new TemplateAPI(this);
}
}

867
src/editor/editorUI.ts Normal file
View File

@ -0,0 +1,867 @@
import Knowledge4Zotero from "../addon";
import { CopyHelper, EditorMessage } from "../utils";
import AddonBase from "../module";
class EditorViews extends AddonBase {
icons: object;
constructor(parent: Knowledge4Zotero) {
super(parent);
this.icons = {
addToNoteEnd: `<svg t="1651124422933" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3269" width="24" height="24"><path d="M896.00324 352c70.7 0 128-57.3 128-128 0-70.6-57.4-128-128-128-70.7 0-128 57.3-128 128 0 18.8 4.1 36.7 11.3 52.8 2.7 6 1.4 13.1-3.3 17.8l-24.2 24.2c-5.7 5.7-14.9 6.3-21.2 1.2-38.1-30.1-86.3-48-138.6-48-18.8 0-37.1 2.3-54.6 6.7-6.9 1.7-14.1-1.4-17.7-7.5l-6.6-11.4c-3.4-5.8-2.7-13.1 1.6-18.3 18.6-22.6 29.7-51.6 29.3-83.2C543.10324 89 486.30324 32.6 417.00324 32c-70.6-0.6-128.1 56.1-129 126.3-0.9 69.5 56.5 128.6 126 129.6 9.4 0.1 18.5-0.7 27.4-2.5 6.8-1.4 13.6 1.7 17.1 7.7l2.2 3.8c4 7 2.2 15.9-4.2 20.7-42.4 32.3-73 79.4-84 133.6-1.5 7.4-8.1 12.7-15.7 12.7h-94.1c-6.6 0-12.6-4-14.9-10.2-18.1-48-64.3-82.2-118.5-82.8C58.70324 370.3 0.50324 427.6 0.00324 498.1-0.49676 569.2 57.00324 627 128.00324 627c56.7 0 104.8-36.9 121.6-87.9 2.2-6.6 8.3-11.1 15.2-11.1h92c7.6 0 14.2 5.4 15.7 12.9 9.5 46.7 33.5 88 67 119.2 5.4 5 6.6 13.2 2.9 19.6l-21.7 37.6c-3.7 6.3-11.1 9.4-18.2 7.4-11.1-3.1-22.7-4.7-34.8-4.7-69.7 0.1-127 56.8-127.8 126.6-0.8 71.7 57.4 130 129.1 129.4 69.5-0.6 126.3-57.3 126.9-126.8 0.3-28-8.5-53.9-23.5-75.1-3.6-5.1-3.9-11.8-0.8-17.2l24.9-43.1c3.9-6.7 12-9.7 19.3-7 23.7 8.6 49.3 13.2 76 13.2 34.9 0 67.9-8 97.3-22.2 7.6-3.7 16.7-0.9 20.9 6.4l37 64c-26.3 23.5-43 57.7-43 95.8 0 70.9 58 128.5 128.9 128 69.7-0.5 126.2-56.7 127.1-126.3 0.9-70.1-57-129.3-127.1-129.7-6.2 0-12.3 0.4-18.3 1.2-6.5 0.9-12.8-2.2-16.1-7.8l-39.2-67.9c-3.4-5.9-2.7-13.3 1.7-18.4 34.2-39.3 54.9-90.7 54.9-147 0-38.9-9.9-75.5-27.4-107.4-3.4-6.2-2.2-13.9 2.8-18.9l28.4-28.4c4.9-4.9 12.4-6 18.7-2.9 17.4 8.6 36.9 13.5 57.6 13.5z m0-192c35.3 0 64 28.7 64 64s-28.7 64-64 64-64-28.7-64-64 28.7-64 64-64zM128.00324 563c-35.3 0-64-28.7-64-64s28.7-64 64-64 64 28.7 64 64-28.7 64-64 64z m240 349c-35.3 0-64-28.7-64-64s28.7-64 64-64 64 28.7 64 64-28.7 64-64 64z m464-112c35.3 0 64 28.7 64 64s-28.7 64-64 64-64-28.7-64-64 28.7-64 64-64zM416.00324 224c-35.3 0-64-28.7-64-64s28.7-64 64-64 64 28.7 64 64-28.7 64-64 64z m289.1 385.1C674.90324 639.4 634.70324 656 592.00324 656s-82.9-16.6-113.1-46.9C448.60324 578.9 432.00324 538.7 432.00324 496s16.6-82.9 46.9-113.1C509.10324 352.6 549.30324 336 592.00324 336s82.9 16.6 113.1 46.9C735.40324 413.1 752.00324 453.3 752.00324 496s-16.6 82.9-46.9 113.1z" p-id="3270" fill="currentColor"></path></svg>`,
addCitation: `<svg t="1652702140873" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2080" width="24" height="24"><path d="M479.615429 372.021945l0 155.216107c-8.769734 78.782298-47.084365 113.813139-114.89068 124.738979L364.724749 599.455841c21.849634-2.15406 36.108383-18.566868 42.673915-49.239448l0-22.978341-72.204485 0L335.194178 372.021945 479.615429 372.021945zM688.806845 372.021945l0 155.216107c-8.769734 76.628238-47.084365 111.608937-114.891703 124.738979L573.915142 599.455841c8.720615-2.15406 17.49035-8.719592 26.261107-19.695574 8.720615-10.92584 14.207583-20.773116 16.412808-29.543873l0-22.978341-71.120804 0L545.468253 372.021945 688.806845 372.021945z" p-id="2081" fill="currentColor"></path></svg>`,
notMainKnowledge: `<svg t="1651124314636" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1689" width="24" height="24"><path d="M877.44 383.786667L624.426667 117.333333C594.986667 86.186667 554.88 69.12 512 69.12s-82.986667 17.066667-112.426667 48.213333L146.56 383.786667a148.266667 148.266667 0 0 0-40.746667 102.4v302.08c0 85.76 69.76 155.52 155.52 155.52h501.546667c85.76 0 155.52-69.76 155.52-155.52V485.973333c0-38.186667-14.506667-74.453333-40.96-102.186666z m-44.373333 404.266666c0 38.826667-31.573333 70.186667-70.186667 70.186667H261.333333c-38.826667 0-70.186667-31.573333-70.186666-70.186667V485.973333c0-16.213333 6.186667-31.786667 17.28-43.52L461.44 176c13.226667-13.866667 31.146667-21.546667 50.56-21.546667s37.333333 7.68 50.56 21.76l253.013333 266.453334c11.306667 11.733333 17.28 27.306667 17.28 43.52v301.866666z" p-id="1690" fill="currentColor"></path><path d="M608 687.786667h-192c-23.466667 0-42.666667 19.2-42.666667 42.666666s19.2 42.666667 42.666667 42.666667h192c23.466667 0 42.666667-19.2 42.666667-42.666667s-19.2-42.666667-42.666667-42.666666z" p-id="1691" fill="currentColor"></path></svg>`,
isMainKnowledge: `<svg t="1651124352868" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1850" width="24" height="24"><path d="M877.44 388.053333L624.426667 121.813333C594.986667 90.666667 554.88 73.386667 512 73.386667s-82.986667 17.066667-112.426667 48.213333L146.56 388.053333a148.266667 148.266667 0 0 0-40.746667 102.4v302.08c0 85.76 69.76 155.52 155.52 155.52h501.546667c85.76 0 155.52-69.76 155.52-155.52V490.453333c0-38.4-14.506667-74.666667-40.96-102.4zM608 777.386667h-192c-23.466667 0-42.666667-19.2-42.666667-42.666667s19.2-42.666667 42.666667-42.666667h192c23.466667 0 42.666667 19.2 42.666667 42.666667s-19.2 42.666667-42.666667 42.666667z" p-id="1851" fill="currentColor"></path></svg>`,
openAttachment: `<svg t="1651595553273" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7641" width="24" height="24"><path d="M950.857143 537.892571a293.924571 293.924571 0 0 0-73.142857-59.904V292.571429l-146.285715-146.285715H146.285714v731.428572h331.702857c15.945143 27.538286 36.205714 52.224 59.904 73.142857H146.285714a73.142857 73.142857 0 0 1-73.142857-73.142857V146.285714a73.142857 73.142857 0 0 1 73.142857-73.142857h621.714286l182.857143 182.857143v281.892571z m-93.549714 266.166858l82.505142 82.541714a37.668571 37.668571 0 0 1-53.211428 53.211428l-82.541714-82.505142a188.233143 188.233143 0 1 1 53.248-53.248z m-47.213715-101.449143a109.714286 109.714286 0 1 0-219.428571 0 109.714286 109.714286 0 0 0 219.428571 0zM202.605714 286.354286h49.371429v24.137143h0.731428c6.326857-10.24 14.372571-17.664 24.137143-22.308572s20.48-6.948571 32.182857-6.948571c14.884571 0 27.684571 2.816 38.4 8.411428 10.715429 5.595429 19.638857 13.056 26.697143 22.308572 7.058286 9.252571 12.324571 20.041143 15.725715 32.365714 3.401143 12.324571 5.12 25.161143 5.12 38.582857 0 12.690286-1.718857 24.868571-5.12 36.571429-3.401143 11.702857-8.594286 22.052571-15.542858 31.085714s-15.616 16.201143-25.965714 21.577143c-10.349714 5.376-22.491429 8.045714-36.388571 8.045714-11.702857 0-22.491429-2.377143-32.365715-7.131428a61.257143 61.257143 0 0 1-24.32-21.028572h-0.731428v89.6H202.605714V286.354286z m358.4 164.937143h-0.731428c-6.107429 10.24-14.08 17.627429-23.954286 22.125714s-21.028571 6.765714-33.462857 6.765714a80.822857 80.822857 0 0 1-37.302857-8.228571 74.898286 74.898286 0 0 1-26.514286-22.308572 101.229714 101.229714 0 0 1-15.725714-32.365714 135.862857 135.862857 0 0 1-5.302857-38.034286c0-12.690286 1.755429-24.941714 5.302857-36.754285 3.547429-11.812571 8.777143-22.235429 15.725714-31.268572s15.652571-16.274286 26.148571-21.76c10.496-5.485714 22.674286-8.228571 36.571429-8.228571 11.227429 0 21.869714 2.377143 32 7.131428s18.102857 11.776 23.954286 21.028572h0.731428v-95.085715h51.931429V475.428571h-49.371429v-24.137142z m99.84-130.194286h-31.085714v-34.742857h31.085714v-14.628572c0-16.822857 5.229714-30.610286 15.725715-41.325714 10.496-10.715429 26.331429-16.091429 47.542857-16.091429 4.644571 0 9.252571 0.182857 13.897143 0.548572 4.644571 0.365714 9.142857 0.658286 13.531428 0.914286v38.765714c-6.107429-0.731429-12.434286-1.097143-19.017143-1.097143-7.058286 0-12.141714 1.645714-15.177143 4.937143-3.035429 3.291429-4.571429 8.850286-4.571428 16.64v11.337143h35.84v34.742857h-35.84V475.428571h-51.931429V321.097143z m-362.788571 120.32c8.521143 0 15.652571-1.718857 21.394286-5.12 5.741714-3.401143 10.349714-7.862857 13.897142-13.348572 3.547429-5.485714 6.034286-11.885714 7.497143-19.2 1.462857-7.314286 2.194286-14.738286 2.194286-22.308571 0-7.570286-0.804571-14.994286-2.377143-22.308571a59.392 59.392 0 0 0-7.862857-19.565715 43.812571 43.812571 0 0 0-14.08-13.897143 39.314286 39.314286 0 0 0-21.028571-5.302857c-8.521143 0-15.652571 1.755429-21.394286 5.302857a42.678857 42.678857 0 0 0-13.897143 13.714286c-3.547429 5.595429-6.034286 12.068571-7.497143 19.382857-1.462857 7.314286-2.194286 14.884571-2.194286 22.674286 0 7.570286 0.804571 14.994286 2.377143 22.308571 1.572571 7.314286 4.132571 13.714286 7.68 19.2 3.547429 5.485714 8.228571 9.947429 14.08 13.348572 5.851429 3.401143 12.909714 5.12 21.211429 5.12z m262.217143-61.074286c0-7.789714-0.731429-15.286857-2.194286-22.491428a54.966857 54.966857 0 0 0-7.497143-19.017143 42.203429 42.203429 0 0 0-13.714286-13.348572 40.228571 40.228571 0 0 0-21.211428-5.12c-8.521143 0-15.725714 1.718857-21.577143 5.12-5.851429 3.401143-10.532571 7.936-14.08 13.531429a59.794286 59.794286 0 0 0-7.68 19.2 104.228571 104.228571 0 0 0-2.377143 22.491428c0 7.314286 0.841143 14.628571 2.56 21.942858 1.718857 7.314286 4.461714 13.824 8.228572 19.565714 3.766857 5.741714 8.521143 10.349714 14.262857 13.897143 5.741714 3.547429 12.617143 5.302857 20.662857 5.302857 8.521143 0 15.652571-1.718857 21.394286-5.12 5.741714-3.401143 10.313143-7.972571 13.714285-13.714286a61.44 61.44 0 0 0 7.314286-19.565714c1.462857-7.314286 2.194286-14.884571 2.194286-22.674286z" p-id="7642" fill="currentColor"></path></svg>`,
switchEditor: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs><style>.cls-1{fill:currentColor;}.cls-2{filter:invert(100%)}</style></defs><rect class="cls-1" width="24" height="24"/><path class="cls-2" d="M9,7.1H2.33L2.14,9.56H2.4c.15-1.77.32-2.14,2-2.14a3.39,3.39,0,0,1,.59,0c.23,0,.23.16.23.41v5.77c0,.37,0,.53-1.15.53H3.61v.34c.45,0,1.56,0,2.06,0s1.64,0,2.09,0v-.34H7.32c-1.15,0-1.15-.16-1.15-.53V7.86c0-.22,0-.37.19-.41a3.9,3.9,0,0,1,.63,0c1.65,0,1.81.37,2,2.14h.27L9,7.1Z"/><path class="cls-2" d="M14.91,14.15h-.27c-.28,1.68-.53,2.48-2.41,2.48H10.78c-.52,0-.54-.08-.54-.44V13.27h1c1.06,0,1.19.35,1.19,1.28h.27v-2.9h-.27c0,.94-.13,1.28-1.19,1.28h-1V10.3c0-.36,0-.44.54-.44h1.41c1.68,0,2,.61,2.14,2.13h.27l-.3-2.46H8.14v.33H8.4c.84,0,.86.12.86.52v5.73c0,.4,0,.52-.86.52H8.14V17h6.31Z"/><path class="cls-2" d="M18.22,10.27l1.5-2.2a1.67,1.67,0,0,1,1.58-.71V7H18.69v.33c.44,0,.68.25.68.5a.37.37,0,0,1-.1.26L18,10,16.61,7.85a.46.46,0,0,1-.07-.16c0-.13.24-.32.7-.33V7c-.37,0-1.18,0-1.59,0s-1,0-1.43,0v.33h.21c.6,0,.81.08,1,.38l2,3-1.79,2.64a1.67,1.67,0,0,1-1.58.73v.34H16.7v-.34c-.5,0-.69-.31-.69-.51s0-.14.11-.26l1.55-2.3,1.73,2.62s.06.09.06.12-.24.32-.72.33v.34c.39,0,1.19,0,1.6,0s1,0,1.42,0v-.34h-.2c-.58,0-.81-.06-1-.4l-2.3-3.49Z"/></svg>`,
export: `<svg t="1651322116327" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11894" width="24" height="24"><path d="M849.2 599v217H178.5V599c-0.7-23.7-20.1-42.7-44-42.7s-43.3 19-44 42.7v252.5c0 28.9 23.6 52.5 52.5 52.5h741.7c28.9 0 52.5-23.6 52.5-52.5V599c-0.7-23.7-20.1-42.7-44-42.7s-43.3 19-44 42.7z" fill="currentColor" p-id="11895"></path><path d="M482.7 135.4l-164 164c-17.1 17.1-17.1 45.1 0 62.2s45.1 17.1 62.2 0l85.7-85.7v314.8c0 26 21.3 47.2 47.2 47.2 26 0 47.2-21.3 47.2-47.2V276l85.7 85.7c17.1 17.1 45.1 17.1 62.2 0s17.1-45.1 0-62.2l-164-164c-17.1-17.2-45.1-17.2-62.2-0.1z" fill="currentColor" p-id="11896"></path></svg>`,
close: `<svg t="1651331457107" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12754" width="24" height="24"><path d="M557.311759 513.248864l265.280473-263.904314c12.54369-12.480043 12.607338-32.704421 0.127295-45.248112-12.512727-12.576374-32.704421-12.607338-45.248112-0.127295L512.127295 467.904421 249.088241 204.063755c-12.447359-12.480043-32.704421-12.54369-45.248112-0.063647-12.512727 12.480043-12.54369 32.735385-0.063647 45.280796l262.975407 263.775299-265.151458 263.744335c-12.54369 12.480043-12.607338 32.704421-0.127295 45.248112 6.239161 6.271845 14.463432 9.440452 22.687703 9.440452 8.160624 0 16.319527-3.103239 22.560409-9.311437l265.216826-263.807983 265.440452 266.240344c6.239161 6.271845 14.432469 9.407768 22.65674 9.407768 8.191587 0 16.352211-3.135923 22.591372-9.34412 12.512727-12.480043 12.54369-32.704421 0.063647-45.248112L557.311759 513.248864z" fill="currentColor" p-id="12755"></path></svg>`,
};
}
public async initEditor(instance: Zotero.EditorInstance) {
const noteItem = instance._item;
const mainNote = this._Addon.WorkspaceWindow.getWorkspaceNote();
const isMainNote = noteItem.id === mainNote.id;
const isPreviewNote =
noteItem.id === this._Addon.WorkspaceWindow.previewItemID;
const isPrint = this._Addon.NoteExport._pdfNoteId === noteItem.id;
const _window = instance._iframeWindow;
const setMainNoteDropDown: Element = await this.addEditorButton(
instance,
"knowledge-start",
isMainNote
? "isMainKnowledge"
: isPreviewNote
? "openAttachment"
: "notMainKnowledge",
isMainNote
? "Edit the Main Note in Workspace"
: isPreviewNote
? "Open Note Attachments"
: "Open Workspace",
isPreviewNote ? "openAttachment" : "openWorkspace",
"start"
);
if (setMainNoteDropDown && !isPreviewNote) {
setMainNoteDropDown.classList.add("more-dropdown");
setMainNoteDropDown.addEventListener("mouseover", async (e) => {
if (setMainNoteDropDown.getElementsByClassName("popup").length > 0) {
return;
}
const recentIds = (
Zotero.Prefs.get("Knowledge4Zotero.recentMainNoteIds") as string
).split(",");
// Add current note
recentIds.splice(0, 0, String(noteItem.id));
// Remove main note and duplicate notes
const recentMainNotes = Zotero.Items.get(
new Array(
...new Set(
recentIds.filter(
(id) =>
Number(id) !==
parseInt(
Zotero.Prefs.get(
"Knowledge4Zotero.mainKnowledgeID"
) as string
)
)
)
)
) as Zotero.Item[];
const buttons = recentMainNotes.map((item) => {
return {
id: `knowledge-setmainnote-popup-${item.id}`,
rank: 0,
text: item.getNoteTitle(),
eventType: "setRecentMainNote",
};
});
const popup: Element = await this.addEditorPopup(
instance,
"knowledge-setmainnote-popup",
buttons,
setMainNoteDropDown,
"left"
);
const titleNode = _window.document.createElement("div");
titleNode.innerHTML = "Set Recent Main Notes";
titleNode.title = "Click item to set it main note";
titleNode.style.textAlign = "center";
popup.childNodes[0].before(
titleNode,
_window.document.createElement("hr")
);
setMainNoteDropDown.addEventListener("mouseleave", (e) => {
popup.remove();
});
setMainNoteDropDown.addEventListener("click", (e) => {
popup.remove();
});
});
}
const addLinkDropDown: Element = await this.addEditorButton(
instance,
"knowledge-addlink",
"addToNoteEnd",
"Add Link of Current Note to Main Note",
"addToNoteEnd",
"middle"
);
if (addLinkDropDown) {
addLinkDropDown.classList.add("more-dropdown");
// If the editor initialization fails, the addLinkDropDown does not exist
if (isMainNote) {
// This is a main knowledge, hide all buttons except the export button and add title
addLinkDropDown.innerHTML = "";
const header = _window.document.createElement("div");
header.setAttribute("title", "This is a Main Note");
header.innerHTML = "Main Note";
header.setAttribute("style", "font-size: medium");
addLinkDropDown.append(header);
} else {
const normalHintText =
"Insert at the end of section.\nHold shift to insert before section.";
const shiftHintText =
"Insert before section.\nRelease shift to insert at the end of section.";
addLinkDropDown.addEventListener(
"mouseover",
async (e: KeyboardEvent) => {
if (addLinkDropDown.getElementsByClassName("popup").length > 0) {
return;
}
let isShift = e.shiftKey;
const hintWindow = this._Addon.ZoteroViews.showProgressWindow(
"Bi-directional Link",
isShift ? shiftHintText : normalHintText,
"default",
// Disable auto close
-1
);
const getButtonParams = () => {
const buttonParam: any[] = [];
for (let node of nodes) {
buttonParam.push({
id: `knowledge-addlink-popup-${
isShift ? node.model.lineIndex - 1 : node.model.endIndex
}`,
text: node.model.name,
rank: node.model.rank,
eventType: "addToNote",
});
}
return buttonParam;
};
const nodes = this._Addon.NoteUtils.getNoteTreeAsList(
this._Addon.WorkspaceWindow.getWorkspaceNote()
);
const buttonParam = getButtonParams();
const popup: HTMLElement = await this.addEditorPopup(
instance,
"knowledge-addlink-popup",
// [{ id: ''; icon: string; eventType: string }],
buttonParam,
addLinkDropDown
);
popup.style.backgroundColor = isShift ? "#f0f9fe" : "";
const leaveAction = (e?) => {
ob?.disconnect();
popup?.remove();
hintWindow?.close();
addLinkDropDown?.removeEventListener("mouseleave", leaveAction);
addLinkDropDown?.removeEventListener("click", leaveAction);
_window.document?.removeEventListener("keydown", keyAction);
_window.document?.removeEventListener("keyup", keyAction);
};
addLinkDropDown.addEventListener("mouseleave", leaveAction);
addLinkDropDown.addEventListener("click", leaveAction);
// Observe the popup remove triggered by button click
const ob = new MutationObserver((e) => {
console.log(e);
if (e[0].removedNodes) {
leaveAction();
}
});
ob.observe(addLinkDropDown, { childList: true });
const keyAction = (e: KeyboardEvent) => {
if (isShift === e.shiftKey) {
return;
}
isShift = e.shiftKey;
console.log(hintWindow);
popup.style.backgroundColor = isShift ? "#f0f9fe" : "";
this._Addon.ZoteroViews.changeProgressWindowDescription(
hintWindow,
isShift ? shiftHintText : normalHintText
);
const buttonParam = getButtonParams();
for (const i in popup.children) {
popup.children[i].id = buttonParam[i].id;
}
};
_window.document.addEventListener("keydown", keyAction);
_window.document.addEventListener("keyup", keyAction);
}
);
}
}
const addCitationButton = await this.addEditorButton(
instance,
"knowledge-addcitation",
"addCitation",
"Insert Citations",
"addCitation",
"middle",
"builtin"
);
let topItem = noteItem.parentItem;
while (topItem && !topItem.isRegularItem()) {
topItem = topItem.parentItem;
}
if (addCitationButton) {
addCitationButton.classList.add("more-dropdown");
if (topItem) {
addCitationButton.addEventListener("mouseover", async (e) => {
if (addCitationButton.getElementsByClassName("popup").length > 0) {
return;
}
const popup: Element = await this.addEditorPopup(
instance,
"knowledge-addcitation-popup",
[
{
id: `knowledge-addcitation-popup-${topItem.id}`,
rank: 0,
text: topItem.getField("title"),
eventType: "insertCitation",
},
],
addCitationButton
);
addCitationButton.addEventListener("mouseleave", (e) => {
popup.remove();
});
addCitationButton.addEventListener("click", (e) => {
popup.remove();
});
});
}
addCitationButton.addEventListener("click", async (e) => {
this._Addon.events.onEditorEvent(
new EditorMessage("insertCitation", {
params: {
noteItem: noteItem,
},
})
);
});
}
await this.addEditorButton(
instance,
"knowledge-end",
isPreviewNote ? "close" : "export",
isPreviewNote ? "Close Preview" : "Export with linked notes",
isPreviewNote ? "closePreview" : "export",
"end"
);
// Title style only for normal window
if (!isPrint) {
const style = _window.document.createElement("style");
style.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
}
`;
_window.document.body.append(style);
}
if (!_window.document.getElementById("betternotes-script")) {
const messageScript = _window.document.createElement("script");
messageScript.id = "betternotes-script";
messageScript.innerHTML = `
window.addEventListener('message', async (e)=>{
if(e.data.type === "exportPDF"){
console.log("exportPDF");
const container = document.getElementById("editor-container");
container.style.display = "none";
const fullPageStyle = document.createElement("style");
fullPageStyle.innerHTML =
"@page { margin: 0; } @media print{ body { height : auto; -webkit-print-color-adjust: exact; color-adjust: exact; }}";
document.body.append(fullPageStyle);
let t = 0;
let imageFlag = false;
while(!imageFlag && t < 500){
await new Promise(function (resolve) {
setTimeout(resolve, 10);
});
imageFlag = !Array.prototype.find.call(document.querySelectorAll('img'), e=>(!e.getAttribute('src') || e.style.display === 'none'));
t += 1;
}
const editNode = document.querySelector(".primary-editor");
const printNode = editNode.cloneNode(true);
printNode.style.padding = "20px";
document.body.append(printNode);
let printFlag = false;
window.onafterprint = (e) => {
console.log('Print Dialog Closed..');
printFlag = true;
document.title = "Printed";
};
window.onmouseover = (e) => {
if (printFlag) {
document.title = "Printed";
printNode.remove();
container.style.removeProperty('display');
}
};
document.title = printNode.firstChild.innerText;
console.log(document.title);
window.print();
}
}, false)
`;
_window.document.head.append(messageScript);
}
const moreDropdown: HTMLElement = Array.prototype.filter.call(
_window.document.querySelectorAll(".more-dropdown"),
(e) => !e.id.includes("knowledge")
)[0];
if (!moreDropdown.getAttribute("ob")) {
moreDropdown.setAttribute("ob", "true");
const dropdownOb = new MutationObserver((e) => {
if (
e[0].addedNodes.length &&
(e[0].addedNodes[0] as HTMLElement).classList.contains("popup")
) {
const dropdownPopup = moreDropdown.querySelector(".popup");
if (dropdownPopup) {
const refreshButton = _window.document.createElement("button");
refreshButton.classList.add("option");
refreshButton.innerText = "Refresh Editor";
refreshButton.addEventListener("click", (e) => {
instance.init({
item: instance._item,
viewMode: instance._viewMode,
readOnly: instance._readOnly,
disableUI: instance._disableUI,
onReturn: instance._onReturn,
iframeWindow: instance._iframeWindow,
popup: instance._popup,
state: instance._state,
});
});
const previewButton = _window.document.createElement("button");
previewButton.classList.add("option");
previewButton.innerText = "Preview in Workspace";
previewButton.addEventListener("click", (e) => {
this._Addon.WorkspaceWindow.setWorkspaceNote(
"preview",
instance._item
);
});
const copyLinkButton = _window.document.createElement("button");
copyLinkButton.classList.add("option");
copyLinkButton.innerText = "Copy Note Link";
copyLinkButton.addEventListener("click", (e) => {
const link = this._Addon.NoteUtils.getNoteLink(noteItem);
const linkTemplate =
this._Addon.TemplateController.renderTemplate(
"[QuickInsert]",
"link, subNoteItem, noteItem",
[link, noteItem, noteItem]
);
new CopyHelper()
.addText(link, "text/unicode")
.addText(linkTemplate, "text/html")
.copy();
this._Addon.ZoteroViews.showProgressWindow(
"Better Notes",
"Note Link Copied"
);
});
const copyLinkAtLineButton =
_window.document.createElement("button");
copyLinkAtLineButton.classList.add("option");
copyLinkAtLineButton.innerText = "Copy Note Link of Current Line";
copyLinkAtLineButton.addEventListener("click", (e) => {
const link = this._Addon.NoteUtils.getNoteLink(noteItem, {
withLine: true,
});
const linkTemplate =
this._Addon.TemplateController.renderTemplate(
"[QuickInsert]",
"link, subNoteItem, noteItem",
[link, noteItem, noteItem]
);
new CopyHelper()
.addText(link, "text/unicode")
.addText(linkTemplate, "text/html")
.copy();
this._Addon.ZoteroViews.showProgressWindow(
"Better Notes",
`Note Link of Line ${
this._Addon.NoteUtils.currentLine[noteItem.id] + 1
} Copied`
);
});
dropdownPopup.append(
previewButton,
refreshButton,
copyLinkButton,
copyLinkAtLineButton
);
}
}
});
dropdownOb.observe(moreDropdown, { childList: true });
}
}
public getEditorElement(_document: Document): Element {
let editor = _document.querySelector(".primary-editor");
return editor;
}
public async addEditorToolBar(editorInstances: Zotero.EditorInstance) {
await editorInstances._initPromise;
await new Promise<void>((resolve, reject) => {
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);
resolve();
});
}
public async addEditorButton(
editorInstances: Zotero.EditorInstance,
id: string,
icon: string,
title: string,
eventType: string,
position: "start" | "middle" | "end",
target: "knowledge" | "builtin" = "knowledge"
) {
// Use Zotero.Notes._editorInstances to find current opened note editor
await editorInstances._initPromise;
const _document = editorInstances._iframeWindow.document;
if (_document.getElementById(id)) {
return;
}
let knowledgeToolBar = _document.getElementById("knowledge-tools");
if (!knowledgeToolBar) {
await this.addEditorToolBar(editorInstances);
}
let toolbar: HTMLElement;
if (target === "knowledge") {
toolbar = _document.getElementById(`knowledge-tools-${position}`);
} else {
toolbar = Array.prototype.find.call(
_document.getElementsByClassName(position),
(e) => e.getAttribute("id") !== `knowledge-tools-${position}`
);
}
const dropdown = _document.createElement("div");
dropdown.setAttribute("class", "dropdown");
dropdown.setAttribute("id", id);
const button = _document.createElement("button");
button.setAttribute("class", "toolbar-button");
button.setAttribute("title", title);
button.setAttribute("eventType", eventType);
button.innerHTML = this.icons[icon];
dropdown.append(button);
toolbar.append(dropdown);
const message = new EditorMessage("", {
itemID: editorInstances._item.id,
editorInstances: editorInstances,
});
dropdown.addEventListener("click", (e: XUL.XULEvent) => {
message.type = e.target.getAttribute("eventType");
message.content.event = e as XUL.XULEvent;
message.content.editorInstance = editorInstances;
this._Addon.events.onEditorEvent(message);
});
return dropdown;
}
public async addEditorPopup(
editorInstances: Zotero.EditorInstance,
id: string,
buttons: { id: string; text: string; rank: number; eventType: string }[],
parentDropDown: Element,
align: "center" | "left" | "right" = "center"
) {
// Use Zotero.Notes._editorInstances to find current opened note editor
await editorInstances._initPromise;
const _document = editorInstances._iframeWindow.document;
let knowledgeToolBar = _document.getElementById("knowledge-tools");
if (!knowledgeToolBar) {
await this.addEditorToolBar(editorInstances);
}
const popup = _document.createElement("div");
popup.setAttribute("class", "popup");
popup.setAttribute("id", id);
for (let buttonParam of buttons) {
const button = _document.createElement("button");
button.setAttribute("class", "option");
button.setAttribute(
"style",
`text-indent: ${(buttonParam.rank - 1) * 5}px;`
);
button.setAttribute("id", buttonParam.id);
button.setAttribute("eventType", buttonParam.eventType);
button.innerHTML =
buttonParam.text.length > 30
? `${buttonParam.text.slice(0, 30)}...`
: buttonParam.text;
popup.append(button);
const message = new EditorMessage("", {
itemID: editorInstances._item.id,
editorInstances: editorInstances,
});
button.addEventListener("click", (e: XUL.XULEvent) => {
message.type = e.target.getAttribute("eventType");
message.content.event = e as XUL.XULEvent;
message.content.editorInstance = editorInstances;
this._Addon.events.onEditorEvent(message);
e.stopPropagation();
popup.remove();
});
}
parentDropDown.append(popup);
Zotero.debug(popup.offsetWidth);
let style: string = "";
if (align === "center") {
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;
}
public async updateEditorPopupButtons(_window: Window, link: string) {
const note: Zotero.Item = link
? (await this._Addon.NoteUtils.getNoteFromLink(link)).item
: undefined;
const mainNote = this._Addon.WorkspaceWindow.getWorkspaceNote();
// If the note is invalid, we remove the buttons
if (note) {
let insertButton = _window.document.getElementById("insert-note-link");
if (insertButton) {
insertButton.remove();
}
insertButton = _window.document.createElement("button");
insertButton.setAttribute("id", "insert-note-link");
insertButton.setAttribute(
"title",
`Import Linked Note: ${note.getNoteTitle()}`
);
insertButton.innerHTML = `<svg t="1652008007954" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10521" width="16" height="16"><path d="M574.3 896H159.7c-17.6 0-31.9-14.3-31.9-32V160c0-17.7 14.3-32 31.9-32h382.7v160c0 35.3 28.6 64 63.8 64h159.5v192c0 17.7 14.3 32 31.9 32 17.6 0 31.9-14.3 31.9-32V270.2c0-8.5-3.3-16.6-9.3-22.6L647.4 73.4c-6-6-14.1-9.4-22.6-9.4h-497C92.6 64 64 92.7 64 128v768c0 35.3 28.6 64 63.8 64h446.5c17.6 0 31.9-14.3 31.9-32s-14.3-32-31.9-32zM638.1 288c-17.6 0-31.9-14.3-31.9-32V128l159.5 160H638.1z" p-id="10522"></path><path d="M418.8 673H225.5c-17.6 0-31.9 14.3-31.9 32s14.3 32 31.9 32h193.3c17.6 0 31.9-14.3 31.9-32s-14.3-32-31.9-32zM608.2 481H225.5c-17.6 0-31.9 14.3-31.9 32s14.3 32 31.9 32h382.7c17.6 0 31.9-14.3 31.9-32s-14.3-32-31.9-32zM225.5 353h191.4c17.6 0 31.9-14.3 31.9-32s-14.3-32-31.9-32H225.5c-17.6 0-31.9 14.3-31.9 32s14.3 32 31.9 32zM862.7 959.4c-23.6 0-47-8.8-64.8-26.6l-24.4-24.4c-12.5-12.5-12.5-32.8 0-45.3s32.7-12.5 45.1 0l24.4 24.4c11.3 11.4 30.7 10.4 43.2-2.1 12.5-12.5 13.4-31.9 2.1-43.3L749.2 702.6c-11.3-11.4-30.7-10.4-43.2 2.1-6.2 6.3-9.8 14.4-10 22.8-0.2 7.9 2.6 15.1 7.9 20.4 12.5 12.5 12.5 32.8 0 45.3s-32.7 12.5-45.1 0c-36.2-36.3-35.2-96.3 2.1-133.8 37.4-37.5 97.2-38.4 133.4-2.1l139.1 139.5c36.2 36.3 35.2 96.3-2.1 133.8-19 19.2-43.9 28.8-68.6 28.8z" p-id="10523"></path><path d="M696.3 883.1c-23.6 0-47-8.8-64.8-26.6l-139-139.6c-17.7-17.8-27.2-41.7-26.6-67.2 0.6-25 10.8-48.6 28.7-66.6 17.9-17.9 41.5-28.2 66.4-28.8 25.5-0.6 49.3 8.9 67 26.6l24.4 24.4c12.5 12.5 12.5 32.8 0 45.3s-32.7 12.5-45.1 0l-24.4-24.4c-5.3-5.3-12.5-8.1-20.4-7.9-8.4 0.2-16.5 3.8-22.8 10-6.2 6.3-9.8 14.4-10 22.8-0.2 7.9 2.6 15.1 7.9 20.4L676.7 811c11.3 11.4 30.7 10.4 43.2-2.1 12.5-12.5 13.4-31.9 2.1-43.3-12.5-12.5-12.5-32.8 0-45.3s32.7-12.5 45.1 0c36.2 36.3 35.3 96.3-2.1 133.8-19.1 19.3-44 29-68.7 29z" p-id="10524"></path></svg>`;
insertButton.addEventListener("click", async (e) => {
let newLines = [];
const convertResult = await this._Addon.NoteUtils.convertNoteLines(
note,
[],
true
);
const subNoteLines = convertResult.lines;
// Prevent note to be too long
if (subNoteLines.join("\n").length > 100000) {
this._Addon.ZoteroViews.showProgressWindow(
"Better Notes",
"The linked note is too long. Import ignored."
);
return;
}
const templateText =
await this._Addon.TemplateController.renderTemplateAsync(
"[QuickImport]",
"subNoteLines, subNoteItem, noteItem",
[subNoteLines, note, mainNote]
);
newLines.push(templateText);
const newLineString = newLines.join("\n");
const notifyFlag: ZoteroPromise = Zotero.Promise.defer();
const notifierName = "insertLinkWait";
this._Addon.events.addNotifyListener(
notifierName,
(
event: string,
type: string,
ids: Array<number>,
extraData: object
) => {
if (
event === "modify" &&
type === "item" &&
ids.includes(mainNote.id)
) {
notifyFlag.resolve();
this._Addon.events.removeNotifyListener(notifierName);
}
}
);
await this._Addon.NoteUtils.modifyLineInNote(
mainNote,
(oldLine: string) => {
Zotero.debug(oldLine);
const params = this._Addon.NoteParse.parseParamsFromLink(link);
const newLink = !params.ignore
? link + (link.includes("?") ? "&ignore=1" : "?ignore=1")
: link;
const linkIndex =
this._Addon.NoteParse.parseLinkIndexInText(oldLine);
Zotero.debug(linkIndex);
return `${oldLine.slice(0, linkIndex[0])}${newLink}${oldLine.slice(
linkIndex[1]
)}\n${newLineString}`;
},
this._Addon.NoteUtils.currentLine[mainNote.id],
true
);
// wait the first modify finish
await notifyFlag.promise;
let hasAttachemnts = false;
for (const _n of [note, ...convertResult.subNotes]) {
if ((Zotero.Items.get(_n.getAttachments()) as Zotero.Item[]).length) {
hasAttachemnts = true;
break;
}
}
if (hasAttachemnts) {
await Zotero.DB.executeTransaction(async () => {
await Zotero.Notes.copyEmbeddedImages(note, mainNote);
for (const subNote of convertResult.subNotes) {
await Zotero.Notes.copyEmbeddedImages(subNote, mainNote);
}
});
await this._Addon.NoteUtils.scrollWithRefresh(
this._Addon.NoteUtils.currentLine[mainNote.id]
);
}
});
let updateButton = _window.document.getElementById("update-note-link");
if (updateButton) {
updateButton.remove();
}
updateButton = _window.document.createElement("button");
updateButton.setAttribute("id", "update-note-link");
updateButton.setAttribute(
"title",
`Update Link Text: ${note.getNoteTitle()}`
);
updateButton.innerHTML = `<svg t="1652685521153" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7063" width="16" height="16"><path d="M271.914667 837.418667C182.314667 756.522667 128 637.653333 128 508.714667 128 304.896 263.338667 129.834667 450.986667 85.333333L469.333333 170.026667c-150.016 35.584-258.304 175.658667-258.304 338.688 0 106.069333 45.866667 203.562667 121.258667 268.373333L426.666667 682.666667v256H170.666667l101.248-101.248zM727.082667 168.917333C831.530667 249.045333 896 377.088 896 517.077333c0 202.922667-135.338667 377.258667-322.986667 421.589334L554.666667 854.357333c150.016-35.456 258.304-174.933333 258.304-337.322666 0-117.12-56.405333-223.786667-146.901334-287.146667L554.666667 341.333333V85.333333h256l-83.584 83.584z" p-id="7064"></path></svg>`;
updateButton.addEventListener("click", async (e) => {
Zotero.debug("ZBN: Update Link Text");
const noteLines = this._Addon.NoteUtils.getLinesInNote(mainNote);
let line = noteLines[this._Addon.NoteUtils.currentLine[mainNote.id]];
Zotero.debug(line);
let linkStart = line.search(/<a /g);
let linkEnd = line.search(/<\/a>/g) + 4;
let beforeLink = line.slice(0, linkStart);
let afterLink = line.slice(linkEnd);
let linkPart = line.slice(linkStart, linkEnd);
let link = this._Addon.NoteParse.parseLinkInText(linkPart);
let currentNote: Zotero.Item;
if (link) {
currentNote = (await this._Addon.NoteUtils.getNoteFromLink(link))
.item;
}
while (
linkPart &&
(!link || !currentNote || currentNote.id !== note.id)
) {
line = afterLink;
beforeLink = beforeLink + linkPart;
line = afterLink;
linkStart = line.search(/<a /g);
linkEnd = line.search(/<\/a>/g) + 4;
beforeLink = beforeLink + line.slice(0, linkStart);
afterLink = line.slice(linkEnd);
linkPart = line.slice(linkStart, linkEnd);
link = this._Addon.NoteParse.parseLinkInText(linkPart);
if (link) {
currentNote = (await this._Addon.NoteUtils.getNoteFromLink(link))
.item;
}
}
if (!linkPart) {
return;
}
beforeLink = beforeLink + linkPart.slice(0, linkPart.search(/>/) + 1);
afterLink = "</a>" + afterLink;
const newLine = `${beforeLink}${currentNote.getNoteTitle()}${afterLink}`;
Zotero.debug(newLine);
noteLines[this._Addon.NoteUtils.currentLine[mainNote.id]] = newLine;
await this._Addon.NoteUtils.setLinesToNote(mainNote, noteLines);
this._Addon.NoteUtils.scrollWithRefresh(
this._Addon.NoteUtils.currentLine[mainNote.id]
);
});
let previewContainer =
_window.document.getElementById("note-link-preview");
if (previewContainer) {
previewContainer.remove();
}
previewContainer = _window.document.createElementNS(
"http://www.w3.org/1999/xhtml",
"div"
);
previewContainer.id = "note-link-preview";
previewContainer.setAttribute(
"style",
"width: 98%;height: 300px;position: absolute;background: white;bottom: 36px;overflow: hidden;box-shadow: 0 0 5px 5px rgba(0,0,0,0.2);border-radius: 5px;cursor: pointer;opacity: 0.9;"
);
previewContainer.className = "ProseMirror primary-editor";
previewContainer.innerHTML =
await this._Addon.NoteParse.parseNoteStyleHTML(note);
previewContainer.addEventListener("click", (e) => {
this._Addon.WorkspaceWindow.setWorkspaceNote("preview", note);
});
const linkPopup = _window.document.querySelector(".link-popup");
if (linkPopup) {
linkPopup.append(insertButton, updateButton, previewContainer);
previewContainer
.querySelector("div[data-schema-version]")
.childNodes.forEach((node) => {
if ((node as Element).setAttribute) {
(node as Element).setAttribute("style", "margin: 0");
} else {
node.remove();
}
});
}
} else {
const insertLink = _window.document.querySelector("#insert-note-link");
if (insertLink) {
insertLink.remove();
}
const updateLink = _window.document.querySelector("#update-note-link");
if (updateLink) {
updateLink.remove();
}
const previewContainer =
_window.document.querySelector("#note-link-preview");
if (previewContainer) {
previewContainer.remove();
}
}
}
public async scrollToLine(
instance: Zotero.EditorInstance,
lineIndex: number
) {
await instance._initPromise;
let editorElement = this.getEditorElement(instance._iframeWindow.document);
const eleList = [];
const diveTagNames = ["OL", "UL", "LI"];
const nodes = Array.from(editorElement.children);
for (let i in nodes) {
const ele = nodes[i];
if (diveTagNames.includes(ele.tagName)) {
this._Addon.NoteParse.parseListElements(
ele as HTMLElement,
eleList,
diveTagNames
);
} else {
eleList.push(ele);
}
}
console.log(eleList, lineIndex);
if (lineIndex >= eleList.length) {
lineIndex = eleList.length - 1;
} else if (lineIndex < 0) {
lineIndex = 0;
}
// @ts-ignore
const scrollNum = eleList[lineIndex].offsetTop;
(editorElement.parentNode as HTMLElement).scrollTo(0, scrollNum);
}
public scrollToPosition(instance: Zotero.EditorInstance, offset: number) {
let editorElement = this.getEditorElement(instance._iframeWindow.document);
// @ts-ignore
(editorElement.parentNode as HTMLElement).scrollTo(0, offset);
}
}
export default EditorViews;

View File

@ -0,0 +1,351 @@
import Knowledge4Zotero from "../addon";
import { pick } from "../utils";
import AddonBase from "../module";
class NoteExport extends AddonBase {
_exportPath: string;
_exportFileInfo: Array<{
link: string;
id: number;
note: Zotero.Item;
filename: string;
}>;
_pdfNoteId: number;
_pdfPrintPromise: ZoteroPromise;
constructor(parent: Knowledge4Zotero) {
super(parent);
this._pdfNoteId = -1;
this._exportFileInfo = [];
}
async exportNoteToFile(
note: Zotero.Item,
convertNoteLinks: boolean = true,
saveMD: boolean = true,
saveNote: boolean = false,
doCopy: boolean = false,
savePDF: boolean = false
) {
if (!saveMD && !saveNote && !doCopy && !savePDF) {
return;
}
this._exportFileInfo = [];
let newNote: Zotero.Item;
if (convertNoteLinks || saveNote) {
const noteID = await ZoteroPane_Local.newNote();
newNote = Zotero.Items.get(noteID) as Zotero.Item;
const rootNoteIds = [note.id];
const convertResult = await this._Addon.NoteUtils.convertNoteLines(
note,
rootNoteIds,
convertNoteLinks
);
await this._Addon.NoteUtils.setLinesToNote(newNote, convertResult.lines);
Zotero.debug(convertResult.subNotes);
await Zotero.DB.executeTransaction(async () => {
await Zotero.Notes.copyEmbeddedImages(note, newNote);
for (const subNote of convertResult.subNotes) {
await Zotero.Notes.copyEmbeddedImages(subNote, newNote);
}
});
} else {
newNote = note;
}
if (saveMD) {
const filename = await pick(
Zotero.getString("fileInterface.export"),
"save",
[["MarkDown File(*.md)", "*.md"]],
`${newNote.getNoteTitle()}.md`
);
if (filename) {
this._exportPath =
Zotero.File.pathToFile(filename).parent.path + "/attachments";
// Convert to unix format
this._exportPath = this._exportPath.replace(/\\/g, "/");
await this._export(newNote, filename, false);
}
}
if (doCopy) {
if (!convertNoteLinks) {
Zotero_File_Interface.exportItemsToClipboard(
[newNote],
Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT
);
this._Addon.ZoteroViews.showProgressWindow(
"Better Notes",
"Note Copied"
);
} else {
alert(
"Select all in the new note window and copy-paste to other applications."
);
ZoteroPane.openNoteWindow(newNote.id);
alert(
"Waiting for paste finish...\nImages may not be copied correctly if OK is pressed before paste."
);
}
}
if (savePDF) {
console.log(newNote);
let _w: Window;
let t = 0;
this._pdfNoteId = newNote.id;
this._pdfPrintPromise = Zotero.Promise.defer();
ZoteroPane.selectItem(note.id);
do {
ZoteroPane.openNoteWindow(newNote.id);
_w = ZoteroPane.findNoteWindow(newNote.id);
console.log(_w);
await Zotero.Promise.delay(10);
t += 1;
} while (!_w && t < 500);
ZoteroPane.selectItem(note.id);
_w.resizeTo(900, 650);
const checkPrint = () => {
try {
const editor: any = _w.document.querySelector("#zotero-note-editor");
const instance: Zotero.EditorInstance = editor.getCurrentInstance();
console.log(instance._iframeWindow.document.title);
if (instance._iframeWindow.document.title === "Printed") {
this._pdfPrintPromise.resolve();
return;
}
} catch (e) {}
setTimeout(checkPrint, 300);
};
checkPrint();
await this._pdfPrintPromise.promise;
console.log("print finish detected");
const closeFlag = _w.confirm(
"Printing finished. Do you want to close the preview window?"
);
if (closeFlag) {
_w.close();
}
}
if (!saveNote) {
if (newNote.id !== note.id) {
const _w: Window = ZoteroPane.findNoteWindow(newNote.id);
if (_w) {
_w.close();
}
await Zotero.Items.erase(newNote.id);
}
} else {
ZoteroPane.openNoteWindow(newNote.id);
}
}
async exportNotesToFile(
notes: Zotero.Item[],
useEmbed: boolean,
useSync: boolean = false
) {
Components.utils.import("resource://gre/modules/osfile.jsm");
this._exportFileInfo = [];
const filepath = await pick(
Zotero.getString(useSync ? "sync.sync" : "fileInterface.export") +
" MarkDown",
"folder"
);
if (!filepath) {
return;
}
this._exportPath = Zotero.File.pathToFile(filepath).path + "/attachments";
// Convert to unix format
this._exportPath = this._exportPath.replace(/\\/g, "/");
notes = notes.filter((n) => n && n.getNote);
if (useEmbed) {
for (const note of notes) {
let newNote: Zotero.Item;
if (this._Addon.NoteParse.parseLinkInText(note.getNote())) {
const noteID = await ZoteroPane_Local.newNote();
newNote = Zotero.Items.get(noteID) as Zotero.Item;
const rootNoteIds = [note.id];
const convertResult = await this._Addon.NoteUtils.convertNoteLines(
note,
rootNoteIds,
true
);
await this._Addon.NoteUtils.setLinesToNote(
newNote,
convertResult.lines
);
Zotero.debug(convertResult.subNotes);
await Zotero.DB.executeTransaction(async () => {
await Zotero.Notes.copyEmbeddedImages(note, newNote);
for (const subNote of convertResult.subNotes) {
await Zotero.Notes.copyEmbeddedImages(subNote, newNote);
}
});
} else {
newNote = note;
}
let filename = `${
Zotero.File.pathToFile(filepath).path
}/${this._getFileName(note)}`;
filename = filename.replace(/\\/g, "/");
await this._export(newNote, filename, newNote.id !== note.id);
}
} else {
// Export every linked note as a markdown file
// Find all linked notes that need to be exported
let allNoteIds: number[] = notes.map((n) => n.id);
for (const note of notes) {
const linkMatches = note
.getNote()
.match(/zotero:\/\/note\/\w+\/\w+\//g);
if (!linkMatches) {
continue;
}
const subNoteIds = (
await Promise.all(
linkMatches.map(async (link) =>
this._Addon.NoteUtils.getNoteFromLink(link)
)
)
)
.filter((res) => res.item)
.map((res) => res.item.id);
allNoteIds = allNoteIds.concat(subNoteIds);
}
allNoteIds = Array.from(new Set(allNoteIds));
const allNoteItems: Zotero.Item[] = Zotero.Items.get(
allNoteIds
) as Zotero.Item[];
const noteLinkDict = allNoteItems.map((_note) => {
return {
link: this._Addon.NoteUtils.getNoteLink(_note),
id: _note.id,
note: _note,
filename: this._getFileName(_note),
};
});
this._exportFileInfo = noteLinkDict;
for (const noteInfo of noteLinkDict) {
let exportPath = `${Zotero.File.pathToFile(filepath).path}/${
noteInfo.filename
}`;
await this._export(noteInfo.note, exportPath, false);
if (useSync) {
this._Addon.SyncController.updateNoteSyncStatus(
noteInfo.note,
Zotero.File.pathToFile(filepath).path,
noteInfo.filename
);
}
}
}
}
async syncNotesToFile(notes: Zotero.Item[], filepath: string) {
this._exportPath = Zotero.File.pathToFile(filepath).path + "/attachments";
// Convert to unix format
this._exportPath = this._exportPath.replace(/\\/g, "/");
// Export every linked note as a markdown file
// Find all linked notes that need to be exported
let allNoteIds: number[] = notes.map((n) => n.id);
for (const note of notes) {
const linkMatches = note.getNote().match(/zotero:\/\/note\/\w+\/\w+\//g);
if (!linkMatches) {
continue;
}
const subNoteIds = (
await Promise.all(
linkMatches.map(async (link) =>
this._Addon.NoteUtils.getNoteFromLink(link)
)
)
)
.filter((res) => res.item)
.map((res) => res.item.id);
allNoteIds = allNoteIds.concat(subNoteIds);
}
allNoteIds = new Array(...new Set(allNoteIds));
// console.log(allNoteIds);
const allNoteItems: Zotero.Item[] = Zotero.Items.get(
allNoteIds
) as Zotero.Item[];
const noteLinkDict = allNoteItems.map((_note) => {
return {
link: this._Addon.NoteUtils.getNoteLink(_note),
id: _note.id,
note: _note,
filename: this._getFileName(_note),
};
});
this._exportFileInfo = noteLinkDict;
for (const note of notes) {
const syncInfo = this._Addon.SyncController.getNoteSyncStatus(note);
let exportPath = `${decodeURIComponent(
syncInfo.path
)}/${decodeURIComponent(syncInfo.filename)}`;
await this._export(note, exportPath, false);
this._Addon.SyncController.updateNoteSyncStatus(note);
}
}
private async _export(
note: Zotero.Item,
filename: string,
deleteAfterExport: boolean
) {
const hasImage = note.getNote().includes("<img");
if (hasImage) {
await Zotero.File.createDirectoryIfMissingAsync(
OS.Path.join(...this._exportPath.split(/\//))
);
}
filename = filename.replace(/\\/g, "/");
filename = OS.Path.join(...filename.split(/\//));
if (!Zotero.isWin && filename.charAt(0) !== "/") {
filename = "/" + filename;
}
const content: string = await this._Addon.NoteParse.parseNoteToMD(note);
console.log(
`Exporting MD file: ${filename}, content length: ${content.length}`
);
await Zotero.File.putContentsAsync(filename, content);
this._Addon.ZoteroViews.showProgressWindow(
"Better Notes",
`Note Saved to ${filename}`
);
if (deleteAfterExport) {
const _w: Window = ZoteroPane.findNoteWindow(note.id);
if (_w) {
_w.close();
}
await Zotero.Items.erase(note.id);
}
}
private _getFileName(noteItem: Zotero.Item) {
return (
this._Addon.TemplateController.renderTemplate("[ExportMDFileName]", "noteItem", [
noteItem,
]) as string
).replace(/\\/g, "-");
}
}
export default NoteExport;

View File

@ -1,7 +1,7 @@
import Knowledge4Zotero from "./addon";
import AddonBase from "./module";
import Knowledge4Zotero from "../addon";
import AddonBase from "../module";
class AddonExport extends AddonBase {
class NoteExportWindow extends AddonBase {
private io: {
dataIn: any;
dataOut: any;
@ -224,4 +224,4 @@ class AddonExport extends AddonBase {
}
}
export default AddonExport;
export default NoteExportWindow;

View File

@ -1,12 +1,12 @@
import AddonBase from "./module";
import AddonBase from "../module";
import { HTML2Markdown, Markdown2HTML } from "./convertMD";
import TurndownService = require("turndown");
const turndownPluginGfm = require("turndown-plugin-gfm");
const TreeModel = require("./treemodel");
const TreeModel = require("tree-model");
const asciidoctor = require("asciidoctor")();
const seedrandom = require("seedrandom");
class AddonParse extends AddonBase {
class NoteParse extends AddonBase {
private getDOMParser(): DOMParser {
if (Zotero.platformMajorVersion > 60) {
return new DOMParser();
@ -35,18 +35,8 @@ class AddonParse extends AddonBase {
}
public parseNoteTree(note: Zotero.Item): TreeModel.Node<object> {
const noteLines = this._Addon.knowledge.getLinesInNote(note);
const noteLines = this._Addon.NoteUtils.getLinesInNote(note);
let tree = new TreeModel();
/*
tree-model/index.js: line 40
TreeModel.prototype.parse = function (model) {
var i, childCount, node;
Annotate the line 40 of:
// if (!(model instanceof Object)) {
// throw new TypeError('Model must be of type object.');
// }
*/
let root = tree.parse({
id: -1,
rank: 0,
@ -74,7 +64,7 @@ class AddonParse extends AddonBase {
link = lineElement.slice(lineElement.search(/href="/g) + 6);
link = link.slice(0, link.search(/"/g));
}
name = this._Addon.parse.parseLineText(lineElement);
name = this._Addon.NoteParse.parseLineText(lineElement);
// Find parent node
let parentNode = lastNode;
@ -206,7 +196,7 @@ class AddonParse extends AddonBase {
const diveTagNames = ["OL", "UL", "LI"];
for (const e of doc.children) {
if (diveTagNames.includes(e.tagName)) {
const innerLines = this.parseListElements(e);
const innerLines = this.parseListElements(e as HTMLElement);
currentLineIndex += innerLines.length;
currentElement = innerLines[innerLines.length - 1];
elements = elements.concat(innerLines);
@ -219,9 +209,9 @@ class AddonParse extends AddonBase {
return elements;
}
parseHTMLLineElement(doc: HTMLElement, lineIndex: number): Element {
parseHTMLLineElement(doc: HTMLElement, lineIndex: number): HTMLElement {
let currentLineIndex = 0;
let currentElement: Element;
let currentElement: HTMLElement;
const diveTagNames = ["OL", "UL", "LI"];
for (const e of doc.children) {
@ -229,7 +219,7 @@ class AddonParse extends AddonBase {
break;
}
if (diveTagNames.includes(e.tagName)) {
const innerLines = this.parseListElements(e);
const innerLines = this.parseListElements(e as HTMLElement);
if (currentLineIndex + innerLines.length > lineIndex) {
// The target line is inside the line list
for (const _e of innerLines) {
@ -245,7 +235,7 @@ class AddonParse extends AddonBase {
}
} else {
currentLineIndex += 1;
currentElement = e;
currentElement = e as HTMLElement;
// console.log(currentLineIndex, e);
}
}
@ -283,13 +273,13 @@ class AddonParse extends AddonBase {
}
let annotationJSONList = [];
for (const annot of annotations) {
const annotJson = await this._Addon.parse.parseAnnotation(annot);
const annotJson = await this._Addon.NoteParse.parseAnnotation(annot);
if (ignoreComment && annotJson.comment) {
annotJson.comment = "";
}
annotationJSONList.push(annotJson);
}
await this._Addon.knowledge.importImagesToNote(note, annotationJSONList);
await this._Addon.NoteUtils.importImagesToNote(note, annotationJSONList);
const html =
Zotero.EditorInstanceUtilities.serializeAnnotations(
annotationJSONList
@ -297,7 +287,7 @@ class AddonParse extends AddonBase {
return html;
}
async parseNoteStyleHTML(item: Zotero.Item, lineCount: 5) {
async parseNoteStyleHTML(item: Zotero.Item, lineCount: number = 5) {
if (!item.isNote()) {
throw new Error("Item is not a note");
}
@ -396,8 +386,8 @@ class AddonParse extends AddonBase {
}
parseListElements(
e: Element,
eleList: Element[] = undefined,
e: HTMLElement,
eleList: HTMLElement[] = undefined,
tags: string[] = ["OL", "UL", "LI"]
) {
if (!eleList) {
@ -405,7 +395,7 @@ class AddonParse extends AddonBase {
}
for (let _e of e.children) {
if (tags.includes(_e.tagName)) {
this.parseListElements(_e, eleList);
this.parseListElements(_e as HTMLElement, eleList);
} else {
eleList.push(e);
}
@ -413,8 +403,7 @@ class AddonParse extends AddonBase {
return eleList;
}
parseNoteHTML(note: Zotero.Item): Element {
note = note || this._Addon.knowledge.getWorkspaceNote();
parseNoteHTML(note: Zotero.Item): HTMLElement {
if (!note) {
return undefined;
}
@ -425,7 +414,7 @@ class AddonParse extends AddonBase {
let parser = this.getDOMParser();
let doc = parser.parseFromString(noteText, "text/html");
let metadataContainer: Element = doc.querySelector(
let metadataContainer: HTMLElement = doc.querySelector(
"body > div[data-schema-version]"
);
return metadataContainer;
@ -573,7 +562,7 @@ class AddonParse extends AddonBase {
Zotero.debug(oldFile);
let ext = oldFile.split(".").pop();
let newAbsPath = OS.Path.join(
...`${this._Addon.knowledge._exportPath}/${imgKey}.${ext}`.split(/\//)
...`${this._Addon.NoteExport._exportPath}/${imgKey}.${ext}`.split(/\//)
);
if (!Zotero.isWin && newAbsPath.charAt(0) !== "/") {
newAbsPath = "/" + newAbsPath;
@ -697,7 +686,7 @@ class AddonParse extends AddonBase {
);
},
replacement: function (content, node: HTMLElement, options) {
replacement: (content, node: HTMLElement, options) => {
var href = node.getAttribute("href");
const cleanAttribute = (attribute) =>
attribute ? attribute.replace(/(\n+\s*)+/g, "\n") : "";
@ -705,14 +694,9 @@ class AddonParse extends AddonBase {
if (title) title = ' "' + title + '"';
if (href.search(/zotero:\/\/note\/\w+\/\w+\//g) !== -1) {
// A note link should be converted if it is in the _exportFileDict
var _Zotero = Components.classes["@zotero.org/Zotero;1"].getService(
Components.interfaces.nsISupports
).wrappedJSObject;
const noteInfo =
_Zotero.Knowledge4Zotero.knowledge._exportFileDict &&
_Zotero.Knowledge4Zotero.knowledge._exportFileDict.find((i) =>
href.includes(i.link)
);
const noteInfo = this._Addon.NoteExport._exportFileInfo.find((i) =>
href.includes(i.link)
);
if (noteInfo) {
href = `./${noteInfo.filename}`;
}
@ -726,4 +710,4 @@ class AddonParse extends AddonBase {
}
}
export default AddonParse;
export default NoteParse;

709
src/editor/noteUtils.ts Normal file
View File

@ -0,0 +1,709 @@
import Knowledge4Zotero from "../addon";
import AddonBase from "../module";
class NoteUtils extends AddonBase {
public currentLine: any;
constructor(parent: Knowledge4Zotero) {
super(parent);
this.currentLine = [];
}
public getLinesInNote(note: Zotero.Item): string[] {
if (!note) {
return [];
}
let noteText: string = note.getNote();
return this._Addon.NoteParse.parseHTMLLines(noteText);
}
public async setLinesToNote(note: Zotero.Item, noteLines: string[]) {
if (!note) {
return [];
}
let noteText: string = note.getNote();
let containerIndex = noteText.search(/data-schema-version="8">/g);
if (containerIndex === -1) {
note.setNote(
`<div data-schema-version="8">${noteLines.join("\n")}</div>`
);
} else {
let noteHead = noteText.substring(0, containerIndex);
note.setNote(
`${noteHead}data-schema-version="8">${noteLines.join("\n")}</div>`
);
}
await note.saveTx();
}
public async addLineToNote(
note: Zotero.Item,
text: string,
lineIndex: number,
forceMetadata: boolean = false,
position: "before" | "after" = "after"
) {
if (!note) {
return;
}
let noteLines = this.getLinesInNote(note);
if (lineIndex < 0) {
lineIndex = this.currentLine[note.id];
lineIndex = lineIndex && lineIndex >= 0 ? lineIndex : noteLines.length;
} else if (lineIndex >= noteLines.length) {
lineIndex = noteLines.length;
}
Zotero.debug(
`insert to ${lineIndex}, it used to be ${noteLines[lineIndex]}`
);
Zotero.debug(text);
const editorInstance = this._Addon.WorkspaceWindow.getEditorInstance(note);
if (editorInstance && !forceMetadata) {
// The note is opened. Add line via note editor
console.log("Add note line via note editor");
const _document = editorInstance._iframeWindow.document;
const currentElement = this._Addon.NoteParse.parseHTMLLineElement(
_document.querySelector(".primary-editor"),
lineIndex
);
const frag = _document.createDocumentFragment();
const temp = _document.createElement("div");
temp.innerHTML = text;
while (temp.firstChild) {
frag.appendChild(temp.firstChild);
}
position === "after"
? currentElement.after(frag)
: currentElement.before(frag);
this._Addon.EditorViews.scrollToPosition(
editorInstance,
currentElement.offsetTop
);
} else {
// The note editor does not exits yet. Fall back to modify the metadata
console.log("Add note line via note metadata");
// insert after/before current line
if (position === "after") {
lineIndex += 1;
}
noteLines.splice(lineIndex, 0, text);
await this.setLinesToNote(note, noteLines);
if (this._Addon.WorkspaceWindow.getWorkspaceNote().id === note.id) {
await this.scrollWithRefresh(lineIndex);
}
}
}
private _dataURLtoBlob(dataurl: string) {
let parts = dataurl.split(",");
let matches = parts[0]?.match(/:(.*?);/);
if (!matches || !matches[1]) {
return;
}
let mime = matches[1];
if (parts[0].indexOf("base64") !== -1) {
let bstr = atob(parts[1]);
let n = bstr.length;
let u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new (Zotero.getMainWindow().Blob)([u8arr], { type: mime });
}
return null;
}
private async _importImage(note: Zotero.Item, src, download = false) {
let blob;
if (src.startsWith("data:")) {
blob = this._dataURLtoBlob(src);
} else if (download) {
let res;
try {
res = await Zotero.HTTP.request("GET", src, { responseType: "blob" });
} catch (e) {
return;
}
blob = res.response;
} else {
return;
}
let attachment = await Zotero.Attachments.importEmbeddedImage({
blob,
parentItemID: note.id,
saveOptions: {},
});
return attachment.key;
}
public async importImagesToNote(note: Zotero.Item, annotations: any) {
for (let annotation of annotations) {
if (annotation.image) {
annotation.imageAttachmentKey = await this._importImage(
note,
annotation.image
);
}
delete annotation.image;
}
}
public async addAnnotationsToNote(
note: Zotero.Item,
annotations: Zotero.Item[],
lineIndex: number
) {
if (!note) {
return;
}
const html = await this._Addon.NoteParse.parseAnnotationHTML(
note,
annotations
);
await this.addLineToNote(note, html, lineIndex);
return html;
}
public addLinkToNote(
targetNote: Zotero.Item,
linkedNote: Zotero.Item,
lineIndex: number,
sectionName: string
) {
if (!targetNote) {
return;
}
if (!linkedNote.isNote()) {
this._Addon.ZoteroViews.showProgressWindow(
"Better Notes",
"Not a note item"
);
return;
}
const link = this.getNoteLink(linkedNote);
const linkText = linkedNote.getNoteTitle().trim();
const linkTemplate = this._Addon.TemplateController.renderTemplate(
"[QuickInsert]",
"link, subNoteItem, noteItem, sectionName, lineIndex",
[link, linkedNote, targetNote, sectionName, lineIndex]
);
this.addLineToNote(targetNote, linkTemplate, lineIndex);
const backLinkTemplate = this._Addon.TemplateController.renderTemplate(
"[QuickBackLink]",
"subNoteItem, noteItem, sectionName, lineIndex",
[linkedNote, targetNote, sectionName, lineIndex],
false
);
if (backLinkTemplate) {
this.addLineToNote(linkedNote, backLinkTemplate, -1);
}
this._Addon.ZoteroViews.showProgressWindow(
"Better Notes",
`Link is added to workspace${lineIndex >= 0 ? ` line ${lineIndex}` : ""}`
);
}
public getNoteLink(
note: Zotero.Item,
options: {
ignore?: boolean;
withLine?: boolean;
} = { ignore: false, withLine: false }
) {
let libraryID = note.libraryID;
let library = Zotero.Libraries.get(libraryID);
let groupID: string;
if (library.libraryType === "user") {
groupID = "u";
} else if (library.libraryType === "group") {
groupID = `${library.id}`;
} else {
return "";
}
let noteKey = note.key;
let link = `zotero://note/${groupID}/${noteKey}/`;
const addParam = (link: string, param: string): string => {
const lastChar = link[link.length - 1];
if (lastChar === "/") {
link += "?";
} else if (lastChar !== "?" && lastChar !== "&") {
link += "&";
}
return `${link}${param}`;
};
if (options.ignore || options.withLine) {
if (options.ignore) {
link = addParam(link, "ignore=1");
}
if (options.withLine) {
if (!this.currentLine[note.id]) {
this.currentLine[note.id] = 0;
}
link = addParam(link, `line=${this.currentLine[note.id]}`);
}
}
return link;
}
public getAnnotationLink(annotation: Zotero.Item) {
let position = JSON.parse(annotation.annotationPosition);
let openURI: string;
const attachment = annotation.parentItem;
let libraryID = attachment.libraryID;
let library = Zotero.Libraries.get(libraryID);
if (library.libraryType === "user") {
openURI = `zotero://open-pdf/library/items/${attachment.key}`;
} else if (library.libraryType === "group") {
openURI = `zotero://open-pdf/groups/${library.id}/items/${attachment.key}`;
} else {
openURI = "";
}
openURI +=
"?page=" +
(position.pageIndex + 1) +
(annotation.key ? "&annotation=" + annotation.key : "");
return openURI;
}
async modifyLineInNote(
note: Zotero.Item,
text: string | Function,
lineIndex: number,
forceMetadata: boolean = false
) {
if (!note) {
return;
}
let noteLines = this.getLinesInNote(note);
if (lineIndex < 0 || lineIndex >= noteLines.length) {
return;
}
if (typeof text === "string") {
noteLines[lineIndex] = text;
} else if (typeof text === "function") {
noteLines[lineIndex] = text(noteLines[lineIndex]);
}
const editorInstance = this._Addon.WorkspaceWindow.getEditorInstance(note);
if (editorInstance && !forceMetadata) {
// The note is opened. Add line via note editor
console.log("Modify note line via note editor");
const _document = editorInstance._iframeWindow.document;
const currentElement: HTMLElement =
this._Addon.NoteParse.parseHTMLLineElement(
_document.querySelector(".primary-editor"),
lineIndex
);
const frag = _document.createDocumentFragment();
const temp = _document.createElement("div");
temp.innerHTML = noteLines[lineIndex];
while (temp.firstChild) {
frag.appendChild(temp.firstChild);
}
currentElement.replaceWith(frag);
this._Addon.EditorViews.scrollToPosition(
editorInstance,
currentElement.offsetTop
);
} else {
await this.setLinesToNote(note, noteLines);
await this.scrollWithRefresh(lineIndex);
}
}
async changeHeadingLineInNote(
note: Zotero.Item,
rankChange: number,
lineIndex: number
) {
if (!note) {
return;
}
const noteLines = this.getLinesInNote(note);
if (lineIndex < 0 || lineIndex >= noteLines.length) {
return;
}
const headerStartReg = new RegExp("<h[1-6]>");
const headerStopReg = new RegExp("</h[1-6]>");
let headerStart = noteLines[lineIndex].search(headerStartReg);
if (headerStart === -1) {
return;
}
let lineRank = parseInt(noteLines[lineIndex][headerStart + 2]) + rankChange;
if (lineRank > 6) {
lineRank = 6;
} else if (lineRank < 1) {
lineRank = 1;
}
this.modifyLineInNote(
note,
noteLines[lineIndex]
.replace(headerStartReg, `<h${lineRank}>`)
.replace(headerStopReg, `</h${lineRank}>`),
lineIndex
);
}
moveHeaderLineInNote(
note: Zotero.Item,
currentNode: TreeModel.Node<object>,
targetNode: TreeModel.Node<object>,
as: "child" | "before" | "after"
) {
if (!note || targetNode.getPath().indexOf(currentNode) >= 0) {
return undefined;
}
let targetIndex = 0;
let targetRank = 1;
let lines = this.getLinesInNote(note);
if (as === "child") {
targetIndex = targetNode.model.endIndex;
targetRank = targetNode.model.rank === 6 ? 6 : targetNode.model.rank + 1;
} else if (as === "before") {
targetIndex = targetNode.model.lineIndex;
targetRank =
targetNode.model.rank === 7
? targetNode.parent.model.rank === 6
? 6
: targetNode.parent.model.rank + 1
: targetNode.model.rank;
} else if (as === "after") {
targetIndex = targetNode.model.endIndex;
targetRank =
targetNode.model.rank === 7
? targetNode.parent.model.rank === 6
? 6
: targetNode.parent.model.rank + 1
: targetNode.model.rank;
}
let rankChange = targetRank - currentNode.model.rank;
Zotero.debug(`move to ${targetIndex}`);
let movedLines = lines.splice(
currentNode.model.lineIndex,
currentNode.model.endIndex - currentNode.model.lineIndex
);
let headerReg = /<\/?h[1-6]>/g;
for (const i in movedLines) {
movedLines[i] = movedLines[i].replace(headerReg, (e) => {
let rank = parseInt(e.slice(-2, -1));
rank += rankChange;
if (rank > 6) {
rank = 6;
}
if (rank < 1) {
rank = 1;
}
return `${e.slice(0, -2)}${rank}>`;
});
}
let newLines = lines
.slice(0, targetIndex)
.concat(movedLines, lines.slice(targetIndex));
this.setLinesToNote(note, newLines);
}
getNoteTree(note: Zotero.Item): TreeModel.Node<object> {
// See http://jnuno.com/tree-model-js
if (!note) {
return undefined;
}
return this._Addon.NoteParse.parseNoteTree(note);
}
getNoteTreeAsList(
note: Zotero.Item,
filterRoot: boolean = true,
filterLink: boolean = true
): TreeModel.Node<object>[] {
if (!note) {
return;
}
return this.getNoteTree(note).all(
(node) =>
(!filterRoot || node.model.lineIndex >= 0) &&
(!filterLink || node.model.rank <= 6)
);
}
getNoteTreeNodeById(
note: Zotero.Item,
id: number,
root: TreeModel.Node<object> = undefined
) {
root = root || this.getNoteTree(note);
return root.first(function (node) {
return node.model.id === id;
});
}
getNoteTreeNodesByRank(
note: Zotero.Item,
rank: number,
root: TreeModel.Node<object> = undefined
) {
root = root || this.getNoteTree(note);
return root.all(function (node) {
return node.model.rank === rank;
});
}
getLineParentNode(
note: Zotero.Item,
lineIndex: number = -1
): TreeModel.Node<object> {
if (lineIndex < 0) {
lineIndex = this.currentLine[note.id];
lineIndex =
lineIndex && lineIndex >= 0
? lineIndex
: this.getLinesInNote(note).length;
}
let nodes = this.getNoteTreeAsList(note);
if (!nodes.length || nodes[0].model.lineIndex > lineIndex) {
// There is no parent node
return undefined;
} else if (nodes[nodes.length - 1].model.lineIndex <= lineIndex) {
return nodes[nodes.length - 1];
} else {
for (let i = 0; i < nodes.length - 1; i++) {
if (
nodes[i].model.lineIndex <= lineIndex &&
nodes[i + 1].model.lineIndex > lineIndex
) {
return nodes[i];
}
}
}
}
async scrollWithRefresh(lineIndex: number) {
await Zotero.Promise.delay(500);
let editorInstance =
await this._Addon.WorkspaceWindow.getWorkspaceEditorInstance();
if (!editorInstance) {
return;
}
this._Addon.EditorViews.scrollToLine(editorInstance, lineIndex);
}
async convertNoteLines(
currentNote: Zotero.Item,
rootNoteIds: number[],
convertNoteLinks: boolean = true
): Promise<{ lines: string[]; subNotes: Zotero.Item[] }> {
Zotero.debug(`convert note ${currentNote.id}`);
let subNotes: Zotero.Item[] = [];
const [..._rootNoteIds] = rootNoteIds;
_rootNoteIds.push(currentNote.id);
let newLines: string[] = [];
const noteLines = this.getLinesInNote(currentNote);
for (let i in noteLines) {
newLines.push(noteLines[i]);
// Convert Link
if (convertNoteLinks) {
let link = this._Addon.NoteParse.parseLinkInText(noteLines[i]);
while (link) {
const linkIndex = noteLines[i].indexOf(link);
const params = this._Addon.NoteParse.parseParamsFromLink(link);
if (
params.ignore ||
// Ignore links that are not in <a>
!noteLines[i].slice(linkIndex - 8, linkIndex).includes("href")
) {
Zotero.debug("ignore link");
noteLines[i] = noteLines[i].substring(
noteLines[i].search(/zotero:\/\/note\//g)
);
noteLines[i] = noteLines[i].substring(
noteLines[i].search(/<\/a>/g) + "</a>".length
);
link = this._Addon.NoteParse.parseLinkInText(noteLines[i]);
continue;
}
Zotero.debug("convert link");
let res = await this.getNoteFromLink(link);
const subNote = res.item;
if (subNote && _rootNoteIds.indexOf(subNote.id) === -1) {
Zotero.debug(`Knowledge4Zotero: Exporting sub-note ${link}`);
const convertResult = await this.convertNoteLines(
subNote,
_rootNoteIds,
convertNoteLinks
);
const subNoteLines = convertResult.lines;
const templateText =
await this._Addon.TemplateController.renderTemplateAsync(
"[QuickImport]",
"subNoteLines, subNoteItem, noteItem",
[subNoteLines, subNote, currentNote]
);
newLines.push(templateText);
subNotes.push(subNote);
subNotes = subNotes.concat(convertResult.subNotes);
}
noteLines[i] = noteLines[i].substring(
noteLines[i].search(/zotero:\/\/note\//g)
);
noteLines[i] = noteLines[i].substring(
noteLines[i].search(/<\/a>/g) + "</a>".length
);
link = this._Addon.NoteParse.parseLinkInText(noteLines[i]);
}
}
}
Zotero.debug(subNotes);
return { lines: newLines, subNotes: subNotes };
}
async getNoteFromLink(uri: string) {
const params = this._Addon.NoteParse.parseParamsFromLink(uri);
if (!params.libraryID) {
return {
item: false,
infoText: "Library does not exist or access denied.",
};
}
Zotero.debug(params);
let item = await Zotero.Items.getByLibraryAndKeyAsync(
params.libraryID,
params.noteKey
);
if (!item || !item.isNote()) {
return {
item: false,
args: params,
infoText: "Note does not exist or is not a note.",
};
}
return {
item: item,
args: params,
infoText: "OK",
};
}
public async onSelectionChange(editor: Zotero.EditorInstance) {
// Update current line index
const _window = editor._iframeWindow;
const selection = _window.document.getSelection();
if (!selection || !selection.focusNode) {
return;
}
const realElement = selection.focusNode.parentElement;
let focusNode = selection.focusNode as XUL.Element;
if (!focusNode || !realElement) {
return;
}
function getChildIndex(node) {
return Array.prototype.indexOf.call(node.parentNode.childNodes, node);
}
// Make sure this is a direct child node of editor
try {
while (
focusNode.parentElement &&
(!focusNode.parentElement.className ||
focusNode.parentElement.className.indexOf("primary-editor") === -1)
) {
focusNode = focusNode.parentNode as XUL.Element;
}
} catch (e) {
return;
}
if (!focusNode.parentElement) {
return;
}
let currentLineIndex = getChildIndex(focusNode);
// Parse list
const diveTagNames = ["OL", "UL", "LI"];
// Find list elements before current line
const listElements = Array.prototype.filter.call(
Array.prototype.slice.call(
focusNode.parentElement.childNodes,
0,
currentLineIndex
),
(e) => diveTagNames.includes(e.tagName)
);
for (const e of listElements) {
currentLineIndex += this._Addon.NoteParse.parseListElements(e).length - 1;
}
// Find list index if current line is inside a list
if (diveTagNames.includes(focusNode.tagName)) {
const eleList = this._Addon.NoteParse.parseListElements(focusNode);
for (const i in eleList) {
if (realElement.parentElement === eleList[i]) {
currentLineIndex += Number(i);
break;
}
}
}
Zotero.debug(`Knowledge4Zotero: line ${currentLineIndex} selected.`);
console.log(currentLineIndex);
Zotero.debug(
`Current Element: ${focusNode.outerHTML}; Real Element: ${realElement.outerHTML}`
);
this.currentLine[editor._item.id] = currentLineIndex;
if (realElement.tagName === "A") {
let link = (realElement as HTMLLinkElement).href;
let linkedNote = (await this.getNoteFromLink(link)).item;
if (linkedNote) {
let t = 0;
let linkPopup = _window.document.querySelector(".link-popup");
while (
!(
linkPopup &&
(linkPopup.querySelector("a") as unknown as HTMLLinkElement)
.href === link
) &&
t < 100
) {
t += 1;
linkPopup = _window.document.querySelector(".link-popup");
await Zotero.Promise.delay(30);
}
await this._Addon.EditorViews.updateEditorPopupButtons(
editor._iframeWindow,
link
);
} else {
await this._Addon.EditorViews.updateEditorPopupButtons(
editor._iframeWindow,
undefined
);
}
}
}
}
export default NoteUtils;

File diff suppressed because it is too large Load Diff

View File

@ -1,33 +0,0 @@
export async function pick(title: string, mode: 'open' | 'save' | 'folder', filters?: [string, string][], suggestion?: string): Promise<string> {
const fp = Components.classes['@mozilla.org/filepicker;1'].createInstance(Components.interfaces.nsIFilePicker)
if (suggestion) fp.defaultString = suggestion
mode = {
open: Components.interfaces.nsIFilePicker.modeOpen,
save: Components.interfaces.nsIFilePicker.modeSave,
folder: Components.interfaces.nsIFilePicker.modeGetFolder,
}[mode]
fp.init(window, title, mode)
for (const [label, ext] of (filters || [])) {
fp.appendFilter(label, ext)
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return new Zotero.Promise(resolve => {
fp.open(userChoice => {
switch (userChoice) {
case Components.interfaces.nsIFilePicker.returnOK:
case Components.interfaces.nsIFilePicker.returnReplace:
resolve(fp.file.path)
break
default: // aka returnCancel
resolve('')
break
}
})
})
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,8 @@
import Knowledge4Zotero from "./addon";
class AddonBase {
protected _Addon: any;
constructor(parent: any) {
protected _Addon: Knowledge4Zotero;
constructor(parent: Knowledge4Zotero) {
this._Addon = parent;
}
}

View File

@ -0,0 +1,340 @@
const CryptoJS = require("crypto-js");
import Knowledge4Zotero from "../addon";
import AddonBase from "../module";
import { EditorMessage } from "../utils";
class ReaderViews extends AddonBase {
icons: object;
constructor(parent: Knowledge4Zotero) {
super(parent);
this.icons = {
createNote: `<svg t="1651630304116" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14011" width="16" height="16"><path d="M791.30324 369.7c-5 5-6.2 12.7-2.8 18.9 17.5 31.9 27.4 68.5 27.4 107.4 0 56.2-20.7 107.6-54.9 147-4.5 5.1-5.1 12.6-1.8 18.4l39.2 67.9c3.3 5.7 9.6 8.7 16.1 7.8 6-0.8 12.1-1.2 18.3-1.2 70.1 0.5 128 59.7 127.1 129.7-0.9 69.7-57.4 125.9-127.1 126.4-70.9 0.5-128.9-57.1-128.9-128 0-38.1 16.7-72.3 43.1-95.8l-37-64c-4.2-7.3-13.3-10-20.9-6.4-29.3 14.2-62.3 22.2-97.2 22.2-26.7 0-52.3-4.7-76-13.2-7.3-2.6-15.4 0.3-19.3 7l-24.9 43.1c-3.1 5.4-2.8 12.1 0.8 17.2 15 21.2 23.7 47.1 23.5 75.1-0.7 69.5-57.5 126.2-127 126.8-71.6 0.6-129.8-57.7-129.1-129.4 0.8-69.7 58-126.5 127.8-126.6 12 0 23.7 1.6 34.8 4.7 7 2 14.5-1.1 18.2-7.4l21.7-37.6c3.7-6.4 2.5-14.6-2.9-19.6-33.6-31.2-57.5-72.6-67-119.2-1.5-7.5-8-12.9-15.7-12.9h-92c-6.9 0-13.1 4.5-15.2 11.1C232.80324 590.2 184.70324 627 128.00324 627 57.00324 627-0.49676 569.2 0.00324 498.1 0.40324 427.5 58.60324 370.3 129.20324 371c54.2 0.5 100.4 34.8 118.5 82.8C250.00324 460 256.00324 464 262.60324 464h94.1c7.6 0 14.2-5.3 15.7-12.7 11-54.2 41.5-101.3 84-133.6 6.4-4.9 8.2-13.8 4.2-20.8l-2.2-3.8c-3.5-6-10.3-9-17.1-7.7-8.8 1.8-18 2.7-27.4 2.5-69.5-1-126.9-60.1-126-129.6 0.9-70.3 58.4-126.9 129-126.3 69.3 0.6 126 57 127 126.2 0.4 31.6-10.6 60.7-29.3 83.2-4.3 5.2-5 12.5-1.6 18.3l6.6 11.4c3.6 6.2 10.8 9.3 17.7 7.5 17.5-4.4 35.8-6.7 54.6-6.7 52.3 0 100.4 17.9 138.6 48 6.4 5 15.5 4.5 21.2-1.2l24.2-24.2c4.7-4.7 6-11.8 3.3-17.8-7.3-16.1-11.3-34-11.3-52.8 0-70.7 57.3-128 128-128 70.6 0 128 57.4 128 128 0 70.7-57.3 128-128 128-20.7 0-40.2-4.9-57.5-13.6-6.2-3.1-13.7-2-18.7 2.9l-28.4 28.5z" p-id="14012" fill="#ffd400"></path></svg>`,
ocrTex: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><defs><style>.cls-1{fill-opacity: 0;}.cls-2{fill:#ffd400;}</style></defs><rect class="cls-1" width="24" height="24"/><path class="cls-2" d="M9,7.1H2.33L2.14,9.56H2.4c.15-1.77.32-2.14,2-2.14a3.39,3.39,0,0,1,.59,0c.23,0,.23.16.23.41v5.77c0,.37,0,.53-1.15.53H3.61v.34c.45,0,1.56,0,2.06,0s1.64,0,2.09,0v-.34H7.32c-1.15,0-1.15-.16-1.15-.53V7.86c0-.22,0-.37.19-.41a3.9,3.9,0,0,1,.63,0c1.65,0,1.81.37,2,2.14h.27L9,7.1Z"/><path class="cls-2" d="M14.91,14.15h-.27c-.28,1.68-.53,2.48-2.41,2.48H10.78c-.52,0-.54-.08-.54-.44V13.27h1c1.06,0,1.19.35,1.19,1.28h.27v-2.9h-.27c0,.94-.13,1.28-1.19,1.28h-1V10.3c0-.36,0-.44.54-.44h1.41c1.68,0,2,.61,2.14,2.13h.27l-.3-2.46H8.14v.33H8.4c.84,0,.86.12.86.52v5.73c0,.4,0,.52-.86.52H8.14V17h6.31Z"/><path class="cls-2" d="M18.22,10.27l1.5-2.2a1.67,1.67,0,0,1,1.58-.71V7H18.69v.33c.44,0,.68.25.68.5a.37.37,0,0,1-.1.26L18,10,16.61,7.85a.46.46,0,0,1-.07-.16c0-.13.24-.32.7-.33V7c-.37,0-1.18,0-1.59,0s-1,0-1.43,0v.33h.21c.6,0,.81.08,1,.38l2,3-1.79,2.64a1.67,1.67,0,0,1-1.58.73v.34H16.7v-.34c-.5,0-.69-.31-.69-.51s0-.14.11-.26l1.55-2.3,1.73,2.62s.06.09.06.12-.24.32-.72.33v.34c.39,0,1.19,0,1.6,0s1,0,1.42,0v-.34h-.2c-.58,0-.81-.06-1-.4l-2.3-3.49Z"/></svg>`,
};
}
async addReaderAnnotationButton(reader: _ZoteroReaderInstance) {
if (!reader) {
return false;
}
await reader._initPromise;
let updateCount = 0;
const _document = reader._iframeWindow.document;
for (const moreButton of _document.getElementsByClassName("more")) {
if (moreButton.getAttribute("knowledgeinit") === "true") {
updateCount += 1;
continue;
}
moreButton.setAttribute("knowledgeinit", "true");
const createNoteButton = _document.createElement("div");
createNoteButton.setAttribute("style", "margin: 5px;");
createNoteButton.title = "Quick Note";
createNoteButton.innerHTML = this.icons["createNote"];
let annotationWrapper = moreButton;
while (!annotationWrapper.getAttribute("data-sidebar-annotation-id")) {
annotationWrapper = annotationWrapper.parentElement;
}
const itemKey = annotationWrapper.getAttribute(
"data-sidebar-annotation-id"
);
const libraryID = (Zotero.Items.get(reader.itemID) as Zotero.Item)
.libraryID;
const annotationItem = await Zotero.Items.getByLibraryAndKeyAsync(
libraryID,
itemKey
);
createNoteButton.addEventListener("click", async (e) => {
await this.createNoteFromAnnotation(annotationItem);
e.preventDefault();
});
createNoteButton.addEventListener("mouseover", (e: XUL.XULEvent) => {
createNoteButton.setAttribute(
"style",
"background: #F0F0F0; margin: 5px;"
);
});
createNoteButton.addEventListener("mouseout", (e: XUL.XULEvent) => {
createNoteButton.setAttribute("style", "margin: 5px;");
});
moreButton.before(createNoteButton);
if (annotationItem.annotationType === "image") {
// Image OCR
const ocrButton = _document.createElement("div");
ocrButton.setAttribute("style", "margin: 5px;");
ocrButton.innerHTML = this.icons["ocrTex"];
ocrButton.title = "OCR LaTex";
ocrButton.addEventListener("click", async (e) => {
await this.OCRImageAnnotation(
(
ocrButton.parentElement.parentElement
.nextSibling as HTMLImageElement
).src,
annotationItem
);
e.preventDefault();
});
ocrButton.addEventListener("mouseover", (e: XUL.XULEvent) => {
ocrButton.setAttribute("style", "background: #F0F0F0; margin: 5px;");
});
ocrButton.addEventListener("mouseout", (e: XUL.XULEvent) => {
ocrButton.setAttribute("style", "margin: 5px;");
});
moreButton.before(ocrButton);
}
updateCount += 1;
}
return reader.annotationItemIDs.length === updateCount;
}
public async buildReaderAnnotationButtons() {
Zotero.debug("Knowledge4Zotero: buildReaderAnnotationButton");
for (const reader of Zotero.Reader._readers) {
Zotero.debug("reader found");
let t = 0;
while (t < 100 && !(await this.addReaderAnnotationButton(reader))) {
await Zotero.Promise.delay(50);
t += 1;
}
}
}
private async createNoteFromAnnotation(annotationItem: Zotero.Item) {
if (annotationItem.annotationComment) {
const text = annotationItem.annotationComment;
let link = this._Addon.NoteParse.parseLinkInText(text);
if (link) {
const note = (await this._Addon.NoteUtils.getNoteFromLink(link)).item;
if (note && note.id) {
await this._Addon.events.onEditorEvent(
new EditorMessage("onNoteLink", {
params: {
item: note,
infoText: "OK",
},
})
);
return;
}
}
}
const note: Zotero.Item = new Zotero.Item("note");
note.libraryID = annotationItem.libraryID;
note.parentID = annotationItem.parentItem.parentID;
await note.saveTx();
ZoteroPane.openNoteWindow(note.id);
let editorInstance: Zotero.EditorInstance =
this._Addon.WorkspaceWindow.getEditorInstance(note);
let t = 0;
// Wait for editor instance
while (t < 10 && !editorInstance) {
await Zotero.Promise.delay(500);
t += 1;
editorInstance = this._Addon.WorkspaceWindow.getEditorInstance(note);
}
const renderredTemplate =
await this._Addon.TemplateController.renderTemplateAsync(
"[QuickNoteV3]",
"annotationItem, topItem, noteItem",
[annotationItem, annotationItem.parentItem.parentItem, note]
);
await this._Addon.NoteUtils.addLineToNote(
note,
renderredTemplate,
0,
false,
"before"
);
const tags = annotationItem.getTags();
for (const tag of tags) {
note.addTag(tag.tag, tag.type);
}
await note.saveTx();
annotationItem.annotationComment = `${
annotationItem.annotationComment ? annotationItem.annotationComment : ""
}\nnote link: "${this._Addon.NoteUtils.getNoteLink(note)}"`;
await annotationItem.saveTx();
}
private async OCRImageAnnotation(src: string, annotationItem: Zotero.Item) {
/*
message.content = {
params: { src: string, annotationItem: Zotero.Item }
}
*/
let result: string;
let success: boolean;
const engine = Zotero.Prefs.get("Knowledge4Zotero.OCREngine");
if (engine === "mathpix") {
const xhr = await Zotero.HTTP.request(
"POST",
"https://api.mathpix.com/v3/text",
{
headers: {
"Content-Type": "application/json; charset=utf-8",
app_id: Zotero.Prefs.get("Knowledge4Zotero.OCRMathpix.Appid"),
app_key: Zotero.Prefs.get("Knowledge4Zotero.OCRMathpix.Appkey"),
},
body: JSON.stringify({
src: src,
math_inline_delimiters: ["$", "$"],
math_display_delimiters: ["$$", "$$"],
rm_spaces: true,
}),
responseType: "json",
}
);
console.log(xhr);
if (xhr && xhr.status && xhr.status === 200 && xhr.response.text) {
result = xhr.response.text;
success = true;
} else {
result =
xhr.status === 200 ? xhr.response.error : `${xhr.status} Error`;
success = false;
}
} else if (engine === "xunfei") {
/**
* 1.Dochttps://www.xfyun.cn/doc/words/formula-discern/API.html
* 2.Error codehttps://www.xfyun.cn/document/error-code
* @author iflytek
*/
const config = {
hostUrl: "https://rest-api.xfyun.cn/v2/itr",
host: "rest-api.xfyun.cn",
appid: Zotero.Prefs.get("Knowledge4Zotero.OCRXunfei.APPID"),
apiSecret: Zotero.Prefs.get("Knowledge4Zotero.OCRXunfei.APISecret"),
apiKey: Zotero.Prefs.get("Knowledge4Zotero.OCRXunfei.APIKey"),
uri: "/v2/itr",
};
let date = new Date().toUTCString();
let postBody = getPostBody();
let digest = getDigest(postBody);
const xhr = await Zotero.HTTP.request("POST", config.hostUrl, {
headers: {
"Content-Type": "application/json",
Accept: "application/json,version=1.0",
Host: config.host,
Date: date,
Digest: digest,
Authorization: getAuthStr(date, digest),
},
body: JSON.stringify(postBody),
responseType: "json",
});
if (xhr?.response?.code === 0) {
result = xhr.response.data.region
.filter((r) => r.type === "text")
.map((r) => r.recog.content)
.join(" ")
.replace(/ifly-latex-(begin)?(end)?/g, "$");
console.log(xhr);
success = true;
} else {
result =
xhr.status === 200
? `${xhr.response.code} ${xhr.response.message}`
: `${xhr.status} Error`;
success = false;
}
function getPostBody() {
let digestObj = {
common: {
app_id: config.appid,
},
business: {
ent: "teach-photo-print",
aue: "raw",
},
data: {
image: src.split(",").pop(),
},
};
return digestObj;
}
function getDigest(body) {
return (
"SHA-256=" +
CryptoJS.enc.Base64.stringify(CryptoJS.SHA256(JSON.stringify(body)))
);
}
function getAuthStr(date, digest) {
let signatureOrigin = `host: ${config.host}\ndate: ${date}\nPOST ${config.uri} HTTP/1.1\ndigest: ${digest}`;
let signatureSha = CryptoJS.HmacSHA256(
signatureOrigin,
config.apiSecret
);
let signature = CryptoJS.enc.Base64.stringify(signatureSha);
let authorizationOrigin = `api_key="${config.apiKey}", algorithm="hmac-sha256", headers="host date request-line digest", signature="${signature}"`;
return authorizationOrigin;
}
} else if (engine === "bing") {
const xhr = await Zotero.HTTP.request(
"POST",
"https://www.bing.com/cameraexp/api/v1/getlatex",
{
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
data: src.split(",").pop(),
inputForm: "Image",
clientInfo: { platform: "edge" },
}),
responseType: "json",
}
);
if (xhr && xhr.status && xhr.status === 200 && !xhr.response.isError) {
result = xhr.response.latex
? `$${xhr.response.latex}$`
: xhr.response.ocrText;
success = true;
} else {
result =
xhr.status === 200
? xhr.response.errorMessage
: `${xhr.status} Error`;
success = false;
}
} else {
result = "OCR Engine Not Found";
success = false;
}
if (success) {
annotationItem.annotationComment = `${
annotationItem.annotationComment
? `${annotationItem.annotationComment}\n`
: ""
}${result}`;
await annotationItem.saveTx();
this._Addon.ZoteroViews.showProgressWindow(
"Better Notes OCR",
`OCR Result: ${result}`
);
} else {
this._Addon.ZoteroViews.showProgressWindow(
"Better Notes OCR",
result,
"fail"
);
}
}
}
export default ReaderViews;

View File

@ -1,114 +1,20 @@
import Knowledge4Zotero from "./addon";
import AddonBase from "./module";
import Knowledge4Zotero from "../addon";
import AddonBase from "../module";
class AddonSync extends AddonBase {
class SyncController extends AddonBase {
triggerTime: number;
private io: {
dataIn: any;
dataOut: any;
deferred?: typeof Promise;
};
private _window: Window;
constructor(parent: Knowledge4Zotero) {
super(parent);
}
doLoad(_window: Window) {
if (this._window && !this._window.closed) {
this._window.close();
}
this._window = _window;
this.io = (this._window as unknown as XUL.XULWindow).arguments[0];
this.doUpdate();
}
doUpdate() {
const syncInfo = this.getNoteSyncStatus(this.io.dataIn);
const syncPathLable = this._window.document.getElementById(
"Knowledge4Zotero-sync-path"
);
const path = `${decodeURIComponent(syncInfo.path)}/${decodeURIComponent(
syncInfo.filename
)}`;
syncPathLable.setAttribute(
"value",
path.length > 50
? `${path.slice(0, 25)}...${path.slice(path.length - 25)}`
: path
);
syncPathLable.setAttribute("tooltiptext", path);
const copyCbk = (event) => {
Zotero.Utilities.Internal.copyTextToClipboard(event.target.tooltipText);
this._Addon.views.showProgressWindow(
"Path Copied",
event.target.tooltipText
);
};
syncPathLable.removeEventListener("click", copyCbk);
syncPathLable.addEventListener("click", copyCbk);
let lastSync: string;
const lastSyncTime = Number(syncInfo.lastsync);
const currentTime = new Date().getTime();
if (currentTime - lastSyncTime <= 60000) {
lastSync = `${Math.round(
(currentTime - lastSyncTime) / 1000
)} seconds ago.`;
} else if (currentTime - lastSyncTime <= 3600000) {
lastSync = `${Math.round(
(currentTime - lastSyncTime) / 60000
)} minutes ago.`;
} else {
lastSync = new Date(lastSyncTime).toLocaleString();
}
this._window.document
.getElementById("Knowledge4Zotero-sync-lastsync")
.setAttribute("value", lastSync);
setTimeout(() => {
if (!this._window.closed) {
this.doUpdate();
}
}, 3000);
}
doUnload() {
this.io.deferred && this.io.deferred.resolve();
}
async doAccept() {
// Update Settings
let enable = (
this._window.document.getElementById(
"Knowledge4Zotero-sync-enable"
) as XUL.Checkbox
).checked;
if (!enable) {
const note = this.io.dataIn;
const allNoteIds = await this.getRelatedNoteIds(note);
const notes = Zotero.Items.get(allNoteIds) as Zotero.Item[];
for (const item of notes) {
await this.removeSyncNote(item);
}
this._Addon.views.showProgressWindow(
"Better Notes",
`Cancel sync of ${notes.length} notes.`
);
}
}
doExport() {
this.io.dataOut.export = true;
(this._window.document.querySelector("dialog") as any).acceptDialog();
}
getSyncNoteIds(): number[] {
const ids = Zotero.Prefs.get("Knowledge4Zotero.syncNoteIds") as string;
return ids.split(",").map((id: string) => Number(id));
}
isSyncNote(note: Zotero.Item): boolean {
const syncNoteIds = this._Addon.sync.getSyncNoteIds();
const syncNoteIds = this.getSyncNoteIds();
return syncNoteIds.includes(note.id);
}
@ -121,7 +27,7 @@ class AddonSync extends AddonBase {
const subNoteIds = (
await Promise.all(
linkMatches.map(async (link) =>
this._Addon.knowledge.getNoteFromLink(link)
this._Addon.NoteUtils.getNoteFromLink(link)
)
)
)
@ -132,8 +38,8 @@ class AddonSync extends AddonBase {
return allNoteIds;
}
async getRelatedNoteIdsFromNotes(notes: Zotero.Item[]): Promise<Number[]> {
let allNoteIds: Number[] = [];
async getRelatedNoteIdsFromNotes(notes: Zotero.Item[]): Promise<number[]> {
let allNoteIds: number[] = [];
for (const note of notes) {
allNoteIds = allNoteIds.concat(await this.getRelatedNoteIds(note));
}
@ -222,7 +128,9 @@ class AddonSync extends AddonBase {
items = items || (Zotero.Items.get(this.getSyncNoteIds()) as Zotero.Item[]);
const toExport = {};
const forceNoteIds = force
? await this.getRelatedNoteIdsFromNotes(useIO ? [this.io.dataIn] : items)
? await this.getRelatedNoteIdsFromNotes(
useIO ? [this._Addon.SyncInfoWindow.io.dataIn] : items
)
: [];
for (const item of items) {
const syncInfo = this.getNoteSyncStatus(item);
@ -242,12 +150,15 @@ class AddonSync extends AddonBase {
}
console.log(toExport);
for (const filepath of Object.keys(toExport)) {
await this._Addon.knowledge.syncNotesToFile(toExport[filepath], filepath);
await this._Addon.NoteExport.syncNotesToFile(
toExport[filepath],
filepath
);
}
if (this._window && !this._window.closed) {
this.doUpdate();
if (this._Addon.SyncInfoWindow._window && !this._Addon.SyncInfoWindow._window.closed) {
this._Addon.SyncInfoWindow.doUpdate();
}
}
}
export default AddonSync;
export default SyncController;

110
src/sync/syncInfoWindow.ts Normal file
View File

@ -0,0 +1,110 @@
import Knowledge4Zotero from "../addon";
import AddonBase from "../module";
class SyncInfoWindow extends AddonBase {
triggerTime: number;
public io: {
dataIn: any;
dataOut: any;
deferred?: typeof Promise;
};
public _window: Window;
constructor(parent: Knowledge4Zotero) {
super(parent);
}
doLoad(_window: Window) {
if (this._window && !this._window.closed) {
this._window.close();
}
this._window = _window;
this.io = (this._window as unknown as XUL.XULWindow).arguments[0];
this.doUpdate();
}
doUpdate() {
const syncInfo = this._Addon.SyncController.getNoteSyncStatus(
this.io.dataIn
);
const syncPathLable = this._window.document.getElementById(
"Knowledge4Zotero-sync-path"
);
const path = `${decodeURIComponent(syncInfo.path)}/${decodeURIComponent(
syncInfo.filename
)}`;
syncPathLable.setAttribute(
"value",
path.length > 50
? `${path.slice(0, 25)}...${path.slice(path.length - 25)}`
: path
);
syncPathLable.setAttribute("tooltiptext", path);
const copyCbk = (event) => {
Zotero.Utilities.Internal.copyTextToClipboard(event.target.tooltipText);
this._Addon.ZoteroViews.showProgressWindow(
"Path Copied",
event.target.tooltipText
);
};
syncPathLable.removeEventListener("click", copyCbk);
syncPathLable.addEventListener("click", copyCbk);
let lastSync: string;
const lastSyncTime = Number(syncInfo.lastsync);
const currentTime = new Date().getTime();
if (currentTime - lastSyncTime <= 60000) {
lastSync = `${Math.round(
(currentTime - lastSyncTime) / 1000
)} seconds ago.`;
} else if (currentTime - lastSyncTime <= 3600000) {
lastSync = `${Math.round(
(currentTime - lastSyncTime) / 60000
)} minutes ago.`;
} else {
lastSync = new Date(lastSyncTime).toLocaleString();
}
this._window.document
.getElementById("Knowledge4Zotero-sync-lastsync")
.setAttribute("value", lastSync);
setTimeout(() => {
if (!this._window.closed) {
this.doUpdate();
}
}, 3000);
}
doUnload() {
this.io.deferred && this.io.deferred.resolve();
}
async doAccept() {
// Update Settings
let enable = (
this._window.document.getElementById(
"Knowledge4Zotero-sync-enable"
) as XUL.Checkbox
).checked;
if (!enable) {
const note = this.io.dataIn;
const allNoteIds = await this._Addon.SyncController.getRelatedNoteIds(
note
);
const notes = Zotero.Items.get(allNoteIds) as Zotero.Item[];
for (const item of notes) {
await this._Addon.SyncController.removeSyncNote(item);
}
this._Addon.ZoteroViews.showProgressWindow(
"Better Notes",
`Cancel sync of ${notes.length} notes.`
);
}
}
doExport() {
this.io.dataOut.export = true;
(this._window.document.querySelector("dialog") as any).acceptDialog();
}
}
export default SyncInfoWindow;

View File

@ -1,7 +1,7 @@
import Knowledge4Zotero from "./addon";
import AddonBase from "./module";
import Knowledge4Zotero from "../addon";
import AddonBase from "../module";
class AddonSyncList extends AddonBase {
class SyncListWindow extends AddonBase {
private _window: Window;
constructor(parent: Knowledge4Zotero) {
super(parent);
@ -30,7 +30,7 @@ class AddonSyncList extends AddonBase {
return;
}
const notes = Zotero.Items.get(
this._Addon.sync.getSyncNoteIds()
this._Addon.SyncController.getSyncNoteIds()
) as Zotero.Item[];
const listbox = this._window.document.getElementById("sync-list");
let e,
@ -40,7 +40,7 @@ class AddonSyncList extends AddonBase {
e.parentElement.removeChild(e);
}
for (const note of notes) {
const syncInfo = this._Addon.sync.getNoteSyncStatus(note);
const syncInfo = this._Addon.SyncController.getNoteSyncStatus(note);
const listitem: XUL.ListItem =
this._window.document.createElement("listitem");
listitem.setAttribute("id", note.id);
@ -119,7 +119,7 @@ class AddonSyncList extends AddonBase {
if (selectedItems.length === 0) {
return;
}
await this._Addon.sync.doSync(selectedItems, true, false);
await this._Addon.SyncController.doSync(selectedItems, true, false);
this.doUpdate();
}
@ -128,7 +128,7 @@ class AddonSyncList extends AddonBase {
if (selectedItems.length === 0) {
return;
}
await this._Addon.knowledge.exportNotesToFile(selectedItems, false, true);
await this._Addon.NoteExport.exportNotesToFile(selectedItems, false, true);
this.doUpdate();
}
@ -154,16 +154,16 @@ class AddonSyncList extends AddonBase {
return;
}
if (this.useRelated()) {
let noteIds: number[] = await this._Addon.sync.getRelatedNoteIdsFromNotes(
let noteIds: number[] = await this._Addon.SyncController.getRelatedNoteIdsFromNotes(
selectedItems
);
selectedItems = Zotero.Items.get(noteIds) as Zotero.Item[];
}
for (const note of selectedItems) {
await this._Addon.sync.removeSyncNote(note);
await this._Addon.SyncController.removeSyncNote(note);
}
this.doUpdate();
}
}
export default AddonSyncList;
export default SyncListWindow;

View File

@ -1,9 +1,8 @@
import Knowledge4Zotero from "./addon";
import { NoteTemplate } from "./base";
import AddonBase from "./module";
import Knowledge4Zotero from "../addon";
import { NoteTemplate } from "../utils";
import AddonBase from "../module";
class AddonTemplate extends AddonBase {
private _window: Window;
class TemplateController extends AddonBase {
_systemTemplateNames: string[];
_defaultTemplates: NoteTemplate[];
constructor(parent: Knowledge4Zotero) {
@ -12,7 +11,7 @@ class AddonTemplate extends AddonBase {
"[QuickInsert]",
"[QuickBackLink]",
"[QuickImport]",
"[QuickNoteV2]",
"[QuickNoteV3]",
"[ExportMDFileName]",
];
this._defaultTemplates = [
@ -32,8 +31,8 @@ class AddonTemplate extends AddonBase {
disabled: false,
},
{
name: "[QuickNoteV2]",
text: '${await new Promise(async (r) => {\nlet res = ""\nif(annotationItem.annotationComment){\nres += Zotero.Knowledge4Zotero.parse.parseMDToHTML(annotationItem.annotationComment);\n}\nres += await Zotero.Knowledge4Zotero.parse.parseAnnotationHTML(noteItem, [annotationItem], true);\nr(res);})}',
name: "[QuickNoteV3]",
text: '${await new Promise(async (r) => {\nlet res = ""\nif(annotationItem.annotationComment){\nres += Zotero.Knowledge4Zotero.NoteParse.parseMDToHTML(annotationItem.annotationComment);\n}\nres += await Zotero.Knowledge4Zotero.NoteParse.parseAnnotationHTML(noteItem, [annotationItem], true);\nr(res);})}',
disabled: false,
},
{
@ -69,248 +68,6 @@ class AddonTemplate extends AddonBase {
];
}
openEditor() {
if (this._window && !this._window.closed) {
this._window.focus();
} else {
window.open(
"chrome://Knowledge4Zotero/content/template.xul",
"_blank",
"chrome,extrachrome,centerscreen,width=800,height=400,resizable=yes"
);
}
}
initTemplates(_window: Window) {
this._window = _window;
this.updateTemplateView();
}
resetTemplates() {
let oldTemplatesRaw: string = Zotero.Prefs.get(
"Knowledge4Zotero.noteTemplate"
) as string;
// Convert old version
if (oldTemplatesRaw) {
const templates: NoteTemplate[] = JSON.parse(oldTemplatesRaw);
for (const template of templates) {
this.setTemplate(template);
}
Zotero.Prefs.clear("Knowledge4Zotero.noteTemplate");
}
// Convert buggy template
if (!this.getTemplateText("[QuickBackLink]").includes("ignore=1")) {
this.setTemplate(
this._defaultTemplates.find((t) => t.name === "[QuickBackLink]")
);
this._Addon.views.showProgressWindow(
"Better Notes",
"The [QuickBackLink] is reset because of missing ignore=1 in link."
);
}
let templateKeys = this.getTemplateKeys();
const currentNames = templateKeys.map((t) => t.name);
for (const defaultTemplate of this._defaultTemplates) {
if (!currentNames.includes(defaultTemplate.name)) {
this.setTemplate(defaultTemplate);
}
}
}
getCitationStyle(): Object {
let format = Zotero.Prefs.get("Knowledge4Zotero.citeFormat") as string;
try {
if (format) {
format = JSON.parse(format);
} else {
throw Error("format not initialized");
}
} catch (e) {
format = Zotero.QuickCopy.getFormatFromURL(
Zotero.QuickCopy.lastActiveURL
);
format = Zotero.QuickCopy.unserializeSetting(format);
Zotero.Prefs.set("Knowledge4Zotero.citeFormat", JSON.stringify(format));
}
return format;
}
getSelectedTemplateName(): string {
const listbox: XUL.ListItem =
this._window.document.getElementById("template-list");
const selectedItem = listbox.selectedItem;
if (selectedItem) {
const name = selectedItem.getAttribute("id");
return name;
}
return "";
}
updateTemplateView() {
const templates = this.getTemplateKeys();
const listbox = this._window.document.getElementById("template-list");
let e,
es = this._window.document.getElementsByTagName("listitem");
while (es.length > 0) {
e = es[0];
e.parentElement.removeChild(e);
}
for (const template of templates) {
const listitem = this._window.document.createElement("listitem");
listitem.setAttribute("id", template.name);
const name = this._window.document.createElement("listcell");
name.setAttribute("label", template.name);
if (this._systemTemplateNames.includes(template.name)) {
listitem.style.color = "#f2ac46";
}
listitem.append(name);
listbox.append(listitem);
}
this.updateEditorView();
}
updateEditorView() {
Zotero.debug("update editor");
console.log("update editor");
const name = this.getSelectedTemplateName();
const templateText = this.getTemplateText(name);
const header: XUL.Textbox =
this._window.document.getElementById("editor-name");
const text: XUL.Textbox =
this._window.document.getElementById("editor-textbox");
const saveTemplate = this._window.document.getElementById("save-template");
const deleteTemplate =
this._window.document.getElementById("delete-template");
const resetTemplate =
this._window.document.getElementById("reset-template");
if (!name) {
header.value = "";
header.setAttribute("disabled", "true");
text.value = "";
text.setAttribute("disabled", "true");
saveTemplate.setAttribute("disabled", "true");
deleteTemplate.setAttribute("disabled", "true");
deleteTemplate.hidden = false;
resetTemplate.hidden = true;
} else {
header.value = name;
if (!this._systemTemplateNames.includes(name)) {
header.removeAttribute("disabled");
deleteTemplate.hidden = false;
resetTemplate.hidden = true;
} else {
header.setAttribute("disabled", "true");
deleteTemplate.setAttribute("disabled", "true");
deleteTemplate.hidden = true;
resetTemplate.hidden = false;
}
text.value = templateText;
text.removeAttribute("disabled");
saveTemplate.removeAttribute("disabled");
deleteTemplate.removeAttribute("disabled");
}
}
createTemplate() {
const template: NoteTemplate = {
name: `New Template: ${new Date().getTime()}`,
text: "",
disabled: false,
};
this.setTemplate(template);
this.updateTemplateView();
}
async importNoteTemplate() {
const io = {
// Not working
singleSelection: true,
dataIn: null,
dataOut: null,
deferred: Zotero.Promise.defer(),
};
(window as unknown as XUL.XULWindow).openDialog(
"chrome://zotero/content/selectItemsDialog.xul",
"",
"chrome,dialog=no,centerscreen,resizable=yes",
io
);
await io.deferred.promise;
const ids = io.dataOut;
const note: Zotero.Item = (Zotero.Items.get(ids) as Zotero.Item[]).filter(
(item: Zotero.Item) => item.isNote()
)[0];
if (!note) {
return;
}
const template: NoteTemplate = {
name: `Template from ${note.getNoteTitle()}: ${new Date().getTime()}`,
text: note.getNote(),
disabled: false,
};
this.setTemplate(template);
this.updateTemplateView();
}
saveSelectedTemplate() {
const name = this.getSelectedTemplateName();
const header: XUL.Textbox =
this._window.document.getElementById("editor-name");
const text: XUL.Textbox =
this._window.document.getElementById("editor-textbox");
if (this._systemTemplateNames.includes(name) && header.value !== name) {
this._Addon.views.showProgressWindow(
"Better Notes",
`Template ${name} is a system template. Modifying template name is not allowed.`
);
return;
}
const template = this.getTemplateKey(name);
template.name = header.value;
template.text = text.value;
this.setTemplate(template);
if (name !== template.name) {
this.removeTemplate(name);
}
this._Addon.views.showProgressWindow(
"Better Notes",
`Template ${template.name} saved.`
);
this.updateTemplateView();
}
deleteSelectedTemplate() {
const name = this.getSelectedTemplateName();
if (this._systemTemplateNames.includes(name)) {
this._Addon.views.showProgressWindow(
"Better Notes",
`Template ${name} is a system template. Removing system template is note allowed.`
);
return;
}
this.removeTemplate(name);
this.updateTemplateView();
}
resetSelectedTemplate() {
const name = this.getSelectedTemplateName();
if (this._systemTemplateNames.includes(name)) {
const text: XUL.Textbox =
this._window.document.getElementById("editor-textbox");
text.value = this._defaultTemplates.find((t) => t.name === name).text;
this._Addon.views.showProgressWindow(
"Better Notes",
`Template ${name} is reset. Please save before leaving.`
);
}
}
renderTemplate(
key: string,
argString: string = "",
@ -468,6 +225,97 @@ class AddonTemplate extends AddonBase {
this.removeTemplateKey(keyName);
Zotero.Prefs.clear(`Knowledge4Zotero.template.${keyName}`);
}
resetTemplates() {
let oldTemplatesRaw: string = Zotero.Prefs.get(
"Knowledge4Zotero.noteTemplate"
) as string;
// Convert old version
if (oldTemplatesRaw) {
const templates: NoteTemplate[] = JSON.parse(oldTemplatesRaw);
for (const template of templates) {
this._Addon.TemplateController.setTemplate(template);
}
Zotero.Prefs.clear("Knowledge4Zotero.noteTemplate");
}
// Convert buggy template
if (
!this._Addon.TemplateController.getTemplateText(
"[QuickBackLink]"
).includes("ignore=1")
) {
this._Addon.TemplateController.setTemplate(
this._Addon.TemplateController._defaultTemplates.find(
(t) => t.name === "[QuickBackLink]"
)
);
this._Addon.ZoteroViews.showProgressWindow(
"Better Notes",
"The [QuickBackLink] is reset because of missing ignore=1 in link."
);
}
let templateKeys = this._Addon.TemplateController.getTemplateKeys();
const currentNames = templateKeys.map((t) => t.name);
for (const defaultTemplate of this._Addon.TemplateController
._defaultTemplates) {
if (!currentNames.includes(defaultTemplate.name)) {
this._Addon.TemplateController.setTemplate(defaultTemplate);
}
}
}
getCitationStyle(): {
mode: string;
contentType: string;
id: string;
locale: string;
} {
let format = Zotero.Prefs.get("Knowledge4Zotero.citeFormat") as string;
try {
if (format) {
format = JSON.parse(format);
} else {
throw Error("format not initialized");
}
} catch (e) {
format = Zotero.QuickCopy.getFormatFromURL(
Zotero.QuickCopy.lastActiveURL
);
format = Zotero.QuickCopy.unserializeSetting(format);
Zotero.Prefs.set("Knowledge4Zotero.citeFormat", JSON.stringify(format));
}
return format as any;
}
}
export default AddonTemplate;
/*
* This part is for the template usage only
* to keep API consistency
*/
class TemplateAPI extends AddonBase {
constructor(parent: Knowledge4Zotero) {
super(parent);
}
public getNoteLink(
note: Zotero.Item,
options: {
ignore?: boolean;
withLine?: boolean;
} = { ignore: false, withLine: false }
) {
return this._Addon.NoteUtils.getNoteLink(note, options);
}
public async getWorkspaceEditorInstance(
type: "main" | "preview" = "main",
wait: boolean = true
) {
return await this._Addon.WorkspaceWindow.getWorkspaceEditorInstance(
type,
wait
);
}
}
export { TemplateController, TemplateAPI };

View File

@ -0,0 +1,214 @@
import Knowledge4Zotero from "../addon";
import { NoteTemplate } from "../utils";
import AddonBase from "../module";
class TemplateWindow extends AddonBase {
private _window: Window;
constructor(parent: Knowledge4Zotero) {
super(parent);
}
openEditor() {
if (this._window && !this._window.closed) {
this._window.focus();
} else {
window.open(
"chrome://Knowledge4Zotero/content/template.xul",
"_blank",
"chrome,extrachrome,centerscreen,width=800,height=400,resizable=yes"
);
}
}
initTemplates(_window: Window) {
this._window = _window;
this.updateTemplateView();
}
getSelectedTemplateName(): string {
const listbox: XUL.ListItem =
this._window.document.getElementById("template-list");
const selectedItem = listbox.selectedItem;
if (selectedItem) {
const name = selectedItem.getAttribute("id");
return name;
}
return "";
}
updateTemplateView() {
const templates = this._Addon.TemplateController.getTemplateKeys();
const listbox = this._window.document.getElementById("template-list");
let e,
es = this._window.document.getElementsByTagName("listitem");
while (es.length > 0) {
e = es[0];
e.parentElement.removeChild(e);
}
for (const template of templates) {
const listitem = this._window.document.createElement("listitem");
listitem.setAttribute("id", template.name);
const name = this._window.document.createElement("listcell");
name.setAttribute("label", template.name);
if (
this._Addon.TemplateController._systemTemplateNames.includes(
template.name
)
) {
listitem.style.color = "#f2ac46";
}
listitem.append(name);
listbox.append(listitem);
}
this.updateEditorView();
}
updateEditorView() {
Zotero.debug("update editor");
console.log("update editor");
const name = this.getSelectedTemplateName();
const templateText = this._Addon.TemplateController.getTemplateText(name);
const header: XUL.Textbox =
this._window.document.getElementById("editor-name");
const text: XUL.Textbox =
this._window.document.getElementById("editor-textbox");
const saveTemplate = this._window.document.getElementById("save-template");
const deleteTemplate =
this._window.document.getElementById("delete-template");
const resetTemplate =
this._window.document.getElementById("reset-template");
if (!name) {
header.value = "";
header.setAttribute("disabled", "true");
text.value = "";
text.setAttribute("disabled", "true");
saveTemplate.setAttribute("disabled", "true");
deleteTemplate.setAttribute("disabled", "true");
deleteTemplate.hidden = false;
resetTemplate.hidden = true;
} else {
header.value = name;
if (!this._Addon.TemplateController._systemTemplateNames.includes(name)) {
header.removeAttribute("disabled");
deleteTemplate.hidden = false;
resetTemplate.hidden = true;
} else {
header.setAttribute("disabled", "true");
deleteTemplate.setAttribute("disabled", "true");
deleteTemplate.hidden = true;
resetTemplate.hidden = false;
}
text.value = templateText;
text.removeAttribute("disabled");
saveTemplate.removeAttribute("disabled");
deleteTemplate.removeAttribute("disabled");
}
}
createTemplate() {
const template: NoteTemplate = {
name: `New Template: ${new Date().getTime()}`,
text: "",
disabled: false,
};
this._Addon.TemplateController.setTemplate(template);
this.updateTemplateView();
}
async importNoteTemplate() {
const io = {
// Not working
singleSelection: true,
dataIn: null,
dataOut: null,
deferred: Zotero.Promise.defer(),
};
(window as unknown as XUL.XULWindow).openDialog(
"chrome://zotero/content/selectItemsDialog.xul",
"",
"chrome,dialog=no,centerscreen,resizable=yes",
io
);
await io.deferred.promise;
const ids = io.dataOut;
const note: Zotero.Item = (Zotero.Items.get(ids) as Zotero.Item[]).filter(
(item: Zotero.Item) => item.isNote()
)[0];
if (!note) {
return;
}
const template: NoteTemplate = {
name: `Template from ${note.getNoteTitle()}: ${new Date().getTime()}`,
text: note.getNote(),
disabled: false,
};
this._Addon.TemplateController.setTemplate(template);
this.updateTemplateView();
}
saveSelectedTemplate() {
const name = this.getSelectedTemplateName();
const header: XUL.Textbox =
this._window.document.getElementById("editor-name");
const text: XUL.Textbox =
this._window.document.getElementById("editor-textbox");
if (
this._Addon.TemplateController._systemTemplateNames.includes(name) &&
header.value !== name
) {
this._Addon.ZoteroViews.showProgressWindow(
"Better Notes",
`Template ${name} is a system template. Modifying template name is not allowed.`
);
return;
}
const template = this._Addon.TemplateController.getTemplateKey(name);
template.name = header.value;
template.text = text.value;
this._Addon.TemplateController.setTemplate(template);
if (name !== template.name) {
this._Addon.TemplateController.removeTemplate(name);
}
this._Addon.ZoteroViews.showProgressWindow(
"Better Notes",
`Template ${template.name} saved.`
);
this.updateTemplateView();
}
deleteSelectedTemplate() {
const name = this.getSelectedTemplateName();
if (this._Addon.TemplateController._systemTemplateNames.includes(name)) {
this._Addon.ZoteroViews.showProgressWindow(
"Better Notes",
`Template ${name} is a system template. Removing system template is note allowed.`
);
return;
}
this._Addon.TemplateController.removeTemplate(name);
this.updateTemplateView();
}
resetSelectedTemplate() {
const name = this.getSelectedTemplateName();
if (this._Addon.TemplateController._systemTemplateNames.includes(name)) {
const text: XUL.Textbox =
this._window.document.getElementById("editor-textbox");
text.value = this._Addon.TemplateController._defaultTemplates.find(
(t) => t.name === name
).text;
this._Addon.ZoteroViews.showProgressWindow(
"Better Notes",
`Template ${name} is reset. Please save before leaving.`
);
}
}
}
export default TemplateWindow;

View File

@ -1,291 +0,0 @@
var mergeSort, findInsertIndex;
mergeSort = require('mergesort');
findInsertIndex = require('find-insert-index');
module.exports = (function () {
'use strict';
var walkStrategies;
walkStrategies = {};
function k(result) {
return function () {
return result;
};
}
function TreeModel(config) {
config = config || {};
this.config = config;
this.config.childrenPropertyName = config.childrenPropertyName || 'children';
this.config.modelComparatorFn = config.modelComparatorFn;
}
function addChildToNode(node, child) {
child.parent = node;
node.children.push(child);
return child;
}
function Node(config, model) {
this.config = config;
this.model = model;
this.children = [];
}
TreeModel.prototype.parse = function (model) {
var i, childCount, node;
// if (!(model instanceof Object)) {
// throw new TypeError('Model must be of type object.');
// }
node = new Node(this.config, model);
if (model[this.config.childrenPropertyName] instanceof Array) {
if (this.config.modelComparatorFn) {
model[this.config.childrenPropertyName] = mergeSort(
this.config.modelComparatorFn,
model[this.config.childrenPropertyName]);
}
for (i = 0, childCount = model[this.config.childrenPropertyName].length; i < childCount; i++) {
addChildToNode(node, this.parse(model[this.config.childrenPropertyName][i]));
}
}
return node;
};
function hasComparatorFunction(node) {
return typeof node.config.modelComparatorFn === 'function';
}
Node.prototype.isRoot = function () {
return this.parent === undefined;
};
Node.prototype.hasChildren = function () {
return this.children.length > 0;
};
function addChild(self, child, insertIndex) {
var index;
if (!(child instanceof Node)) {
throw new TypeError('Child must be of type Node.');
}
child.parent = self;
if (!(self.model[self.config.childrenPropertyName] instanceof Array)) {
self.model[self.config.childrenPropertyName] = [];
}
if (hasComparatorFunction(self)) {
// Find the index to insert the child
index = findInsertIndex(
self.config.modelComparatorFn,
self.model[self.config.childrenPropertyName],
child.model);
// Add to the model children
self.model[self.config.childrenPropertyName].splice(index, 0, child.model);
// Add to the node children
self.children.splice(index, 0, child);
} else {
if (insertIndex === undefined) {
self.model[self.config.childrenPropertyName].push(child.model);
self.children.push(child);
} else {
if (insertIndex < 0 || insertIndex > self.children.length) {
throw new Error('Invalid index.');
}
self.model[self.config.childrenPropertyName].splice(insertIndex, 0, child.model);
self.children.splice(insertIndex, 0, child);
}
}
return child;
}
Node.prototype.addChild = function (child) {
return addChild(this, child);
};
Node.prototype.addChildAtIndex = function (child, index) {
if (hasComparatorFunction(this)) {
throw new Error('Cannot add child at index when using a comparator function.');
}
return addChild(this, child, index);
};
Node.prototype.setIndex = function (index) {
if (hasComparatorFunction(this)) {
throw new Error('Cannot set node index when using a comparator function.');
}
if (this.isRoot()) {
if (index === 0) {
return this;
}
throw new Error('Invalid index.');
}
if (index < 0 || index >= this.parent.children.length) {
throw new Error('Invalid index.');
}
var oldIndex = this.parent.children.indexOf(this);
this.parent.children.splice(index, 0, this.parent.children.splice(oldIndex, 1)[0]);
this.parent.model[this.parent.config.childrenPropertyName]
.splice(index, 0, this.parent.model[this.parent.config.childrenPropertyName].splice(oldIndex, 1)[0]);
return this;
};
Node.prototype.getPath = function () {
var path = [];
(function addToPath(node) {
path.unshift(node);
if (!node.isRoot()) {
addToPath(node.parent);
}
})(this);
return path;
};
Node.prototype.getIndex = function () {
if (this.isRoot()) {
return 0;
}
return this.parent.children.indexOf(this);
};
/**
* Parse the arguments of traversal functions. These functions can take one optional
* first argument which is an options object. If present, this object will be stored
* in args.options. The only mandatory argument is the callback function which can
* appear in the first or second position (if an options object is given). This
* function will be saved to args.fn. The last optional argument is the context on
* which the callback function will be called. It will be available in args.ctx.
*
* @returns Parsed arguments.
*/
function parseArgs() {
var args = {};
if (arguments.length === 1) {
if (typeof arguments[0] === 'function') {
args.fn = arguments[0];
} else {
args.options = arguments[0];
}
} else if (arguments.length === 2) {
if (typeof arguments[0] === 'function') {
args.fn = arguments[0];
args.ctx = arguments[1];
} else {
args.options = arguments[0];
args.fn = arguments[1];
}
} else {
args.options = arguments[0];
args.fn = arguments[1];
args.ctx = arguments[2];
}
args.options = args.options || {};
if (!args.options.strategy) {
args.options.strategy = 'pre';
}
if (!walkStrategies[args.options.strategy]) {
throw new Error('Unknown tree walk strategy. Valid strategies are \'pre\' [default], \'post\' and \'breadth\'.');
}
return args;
}
Node.prototype.walk = function () {
var args;
args = parseArgs.apply(this, arguments);
walkStrategies[args.options.strategy].call(this, args.fn, args.ctx);
};
walkStrategies.pre = function depthFirstPreOrder(callback, context) {
var i, childCount, keepGoing;
keepGoing = callback.call(context, this);
for (i = 0, childCount = this.children.length; i < childCount; i++) {
if (keepGoing === false) {
return false;
}
keepGoing = depthFirstPreOrder.call(this.children[i], callback, context);
}
return keepGoing;
};
walkStrategies.post = function depthFirstPostOrder(callback, context) {
var i, childCount, keepGoing;
for (i = 0, childCount = this.children.length; i < childCount; i++) {
keepGoing = depthFirstPostOrder.call(this.children[i], callback, context);
if (keepGoing === false) {
return false;
}
}
keepGoing = callback.call(context, this);
return keepGoing;
};
walkStrategies.breadth = function breadthFirst(callback, context) {
var queue = [this];
(function processQueue() {
var i, childCount, node;
if (queue.length === 0) {
return;
}
node = queue.shift();
for (i = 0, childCount = node.children.length; i < childCount; i++) {
queue.push(node.children[i]);
}
if (callback.call(context, node) !== false) {
processQueue();
}
})();
};
Node.prototype.all = function () {
var args, all = [];
args = parseArgs.apply(this, arguments);
args.fn = args.fn || k(true);
walkStrategies[args.options.strategy].call(this, function (node) {
if (args.fn.call(args.ctx, node)) {
all.push(node);
}
}, args.ctx);
return all;
};
Node.prototype.first = function () {
var args, first;
args = parseArgs.apply(this, arguments);
args.fn = args.fn || k(true);
walkStrategies[args.options.strategy].call(this, function (node) {
if (args.fn.call(args.ctx, node)) {
first = node;
return false;
}
}, args.ctx);
return first;
};
Node.prototype.drop = function () {
var indexOfChild;
if (!this.isRoot()) {
indexOfChild = this.parent.children.indexOf(this);
this.parent.children.splice(indexOfChild, 1);
this.parent.model[this.config.childrenPropertyName].splice(indexOfChild, 1);
this.parent = undefined;
delete this.parent;
}
return this;
};
return TreeModel;
})();

View File

@ -76,4 +76,45 @@ class CopyHelper {
);
}
}
export { EditorMessage, OutlineType, NoteTemplate, CopyHelper };
async function pick(
title: string,
mode: "open" | "save" | "folder",
filters?: [string, string][],
suggestion?: string
): Promise<string> {
const fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(
Components.interfaces.nsIFilePicker
);
if (suggestion) fp.defaultString = suggestion;
mode = {
open: Components.interfaces.nsIFilePicker.modeOpen,
save: Components.interfaces.nsIFilePicker.modeSave,
folder: Components.interfaces.nsIFilePicker.modeGetFolder,
}[mode];
fp.init(window, title, mode);
for (const [label, ext] of filters || []) {
fp.appendFilter(label, ext);
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return new Zotero.Promise((resolve) => {
fp.open((userChoice) => {
switch (userChoice) {
case Components.interfaces.nsIFilePicker.returnOK:
case Components.interfaces.nsIFilePicker.returnReplace:
resolve(fp.file.path);
break;
default: // aka returnCancel
resolve("");
break;
}
});
});
}
export { EditorMessage, OutlineType, NoteTemplate, CopyHelper, pick };

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,9 @@
/*
* This file contains the first-run wizard window code
*/
import Knowledge4Zotero from "./addon";
import { EditorMessage } from "./base";
import { EditorMessage } from "./utils";
import AddonBase from "./module";
class AddonWizard extends AddonBase {
@ -272,7 +276,7 @@ class AddonWizard extends AddonBase {
Zotero.locale === "zh-CN" ? this.templateCN : this.template
);
await this._Addon.events.onEditorEvent(
new EditorMessage("setMainKnowledge", {
new EditorMessage("setMainNote", {
params: { itemID: noteID, enableConfirm: false, enableOpen: true },
})
);

View File

@ -0,0 +1,49 @@
import Knowledge4Zotero from "../addon";
import { OutlineType } from "../utils";
import AddonBase from "../module";
class WorkspaceMenu extends AddonBase {
constructor(parent: Knowledge4Zotero) {
super(parent);
}
public getWorkspaceMenuWindow(): Window {
return this._Addon.WorkspaceWindow.workspaceTabId
? this._Addon.WorkspaceWindow.workspaceTabId !== "WINDOW"
? window
: this._Addon.WorkspaceWindow.getWorkspaceWindow()
: window;
}
public updateViewMenu() {
Zotero.debug(
`updateViewMenu, ${this._Addon.WorkspaceOutline.currentOutline}`
);
const _mainWindow = this.getWorkspaceMenuWindow();
const treeview = _mainWindow.document.getElementById("menu_treeview");
this._Addon.WorkspaceOutline.currentOutline === OutlineType.treeView
? treeview.setAttribute("checked", true as any)
: treeview.removeAttribute("checked");
const mindmap = _mainWindow.document.getElementById("menu_mindmap");
this._Addon.WorkspaceOutline.currentOutline === OutlineType.mindMap
? mindmap.setAttribute("checked", true as any)
: mindmap.removeAttribute("checked");
const bubblemap = _mainWindow.document.getElementById("menu_bubblemap");
this._Addon.WorkspaceOutline.currentOutline === OutlineType.bubbleMap
? bubblemap.setAttribute("checked", true as any)
: bubblemap.removeAttribute("checked");
const noteFontSize = Zotero.Prefs.get("note.fontSize");
for (let menuitem of this._Addon.WorkspaceWindow.workspaceWindow.document.querySelectorAll(
`#note-font-size-menu menuitem`
)) {
if (parseInt(menuitem.getAttribute("label")) == noteFontSize) {
menuitem.setAttribute("checked", true as any);
} else {
menuitem.removeAttribute("checked");
}
}
}
}
export default WorkspaceMenu;

View File

@ -0,0 +1,94 @@
import Knowledge4Zotero from "../addon";
import { OutlineType } from "../utils";
import AddonBase from "../module";
class WorkspaceOutline extends AddonBase {
public currentOutline: OutlineType;
public currentNodeID: number;
constructor(parent: Knowledge4Zotero) {
super(parent);
this.currentOutline = OutlineType.treeView;
this.currentNodeID = -1;
}
public switchView(newType: OutlineType = undefined) {
if (!newType) {
newType = this.currentOutline + 1;
}
if (newType > OutlineType.bubbleMap) {
newType = OutlineType.treeView;
}
const mindmap =
this._Addon.WorkspaceWindow.workspaceWindow.document.getElementById(
"mindmap-container"
);
const oldIframe =
this._Addon.WorkspaceWindow.workspaceWindow.document.getElementById(
"mindmapIframe"
);
if (oldIframe) {
oldIframe.remove();
}
this.currentOutline = newType;
const srcList = [
"",
"chrome://Knowledge4Zotero/content/treeView.html",
"chrome://Knowledge4Zotero/content/mindMap.html",
"chrome://Knowledge4Zotero/content/bubbleMap.html",
];
const iframe =
this._Addon.WorkspaceWindow.workspaceWindow.document.createElement(
"iframe"
);
iframe.setAttribute("id", "mindmapIframe");
iframe.setAttribute("src", srcList[this.currentOutline]);
mindmap.append(iframe);
this.resizeOutline();
this.updateOutline();
// Clear stored node id
this.currentNodeID = -1;
this._Addon.WorkspaceMenu.updateViewMenu();
}
public async updateOutline() {
Zotero.debug("Knowledge4Zotero: updateMindMap");
// await this._initIframe.promise;
const _window = this._Addon.WorkspaceWindow.getWorkspaceWindow();
if (!_window) {
return;
}
const iframe = _window.document.getElementById(
"mindmapIframe"
) as HTMLIFrameElement;
iframe.contentWindow.postMessage(
{
type: "setMindMapData",
nodes: this._Addon.NoteUtils.getNoteTreeAsList(
this._Addon.WorkspaceWindow.getWorkspaceNote(),
true,
false
),
},
"*"
);
}
public resizeOutline() {
const iframe =
this._Addon.WorkspaceWindow.workspaceWindow.document.getElementById(
"mindmapIframe"
);
const container =
this._Addon.WorkspaceWindow.workspaceWindow.document.getElementById(
"zotero-knowledge-outline"
);
if (iframe) {
iframe.style.height = `${container.clientHeight - 60}px`;
iframe.style.width = `${container.clientWidth - 10}px`;
}
}
}
export default WorkspaceOutline;

View File

@ -0,0 +1,333 @@
import Knowledge4Zotero from "../addon";
import { EditorMessage, OutlineType } from "../utils";
import AddonBase from "../module";
class WorkspaceWindow extends AddonBase {
private _initIframe: ZoteroPromise;
public workspaceWindow: Window;
public workspaceTabId: string;
public workspaceNoteEditor: Zotero.EditorInstance | undefined;
public previewItemID: number;
private _firstInit: boolean;
public _workspacePromise: ZoteroPromise;
constructor(parent: Knowledge4Zotero) {
super(parent);
this._initIframe = Zotero.Promise.defer();
this.workspaceTabId = "";
this._firstInit = true;
}
public getWorkspaceWindow(): Window | undefined {
if (this.workspaceWindow && !this.workspaceWindow.closed) {
return this.workspaceWindow;
}
return undefined;
}
async openWorkspaceWindow(
type: "window" | "tab" = "tab",
reopen: boolean = false,
select: boolean = true
) {
if (this.getWorkspaceWindow()) {
if (!reopen) {
Zotero.debug("openWorkspaceWindow: focus");
if (this.workspaceTabId !== "WINDOW") {
Zotero_Tabs.select(this.workspaceTabId);
} else {
(this.getWorkspaceWindow() as Window).focus();
}
return;
} else {
Zotero.debug("openWorkspaceWindow: reopen");
this.closeWorkspaceWindow();
}
}
this._workspacePromise = Zotero.Promise.defer();
this._firstInit = true;
if (type === "window") {
Zotero.debug("openWorkspaceWindow: as window");
this._initIframe = Zotero.Promise.defer();
let win = window.open(
"chrome://Knowledge4Zotero/content/workspace.xul",
"_blank",
"chrome,extrachrome,menubar,resizable,scrollbars,status,width=1000,height=600"
);
this.workspaceWindow = win as Window;
this.workspaceTabId = "WINDOW";
await this.waitWorkspaceReady();
this.setWorkspaceNote("main");
this._Addon.NoteUtils.currentLine[this.getWorkspaceNote().id] = -1;
this.initKnowledgeWindow();
this._Addon.WorkspaceOutline.switchView(OutlineType.treeView);
this._Addon.WorkspaceOutline.updateOutline();
this._Addon.ZoteroViews.updateAutoInsertAnnotationsMenu();
} else {
Zotero.debug("openWorkspaceWindow: as tab");
this._initIframe = Zotero.Promise.defer();
// Avoid sidebar show up
Zotero_Tabs.jump(0);
let { id, container } = Zotero_Tabs.add({
type: "betternotes",
title: Zotero.locale.includes("zh") ? "工作区" : "Workspace",
index: 1,
data: {},
select: select,
onClose: () => (this.workspaceTabId = ""),
});
this.workspaceTabId = id;
const _iframe = window.document.createElement("browser");
_iframe.setAttribute("class", "reader");
_iframe.setAttribute("flex", "1");
_iframe.setAttribute("type", "content");
_iframe.setAttribute(
"src",
"chrome://Knowledge4Zotero/content/workspace.xul"
);
container.appendChild(_iframe);
// @ts-ignore
this.workspaceWindow = _iframe.contentWindow;
await this.waitWorkspaceReady();
this._Addon.ZoteroViews.hideMenuBar(this.workspaceWindow.document);
this._Addon.NoteUtils.currentLine[this.getWorkspaceNote().id] = -1;
this.initKnowledgeWindow();
this._Addon.WorkspaceOutline.switchView(OutlineType.treeView);
this._Addon.WorkspaceOutline.updateOutline();
}
}
public closeWorkspaceWindow() {
if (this.getWorkspaceWindow()) {
if (this.workspaceTabId !== "WINDOW") {
Zotero_Tabs.close(this.workspaceTabId);
} else {
(this.getWorkspaceWindow() as Window).close();
}
}
this.workspaceTabId = "";
}
public async waitWorkspaceReady() {
let _window = this.getWorkspaceWindow() as Window;
if (!_window) {
return false;
}
let t = 0;
await this._workspacePromise.promise;
return true;
}
public initKnowledgeWindow() {
this.workspaceWindow.addEventListener(
"message",
(e) => this.messageHandler(e),
false
);
this._Addon.WorkspaceOutline.currentOutline = OutlineType.treeView;
this.workspaceWindow.document
.getElementById("outline-switchview")
.addEventListener("click", async (e) => {
this._Addon.WorkspaceOutline.switchView();
});
this.workspaceWindow.addEventListener("resize", (e) =>
this._Addon.WorkspaceOutline.resizeOutline()
);
this.workspaceWindow.document
.getElementById("outline-splitter")
.addEventListener("mouseup", async (e) => {
this._Addon.WorkspaceOutline.resizeOutline();
});
this.workspaceWindow.addEventListener("close", (e) => {
this.workspaceWindow = undefined;
this.workspaceTabId = "";
this._Addon.ZoteroViews.updateAutoInsertAnnotationsMenu();
});
}
private async messageHandler(e) {
Zotero.debug(`Knowledge4Zotero: view message ${e.data.type}`);
console.log(`Knowledge4Zotero: view message ${e.data.type}`);
if (e.data.type === "ready") {
this._initIframe.resolve();
} else if (e.data.type === "getMindMapData") {
this._Addon.WorkspaceOutline.updateOutline();
} else if (e.data.type === "jumpNode") {
this._Addon.events.onEditorEvent(
new EditorMessage("jumpNode", {
params: e.data,
})
);
} else if (e.data.type === "jumpNote") {
Zotero.debug(e.data);
this._Addon.events.onEditorEvent(
new EditorMessage("onNoteLink", {
params: await this._Addon.NoteUtils.getNoteFromLink(e.data.link),
})
);
} else if (e.data.type === "moveNode") {
this._Addon.events.onEditorEvent(
new EditorMessage("moveNode", {
params: e.data,
})
);
}
}
public async getWorkspaceEditor(type: "main" | "preview" = "main") {
let _window = this._Addon.WorkspaceWindow.getWorkspaceWindow() as Window;
if (!_window) {
return;
}
await this._Addon.WorkspaceWindow.waitWorkspaceReady();
return _window.document.getElementById(`zotero-note-editor-${type}`);
}
getWorkspaceNote(): Zotero.Item {
return Zotero.Items.get(
Zotero.Prefs.get("Knowledge4Zotero.mainKnowledgeID") as number
) as Zotero.Item;
}
async getWorkspaceEditorInstance(
type: "main" | "preview" = "main",
wait: boolean = true
): Promise<Zotero.EditorInstance | undefined> {
let noteEditor = (await this.getWorkspaceEditor(type)) as any;
if (!noteEditor) {
return;
}
let t = 0;
while (wait && !noteEditor.getCurrentInstance() && t < 500) {
t += 1;
await Zotero.Promise.delay(10);
}
this.workspaceNoteEditor =
noteEditor.getCurrentInstance() as Zotero.EditorInstance;
return this.workspaceNoteEditor;
}
getEditorInstance(note: Zotero.Item) {
// If there are multiple editors of main note available, we use the workspace editor.
if (
note.id === this.getWorkspaceNote().id &&
this.getWorkspaceWindow() &&
this.workspaceNoteEditor &&
!Components.utils.isDeadWrapper(this.workspaceNoteEditor._iframeWindow)
) {
return this.workspaceNoteEditor;
}
const editor = (
Zotero.Notes._editorInstances as Zotero.EditorInstance[]
).find(
(e) =>
e._item.id === note.id &&
!Components.utils.isDeadWrapper(e._iframeWindow)
);
if (note.id === this.getWorkspaceNote().id) {
this.workspaceNoteEditor = editor;
}
return editor;
}
async setWorkspaceNote(
type: "main" | "preview" = "main",
note: Zotero.Item | undefined = undefined,
showPopup: boolean = true
) {
let _window = this.getWorkspaceWindow() as Window;
note = note || this.getWorkspaceNote();
if (!_window) {
return;
}
if (type === "preview") {
const splitter = _window.document.getElementById("preview-splitter");
splitter && splitter.setAttribute("state", "open");
this.previewItemID = note.id;
} else {
// Set line to default
this._Addon.NoteUtils.currentLine[note.id] = -1;
if (Zotero.Prefs.get("Knowledge4Zotero.mainKnowledgeID") !== note.id) {
Zotero.Prefs.set("Knowledge4Zotero.mainKnowledgeID", note.id);
}
}
await this.waitWorkspaceReady();
let noteEditor: any = await this.getWorkspaceEditor(type);
if (!noteEditor._initialized) {
noteEditor._iframe.contentWindow.addEventListener(
"drop",
(event) => {
noteEditor._iframe.contentWindow.wrappedJSObject.droppedData =
Components.utils.cloneInto(
{
"text/plain": event.dataTransfer.getData("text/plain"),
"text/html": event.dataTransfer.getData("text/html"),
"zotero/annotation":
event.dataTransfer.getData("zotero/annotation"),
"zotero/item": event.dataTransfer.getData("zotero/item"),
},
noteEditor._iframe.contentWindow
);
},
true
);
noteEditor._initialized = true;
}
noteEditor.mode = "edit";
noteEditor.viewMode = "library";
noteEditor.parent = null;
noteEditor.item = note;
if (!noteEditor || !noteEditor.getCurrentInstance()) {
await noteEditor.initEditor();
}
await noteEditor._editorInstance._initPromise;
const position = (
this._Addon.EditorViews.getEditorElement(
noteEditor._editorInstance._iframeWindow.document
).parentNode as HTMLElement
).scrollTop;
// Due to unknown reasons, only after the second init the editor will be correctly loaded.
// Thus we must init it twice
if (this._firstInit) {
this._firstInit = false;
await noteEditor.initEditor();
}
this._Addon.EditorViews.scrollToPosition(
noteEditor._editorInstance,
position
);
if (type === "main") {
this._Addon.WorkspaceOutline.updateOutline();
this._Addon.ZoteroViews.updateWordCount();
const recentMainNotes = Zotero.Items.get(
new Array(
...new Set(
(
Zotero.Prefs.get("Knowledge4Zotero.recentMainNoteIds") as string
).split(",")
)
)
) as Zotero.Item[];
recentMainNotes.splice(0, 0, note);
Zotero.Prefs.set(
"Knowledge4Zotero.recentMainNoteIds",
new Array(...new Set(recentMainNotes.map((item) => String(item.id))))
.slice(0, 10)
.filter((id) => id)
.join(",")
);
if (showPopup) {
this._Addon.ZoteroViews.showProgressWindow(
"Better Notes",
`Set main Note to: ${note.getNoteTitle()}`
);
}
}
}
}
export default WorkspaceWindow;

1414
src/zotero/events.ts Normal file

File diff suppressed because it is too large Load Diff

333
src/zotero/views.ts Normal file
View File

@ -0,0 +1,333 @@
import Knowledge4Zotero from "../addon";
import { EditorMessage } from "../utils";
import AddonBase from "../module";
class ZoteroViews extends AddonBase {
progressWindowIcon: object;
icons: object;
constructor(parent: Knowledge4Zotero) {
super(parent);
this.progressWindowIcon = {
success: "chrome://zotero/skin/tick.png",
fail: "chrome://zotero/skin/cross.png",
default: "chrome://Knowledge4Zotero/skin/favicon.png",
};
this.icons = {
tabIcon: `<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="icon icon-bg"><path d="M791.30324 369.7c-5 5-6.2 12.7-2.8 18.9 17.5 31.9 27.4 68.5 27.4 107.4 0 56.2-20.7 107.6-54.9 147-4.5 5.1-5.1 12.6-1.8 18.4l39.2 67.9c3.3 5.7 9.6 8.7 16.1 7.8 6-0.8 12.1-1.2 18.3-1.2 70.1 0.5 128 59.7 127.1 129.7-0.9 69.7-57.4 125.9-127.1 126.4-70.9 0.5-128.9-57.1-128.9-128 0-38.1 16.7-72.3 43.1-95.8l-37-64c-4.2-7.3-13.3-10-20.9-6.4-29.3 14.2-62.3 22.2-97.2 22.2-26.7 0-52.3-4.7-76-13.2-7.3-2.6-15.4 0.3-19.3 7l-24.9 43.1c-3.1 5.4-2.8 12.1 0.8 17.2 15 21.2 23.7 47.1 23.5 75.1-0.7 69.5-57.5 126.2-127 126.8-71.6 0.6-129.8-57.7-129.1-129.4 0.8-69.7 58-126.5 127.8-126.6 12 0 23.7 1.6 34.8 4.7 7 2 14.5-1.1 18.2-7.4l21.7-37.6c3.7-6.4 2.5-14.6-2.9-19.6-33.6-31.2-57.5-72.6-67-119.2-1.5-7.5-8-12.9-15.7-12.9h-92c-6.9 0-13.1 4.5-15.2 11.1C232.80324 590.2 184.70324 627 128.00324 627 57.00324 627-0.49676 569.2 0.00324 498.1 0.40324 427.5 58.60324 370.3 129.20324 371c54.2 0.5 100.4 34.8 118.5 82.8C250.00324 460 256.00324 464 262.60324 464h94.1c7.6 0 14.2-5.3 15.7-12.7 11-54.2 41.5-101.3 84-133.6 6.4-4.9 8.2-13.8 4.2-20.8l-2.2-3.8c-3.5-6-10.3-9-17.1-7.7-8.8 1.8-18 2.7-27.4 2.5-69.5-1-126.9-60.1-126-129.6 0.9-70.3 58.4-126.9 129-126.3 69.3 0.6 126 57 127 126.2 0.4 31.6-10.6 60.7-29.3 83.2-4.3 5.2-5 12.5-1.6 18.3l6.6 11.4c3.6 6.2 10.8 9.3 17.7 7.5 17.5-4.4 35.8-6.7 54.6-6.7 52.3 0 100.4 17.9 138.6 48 6.4 5 15.5 4.5 21.2-1.2l24.2-24.2c4.7-4.7 6-11.8 3.3-17.8-7.3-16.1-11.3-34-11.3-52.8 0-70.7 57.3-128 128-128 70.6 0 128 57.4 128 128 0 70.7-57.3 128-128 128-20.7 0-40.2-4.9-57.5-13.6-6.2-3.1-13.7-2-18.7 2.9l-28.4 28.5z" fill="#f2ac46"/></svg>`,
openWorkspaceCollectionView: `<svg t="1651317033804" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2432" width="100%" height="100%"><path d="M874.9 459.4c-18.8 0-34 15.2-34 34v355.7c0 18.6-15.5 33.7-34.5 33.7H181.5c-19 0-34.5-15.1-34.5-33.7V232.3c0-18.6 15.5-33.7 34.5-33.7H541c18.8 0 34-15.2 34-34s-15.2-34-34-34H181.5C125 130.6 79 176.2 79 232.3v616.8c0 56 46 101.7 102.5 101.7h624.9c56.5 0 102.5-45.6 102.5-101.7V493.4c0-18.8-15.2-34-34-34z" fill="currentColor" p-id="2433"></path><path d="M885.5 82.7H657.1c-18.8 0-34 15.2-34 34s15.2 34 34 34h169.7L358.5 619.1c-13.3 13.3-13.3 34.8 0 48.1 6.6 6.6 15.3 10 24 10s17.4-3.3 24-10l470-470v169.7c0 18.8 15.2 34 34 34s34-15.2 34-34V141.5c0.1-32.4-26.4-58.8-59-58.8z" fill="currentColor" p-id="2434"></path></svg>`,
};
}
public hideMenuBar(_document: Document) {
_document.getElementById("better-notes-menu").hidden = true;
}
public keppDefaultMenuOrder() {
const fileMenu = document.querySelector("#menu_FilePopup");
const editMenu = document.querySelector("#menu_EditPopup");
const exit = fileMenu.querySelector("#menu_FileQuitItem");
// exit.remove();
const prefs = editMenu.querySelector("#menu_preferences");
// prefs.remove();
if (exit) {
for (const ele of fileMenu.querySelectorAll(".menu-betternotes")) {
exit.before(ele);
}
}
if (prefs) {
for (const ele of editMenu.querySelectorAll(".menu-betternotes")) {
prefs.before(ele);
}
}
}
public 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));
// Disable Zotero pdf export
(document.getElementById("menu_export_files") as XUL.Element).disabled =
!hidden;
}
public switchKey(disabled: boolean) {
document
.querySelectorAll(".key-type-betternotes")
.forEach((el) => (el as XUL.Element).setAttribute("disabled", disabled));
}
public addNewMainNoteButton() {
// Top toolbar button
let addNoteItem = document
.getElementById("zotero-tb-note-add")
.getElementsByTagName("menuitem")[1];
let button = document.createElement("menuitem");
button.setAttribute("id", "zotero-tb-knowledge-openwindow");
button.setAttribute("label", "New Main Note");
button.addEventListener("click", (e) => {
this._Addon.events.onEditorEvent(
new EditorMessage("createWorkspace", {})
);
});
button.setAttribute("class", "menuitem-iconic");
button.setAttribute(
"style",
"list-style-image: url('chrome://Knowledge4Zotero/skin/favicon.png');"
);
addNoteItem.after(button);
}
public addOpenWorkspaceButton() {
// Left collection tree view button
const treeRow = document.createElement("html:div");
treeRow.setAttribute("class", "row");
treeRow.setAttribute(
"style",
"height: 22px; margin: 0 0 0 0; padding: 0 6px 0 6px;"
);
const span1 = document.createElement("span");
span1.setAttribute("class", "cell label primary");
const span2 = document.createElement("span");
span2.setAttribute("class", "icon icon-twisty twisty open");
span2.innerHTML = this.icons["openWorkspaceCollectionView"];
const span3 = document.createElement("span");
span3.setAttribute("class", "icon icon-bg cell-icon");
span3.setAttribute(
"style",
"background-image:url(chrome://Knowledge4Zotero/skin/favicon.png)"
);
const span4 = document.createElement("span");
span4.setAttribute("class", "cell-text");
span4.setAttribute("style", "margin-left: 6px;");
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", { event: e })
);
});
treeRow.addEventListener("mouseover", (e: XUL.XULEvent) => {
treeRow.setAttribute(
"style",
"height: 22px; margin: 0 0 0 0; padding: 0 6px 0 6px; background-color: grey;"
);
});
treeRow.addEventListener("mouseleave", (e: XUL.XULEvent) => {
treeRow.setAttribute(
"style",
"height: 22px; margin: 0 0 0 0; padding: 0 6px 0 6px;"
);
});
treeRow.addEventListener("mousedown", (e: XUL.XULEvent) => {
treeRow.setAttribute(
"style",
"height: 22px; margin: 0 0 0 0; padding: 0 6px 0 6px; color: #FFFFFF;"
);
});
treeRow.addEventListener("mouseup", (e: XUL.XULEvent) => {
treeRow.setAttribute(
"style",
"height: 22px; margin: 0 0 0 0; padding: 0 6px 0 6px;"
);
});
document
.getElementById("zotero-collections-tree-container")
.children[0].before(treeRow);
}
public updateTemplateMenu(type: "Note" | "Item" | "Text") {
const _window = this._Addon.WorkspaceMenu.getWorkspaceMenuWindow();
// If tab is open but not selected, we use copy mode
const copyMode =
Boolean(
this._Addon.WorkspaceWindow.workspaceTabId &&
this._Addon.WorkspaceWindow.workspaceTabId !== "WINDOW" &&
Zotero_Tabs.selectedID !== this._Addon.WorkspaceWindow.workspaceTabId
) || !this._Addon.WorkspaceWindow.workspaceTabId;
Zotero.debug(`updateTemplateMenu`);
let templates = this._Addon.TemplateController.getTemplateKeys()
.filter((e) => e.name.indexOf(type) !== -1)
.filter(
(e) =>
!this._Addon.TemplateController._systemTemplateNames.includes(e.name)
);
const popup = _window.document.getElementById(
`menu_insert${type}TemplatePopup`
);
popup.innerHTML = "";
if (templates.length === 0) {
templates = [
{
name: "No Template",
text: "",
disabled: true,
},
];
}
for (const template of templates) {
const menuitem = _window.document.createElement("menuitem");
menuitem.setAttribute("id", template.name);
menuitem.setAttribute("label", template.name);
menuitem.setAttribute(
"oncommand",
`
Zotero.Knowledge4Zotero.events.onEditorEvent({
type: "insert${type}UsingTemplate",
content: {
params: { templateName: "${template.name}", copy: ${copyMode} },
},
});`
);
if (template.disabled) {
menuitem.setAttribute("disabled", true as any);
}
popup.append(menuitem);
}
}
public updateCitationStyleMenu() {
const _window = this._Addon.WorkspaceMenu.getWorkspaceMenuWindow();
Zotero.debug(`updateCitationStyleMenu`);
const popup = _window.document.getElementById("menu_citeSettingPopup");
popup.innerHTML = "";
let format = this._Addon.TemplateController.getCitationStyle();
// add styles to list
const styles = Zotero.Styles.getVisible();
styles.forEach(function (style) {
const val = JSON.stringify({
mode: "bibliography",
contentType: "html",
id: style.styleID,
locale: "",
});
const itemNode: XUL.Element = document.createElement("menuitem");
itemNode.setAttribute("value", val);
itemNode.setAttribute("label", style.title);
itemNode.setAttribute("type", "checkbox");
itemNode.setAttribute(
"oncommand",
"Zotero.Prefs.set('Knowledge4Zotero.citeFormat', event.target.value)"
);
popup.appendChild(itemNode);
if (format.id == style.styleID) {
itemNode.setAttribute("checked", true);
}
});
}
public updateOCRStyleMenu() {
Zotero.debug(`updateOCRStyleMenu`);
const popup = document.getElementById("menu_ocrsettingpopup");
Array.prototype.forEach.call(popup.children, (e) =>
e.setAttribute("checked", false)
);
let engine = Zotero.Prefs.get("Knowledge4Zotero.OCREngine");
if (!engine) {
engine = "bing";
Zotero.Prefs.set("Knowledge4Zotero.OCREngine", engine);
}
(
document.getElementById(`menu_ocr_${engine}_betternotes`) as XUL.Menuitem
).setAttribute("checked", true);
}
public updateWordCount() {
const _window = this._Addon.WorkspaceMenu.getWorkspaceMenuWindow();
if (!_window) {
return;
}
Zotero.debug("updateWordCount");
const menuitem = _window.document.getElementById(
"menu_wordcount_betternotes"
);
function fnGetCpmisWords(str) {
let sLen = 0;
try {
// replace break lines
str = str.replace(/(\r\n+|\s+| +)/g, "龘");
// letter, numbers to 'm' letter
str = str.replace(/[\x00-\xff]/g, "m");
// make neighbor 'm' to be one letter
str = str.replace(/m+/g, "*");
// remove white space
str = str.replace(/龘+/g, "");
sLen = str.length;
} catch (e) {}
return sLen;
}
menuitem.setAttribute(
"label",
`Word Count: ${fnGetCpmisWords(
this._Addon.NoteParse.parseNoteHTML(
this._Addon.WorkspaceWindow.getWorkspaceNote()
).innerText
)}`
);
}
public updateAutoInsertAnnotationsMenu() {
const _window = this._Addon.WorkspaceMenu.getWorkspaceMenuWindow();
Zotero.debug("updateAutoInsertAnnotationsMenu");
let autoAnnotation = Zotero.Prefs.get("Knowledge4Zotero.autoAnnotation");
if (typeof autoAnnotation === "undefined") {
autoAnnotation = false;
Zotero.Prefs.set("Knowledge4Zotero.autoAnnotation", autoAnnotation);
}
const menuitem: XUL.Element = _window.document.getElementById(
"menu_autoannotation_betternotes"
);
if (autoAnnotation) {
menuitem.setAttribute("checked", true);
} else {
menuitem.removeAttribute("checked");
}
// Hide main window menu if the standalone window is already opened
window.document.getElementById("menu_autoannotation_betternotes").hidden =
_window !== window;
}
public showProgressWindow(
header: string,
context: string,
type: "default" | "success" | "fail" = "default",
t: number = 5000
) {
let progressWindow = new Zotero.ProgressWindow({ closeOnClick: true });
progressWindow.changeHeadline(header);
progressWindow.progress = new progressWindow.ItemProgress(
this.progressWindowIcon[type],
context
);
progressWindow.show();
if (t > 0) {
progressWindow.startCloseTimer(t);
}
return progressWindow;
}
public changeProgressWindowDescription(progressWindow: any, context: string) {
if (!progressWindow || progressWindow.closed) {
return;
}
progressWindow.progress._itemText.innerHTML = context;
}
}
export default ZoteroViews;

4
typing/global.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare interface ZoteroPromise {
promise: Promise<void>;
resolve: () => void;
}