add url snapshot
This commit is contained in:
parent
6401334731
commit
de618f58d7
3
.gitignore
vendored
3
.gitignore
vendored
@ -4,4 +4,5 @@ node_modules
|
|||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
yarn.lock
|
yarn.lock
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.env
|
.env
|
||||||
|
zotero
|
||||||
@ -8,33 +8,33 @@
|
|||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<?xml-stylesheet href="chrome://zotero-platform/content/zotero.css" type="text/css"?>
|
<?xml-stylesheet href="chrome://zotero-platform/content/zotero.css" type="text/css"?>
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<window
|
<window id="url-snapshot-dialog"
|
||||||
id="__addonRef__-standalone"
|
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||||
title="Save Web Snapshot Panel"
|
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
title="Import URLs with Snapshots"
|
||||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
style="width: 500px;">
|
||||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
|
||||||
windowtype="__addonRef__-standalone"
|
<vbox flex="1" style="padding: 20px;">
|
||||||
persist="screenX screenY width height"
|
<label value="Enter URLs (one per line):" />
|
||||||
onload=""
|
<html:textarea id="urls-input" rows="10" style="margin: 10px 0;" />
|
||||||
>
|
|
||||||
<xul:linkset>
|
<hbox style="margin-top: 10px; justify-content: flex-end;">
|
||||||
<html:link rel="localization" href="browser/menubar.ftl" />
|
<button label="Cancel" oncommand="cancel();" />
|
||||||
<html:link rel="localization" href="browser/browserSets.ftl" />
|
<button label="Import" oncommand="accept();" default="true" style="margin-left: 10px;" />
|
||||||
<html:link rel="localization" href="toolkit/global/textActions.ftl" />
|
</hbox>
|
||||||
<html:link rel="localization" href="zotero.ftl" />
|
</vbox>
|
||||||
<html:link rel="localization" href="__addonRef__-standalone.ftl" />
|
|
||||||
</xul:linkset>
|
<script>
|
||||||
<script src="chrome://zotero/content/include.js"></script>
|
const params = window.arguments[0];
|
||||||
<script src="chrome://zotero/content/customElements.js"></script>
|
|
||||||
<script src="chrome://__addonRef__/content/scripts/customElements.js"></script>
|
function accept() {
|
||||||
<vbox id="dialog-content" align="center">
|
params.dataOut = document.getElementById('urls-input').value;
|
||||||
<label value="请输入网页 URL:" />
|
window.close();
|
||||||
<textbox id="url-textbox" width="200" height="300" />
|
}
|
||||||
<hbox>
|
|
||||||
<button id="add-source" value="添加" data-l10n-id="add-source" />
|
function cancel() {
|
||||||
<button id="pin-window" data-l10n-id="pin-window" />
|
params.dataOut = null;
|
||||||
</hbox>
|
window.close();
|
||||||
</vbox>
|
}
|
||||||
<html:div class="separator"></html:div>
|
</script>
|
||||||
</window>
|
</window>
|
||||||
|
|||||||
@ -9,4 +9,6 @@ prefs-table-title = Title
|
|||||||
prefs-table-detail = Detail
|
prefs-table-detail = Detail
|
||||||
tabpanel-lib-tab-label = Lib Tab
|
tabpanel-lib-tab-label = Lib Tab
|
||||||
tabpanel-reader-tab-label = Reader Tab
|
tabpanel-reader-tab-label = Reader Tab
|
||||||
snapshot-dialog-title = Snapshot
|
snapshot-dialog-title = Snapshot
|
||||||
|
scrap-webpage = Scrap Webpage
|
||||||
|
scrap-finish = Scrap Finish
|
||||||
@ -9,4 +9,6 @@ prefs-table-title = 标题
|
|||||||
prefs-table-detail = 详情
|
prefs-table-detail = 详情
|
||||||
tabpanel-lib-tab-label = 库标签
|
tabpanel-lib-tab-label = 库标签
|
||||||
tabpanel-reader-tab-label = 阅读器标签
|
tabpanel-reader-tab-label = 阅读器标签
|
||||||
snapshot-dialog-title = 快照
|
snapshot-dialog-title = 快照
|
||||||
|
scrap-webpage = 获取网页中
|
||||||
|
scrap-finish = 获取完成
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { getLocaleID, getString } from "../utils/locale";
|
import { getLocaleID, getString } from "../utils/locale";
|
||||||
import { URLInputDialog } from "./urlDialog";
|
import { UrlDialog } from "./urlDialog";
|
||||||
|
|
||||||
function example(
|
function example(
|
||||||
target: any,
|
target: any,
|
||||||
@ -145,7 +145,7 @@ export class UIExampleFactory {
|
|||||||
id: "zotero-itemmenu-addontemplate-test",
|
id: "zotero-itemmenu-addontemplate-test",
|
||||||
label: getString("menuitem-label"),
|
label: getString("menuitem-label"),
|
||||||
commandListener: (ev) => {
|
commandListener: (ev) => {
|
||||||
const dialog = new URLInputDialog();
|
const dialog = new UrlDialog(Zotero.getMainWindow());
|
||||||
dialog.open();
|
dialog.open();
|
||||||
},
|
},
|
||||||
icon: menuIcon,
|
icon: menuIcon,
|
||||||
|
|||||||
@ -1,130 +1,165 @@
|
|||||||
import { config } from "../../package.json";
|
import { config } from "../../package.json";
|
||||||
import { getString } from "../utils/locale";
|
import { getString } from "../utils/locale";
|
||||||
|
|
||||||
export class URLInputDialog {
|
export class UrlDialog {
|
||||||
private window: Window;
|
private window: Window;
|
||||||
private dialog: Window | null;
|
private document: Document;
|
||||||
|
|
||||||
constructor() {
|
constructor(win: Window) {
|
||||||
this.window = Zotero.getMainWindow();
|
this.window = win;
|
||||||
this.dialog = null;
|
this.document = win.document;
|
||||||
}
|
}
|
||||||
|
|
||||||
public open() {
|
public open(): Promise<string[]> {
|
||||||
// 创建对话框 HTML 内容
|
return new Promise((resolve, reject) => {
|
||||||
const dialogContent = `
|
const params = {
|
||||||
<div style="padding: 20px;">
|
urls: "",
|
||||||
<form id="snapshot-form">
|
dataOut: null,
|
||||||
<div style="margin-bottom: 15px;">
|
};
|
||||||
<label for="url-input" style="display: block; margin-bottom: 5px;">Enter webpage URL:</label>
|
|
||||||
<input type="url" id="url-input" style="width: 100%; padding: 5px;"
|
|
||||||
required placeholder="https://example.com">
|
|
||||||
</div>
|
|
||||||
<div style="display: flex; justify-content: flex-end; gap: 10px;">
|
|
||||||
<button type="button" id="cancel-button">Cancel</button>
|
|
||||||
<button type="submit" id="save-button">Save Snapshot</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// 创建对话框参数
|
ztoolkit.getGlobal("openDialog")(
|
||||||
const params: any = {
|
`chrome://${config.addonRef}/content/dialog.xhtml`,
|
||||||
dataIn: {
|
"",
|
||||||
url: "",
|
"chrome,centerscreen,modal,resizable=true,width=500,height=300",
|
||||||
accepted: false,
|
params,
|
||||||
},
|
);
|
||||||
dataOut: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 打开对话框
|
if (params.dataOut === null) {
|
||||||
this.dialog = ztoolkit.getGlobal("openDialog")(
|
reject(new Error("Dialog cancelled"));
|
||||||
`chrome://${config.addonRef}/content/dialog.xhtml`,
|
} else {
|
||||||
"",
|
const urls = params.dataOut
|
||||||
"chrome,centerscreen,resizable=false,width=500,height=300",
|
.split(/[\n,]+/)
|
||||||
params,
|
.map((url: string) => url.trim())
|
||||||
);
|
.filter((url: string) => url);
|
||||||
|
resolve(urls);
|
||||||
// 设置对话框内容和事件监听
|
}
|
||||||
this.dialog!.addEventListener("load", () => {
|
|
||||||
const doc = this.dialog!.document;
|
|
||||||
|
|
||||||
// 设置对话框标题
|
|
||||||
doc.title = getString("snapshot-dialog-title");
|
|
||||||
|
|
||||||
// 插入自定义内容
|
|
||||||
const container = doc.getElementById("dialog-content");
|
|
||||||
ztoolkit.log(container);
|
|
||||||
|
|
||||||
// 表单提交处理
|
|
||||||
// const form = doc.getElementById("snapshot-form") as HTMLFormElement;
|
|
||||||
// form.addEventListener("submit", (e) => {
|
|
||||||
// e.preventDefault();
|
|
||||||
// const urlInput = doc.getElementById("url-input") as HTMLInputElement;
|
|
||||||
// params.dataOut = {
|
|
||||||
// url: urlInput.value,
|
|
||||||
// accepted: true,
|
|
||||||
// };
|
|
||||||
// this.dialog?.close();
|
|
||||||
// });
|
|
||||||
|
|
||||||
// 取消按钮处理
|
|
||||||
// const cancelButton = doc.getElementById("cancel-button")!;
|
|
||||||
// cancelButton.addEventListener("click", () => {
|
|
||||||
// params.dataOut = {
|
|
||||||
// accepted: false,
|
|
||||||
// };
|
|
||||||
// this.dialog?.close();
|
|
||||||
// });
|
|
||||||
|
|
||||||
// 自动聚焦到 URL 输入框
|
|
||||||
// const urlInput = doc.getElementById("url-input") as HTMLInputElement;
|
|
||||||
// urlInput.focus();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 等待对话框关闭
|
|
||||||
if (params.dataOut?.accepted) {
|
|
||||||
this.saveSnapshot(params.dataOut.url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async saveSnapshot(url: string) {
|
|
||||||
try {
|
|
||||||
// 创建新的 webpage 类型条目
|
|
||||||
const item = new Zotero.Item("webpage");
|
|
||||||
|
|
||||||
// 获取网页内容
|
|
||||||
const response = await fetch(url);
|
|
||||||
const html = await response.text();
|
|
||||||
|
|
||||||
// 设置基本元数据
|
|
||||||
item.setField("title", this.extractTitle(html));
|
|
||||||
item.setField("url", url);
|
|
||||||
item.setField("accessDate", Zotero.Date.dateToSQL(new Date(), true));
|
|
||||||
|
|
||||||
await item.saveTx();
|
|
||||||
|
|
||||||
// 保存网页内容作为附件
|
|
||||||
const attachment = await Zotero.Attachments.importFromURL({
|
|
||||||
url: url,
|
|
||||||
parentItemID: item.id,
|
|
||||||
contentType: "text/html",
|
|
||||||
title: item.getField("title"),
|
|
||||||
});
|
|
||||||
|
|
||||||
// 选中新创建的条目
|
|
||||||
const zp = Zotero.getActiveZoteroPane();
|
|
||||||
zp.selectItem(item.id);
|
|
||||||
|
|
||||||
Zotero.debug(`Web Snapshot: Successfully saved ${url}`);
|
|
||||||
} catch (error) {
|
|
||||||
Zotero.debug(`Web Snapshot error: ${error}`);
|
|
||||||
this.window.alert(getString("snapshot.error"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extractTitle(html: string): string {
|
|
||||||
const match = html.match(/<title>(.*?)<\/title>/i);
|
|
||||||
return match ? match[1] : getString("snapshot.untitled");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// export class URLInputDialog {
|
||||||
|
// private window: Window;
|
||||||
|
// private dialog: Window | null;
|
||||||
|
|
||||||
|
// constructor() {
|
||||||
|
// this.window = Zotero.getMainWindow();
|
||||||
|
// this.dialog = null;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public open() {
|
||||||
|
// // 创建对话框参数
|
||||||
|
// const params: any = {
|
||||||
|
// dataIn: {
|
||||||
|
// url: "",
|
||||||
|
// accepted: false,
|
||||||
|
// },
|
||||||
|
// dataOut: null,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// // 打开对话框
|
||||||
|
// this.dialog = ztoolkit.getGlobal("openDialog")(
|
||||||
|
// `chrome://${config.addonRef}/content/dialog.xhtml`,
|
||||||
|
// "",
|
||||||
|
// "chrome,centerscreen,resizable=true,width=500,height=300",
|
||||||
|
// params,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// // 设置对话框内容和事件监听
|
||||||
|
// this.dialog!.addEventListener("load", () => {
|
||||||
|
// const doc = this.dialog!.document;
|
||||||
|
|
||||||
|
// // 设置对话框标题
|
||||||
|
// doc.title = getString("snapshot-dialog-title");
|
||||||
|
|
||||||
|
// // 保存按钮处理
|
||||||
|
// const saveButton = doc.getElementById("save-button")!;
|
||||||
|
// saveButton.addEventListener("click", () => {
|
||||||
|
// ztoolkit.log("click the save button");
|
||||||
|
// const urlInput = doc.querySelector("#url-textbox") as XULTextBoxElement;
|
||||||
|
// params.dataOut = {
|
||||||
|
// url: urlInput.value,
|
||||||
|
// accepted: true,
|
||||||
|
// };
|
||||||
|
// ztoolkit.log(params);
|
||||||
|
// this.saveSnapshot(
|
||||||
|
// "https://mp.weixin.qq.com/s/ppg7eMMIFWCZ-eHzd3QtAA",
|
||||||
|
// ).then(() => this.dialog?.close());
|
||||||
|
// });
|
||||||
|
|
||||||
|
// // 取消按钮处理
|
||||||
|
// const cancelButton = doc.getElementById("cancel-button")!;
|
||||||
|
// cancelButton.addEventListener("click", () => {
|
||||||
|
// params.dataOut = {
|
||||||
|
// accepted: false,
|
||||||
|
// };
|
||||||
|
// this.dialog?.close();
|
||||||
|
// });
|
||||||
|
|
||||||
|
// // 自动聚焦到 URL 输入框
|
||||||
|
// const urlInput = doc.querySelector("#url-textbox") as XULTextBoxElement;
|
||||||
|
// urlInput.focus();
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// async saveSnapshot(url: string) {
|
||||||
|
// try {
|
||||||
|
// // 使用解析后的第一个条目
|
||||||
|
// const items = await this.processUrl(url);
|
||||||
|
// if (items == undefined || items.length < 1) return;
|
||||||
|
// ztoolkit.log(items);
|
||||||
|
// const item = items[0];
|
||||||
|
// // 选中新创建的条目
|
||||||
|
// const attachment = await Zotero.Attachments.importFromURL({
|
||||||
|
// url: url,
|
||||||
|
// parentItemID: item.id,
|
||||||
|
// contentType: "text/html",
|
||||||
|
// title: "Snapshot",
|
||||||
|
// });
|
||||||
|
// const zp = Zotero.getActiveZoteroPane();
|
||||||
|
// zp.selectItem(item.id);
|
||||||
|
|
||||||
|
// Zotero.debug(`Web Snapshot: Successfully saved ${url}`);
|
||||||
|
// } catch (error) {
|
||||||
|
// Zotero.debug(`Web Snapshot error: ${error}`);
|
||||||
|
// this.window.alert(getString("snapshot.error"));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// private async processUrl(url: string): Promise<Zotero.Item[] | undefined> {
|
||||||
|
// // 获取合适的translator
|
||||||
|
// const translate = new Zotero.Translate.Web();
|
||||||
|
// let items: Zotero.Item[] | undefined = undefined;
|
||||||
|
// const { HiddenBrowser } = ChromeUtils.import(
|
||||||
|
// "chrome://zotero/content/HiddenBrowser.jsm",
|
||||||
|
// );
|
||||||
|
// try {
|
||||||
|
// const browser = new HiddenBrowser({
|
||||||
|
// docShell: { allowImages: true },
|
||||||
|
// });
|
||||||
|
// await browser.load(url, { requireSuccessfulStatus: true });
|
||||||
|
// const doc = await browser.getDocument();
|
||||||
|
// translate.setDocument(doc);
|
||||||
|
// const translators = await translate.getTranslators(false);
|
||||||
|
// if (!translators.length) {
|
||||||
|
// ztoolkit.log("No compatible translator found");
|
||||||
|
// return items;
|
||||||
|
// }
|
||||||
|
// // 使用第一个匹配的translator
|
||||||
|
// translate.setTranslator(translators[0]);
|
||||||
|
|
||||||
|
// // 执行翻译
|
||||||
|
// const { library, collection, editable } =
|
||||||
|
// Zotero.Server.Connector.getSaveTarget();
|
||||||
|
// const libraryID = library.libraryID;
|
||||||
|
// const targetID = collection ? collection.treeViewID : library.treeViewID;
|
||||||
|
// items = await translate.translate({
|
||||||
|
// libraryID,
|
||||||
|
// collections: collection ? [collection.id] : false,
|
||||||
|
// saveAttachments: false,
|
||||||
|
// });
|
||||||
|
// } catch (e) {
|
||||||
|
// ztoolkit.log("Error processing URL: " + e);
|
||||||
|
// }
|
||||||
|
// return items;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|||||||
78
src/modules/urlSnapshot.ts
Normal file
78
src/modules/urlSnapshot.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { getString } from "../utils/locale";
|
||||||
|
|
||||||
|
export class UrlSnapshot {
|
||||||
|
public async processUrl(url: string): Promise<boolean> {
|
||||||
|
const { HiddenBrowser } = ChromeUtils.import(
|
||||||
|
"chrome://zotero/content/HiddenBrowser.jsm",
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
const translate = new Zotero.Translate.Web();
|
||||||
|
const browser = new HiddenBrowser({
|
||||||
|
docShell: { allowImages: true },
|
||||||
|
});
|
||||||
|
await browser.load(url, { requireSuccessfulStatus: true });
|
||||||
|
const doc = await browser.getDocument();
|
||||||
|
translate.setDocument(doc);
|
||||||
|
const translators = await translate.getTranslators(false);
|
||||||
|
if (!translators.length) {
|
||||||
|
ztoolkit.log("No compatible translator found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
translate.setTranslator(translators[0]);
|
||||||
|
|
||||||
|
const { library, collection, editable } =
|
||||||
|
Zotero.Server.Connector.getSaveTarget();
|
||||||
|
const libraryID = library.libraryID;
|
||||||
|
const targetID = collection ? collection.treeViewID : library.treeViewID;
|
||||||
|
|
||||||
|
const items = await translate.translate({
|
||||||
|
libraryID,
|
||||||
|
collections: collection ? [collection.id] : false,
|
||||||
|
saveAttachments: false,
|
||||||
|
});
|
||||||
|
await Zotero.Attachments.importFromURL({
|
||||||
|
url: url,
|
||||||
|
parentItemID: items[0].id,
|
||||||
|
contentType: "text/html",
|
||||||
|
title: "Snapshot",
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
ztoolkit.log(`Error processing URL: ${e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async processBatchUrls(
|
||||||
|
urls: string[],
|
||||||
|
): Promise<Array<{ url: string; success: boolean }>> {
|
||||||
|
const results = [];
|
||||||
|
const popupWin = new ztoolkit.ProgressWindow(addon.data.config.addonName, {
|
||||||
|
closeOnClick: true,
|
||||||
|
closeTime: -1,
|
||||||
|
})
|
||||||
|
.createLine({
|
||||||
|
text: getString("scrap-webpage"),
|
||||||
|
type: "default",
|
||||||
|
progress: 0,
|
||||||
|
})
|
||||||
|
.show();
|
||||||
|
for (const url of urls) {
|
||||||
|
const success = await this.processUrl(url);
|
||||||
|
results.push({ url, success });
|
||||||
|
const pgn = Math.round((urls.indexOf(url) * 100) / urls.length);
|
||||||
|
popupWin.changeLine({
|
||||||
|
progress: pgn,
|
||||||
|
text: `[${pgn}%] ${getString("scrap-webpage")}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
popupWin.changeLine({
|
||||||
|
progress: 100,
|
||||||
|
text: `[100%] ${getString("scrap-finish")}`,
|
||||||
|
});
|
||||||
|
popupWin.startCloseTimer(500);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import { config } from "../../package.json";
|
import { config } from "../../package.json";
|
||||||
import { getString } from "../utils/locale";
|
import { getString } from "../utils/locale";
|
||||||
import { URLInputDialog } from "./urlDialog";
|
import { UrlDialog, URLInputDialog } from "./urlDialog";
|
||||||
|
import { UrlSnapshot } from "./urlSnapshot";
|
||||||
|
|
||||||
export default class Views {
|
export default class Views {
|
||||||
private id = "zotero-zetter-container";
|
private id = "zotero-zetter-container";
|
||||||
@ -29,8 +30,25 @@ export default class Views {
|
|||||||
const papersgptState = Zotero.Prefs.get(
|
const papersgptState = Zotero.Prefs.get(
|
||||||
`${config.addonRef}.papersgptState`,
|
`${config.addonRef}.papersgptState`,
|
||||||
);
|
);
|
||||||
const dialog = new URLInputDialog();
|
try {
|
||||||
dialog.open();
|
const dialog = new UrlDialog(Zotero.getMainWindow());
|
||||||
|
const urls = await dialog.open();
|
||||||
|
|
||||||
|
if (urls && urls.length > 0) {
|
||||||
|
const snapshot = new UrlSnapshot();
|
||||||
|
const results = await snapshot.processBatchUrls(urls);
|
||||||
|
|
||||||
|
// Show results
|
||||||
|
const message = results
|
||||||
|
.map((r) => `${r.url}: ${r.success ? "Success" : "Failed"}`)
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
alert("Import Results:\n\n" + message);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// User cancelled or error occurred
|
||||||
|
Zotero.debug("URL Snapshot operation cancelled or failed");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
const searchNode = toolbar.querySelector("#zotero-tb-search");
|
const searchNode = toolbar.querySelector("#zotero-tb-search");
|
||||||
newNode.style.listStyleImage = `url(chrome://${config.addonRef}/content/icons/favicon.png)`;
|
newNode.style.listStyleImage = `url(chrome://${config.addonRef}/content/icons/favicon.png)`;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user