add url snapshot
This commit is contained in:
parent
6d4576d5b1
commit
859e3c6b48
@ -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
31
addon/content/dialog.html
Normal 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>
|
||||||
14
package.json
14
package.json
@ -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",
|
||||||
|
|||||||
39
src/hooks.ts
39
src/hooks.ts
@ -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> {
|
||||||
|
|||||||
@ -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
129
src/modules/urlDialog.ts
Normal 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
22
src/modules/views.ts
Normal 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",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user