add url snapshot

This commit is contained in:
denislov 2025-01-16 22:20:10 +08:00
parent 6d4576d5b1
commit 859e3c6b48
7 changed files with 217 additions and 30 deletions

View File

@ -7,17 +7,17 @@
# The path of the Zotero binary file. # The path of the Zotero binary file.
# The path delimiter should be escaped as `\\` for win32. # The path delimiter should be escaped as `\\` for win32.
# The path is `*/Zotero.app/Contents/MacOS/zotero` for MacOS. # The path is `*/Zotero.app/Contents/MacOS/zotero` for MacOS.
ZOTERO_PLUGIN_ZOTERO_BIN_PATH = /path/to/zotero.exe ZOTERO_PLUGIN_ZOTERO_BIN_PATH = D:/Applications/Scoop/apps/zotero/current/zotero.exe
# The path of the profile used for development. # The path of the profile used for development.
# Start the profile manager by `/path/to/zotero.exe -p` to create a profile for development. # Start the profile manager by `/path/to/zotero.exe -p` to create a profile for development.
# @see https://www.zotero.org/support/kb/profile_directory # @see https://www.zotero.org/support/kb/profile_directory
ZOTERO_PLUGIN_PROFILE_PATH = /path/to/profile ZOTERO_PLUGIN_PROFILE_PATH = D:/TEMP/zotero_profile
# The directory where the database is located. # The directory where the database is located.
# If this field is kept empty, Zotero will start with the default data. # If this field is kept empty, Zotero will start with the default data.
# @see https://www.zotero.org/support/zotero_data # @see https://www.zotero.org/support/zotero_data
ZOTERO_PLUGIN_DATA_DIR = ZOTERO_PLUGIN_DATA_DIR =D:/TEMP/zotero
# Custom commands to kill Zotero processes. # Custom commands to kill Zotero processes.
# Commands for different platforms are already built into zotero-plugin, # Commands for different platforms are already built into zotero-plugin,

31
addon/content/dialog.html Normal file
View File

@ -0,0 +1,31 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<dialog
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="保存网页快照"
buttons="accept,cancel"
onload="onLoad();"
ondialogaccept="return onAccept();"
>
<script>
function onLoad() {
document.getElementById("url-textbox").focus();
}
function onAccept() {
let url = document.getElementById("url-textbox").value;
if (!url) {
alert("请输入 URL");
return false;
}
window.arguments[0].callback(url);
return true;
}
</script>
<vbox>
<label value="请输入网页 URL:" />
<textbox id="url-textbox" width="400" />
</vbox>
</dialog>

View File

@ -1,13 +1,13 @@
{ {
"name": "zotero-plugin-template", "name": "zetter",
"version": "3.0.2", "version": "3.0.2",
"description": "Zotero Plugin Template", "description": "Zotero Better Plugin",
"config": { "config": {
"addonName": "Zotero Plugin Template", "addonName": "Zotero Better",
"addonID": "addontemplate@euclpts.com", "addonID": "wanghai915@qq.com",
"addonRef": "addontemplate", "addonRef": "zetter",
"addonInstance": "AddonTemplate", "addonInstance": "Zetter",
"prefsPrefix": "extensions.zotero.addontemplate" "prefsPrefix": "extensions.zotero.zetter"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -5,6 +5,8 @@ import {
PromptExampleFactory, PromptExampleFactory,
UIExampleFactory, UIExampleFactory,
} from "./modules/examples"; } from "./modules/examples";
import { registerViews } from "./modules/views";
import { getString, initLocale } from "./utils/locale"; import { getString, initLocale } from "./utils/locale";
import { registerPrefsScripts } from "./modules/preferenceScript"; import { registerPrefsScripts } from "./modules/preferenceScript";
import { createZToolkit } from "./utils/ztoolkit"; import { createZToolkit } from "./utils/ztoolkit";
@ -17,22 +19,21 @@ async function onStartup() {
]); ]);
initLocale(); initLocale();
// BasicExampleFactory.registerPrefs();
BasicExampleFactory.registerPrefs(); // BasicExampleFactory.registerNotifier();
BasicExampleFactory.registerNotifier(); // KeyExampleFactory.registerShortcuts();
KeyExampleFactory.registerShortcuts(); // await UIExampleFactory.registerExtraColumn();
await UIExampleFactory.registerExtraColumn(); // await UIExampleFactory.registerExtraColumnWithCustomCell();
await UIExampleFactory.registerExtraColumnWithCustomCell(); // UIExampleFactory.registerItemPaneCustomInfoRow();
UIExampleFactory.registerItemPaneCustomInfoRow(); // UIExampleFactory.registerItemPaneSection();
UIExampleFactory.registerItemPaneSection(); // UIExampleFactory.registerReaderItemPaneSection();
UIExampleFactory.registerReaderItemPaneSection();
await Promise.all( await Promise.all(
Zotero.getMainWindows().map((win) => onMainWindowLoad(win)), Zotero.getMainWindows().map((win) => onMainWindowLoad(win)),
@ -65,29 +66,29 @@ async function onMainWindowLoad(win: Window): Promise<void> {
text: `[30%] ${getString("startup-begin")}`, text: `[30%] ${getString("startup-begin")}`,
}); });
UIExampleFactory.registerStyleSheet(win); // UIExampleFactory.registerStyleSheet(win);
registerViews(win);
UIExampleFactory.registerRightClickMenuItem(); UIExampleFactory.registerRightClickMenuItem();
UIExampleFactory.registerRightClickMenuPopup(win); // UIExampleFactory.registerRightClickMenuPopup(win);
UIExampleFactory.registerWindowMenuWithSeparator(); // UIExampleFactory.registerWindowMenuWithSeparator();
PromptExampleFactory.registerNormalCommandExample(); // PromptExampleFactory.registerNormalCommandExample();
PromptExampleFactory.registerAnonymousCommandExample(win); // PromptExampleFactory.registerAnonymousCommandExample(win);
PromptExampleFactory.registerConditionalCommandExample(); // PromptExampleFactory.registerConditionalCommandExample();
await Zotero.Promise.delay(1000); // await Zotero.Promise.delay(1000);
popupWin.changeLine({ popupWin.changeLine({
progress: 100, progress: 100,
text: `[100%] ${getString("startup-finish")}`, text: `[100%] ${getString("startup-finish")}`,
}); });
popupWin.startCloseTimer(5000); popupWin.startCloseTimer(3000);
addon.hooks.onDialogEvents("dialogExample"); // addon.hooks.onDialogEvents("dialogExample");
} }
async function onMainWindowUnload(win: Window): Promise<void> { async function onMainWindowUnload(win: Window): Promise<void> {

View File

@ -1,4 +1,5 @@
import { getLocaleID, getString } from "../utils/locale"; import { getLocaleID, getString } from "../utils/locale";
import { URLInputDialog } from "./urlDialog";
function example( function example(
target: any, target: any,
@ -143,7 +144,10 @@ export class UIExampleFactory {
tag: "menuitem", tag: "menuitem",
id: "zotero-itemmenu-addontemplate-test", id: "zotero-itemmenu-addontemplate-test",
label: getString("menuitem-label"), label: getString("menuitem-label"),
commandListener: (ev) => addon.hooks.onDialogEvents("dialogExample"), commandListener: (ev) => {
const dialog = new URLInputDialog();
dialog.open();
},
icon: menuIcon, icon: menuIcon,
}); });
} }

129
src/modules/urlDialog.ts Normal file
View File

@ -0,0 +1,129 @@
import { config } from "../../package.json";
import { getString } from "../utils/locale";
export class URLInputDialog {
private window: Window;
private dialog: Window;
constructor() {
this.window = Zotero.getMainWindow();
}
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,
},
dataOut: null,
};
// 打开对话框
this.dialog = this.window.openDialog(
rootURI + "content/dialog/dialog.html",
"",
"chrome,centerscreen,resizable=false,width=500,height=200",
params,
);
// 设置对话框内容和事件监听
this.dialog.addEventListener("load", () => {
const doc = this.dialog.document;
// 设置对话框标题
doc.title = getString("snapshot-dialog.title");
// 插入自定义内容
const container = doc.querySelector(".dialog-content");
container.innerHTML = dialogContent;
// 表单提交处理
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");
}
}

22
src/modules/views.ts Normal file
View File

@ -0,0 +1,22 @@
import { config } from "../../package.json";
import { getString } from "../utils/locale";
import { URLInputDialog } from "./urlDialog";
export async function registerViews(window: Window) {
// 注册工具栏按钮
ztoolkit.UI.createElement(window.document, "button", {
id: `${config.addonRef}-toolbar-button`,
listeners: [
{
type: "click",
listener: (e) => {
const dialog = new URLInputDialog();
dialog.open();
dialog.saveSnapshot(
"https://blog.csdn.net/qq_43210428/article/details/120384547",
);
},
},
],
});
}