From 6f6bcec8113f835bd03fd4e6b9753e78af37bf61 Mon Sep 17 00:00:00 2001 From: Fmajor Date: Thu, 28 Nov 2024 14:18:56 +0800 Subject: [PATCH 1/3] add menu `Match Attanger Attachment` --- src/modules/menu.ts | 71 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/src/modules/menu.ts b/src/modules/menu.ts index 371e7f6..061422e 100644 --- a/src/modules/menu.ts +++ b/src/modules/menu.ts @@ -109,6 +109,19 @@ export default class Menu { registerShortcut("matchAttachment.shortcut", async () => { await matchAttachment(); }); + // 精确匹配附件(匹配插件自己生成的附件) + ztoolkit.Menu.register("item", { + tag: "menuitem", + label: "Match Attanger Attachment", + icon: addon.data.icons.matchAttachment, + getVisibility: () => { + const items = ZoteroPane.getSelectedItems(); + return items.some((i) => i.isTopLevelItem() && i.isRegularItem()); + }, + commandListener: async (_ev) => { + await matchAttangerAttachment(); + }, + }); // 附加新文件 // 条目 const attachNewFileCallback = async () => { @@ -460,6 +473,62 @@ async function matchAttachment() { } } +async function matchAttangerAttachment() { + const sourceDir = await checkDir("sourceDir", "source path"); + if (!sourceDir) { + ztoolkit.log("source dir is empty, exit"); + return + } + const fileTypes = getPref("fileTypes") as string; + const fileTypeList = fileTypes.split(",") + ztoolkit.log('match attanger attachments for these file types: ', fileTypeList); + + const items = ZoteroPane.getSelectedItems() + .filter((i) => i.isTopLevelItem() && i.isRegularItem()) + .sort((a, b) => getPlainTitle(a).length - getPlainTitle(b).length); + + for (const item of items) { + // 使用 getCollectionPathsOfItem 获取条目所在的分类路径 + let collectionPath = getCollectionPathsOfItem(item); + if (collectionPath === undefined) collectionPath = '' + + const existAttachments = item + .getAttachments() + .map((id) => Zotero.Items.get(id)) + .filter((item) => item.isAttachment()) + .map((item) => item.getField('title')) + + const realRoot = PathUtils.joinRelative(sourceDir, collectionPath); // 拼接出实际文件目录路径 + const attachmentBaseName = Zotero.Attachments.getFileBaseNameFromItem(item); + + ztoolkit.log('item: ', item.getDisplayTitle()); + // ztoolkit.log(' collection:', collectionPath); + // ztoolkit.log(' realRoot:', realRoot); + // ztoolkit.log(' exist attachments:', existAttachments); + + for (const ext of fileTypeList) { + const fullpath = PathUtils.joinRelative(realRoot, `${attachmentBaseName}.${ext}`) + const file = Zotero.File.pathToFile(fullpath); + // Check if the file exists before attempting to import + if (file.exists()) { + try { + const attItem = await Zotero.Attachments.importFromFile({ + file: fullpath, + libraryID: item.libraryID, + parentItemID: item.id, + }); + showAttachmentItem(attItem); + ztoolkit.log('Imported attachment:', attItem.getDisplayTitle()); + } catch (error) { + ztoolkit.log('Error importing attachment:', error); + } + } else { + ztoolkit.log('File does not exist:', fullpath); + } + } + } +} + async function openUsing(fileHandler: string, fileType = "pdf") { const selectedItems = ZoteroPane.getSelectedItems(); const ids: number[] = []; @@ -673,7 +742,7 @@ export async function moveFile(attItem: any) { } // await Zotero.File.createDirectoryIfMissingAsync(destDir); // 移动文件到目标文件夹 - ztoolkit.log(sourcePath, destPath) + ztoolkit.log(sourcePath, destPath); try { await IOUtils.move(sourcePath, destPath); } catch (e) { From d896312c5f60ccbf326c62f36cb4e3078936d5eb Mon Sep 17 00:00:00 2001 From: Fmajor Date: Thu, 28 Nov 2024 15:27:24 +0800 Subject: [PATCH 2/3] add new function `getSubfolderPath` --- src/modules/menu.ts | 74 +++++++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/src/modules/menu.ts b/src/modules/menu.ts index 061422e..c3867ad 100644 --- a/src/modules/menu.ts +++ b/src/modules/menu.ts @@ -481,7 +481,7 @@ async function matchAttangerAttachment() { } const fileTypes = getPref("fileTypes") as string; const fileTypeList = fileTypes.split(",") - ztoolkit.log('match attanger attachments for these file types: ', fileTypeList); + // ztoolkit.log('match attanger attachments for these file types: ', fileTypeList); const items = ZoteroPane.getSelectedItems() .filter((i) => i.isTopLevelItem() && i.isRegularItem()) @@ -489,8 +489,8 @@ async function matchAttangerAttachment() { for (const item of items) { // 使用 getCollectionPathsOfItem 获取条目所在的分类路径 - let collectionPath = getCollectionPathsOfItem(item); - if (collectionPath === undefined) collectionPath = '' + // let collectionPath = getCollectionPathsOfItem(item); + // if (collectionPath === undefined) collectionPath = '' const existAttachments = item .getAttachments() @@ -498,13 +498,14 @@ async function matchAttangerAttachment() { .filter((item) => item.isAttachment()) .map((item) => item.getField('title')) - const realRoot = PathUtils.joinRelative(sourceDir, collectionPath); // 拼接出实际文件目录路径 + const subfolder = getSubfolderPath(item) + + const realRoot = PathUtils.joinRelative(sourceDir, subfolder); // 拼接出实际文件目录路径 const attachmentBaseName = Zotero.Attachments.getFileBaseNameFromItem(item); ztoolkit.log('item: ', item.getDisplayTitle()); - // ztoolkit.log(' collection:', collectionPath); - // ztoolkit.log(' realRoot:', realRoot); - // ztoolkit.log(' exist attachments:', existAttachments); + ztoolkit.log(' realRoot:', realRoot); + ztoolkit.log(' exist attachments:', existAttachments); for (const ext of fileTypeList) { const fullpath = PathUtils.joinRelative(realRoot, `${attachmentBaseName}.${ext}`) @@ -522,8 +523,6 @@ async function matchAttangerAttachment() { } catch (error) { ztoolkit.log('Error importing attachment:', error); } - } else { - ztoolkit.log('File does not exist:', fullpath); } } } @@ -616,39 +615,34 @@ async function renameFile(attItem: Zotero.Item, retry = 0) { return attItem; } + /** - * 移动文件 - * @param item Attachment Item + * 得到附件的中间路径(对于subfolderFormat进行格式化) + * @param item Item */ -export async function moveFile(attItem: any) { - if (!checkFileType(attItem)) { - return; - } - let destDir = await checkDir("destDir", "destination directory"); - // 1. 目标根路径 - if (!destDir) return; - // 2. 中间路径 +export function getSubfolderPath(item: Zotero.Item) { let subfolder = ""; const subfolderFormat = getPref("subfolderFormat") as string; - // Zotero.Attachments.getFileBaseNameFromItem 补充不支持的变量 - // 3. 得到最终路径 - // @ts-ignore 未添加属性 - const _getValidFileName = Zotero.File.getValidFileName; - // @ts-ignore 未添加属性 - Zotero.File.getValidFileName = (fileName) => - // @ts-ignore no-useless-escape - fileName.replace(/[?\*:|"<>]/g, ""); if (subfolderFormat.length > 0) { + // Zotero.Attachments.getFileBaseNameFromItem 补充不支持的变量 + // 3. 得到最终路径 + // @ts-ignore 未添加属性 + const _getValidFileName = Zotero.File.getValidFileName; + // @ts-ignore 未添加属性 + Zotero.File.getValidFileName = (fileName) => + // @ts-ignore no-useless-escape + fileName.replace(/[?\*:|"<>]/g, ""); + subfolder = subfolderFormat .split(/[\\/]/) .map((formatString: string) => { - ztoolkit.log(formatString); + // ztoolkit.log(formatString); if (formatString == "{{collection}}") { - return getCollectionPathsOfItem(attItem.topLevelItem); + return getCollectionPathsOfItem(item); } else { return getValidFolderName( Zotero.Attachments.getFileBaseNameFromItem( - attItem.topLevelItem, + item, formatString, ), ); @@ -662,7 +656,24 @@ export async function moveFile(attItem: any) { } // @ts-ignore 未添加属性 Zotero.File.getValidFileName = _getValidFileName; - ztoolkit.log("subfolder", subfolder); + } + return subfolder +} + +/** + * 移动文件 + * @param item Attachment Item + */ +export async function moveFile(attItem: any) { + if (!checkFileType(attItem)) { + return; + } + let destDir = await checkDir("destDir", "destination directory"); + // 1. 目标根路径 + if (!destDir) return; + // 2. 中间路径 (计算过程放入函数getSubfolderPath,其被多次复用) + const subfolder = getSubfolderPath(attItem.topLevelItem); + if (subfolder.length > 0) { destDir = PathUtils.joinRelative(destDir, subfolder); } const sourcePath = (await attItem.getFilePathAsync()) as string; @@ -742,7 +753,6 @@ export async function moveFile(attItem: any) { } // await Zotero.File.createDirectoryIfMissingAsync(destDir); // 移动文件到目标文件夹 - ztoolkit.log(sourcePath, destPath); try { await IOUtils.move(sourcePath, destPath); } catch (e) { From 1b68d85f572abb91700458d7d691545ebe99bcc1 Mon Sep 17 00:00:00 2001 From: Fmajor Date: Thu, 28 Nov 2024 17:41:42 +0800 Subject: [PATCH 3/3] only import non-exist files --- src/modules/menu.ts | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/modules/menu.ts b/src/modules/menu.ts index c3867ad..8e2598c 100644 --- a/src/modules/menu.ts +++ b/src/modules/menu.ts @@ -504,24 +504,29 @@ async function matchAttangerAttachment() { const attachmentBaseName = Zotero.Attachments.getFileBaseNameFromItem(item); ztoolkit.log('item: ', item.getDisplayTitle()); - ztoolkit.log(' realRoot:', realRoot); - ztoolkit.log(' exist attachments:', existAttachments); + ztoolkit.log('| realRoot:', realRoot); + ztoolkit.log('| exist attachments:', existAttachments); for (const ext of fileTypeList) { const fullpath = PathUtils.joinRelative(realRoot, `${attachmentBaseName}.${ext}`) const file = Zotero.File.pathToFile(fullpath); + const basename = file.leafName // Check if the file exists before attempting to import if (file.exists()) { - try { - const attItem = await Zotero.Attachments.importFromFile({ - file: fullpath, - libraryID: item.libraryID, - parentItemID: item.id, - }); - showAttachmentItem(attItem); - ztoolkit.log('Imported attachment:', attItem.getDisplayTitle()); - } catch (error) { - ztoolkit.log('Error importing attachment:', error); + if (!existAttachments.includes(basename)) { + try { + const attItem = await Zotero.Attachments.importFromFile({ + file: fullpath, + libraryID: item.libraryID, + parentItemID: item.id, + }); + showAttachmentItem(attItem); + ztoolkit.log('| Imported attachment:', attItem.getDisplayTitle()); + } catch (error) { + ztoolkit.log('| Error importing attachment:', error); + } + } else { + ztoolkit.log('| skip exists:', basename); } } }