Skip to content

Commit a95dbd0

Browse files
committed
update: register the right click menu for rename
modified: src/hooks.ts new file: src/modules/rename.ts
1 parent 0b91468 commit a95dbd0

File tree

5 files changed

+167
-31
lines changed

5 files changed

+167
-31
lines changed

addon/locale/en-US/addon.ftl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
startup-begin = Addon is loading
22
startup-finish = Addon is ready
3+
menuitem-renamePDF = Rename attachment PDF
34
menuitem-label = Addon Template: Helper Examples
45
menupopup-label = Addon Template: Menupopup
56
menuitem-submenulabel = Addon Template

addon/locale/zh-CN/addon.ftl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
startup-begin = 插件加载中
22
startup-finish = 插件已就绪
3+
menuitem-renamePDF = 重命名PDF附件
34
menuitem-label = 插件模板: 帮助工具样例
45
menupopup-label = 插件模板: 弹出菜单
56
menuitem-submenulabel = 插件模板:子菜单

src/hooks.ts

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
import { config } from "../package.json";
77
import { getString, initLocale } from "./utils/locale";
88
import { registerPrefsScripts } from "./modules/preferenceScript";
9+
import { renameSelectedItems } from "./modules/rename";
910

1011
async function onStartup() {
1112
await Promise.all([
@@ -19,36 +20,10 @@ async function onStartup() {
1920
`chrome://${config.addonRef}/content/icons/favicon.png`
2021
);
2122

22-
const popupWin = new ztoolkit.ProgressWindow(config.addonName, {
23-
closeOnClick: true,
24-
closeTime: -1,
25-
})
26-
.createLine({
27-
text: getString("startup-begin"),
28-
type: "default",
29-
progress: 0,
30-
})
31-
.show();
32-
3323
BasicExampleFactory.registerPrefs();
34-
3524
KeyExampleFactory.registerShortcuts();
36-
37-
await Zotero.Promise.delay(1000);
38-
popupWin.changeLine({
39-
progress: 30,
40-
text: `[30%] ${getString("startup-begin")}`,
41-
});
42-
43-
UIExampleFactory.registerRightClickMenuItem();
44-
45-
popupWin.changeLine({
46-
progress: 100,
47-
text: `[100%] ${getString("startup-finish")}`,
48-
});
49-
popupWin.startCloseTimer(5000);
25+
UIExampleFactory.registerRightClickMenuItemRename();
5026
}
51-
5227
function onShutdown(): void {
5328
ztoolkit.unregisterAll();
5429
addon.data.dialog?.window?.close();
@@ -98,4 +73,5 @@ export default {
9873
onShutdown,
9974
onPrefsEvent,
10075
onShortcuts,
76+
renameSelectedItems,
10177
};

src/modules/examples.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,14 +132,14 @@ export class KeyExampleFactory {
132132

133133
export class UIExampleFactory {
134134
@example
135-
static registerRightClickMenuItem() {
135+
static registerRightClickMenuItemRename() {
136136
const menuIcon = `chrome://${config.addonRef}/content/icons/favicon@0.5x.png`;
137137
// item menuitem with icon
138138
ztoolkit.Menu.register("item", {
139139
tag: "menuitem",
140-
id: "zotero-itemmenu-addontemplate-test",
141-
label: getString("menuitem-label"),
142-
// commandListener: (ev) => addon.hooks.onDialogEvents("dialogExample"),
140+
id: "zotero-itemmenu-renamePDF",
141+
label: getString("menuitem-renamePDF"),
142+
commandListener: (ev) => addon.hooks.renameSelectedItems(),
143143
icon: menuIcon,
144144
});
145145
}

src/modules/rename.ts

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import { config } from "../../package.json";
2+
export { renameSelectedItems };
3+
4+
function messageWindow(info: string, status: string) {
5+
new ztoolkit.ProgressWindow(config.addonName, {
6+
closeOnClick: true,
7+
})
8+
.createLine({
9+
text: info,
10+
type: status,
11+
icon: `chrome://${config.addonRef}/content/icons/favicon.png`,
12+
})
13+
.show();
14+
}
15+
16+
async function renameSelectedItems() {
17+
const items = getSelectedItems();
18+
if (items.length === 0) {
19+
messageWindow("No items selected", "fail");
20+
return;
21+
} else if (items.length > 1) {
22+
messageWindow(" " + items.length + " items selected", "default");
23+
}
24+
25+
for (const item of items) {
26+
const attachments = getAttachmentFromItem(item);
27+
const oldTitle = item.getField("title").toString().slice(0, 10);
28+
if (attachments.length === 0) {
29+
messageWindow("No attachments found for " + oldTitle, "fail");
30+
continue;
31+
} else if (attachments.length > 1) {
32+
messageWindow(
33+
" " + attachments.length + " attachments found for " + oldTitle,
34+
"default"
35+
);
36+
}
37+
const att = attachments[0];
38+
const newAttName = getAttachmentName(item);
39+
const status = await att.renameAttachmentFile(newAttName);
40+
if (status === true) {
41+
messageWindow(newAttName, "success");
42+
if (newAttName !== att.getField("title")) {
43+
att.setField("title", newAttName);
44+
att.saveTx();
45+
}
46+
} else if (status === -1) {
47+
messageWindow("Destination file exists; use force to overwrite.", "fail");
48+
} else {
49+
messageWindow("Attachment file not found.", "fail");
50+
}
51+
}
52+
}
53+
54+
function getSelectedItems() {
55+
let items = Zotero.getActiveZoteroPane().getSelectedItems();
56+
// get regular items
57+
let itemIds = items
58+
.filter((item) => item.isRegularItem())
59+
.map((item) => item.id as number);
60+
// get items from attachment
61+
const itemIdsFromAttachment = items
62+
.filter((item) => item.isAttachment())
63+
.map((item) => item.parentItemID as number);
64+
// remove duplicate items
65+
itemIds = itemIds.concat(itemIdsFromAttachment);
66+
itemIds = Zotero.Utilities.arrayUnique(itemIds);
67+
items = itemIds.map((id) => Zotero.Items.get(id));
68+
return items;
69+
}
70+
71+
function getAttachmentFromItem(item: Zotero.Item) {
72+
let attachments = item.getAttachments().map((id) => Zotero.Items.get(id));
73+
// attachments = attachments.filter(att => att.attachmentLinkMode === Zotero.Attachments.LINK_MODE_LINKED_FILE);
74+
attachments = attachments.filter((att) =>
75+
att.getDisplayTitle().endsWith(".pdf")
76+
);
77+
return attachments;
78+
}
79+
80+
function getAttachmentName(item: Zotero.Item) {
81+
const jst = getJournalShortTitle(item);
82+
let shortTitle = item.getField("shortTitle");
83+
if (!shortTitle) {
84+
shortTitle = item.getField("title");
85+
}
86+
const year = item.getField("year");
87+
const newFileName = `${jst}_${year}_${shortTitle}.pdf`;
88+
Zotero.debug("[renamePDF] New file name: " + newFileName);
89+
return newFileName;
90+
}
91+
92+
function getJournalShortTitle(item: Zotero.Item) {
93+
const tags = item.getTags();
94+
// Find the tag that contains the journal short title
95+
// For example, the tag might be "Object { tag: "Jab/#IJCV" }"
96+
const journalTag = tags.find((tag) => tag.tag.startsWith("Jab/#"));
97+
let title = "";
98+
if (journalTag) {
99+
title = journalTag.tag.split("/#")[1];
100+
Zotero.debug("[renamePDF] Found journal short title from tag: " + title);
101+
} else {
102+
title = generateJournalShortTitle(item);
103+
Zotero.debug("[renamePDF] Generated journal short title: " + title);
104+
if (title !== "") {
105+
item.addTag("Jab/#" + title);
106+
item.saveTx();
107+
}
108+
}
109+
return title;
110+
}
111+
112+
function generateJournalShortTitle(item: Zotero.Item) {
113+
let jst = "Pre";
114+
if (item.itemType === "journalArticle") {
115+
const journalName = item.getField("publicationTitle").toString();
116+
if (journalName.includes("arXiv")) {
117+
return jst;
118+
}
119+
jst = firstLetterOfEachWord(journalName);
120+
} else if (item.itemType === "conferencePaper") {
121+
const conferenceName = item.getField("conferenceName").toString();
122+
// use abbreviations in parentheses
123+
const patt = /.*\((.+)\)/;
124+
jst = conferenceName?.match(patt)?.[1] ?? "";
125+
if (jst === "") {
126+
// use first letter of each word
127+
jst = firstLetterOfEachWord(conferenceName);
128+
}
129+
} else if (item.itemType === "bookSection") {
130+
const bookTitle = item.getField("bookTitle").toString();
131+
// if bookTitle contains "ECCV" or "ACCV", use it as the journal short title
132+
if (bookTitle.includes("ECCV")) {
133+
jst = "ECCV";
134+
} else if (bookTitle.includes("ACCV")) {
135+
jst = "ACCV";
136+
} else {
137+
jst = "Book";
138+
}
139+
}
140+
return jst;
141+
}
142+
143+
function firstLetterOfEachWord(str: string) {
144+
// Use each capitalized initial letter of the journal title as an abbreviation
145+
const words = str.split(" ");
146+
// remove lowercase words and "IEEE", "ACM", "The", numbers, etc.
147+
const capitalizedWords = words.filter(
148+
(word) =>
149+
word[0] === word[0].toUpperCase() &&
150+
word !== "IEEE" &&
151+
word !== "ACM" &&
152+
word !== "The" &&
153+
!word.match(/\d+/)
154+
);
155+
// use first letter of each word as abbreviation
156+
const jab = capitalizedWords.map((word) => word[0]).join("");
157+
return jab;
158+
}

0 commit comments

Comments
 (0)