diff --git a/addon/locale/en-US/addon.ftl b/addon/locale/en-US/addon.ftl index e82c237..641755f 100644 --- a/addon/locale/en-US/addon.ftl +++ b/addon/locale/en-US/addon.ftl @@ -22,6 +22,7 @@ menuAddNote-newMainNote-enterNoteTitle = Enter new note title: menuAddNote-newMainNote-openWorkspaceTab = Open note workspace now? menuAddNote-newTemplateStandaloneNote = New Standalone Note from Template menuAddNote-newTemplateItemNote = New Item Note from Template +menuAddNote-importMD = Import MarkDown File as Note menuAddReaderNote-newTemplateNote = New Item Note from Template @@ -122,3 +123,4 @@ templatePicker-itemData-title = Choose Item Template Data Source alert-notValidCollectionError = Please select a valid collection. alert-notValidParentItemError = No valid parent item. alert-notValidWorkspaceNote = Workspace note is not set. Create one? +alert-syncImportedNotes = Keep imported notes in sync with MarkDown files? diff --git a/addon/locale/ru-RU/addon.ftl b/addon/locale/ru-RU/addon.ftl index 7321651..50f366e 100644 --- a/addon/locale/ru-RU/addon.ftl +++ b/addon/locale/ru-RU/addon.ftl @@ -22,6 +22,7 @@ menuAddNote-newMainNote-enterNoteTitle=Ввести имя новой замет menuAddNote-newMainNote-openWorkspaceTab=Открыть пространство заметок? menuAddNote-newTemplateStandaloneNote=Новая отдельная заметка из шаблона menuAddNote-newTemplateItemNote=Новая элементная заметка из шаблона +menuAddNote-importMD = Импорт файла MarkDown в качестве примечания menuAddReaderNote-newTemplateNote=Новая элементная заметка из шаблона @@ -122,3 +123,4 @@ templatePicker-itemData-title=Choose Item Template Data Source alert-notValidCollectionError=Выберите валидную коллекцию. alert-notValidParentItemError=Нет валидного родительского элемента. alert-notValidWorkspaceNote=Заметка рабочего пространства не установлена. Создать? +alert-syncImportedNotes = Синхронизировать импортированные заметки с файлами MarkDown? diff --git a/addon/locale/zh-CN/addon.ftl b/addon/locale/zh-CN/addon.ftl index 5cae6d0..7240259 100644 --- a/addon/locale/zh-CN/addon.ftl +++ b/addon/locale/zh-CN/addon.ftl @@ -22,6 +22,7 @@ menuAddNote-newMainNote-enterNoteTitle=请输入新笔记的标题: menuAddNote-newMainNote-openWorkspaceTab=现在打开笔记工作区吗? menuAddNote-newTemplateStandaloneNote=从模板新建独立笔记 menuAddNote-newTemplateItemNote=从模板新建条目子笔记 +menuAddNote-importMD = 导入MarkDown为笔记 menuAddReaderNote-newTemplateNote=从模板新建条目子笔记 @@ -80,7 +81,7 @@ syncInfo-sync=同步 syncInfo-unSync=取消同步 syncInfo-reveal=在文件夹中显示 syncInfo-manager=同步管理 -syncInfo-export=导出为--- +syncInfo-export=导出为... syncInfo-cancel=关闭 sync-start-hint=自动同步已启用, 间隔 @@ -122,3 +123,4 @@ templatePicker-itemData-title=选择条目模板数据源 alert-notValidCollectionError=请选择一个有效的分类。 alert-notValidParentItemError=无效的父条目。 alert-notValidWorkspaceNote=工作区笔记未设置。创建一个吗? +alert-syncImportedNotes = 保持导入的笔记与 MarkDown 文件同步? diff --git a/package.json b/package.json index 905850e..a1b3be8 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "unist-util-visit": "^4.1.2", "unist-util-visit-parents": "^5.1.3", "yamljs": "^0.3.0", - "zotero-plugin-toolkit": "2.1.5" + "zotero-plugin-toolkit": "^2.1.6" }, "devDependencies": { "@types/browser-or-node": "^1.3.0", diff --git a/src/hooks.ts b/src/hooks.ts index 30e799f..39d72a9 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -44,6 +44,7 @@ import { showTemplateEditor } from "./modules/template/editorWindow"; import { createNoteFromTemplate, createWorkspaceNote, + createNoteFromMD, } from "./modules/createNote"; import { annotationTagAction } from "./modules/annotationTagAction"; @@ -302,6 +303,8 @@ const onCreateWorkspaceNote = createWorkspaceNote; const onCreateNoteFromTemplate = createNoteFromTemplate; +const onCreateNoteFromMD = createNoteFromMD; + // Add your hooks here. For element click, etc. // Keep in mind hooks only do dispatch. Don't add code that does real jobs in hooks. // Otherwise the code would be hard to read and maintain. @@ -328,4 +331,5 @@ export default { onShowTemplateEditor, onCreateWorkspaceNote, onCreateNoteFromTemplate, + onCreateNoteFromMD, }; diff --git a/src/modules/createNote.ts b/src/modules/createNote.ts index 3aca0e5..72ed372 100644 --- a/src/modules/createNote.ts +++ b/src/modules/createNote.ts @@ -1,7 +1,7 @@ import { getString } from "../utils/locale"; import { config } from "../../package.json"; -export { createWorkspaceNote, createNoteFromTemplate }; +export { createWorkspaceNote, createNoteFromTemplate, createNoteFromMD }; async function createWorkspaceNote() { const currentCollection = ZoteroPane.getSelectedCollection(); @@ -81,3 +81,41 @@ async function createNoteFromTemplate( }); } } + +async function createNoteFromMD() { + const currentCollection = ZoteroPane.getSelectedCollection(); + if (!currentCollection) { + window.alert(getString("alert.notValidCollectionError")); + return; + } + + const syncNotes = window.confirm(getString("alert-syncImportedNotes")); + + const filePaths = (await new ztoolkit.FilePicker( + "Import MarkDown", + "multiple", + [[`MarkDown(*.md)`, `*.md`]] + ).open()) as string[]; + + if (!filePaths.length) { + return; + } + + for (const filepath of filePaths) { + const noteItem = await addon.api.$import.fromMD(filepath, { + ignoreVersion: true, + }); + if (noteItem && syncNotes) { + const pathSplit = Zotero.File.normalizeToUnix(filepath).split("/"); + const stat = await OS.File.stat(filepath); + addon.api.sync.updateSyncStatus(noteItem.id, { + itemID: noteItem.id, + path: Zotero.File.normalizeToUnix(pathSplit.slice(0, -1).join("/")), + filename: pathSplit.pop() || "", + lastsync: new Date().getTime(), + md5: "", + noteMd5: Zotero.Utilities.Internal.md5(noteItem.getNote(), false), + }); + } + } +} diff --git a/src/modules/export/api.ts b/src/modules/export/api.ts index ffdf401..ac05896 100644 --- a/src/modules/export/api.ts +++ b/src/modules/export/api.ts @@ -68,10 +68,10 @@ async function exportNotes( ); if (options.exportMD) { if (options.setAutoSync) { - const raw = await new ztoolkit.FilePicker( + const raw = (await new ztoolkit.FilePicker( `${getString("fileInterface.sync")} MarkDown File`, "folder" - ).open(); + ).open()) as string; if (raw) { const syncDir = formatPath(raw); // Hard reset sync status for input notes @@ -140,12 +140,12 @@ async function toMD( ) { let filename = options.filename; if (!filename) { - const raw = await new ztoolkit.FilePicker( + const raw = (await new ztoolkit.FilePicker( `${Zotero.getString("fileInterface.export")} MarkDown File`, "save", [["MarkDown File(*.md)", "*.md"]], `${noteItem.getNoteTitle()}.md` - ).open(); + ).open()) as string; if (!raw) return; filename = formatPath(raw, ".md"); } @@ -171,24 +171,24 @@ async function toSync( } async function toDocx(noteItem: Zotero.Item) { - const raw = await new ztoolkit.FilePicker( + const raw = (await new ztoolkit.FilePicker( `${Zotero.getString("fileInterface.export")} MS Word Docx`, "save", [["MS Word Docx File(*.docx)", "*.docx"]], `${noteItem.getNoteTitle()}.docx` - ).open(); + ).open()) as string; if (!raw) return; const filename = formatPath(raw, ".docx"); await addon.api.$export.saveDocx(filename, noteItem.id); } async function toFreeMind(noteItem: Zotero.Item) { - const raw = await new ztoolkit.FilePicker( + const raw = (await new ztoolkit.FilePicker( `${Zotero.getString("fileInterface.export")} FreeMind XML`, "save", [["FreeMind XML File(*.mm)", "*.mm"]], `${noteItem.getNoteTitle()}.mm` - ).open(); + ).open()) as string; if (!raw) return; const filename = formatPath(raw, ".mm"); await addon.api.$export.saveFreeMind(filename, noteItem.id); diff --git a/src/modules/menu.ts b/src/modules/menu.ts index 9e8641c..c318cdb 100644 --- a/src/modules/menu.ts +++ b/src/modules/menu.ts @@ -108,8 +108,7 @@ export function registerMenus() { }, }); - // menuTools - // menuEdit + // menuFile const menuFileAnchor = document.querySelector( "#menu_newCollection" ) as XUL.MenuItem; @@ -227,9 +226,21 @@ export function registerMenus() { "menuFile", { tag: "menuitem", - label: getString("menuAddNote.newMainNote"), + label: getString("menuAddNote-importMD"), icon: `chrome://${config.addonRef}/content/icons/favicon.png`, - commandListener: addon.hooks.onCreateWorkspaceNote, + commandListener: () => addon.hooks.onCreateNoteFromMD(), + }, + "after", + menuFileAnchor + ); + ztoolkit.Menu.register( + "menuFile", + { + tag: "menuitem", + label: getString("menuAddNote.newTemplateItemNote"), + icon: `chrome://${config.addonRef}/content/icons/favicon.png`, + commandListener: () => + addon.hooks.onCreateNoteFromTemplate("item", "library"), }, "after", menuFileAnchor @@ -249,10 +260,9 @@ export function registerMenus() { "menuFile", { tag: "menuitem", - label: getString("menuAddNote.newTemplateItemNote"), + label: getString("menuAddNote.newMainNote"), icon: `chrome://${config.addonRef}/content/icons/favicon.png`, - commandListener: () => - addon.hooks.onCreateNoteFromTemplate("item", "library"), + commandListener: addon.hooks.onCreateWorkspaceNote, }, "after", menuFileAnchor @@ -281,6 +291,12 @@ export function registerMenus() { commandListener: () => addon.hooks.onCreateNoteFromTemplate("item", "library"), }); + ztoolkit.Menu.register(newNoteMenu, { + tag: "menuitem", + label: getString("menuAddNote-importMD"), + icon: `chrome://${config.addonRef}/content/icons/favicon.png`, + commandListener: () => addon.hooks.onCreateNoteFromMD(), + }); // create note menu in reader side panel ztoolkit.Menu.register( diff --git a/src/modules/sync/api.ts b/src/modules/sync/api.ts index dba90a1..a71426e 100644 --- a/src/modules/sync/api.ts +++ b/src/modules/sync/api.ts @@ -77,6 +77,7 @@ function updateSyncStatus(noteId: number, status: SyncStatus) { addSyncNote(noteId); setPref(`syncDetail-${noteId}`, JSON.stringify(status)); } + function getNoteStatus(noteId: number) { const noteItem = Zotero.Items.get(noteId); if (!noteItem?.isNote()) { @@ -183,6 +184,16 @@ async function getMDStatus( } async function getMDFileName(noteId: number, searchDir?: string) { + const syncStatus = getSyncStatus(noteId); + // If the note is already synced, use the filename in sync status + if ( + (!searchDir || searchDir === syncStatus.path) && + syncStatus.filename && + OS.File.exists(`${syncStatus.path}/${syncStatus.filename}`) + ) { + return syncStatus.filename; + } + // If the note is not synced or the synced file does not exists, search for the latest file with the same key const noteItem = Zotero.Items.get(noteId); if (searchDir !== undefined && (await OS.File.exists(searchDir))) { const mdRegex = /\.(md|MD|Md|mD)$/; @@ -209,6 +220,7 @@ async function getMDFileName(noteId: number, searchDir?: string) { return matchedFileName; } } + // If no file found, use the template to generate a new filename return await addon.api.template.runTemplate( "[ExportMDFileNameV2]", "noteItem",