diff --git a/addon/chrome/content/preferences.xhtml b/addon/chrome/content/preferences.xhtml index 8b13789..e69de29 100644 --- a/addon/chrome/content/preferences.xhtml +++ b/addon/chrome/content/preferences.xhtml @@ -1 +0,0 @@ - diff --git a/addon/prefs.js b/addon/prefs.js index beff801..2a49b2b 100644 --- a/addon/prefs.js +++ b/addon/prefs.js @@ -1 +1 @@ -/* eslint-disable no-undef */ \ No newline at end of file +/* eslint-disable no-undef */ diff --git a/src/hooks.ts b/src/hooks.ts index 37c0bc3..fc770f7 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -1,7 +1,4 @@ -import { - RegisterFactory, - UIFactory, -} from "./modules/Common"; +import { RegisterFactory, UIFactory } from "./modules/Common"; import { config } from "../package.json"; import { getString, initLocale } from "./utils/locale"; import { registerPrefsScripts } from "./modules/preferenceScript"; @@ -17,7 +14,7 @@ async function onStartup() { ]); initLocale(); - await DataStorage.instance(TLDRFieldKey).getAsync(); // 加载TLDR数据 + await DataStorage.instance(TLDRFieldKey).getAsync(); // 加载TLDR数据 RegisterFactory.registerPrefs(); @@ -92,9 +89,11 @@ function onLoad() { (async () => { let needFetchItems: Zotero.Item[] = []; for (const lib of Zotero.Libraries.getAll()) { - needFetchItems = needFetchItems.concat((await Zotero.Items.getAll(lib.id)).filter((item: Zotero.Item) => { - return item.isRegularItem() && !item.isCollection(); - })); + needFetchItems = needFetchItems.concat( + (await Zotero.Items.getAll(lib.id)).filter((item: Zotero.Item) => { + return item.isRegularItem() && !item.isCollection(); + }), + ); } onUpdateItems(needFetchItems, false); })(); @@ -102,7 +101,7 @@ function onLoad() { function noNotifyDeleteItem(ids: (string | number)[]) { DataStorage.instance(TLDRFieldKey).modify((data: any) => { - ids.forEach(id => { + ids.forEach((id) => { delete data[id]; }); return data; @@ -125,11 +124,17 @@ function onNotifyAddItems(ids: (string | number)[]) { function onUpdateItems(items: Zotero.Item[], forceFetch: boolean = false) { items = items.filter((item: Zotero.Item) => { - if (!item.getField('title')) { return false; } - if (!forceFetch) { return DataStorage.instance(TLDRFieldKey).get()[item.id] === undefined; } + if (!item.getField("title")) { + return false; + } + if (!forceFetch) { + return DataStorage.instance(TLDRFieldKey).get()[item.id] === undefined; + } return true; }); - if (items.length <= 0) { return; } + if (items.length <= 0) { + return; + } const newPopWin = (closeOnClick = false) => { return new ztoolkit.ProgressWindow(config.addonName, { closeOnClick: closeOnClick, @@ -138,7 +143,7 @@ function onUpdateItems(items: Zotero.Item[], forceFetch: boolean = false) { type: "default", progress: 0, }); - } + }; const popupWin = newPopWin().show(-1); (async function () { const count = items.length; @@ -146,12 +151,16 @@ function onUpdateItems(items: Zotero.Item[], forceFetch: boolean = false) { const succeedItems: Zotero.Item[] = []; await (async function () { for (const [index, item] of items.entries()) { - await new TLDRFetcher(item).fetchTLDR() ? succeedItems.push(item) : failedItems.push(item); + (await new TLDRFetcher(item).fetchTLDR()) + ? succeedItems.push(item) + : failedItems.push(item); await Zotero.Promise.delay(50); ztoolkit.ItemBox.refresh(); popupWin.changeLine({ - progress: index * 100 / count, - text: `Waiting: ${count - index - 1}, succeed: ${succeedItems.length}, failed: ${failedItems.length}`, + progress: (index * 100) / count, + text: `Waiting: ${count - index - 1}, succeed: ${ + succeedItems.length + }, failed: ${failedItems.length}`, }); } })(); diff --git a/src/modules/Common.ts b/src/modules/Common.ts index 259fcf7..fa9c684 100644 --- a/src/modules/Common.ts +++ b/src/modules/Common.ts @@ -11,7 +11,7 @@ export class RegisterFactory { event: string, type: string, ids: number[] | string[], - extraData: { [key: string]: any } + extraData: { [key: string]: any }, ) => { if (!addon?.data.alive) { this.unregisterNotifier(notifierID); @@ -22,9 +22,7 @@ export class RegisterFactory { }; // Register the callback in Zotero as an item observer - const notifierID = Zotero.Notifier.registerObserver(callback, [ - "item", - ]); + const notifierID = Zotero.Notifier.registerObserver(callback, ["item"]); // Unregister callback when the window closes (important to avoid a memory leak) window.addEventListener( @@ -32,7 +30,7 @@ export class RegisterFactory { (e: Event) => { this.unregisterNotifier(notifierID); }, - false + false, ); } @@ -40,7 +38,6 @@ export class RegisterFactory { Zotero.Notifier.unregisterObserver(notifierID); } - // 注册首选项配置 static registerPrefs() { // const prefOptions = { @@ -79,9 +76,13 @@ export class UIFactory { tag: "menuitem", id: "zotero-collectionmenu-tldr", label: getString("menucollection-updatetldrlabel"), - commandListener: (ev) => addon.hooks.onUpdateItems(ZoteroPane.getSelectedCollection()?.getChildItems() ?? [], false), + commandListener: (ev) => + addon.hooks.onUpdateItems( + ZoteroPane.getSelectedCollection()?.getChildItems() ?? [], + false, + ), icon: menuIcon, - }) + }); } // tldr行 @@ -120,4 +121,4 @@ export class UIFactory { }, ); } -} \ No newline at end of file +} diff --git a/src/modules/dataStorage.ts b/src/modules/dataStorage.ts index de04411..6906bc6 100644 --- a/src/modules/dataStorage.ts +++ b/src/modules/dataStorage.ts @@ -1,80 +1,89 @@ import { config } from "../../package.json"; export class Data { - private filePath: string - private inited = false; - private _data: any + private filePath: string; + private inited = false; + private _data: any; - constructor(filePath: string) { - this.filePath = filePath; - } + constructor(filePath: string) { + this.filePath = filePath; + } - async getAsync() { - await this.initDataIfNeed(); - return this.data; - } + async getAsync() { + await this.initDataIfNeed(); + return this.data; + } - get() { - return this.data; - } + get() { + return this.data; + } - async modify(action: (data: any) => Promise) { - await this.initDataIfNeed(); - const data = await this.data; - const newData = await action(data); - try { - await IOUtils.writeJSON(this.filePath, newData, {mode: 'overwrite', compress: false}); - this.data = newData; - return newData; - } catch (error) { - return data; - } + async modify(action: (data: any) => Promise) { + await this.initDataIfNeed(); + const data = await this.data; + const newData = await action(data); + try { + await IOUtils.writeJSON(this.filePath, newData, { + mode: "overwrite", + compress: false, + }); + this.data = newData; + return newData; + } catch (error) { + return data; } + } - async delete() { - try { - await IOUtils.remove(this.filePath); - this.data = {}; - return true; - } catch (error) { - return false; - } + async delete() { + try { + await IOUtils.remove(this.filePath); + this.data = {}; + return true; + } catch (error) { + return false; } + } - private get data() { - return this._data; - } + private get data() { + return this._data; + } - private set data(value: any) { - this._data = value; - } + private set data(value: any) { + this._data = value; + } - private async initDataIfNeed() { - if (this.inited) { return; } - try { - this.data = await IOUtils.readJSON(this.filePath, {decompress: false}); - } catch (error) { - this.data = {}; - } + private async initDataIfNeed() { + if (this.inited) { + return; } + try { + this.data = await IOUtils.readJSON(this.filePath, { decompress: false }); + } catch (error) { + this.data = {}; + } + } } export class DataStorage { - private readonly dataDir = PathUtils.join(PathUtils.profileDir, 'extensions', config.addonName); - private dataMap: { [key: string]: Data } = {}; + private readonly dataDir = PathUtils.join( + PathUtils.profileDir, + "extensions", + config.addonName, + ); + private dataMap: { [key: string]: Data } = {}; - private static shared = new DataStorage(); + private static shared = new DataStorage(); - static instance(dataType: string) { - const path = PathUtils.join(this.shared.dataDir, dataType); - if (this.shared.dataMap[dataType] === undefined) { - const data = new Data(path); - this.shared.dataMap[dataType] = data; - return data; - } else { - return this.shared.dataMap[dataType]; - } + static instance(dataType: string) { + const path = PathUtils.join(this.shared.dataDir, dataType); + if (this.shared.dataMap[dataType] === undefined) { + const data = new Data(path); + this.shared.dataMap[dataType] = data; + return data; + } else { + return this.shared.dataMap[dataType]; } + } - private constructor() { } + private constructor() {} } diff --git a/src/modules/preferenceScript.ts b/src/modules/preferenceScript.ts index 790be70..b929210 100644 --- a/src/modules/preferenceScript.ts +++ b/src/modules/preferenceScript.ts @@ -14,5 +14,4 @@ export async function registerPrefsScripts(_window: Window) { bindPrefEvents(); } -function bindPrefEvents() { -} +function bindPrefEvents() {} diff --git a/src/modules/tldrFetcher.ts b/src/modules/tldrFetcher.ts index f523005..90b00bd 100644 --- a/src/modules/tldrFetcher.ts +++ b/src/modules/tldrFetcher.ts @@ -1,157 +1,165 @@ import { DataStorage } from "./dataStorage"; type SemanticScholarItemInfo = { - title?: string, - abstract?: string, - tldr?: string, + title?: string; + abstract?: string; + tldr?: string; }; -export const TLDRFieldKey = 'TLDR'; -export const TLDRUnrelated = 'tldr-unrelated' // semantic scholar 找到了该item,但是该item没有tldr -export const TLDRItemNotFound = 'tldr-itemnotfound' // semantic scholar 找不到该item, +export const TLDRFieldKey = "TLDR"; +export const TLDRUnrelated = "tldr-unrelated"; // semantic scholar 找到了该item,但是该item没有tldr +export const TLDRItemNotFound = "tldr-itemnotfound"; // semantic scholar 找不到该item, export class TLDRFetcher { - private readonly zoteroItem: Zotero.Item; - private readonly title?: string; - private readonly abstract?: string; + private readonly zoteroItem: Zotero.Item; + private readonly title?: string; + private readonly abstract?: string; - constructor(item: Zotero.Item) { - this.zoteroItem = item; - if (item.isRegularItem() && !item.isCollection()) { - this.title = item.getField('title') as string; - this.abstract = item.getField('abstractNote') as string; - } + constructor(item: Zotero.Item) { + this.zoteroItem = item; + if (item.isRegularItem() && !item.isCollection()) { + this.title = item.getField("title") as string; + this.abstract = item.getField("abstractNote") as string; } + } - async fetchTLDR() { - if (!this.title || this.title.length <= 0) { return false; } - try { - const infos = await this.fetchRelevanceItemInfos(this.title); - for (const info of infos) { - let match = false; - if (info.title && this.title && this.checkLCS(info.title, this.title)) { - match = true; - } else if (info.abstract && this.abstract && this.checkLCS(info.abstract, this.abstract)) { - match = true; - } - if (match) { - const result = info.tldr ?? TLDRUnrelated; - DataStorage.instance(TLDRFieldKey).modify((data: any) => { - data[this.zoteroItem.id] = result; - return data; - }); - return true; - } - } - DataStorage.instance(TLDRFieldKey).modify((data: any) => { - data[this.zoteroItem.id] = TLDRItemNotFound; - return data; - }); - return false; - } catch (error) { - Zotero.log(`post semantic scholar request error: ${error}`); - return false; - } + async fetchTLDR() { + if (!this.title || this.title.length <= 0) { + return false; } + try { + const infos = await this.fetchRelevanceItemInfos(this.title); + for (const info of infos) { + let match = false; + if (info.title && this.title && this.checkLCS(info.title, this.title)) { + match = true; + } else if ( + info.abstract && + this.abstract && + this.checkLCS(info.abstract, this.abstract) + ) { + match = true; + } + if (match) { + const result = info.tldr ?? TLDRUnrelated; + DataStorage.instance(TLDRFieldKey).modify((data: any) => { + data[this.zoteroItem.id] = result; + return data; + }); + return true; + } + } + DataStorage.instance(TLDRFieldKey).modify((data: any) => { + data[this.zoteroItem.id] = TLDRItemNotFound; + return data; + }); + return false; + } catch (error) { + Zotero.log(`post semantic scholar request error: ${error}`); + return false; + } + } - private async fetchRelevanceItemInfos(title: string): Promise { - const semanticScholarURL = 'https://www.semanticscholar.org/api/1/search'; - const params = { - queryString: title, - page: 1, - pageSize: 10, - sort: 'relevance', - authors: [], - coAuthors: [], - venues: [], - performTitleMatch: true, - requireViewablePdf: false, - includeTldrs: true, + private async fetchRelevanceItemInfos( + title: string, + ): Promise { + const semanticScholarURL = "https://www.semanticscholar.org/api/1/search"; + const params = { + queryString: title, + page: 1, + pageSize: 10, + sort: "relevance", + authors: [], + coAuthors: [], + venues: [], + performTitleMatch: true, + requireViewablePdf: false, + includeTldrs: true, + }; + const resp = await Zotero.HTTP.request("POST", semanticScholarURL, { + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(params), + }); + if (resp.status === 200) { + const results = JSON.parse(resp.response).results; + return results.map((item: any) => { + const result = { + title: item.title.text, + abstract: item.paperAbstract.text, + tldr: undefined, }; - const resp = await Zotero.HTTP.request("POST", semanticScholarURL, { - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify(params), - }); - if (resp.status === 200) { - const results = JSON.parse(resp.response).results; - return results.map((item: any) => { - const result = { - title: item.title.text, - abstract: item.paperAbstract.text, - tldr: undefined, - }; - if (item.tldr) { - result.tldr = item.tldr.text; - } - return result; - }); + if (item.tldr) { + result.tldr = item.tldr.text; } - return []; + return result; + }); } + return []; + } - private checkLCS(pattern: string, content: string): boolean { - const LCS = StringMatchUtils.longestCommonSubsequence(pattern, content); - return LCS.length >= Math.max(pattern.length, content.length) * 0.9 - } + private checkLCS(pattern: string, content: string): boolean { + const LCS = StringMatchUtils.longestCommonSubsequence(pattern, content); + return LCS.length >= Math.max(pattern.length, content.length) * 0.9; + } } - class StringMatchUtils { - static longestCommonSubsequence(text1: string, text2: string): string { - const m = text1.length; - const n = text2.length; - - const dp: number[][] = new Array(m + 1); - for (let i = 0; i <= m; i++) { - dp[i] = new Array(n + 1).fill(0); - } - - for (let i = 1; i <= m; i++) { - for (let j = 1; j <= n; j++) { - if (text1[i - 1] === text2[j - 1]) { - dp[i][j] = dp[i - 1][j - 1] + 1; - } else { - dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); - } - } - } - - let i = m, j = n; - const lcs: string[] = []; - while (i > 0 && j > 0) { - if (text1[i - 1] === text2[j - 1]) { - lcs.unshift(text1[i - 1]); - i--; - j--; - } else if (dp[i - 1][j] > dp[i][j - 1]) { - i--; - } else { - j--; - } - } - - return lcs.join(''); + static longestCommonSubsequence(text1: string, text2: string): string { + const m = text1.length; + const n = text2.length; + + const dp: number[][] = new Array(m + 1); + for (let i = 0; i <= m; i++) { + dp[i] = new Array(n + 1).fill(0); } - // static minWindow(s: string, t: string): [number, number] | null { - // const m = s.length, n = t.length - // let start = -1, minLen = Number.MAX_SAFE_INTEGER, i = 0, j = 0, end; - // while (i < m) { - // if (s[i] == t[j]) { - // if (++j == n) { - // end = i + 1; - // while (--j >= 0) { - // while (s[i--] != t[j]); - // } - // ++i; ++j; - // if (end - i < minLen) { - // minLen = end - i; - // start = i; - // } - // } - // } - // ++i; - // } - // return start == -1 ? null : [start, minLen]; - // } -} \ No newline at end of file + for (let i = 1; i <= m; i++) { + for (let j = 1; j <= n; j++) { + if (text1[i - 1] === text2[j - 1]) { + dp[i][j] = dp[i - 1][j - 1] + 1; + } else { + dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); + } + } + } + + let i = m, + j = n; + const lcs: string[] = []; + while (i > 0 && j > 0) { + if (text1[i - 1] === text2[j - 1]) { + lcs.unshift(text1[i - 1]); + i--; + j--; + } else if (dp[i - 1][j] > dp[i][j - 1]) { + i--; + } else { + j--; + } + } + + return lcs.join(""); + } + + // static minWindow(s: string, t: string): [number, number] | null { + // const m = s.length, n = t.length + // let start = -1, minLen = Number.MAX_SAFE_INTEGER, i = 0, j = 0, end; + // while (i < m) { + // if (s[i] == t[j]) { + // if (++j == n) { + // end = i + 1; + // while (--j >= 0) { + // while (s[i--] != t[j]); + // } + // ++i; ++j; + // if (end - i < minLen) { + // minLen = end - i; + // start = i; + // } + // } + // } + // ++i; + // } + // return start == -1 ? null : [start, minLen]; + // } +}