add url snapshot
This commit is contained in:
parent
6401334731
commit
de618f58d7
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@ pnpm-lock.yaml
|
||||
yarn.lock
|
||||
.DS_Store
|
||||
.env
|
||||
zotero
|
||||
@ -8,33 +8,33 @@
|
||||
<!-- prettier-ignore -->
|
||||
<?xml-stylesheet href="chrome://zotero-platform/content/zotero.css" type="text/css"?>
|
||||
<!-- prettier-ignore -->
|
||||
<window
|
||||
id="__addonRef__-standalone"
|
||||
title="Save Web Snapshot Panel"
|
||||
<window id="url-snapshot-dialog"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
windowtype="__addonRef__-standalone"
|
||||
persist="screenX screenY width height"
|
||||
onload=""
|
||||
>
|
||||
<xul:linkset>
|
||||
<html:link rel="localization" href="browser/menubar.ftl" />
|
||||
<html:link rel="localization" href="browser/browserSets.ftl" />
|
||||
<html:link rel="localization" href="toolkit/global/textActions.ftl" />
|
||||
<html:link rel="localization" href="zotero.ftl" />
|
||||
<html:link rel="localization" href="__addonRef__-standalone.ftl" />
|
||||
</xul:linkset>
|
||||
<script src="chrome://zotero/content/include.js"></script>
|
||||
<script src="chrome://zotero/content/customElements.js"></script>
|
||||
<script src="chrome://__addonRef__/content/scripts/customElements.js"></script>
|
||||
<vbox id="dialog-content" align="center">
|
||||
<label value="请输入网页 URL:" />
|
||||
<textbox id="url-textbox" width="200" height="300" />
|
||||
<hbox>
|
||||
<button id="add-source" value="添加" data-l10n-id="add-source" />
|
||||
<button id="pin-window" data-l10n-id="pin-window" />
|
||||
title="Import URLs with Snapshots"
|
||||
style="width: 500px;">
|
||||
|
||||
<vbox flex="1" style="padding: 20px;">
|
||||
<label value="Enter URLs (one per line):" />
|
||||
<html:textarea id="urls-input" rows="10" style="margin: 10px 0;" />
|
||||
|
||||
<hbox style="margin-top: 10px; justify-content: flex-end;">
|
||||
<button label="Cancel" oncommand="cancel();" />
|
||||
<button label="Import" oncommand="accept();" default="true" style="margin-left: 10px;" />
|
||||
</hbox>
|
||||
</vbox>
|
||||
<html:div class="separator"></html:div>
|
||||
|
||||
<script>
|
||||
const params = window.arguments[0];
|
||||
|
||||
function accept() {
|
||||
params.dataOut = document.getElementById('urls-input').value;
|
||||
window.close();
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
params.dataOut = null;
|
||||
window.close();
|
||||
}
|
||||
</script>
|
||||
</window>
|
||||
|
||||
@ -10,3 +10,5 @@ prefs-table-detail = Detail
|
||||
tabpanel-lib-tab-label = Lib Tab
|
||||
tabpanel-reader-tab-label = Reader Tab
|
||||
snapshot-dialog-title = Snapshot
|
||||
scrap-webpage = Scrap Webpage
|
||||
scrap-finish = Scrap Finish
|
||||
@ -10,3 +10,5 @@ prefs-table-detail = 详情
|
||||
tabpanel-lib-tab-label = 库标签
|
||||
tabpanel-reader-tab-label = 阅读器标签
|
||||
snapshot-dialog-title = 快照
|
||||
scrap-webpage = 获取网页中
|
||||
scrap-finish = 获取完成
|
||||
@ -1,5 +1,5 @@
|
||||
import { getLocaleID, getString } from "../utils/locale";
|
||||
import { URLInputDialog } from "./urlDialog";
|
||||
import { UrlDialog } from "./urlDialog";
|
||||
|
||||
function example(
|
||||
target: any,
|
||||
@ -145,7 +145,7 @@ export class UIExampleFactory {
|
||||
id: "zotero-itemmenu-addontemplate-test",
|
||||
label: getString("menuitem-label"),
|
||||
commandListener: (ev) => {
|
||||
const dialog = new URLInputDialog();
|
||||
const dialog = new UrlDialog(Zotero.getMainWindow());
|
||||
dialog.open();
|
||||
},
|
||||
icon: menuIcon,
|
||||
|
||||
@ -1,74 +1,92 @@
|
||||
import { config } from "../../package.json";
|
||||
import { getString } from "../utils/locale";
|
||||
|
||||
export class URLInputDialog {
|
||||
export class UrlDialog {
|
||||
private window: Window;
|
||||
private dialog: Window | null;
|
||||
private document: Document;
|
||||
|
||||
constructor() {
|
||||
this.window = Zotero.getMainWindow();
|
||||
this.dialog = null;
|
||||
constructor(win: Window) {
|
||||
this.window = win;
|
||||
this.document = win.document;
|
||||
}
|
||||
|
||||
public open() {
|
||||
// 创建对话框 HTML 内容
|
||||
const dialogContent = `
|
||||
<div style="padding: 20px;">
|
||||
<form id="snapshot-form">
|
||||
<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>
|
||||
`;
|
||||
|
||||
// 创建对话框参数
|
||||
const params: any = {
|
||||
dataIn: {
|
||||
url: "",
|
||||
accepted: false,
|
||||
},
|
||||
public open(): Promise<string[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const params = {
|
||||
urls: "",
|
||||
dataOut: null,
|
||||
};
|
||||
|
||||
// 打开对话框
|
||||
this.dialog = ztoolkit.getGlobal("openDialog")(
|
||||
ztoolkit.getGlobal("openDialog")(
|
||||
`chrome://${config.addonRef}/content/dialog.xhtml`,
|
||||
"",
|
||||
"chrome,centerscreen,resizable=false,width=500,height=300",
|
||||
"chrome,centerscreen,modal,resizable=true,width=500,height=300",
|
||||
params,
|
||||
);
|
||||
|
||||
// 设置对话框内容和事件监听
|
||||
this.dialog!.addEventListener("load", () => {
|
||||
const doc = this.dialog!.document;
|
||||
if (params.dataOut === null) {
|
||||
reject(new Error("Dialog cancelled"));
|
||||
} else {
|
||||
const urls = params.dataOut
|
||||
.split(/[\n,]+/)
|
||||
.map((url: string) => url.trim())
|
||||
.filter((url: string) => url);
|
||||
resolve(urls);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 设置对话框标题
|
||||
doc.title = getString("snapshot-dialog-title");
|
||||
// export class URLInputDialog {
|
||||
// private window: Window;
|
||||
// private dialog: Window | null;
|
||||
|
||||
// 插入自定义内容
|
||||
const container = doc.getElementById("dialog-content");
|
||||
ztoolkit.log(container);
|
||||
// constructor() {
|
||||
// this.window = Zotero.getMainWindow();
|
||||
// this.dialog = null;
|
||||
// }
|
||||
|
||||
// 表单提交处理
|
||||
// const form = doc.getElementById("snapshot-form") as HTMLFormElement;
|
||||
// form.addEventListener("submit", (e) => {
|
||||
// e.preventDefault();
|
||||
// const urlInput = doc.getElementById("url-input") as HTMLInputElement;
|
||||
// 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,
|
||||
// };
|
||||
// this.dialog?.close();
|
||||
// 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 = {
|
||||
@ -77,54 +95,71 @@ export class URLInputDialog {
|
||||
// this.dialog?.close();
|
||||
// });
|
||||
|
||||
// 自动聚焦到 URL 输入框
|
||||
// const urlInput = doc.getElementById("url-input") as HTMLInputElement;
|
||||
// // 自动聚焦到 URL 输入框
|
||||
// const urlInput = doc.querySelector("#url-textbox") as XULTextBoxElement;
|
||||
// urlInput.focus();
|
||||
});
|
||||
// });
|
||||
// }
|
||||
|
||||
// 等待对话框关闭
|
||||
if (params.dataOut?.accepted) {
|
||||
this.saveSnapshot(params.dataOut.url);
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
|
||||
async saveSnapshot(url: string) {
|
||||
try {
|
||||
// 创建新的 webpage 类型条目
|
||||
const item = new Zotero.Item("webpage");
|
||||
// Zotero.debug(`Web Snapshot: Successfully saved ${url}`);
|
||||
// } catch (error) {
|
||||
// Zotero.debug(`Web Snapshot error: ${error}`);
|
||||
// this.window.alert(getString("snapshot.error"));
|
||||
// }
|
||||
// }
|
||||
|
||||
// 获取网页内容
|
||||
const response = await fetch(url);
|
||||
const html = await response.text();
|
||||
// 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]);
|
||||
|
||||
// 设置基本元数据
|
||||
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");
|
||||
}
|
||||
}
|
||||
// // 执行翻译
|
||||
// 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 { getString } from "../utils/locale";
|
||||
import { URLInputDialog } from "./urlDialog";
|
||||
import { UrlDialog, URLInputDialog } from "./urlDialog";
|
||||
import { UrlSnapshot } from "./urlSnapshot";
|
||||
|
||||
export default class Views {
|
||||
private id = "zotero-zetter-container";
|
||||
@ -29,8 +30,25 @@ export default class Views {
|
||||
const papersgptState = Zotero.Prefs.get(
|
||||
`${config.addonRef}.papersgptState`,
|
||||
);
|
||||
const dialog = new URLInputDialog();
|
||||
dialog.open();
|
||||
try {
|
||||
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");
|
||||
newNode.style.listStyleImage = `url(chrome://${config.addonRef}/content/icons/favicon.png)`;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user