Skip to content

Commit

Permalink
✨ add: #95
Browse files Browse the repository at this point in the history
  • Loading branch information
volatile-static committed Feb 3, 2024
1 parent 0f82caa commit ca22df7
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 48 deletions.
11 changes: 8 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@
"greenfrog",
"groupbox",
"highcharts",
"menuseparator",
"modeless",
"Networkgraph",
"Packedbubble",
"popupshowing",
"prefs",
"Sankey",
"solidgauge",
"tdesign",
"toolbarbutton",
"toolbaritem",
"tooltiptext",
"unfiled",
"unplugin",
"wordcloud",
Expand All @@ -23,12 +28,12 @@
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[typescript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
"editor.defaultFormatter": "Vue.volar"
},
"typescript.tsdk": "node_modules/typescript/lib",
"editor.suggestSelection": "recentlyUsed",
"[javascript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
Expand All @@ -46,7 +51,7 @@
"editor.defaultFormatter": "vscode.css-language-features"
},
"[typescriptreact]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
Expand Down
11 changes: 11 additions & 0 deletions addon/content/icons/history.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 11 additions & 5 deletions src/bootstrap/addon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export default class Addon extends toolBase.BasicTool {
overviewTabID?: string;
private notifierID?: string;
private readonly prefsObserverIDs: Symbol[] = [];
private readonly listenerMap = new Map<[Node, string], EventListenerOrEventListenerObject>();

constructor() {
super();
Expand Down Expand Up @@ -121,6 +122,11 @@ export default class Addon extends toolBase.BasicTool {
);
}

registerListener(node: Node, type: string, listener: EventListenerOrEventListenerObject) {
node.addEventListener(type, listener);
this.listenerMap.set([node, type], listener);
}

/**
* 初始化插件时调用
*/
Expand All @@ -136,7 +142,8 @@ export default class Addon extends toolBase.BasicTool {
label: config.addonName,
});

document.getElementById('zotero-itemmenu')?.addEventListener(
this.registerListener(
document.getElementById('zotero-itemmenu')!,
'popupshowing',
hideDeleteMenuForHistory
);
Expand Down Expand Up @@ -195,11 +202,10 @@ export default class Addon extends toolBase.BasicTool {
this.overviewTabID && Zotero_Tabs.close(this.overviewTabID);
this.notifierID && Zotero.Notifier.unregisterObserver(this.notifierID);
this.prefsObserverIDs.forEach(id => Zotero.Prefs.unregisterObserver(id));
ZoteroPane.itemsView.onSelect.removeListener(onItemSelect);
document.getElementById('zotero-itemmenu')?.removeEventListener(
'popupshowing',
hideDeleteMenuForHistory
this.listenerMap.forEach((listener, [node, type]) =>
node.removeEventListener(type, listener)
);
ZoteroPane.itemsView.onSelect.removeListener(onItemSelect);
toolBase.unregister(this);
}

Expand Down
147 changes: 107 additions & 40 deletions src/bootstrap/modules/recent.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { config } from '../../../package.json';
import { isValid } from './utils';

export default function () {
// 注册“最近在读”菜单
Expand All @@ -19,47 +20,113 @@ export default function () {
'popupshowing',
function (this: XUL.Menu, event: Event) {
const popup = event.target as XUL.MenuPopup;
while (popup.hasChildNodes())
popup.removeChild(popup.lastChild!);
popup.replaceChildren();
if (__dev__)
addon.log('recent menu', popup.childElementCount);

addon.history
.getAll()
.map((his, id) =>
his ? { tim: his.record.lastTime ?? 0, id } : undefined
)
.filter(obj => obj)
.sort((a, b) => b!.tim - a!.tim)
.slice(0, 10)
.forEach(async obj => {
const attachment = await Zotero.Items.getAsync(obj!.id),
topLevel = attachment.parentItemID
? attachment.parentItem!
: attachment,
name = topLevel.getField('title') as string;
addon.ui.appendElement(
{
tag: 'menuitem',
classList: ['menuitem-iconic'],
styles: {
// @ts-ignore
'list-style-image': `url('${topLevel.getImageSrc()}')`,
},
attributes: {
label: name,
tooltiptext: name,
},
listeners: [
{
type: 'command',
listener: () => {
ZoteroPane.viewAttachment(obj!.id);
},
},
],
},
popup
);
});
getHistoryInfo().forEach(async info => {
const { id, name, image } = await info;
addon.ui.appendElement({
tag: 'menuitem',
classList: ['menuitem-iconic'],
styles: {
// @ts-ignore
'list-style-image': `url('${image}')`,
},
attributes: {
label: name,
tooltiptext: name,
},
listeners: [{
type: 'command',
listener: () => ZoteroPane.viewAttachment(id)
}],
}, popup);
});
}
);
addon.registerListener(Zotero_Tabs.tabsMenuPanel, 'popupshowing', addRecentTabsMenu);
addon.registerListener(document.getElementById('zotero-tabs-menu-filter')!, 'input', addRecentTabsMenu);
}

async function addRecentTabsMenu() {
const openedItems = Zotero_Tabs.getState().map(tab => tab.data?.itemID).filter(isValid),
regex = new RegExp(`(${Zotero.Utilities.quotemeta(Zotero_Tabs._tabsMenuFilter)})`, 'gi');
let tabIndex = Zotero_Tabs.tabsMenuList.querySelectorAll('*[tabindex]').length;
if (__dev__)
addon.log('recent tabs menu', tabIndex, regex);
addon.ui.appendElement({
tag: 'menuseparator',
id: 'chartero-tabs-menu-separator',
ignoreIfExists: true
}, Zotero_Tabs.tabsMenuList);

for (const info of getHistoryInfo()) {
const { id, name, iconType } = await info;
if (openedItems.includes(id) || !regex.test(name)) continue;

const title = name.replace(regex, match => {
const b = document.createElementNS('http://www.w3.org/1999/xhtml', 'b');
b.textContent = match;
return b.outerHTML;
});
addon.ui.appendElement({
tag: 'toolbaritem',
children: [{
tag: 'toolbarbutton',
attributes: {
flex: '1',
tabindex: ++tabIndex,
'aria-label': name,
'tooltiptext': name,
},
classList: ['zotero-tabs-menu-entry', 'title'],
listeners: [{
type: 'command',
listener: () => ZoteroPane.viewAttachment(id)
}],
children: [{
tag: 'span',
classList: ['icon', 'icon-css', 'tab-icon', 'icon-item-type'],
attributes: { 'data-item-type': iconType }
}, {
tag: 'description',
attributes: { flex: '1' },
properties: { innerHTML: title }
}]
}, {
tag: 'toolbarbutton',
classList: ['zotero-tabs-menu-entry'],
attributes: { tabindex: ++tabIndex },
styles: {
// @ts-ignore
'list-style-image': 'url(chrome://chartero/content/icons/history.svg)',
border: '0px',
color: 'var(--fill-secondary)',
fill: 'currentColor',
width: '22px',
pointerEvents: 'none'
}
}]
}, Zotero_Tabs.tabsMenuList);
}
}

function getHistoryInfo() {
return addon.history
.getAll()
.map((his, id) => (his ? { tim: his.record.lastTime ?? 0, id } : undefined))
.filter(isValid)
.sort((a, b) => b.tim - a.tim)
.slice(0, 10)
.map(async his => {
const attachment = await Zotero.Items.getAsync(his.id),
topLevel = attachment.topLevelItem;
return {
id: his.id,
name: topLevel.getField('title'),
image: topLevel.getImageSrc(),
iconType: topLevel.getItemTypeIconName()
};
});
}
4 changes: 4 additions & 0 deletions src/bootstrap/modules/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,7 @@ export class DebuggerBackend implements _ZoteroTypes.Server.Endpoint {
}
};
}

export function isValid<T>(x: T | undefined | null): x is T {
return Boolean(x);
}

0 comments on commit ca22df7

Please sign in to comment.