Files
html2pptx/web-to-pixso/popup.js
T
李进 330f4e96ce feat: 加入 web-to-pixso 插件源码
Chrome 扩展,用于从网页提取 DOM 树为 JSON 格式。
核心文件:capture.js(76KB,提取逻辑)
2026-07-27 12:22:27 +08:00

222 lines
7.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const SETTINGS_KEY = "webToPixsoSettings";
const HELP_URL = "https://z8qrcvi3n5.feishu.cn/wiki/RV8TwlhFyiGsEekQXk8cX5SHn6f";
const DEFAULT_SETTINGS = {
useProxy: false,
concurrency: "8",
captureMode: "mixed",
captureWidth: null
};
const MIN_CAPTURE_WIDTH = 320;
const MAX_CAPTURE_WIDTH = 3840;
const captureModeSelect = document.getElementById("captureMode");
const proxyToggle = document.getElementById("proxyToggle");
const captureWidthInput = document.getElementById("captureWidth");
const concurrencySelect = document.getElementById("concurrency");
const captureBtn = document.getElementById("captureBtn");
const btnText = document.getElementById("btnText");
const selectBtn = document.getElementById("selectBtn");
const selectBtnText = document.getElementById("selectBtnText");
const status = document.getElementById("status");
const statusText = document.getElementById("statusText");
const progressFill = document.getElementById("progressFill");
const closeBtn = document.getElementById("closeBtn");
const helpLink = document.getElementById("helpLink");
function normalizeSettings(value = {}) {
const concurrency = String(value.concurrency || DEFAULT_SETTINGS.concurrency);
const captureWidth = normalizeCaptureWidth(value.captureWidth, null);
return {
useProxy: Boolean(value.useProxy),
concurrency: ["4", "6", "8", "10", "12", "16", "20", "infinite"].includes(concurrency)
? concurrency
: DEFAULT_SETTINGS.concurrency,
captureMode: value.captureMode === "editable" ? "editable" : "mixed",
captureWidth
};
}
function normalizeCaptureWidth(value, fallback = null) {
const number = Number.parseInt(String(value || "").replace(/\D+/g, ""), 10);
if (!Number.isFinite(number)) return fallback;
return Math.max(MIN_CAPTURE_WIDTH, Math.min(MAX_CAPTURE_WIDTH, number));
}
async function getCurrentViewportWidth() {
try {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab?.id) return null;
const [{ result }] = await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: () => Math.round(window.innerWidth || document.documentElement.clientWidth || 0)
});
return normalizeCaptureWidth(result, null);
} catch {
return null;
}
}
async function getSettings() {
const result = await chrome.storage.local.get({ [SETTINGS_KEY]: DEFAULT_SETTINGS });
return normalizeSettings(result[SETTINGS_KEY]);
}
async function saveSettings(settings) {
await chrome.storage.local.set({ [SETTINGS_KEY]: normalizeSettings(settings) });
}
function setProgress(percent, text) {
status.hidden = false;
progressFill.style.width = `${Math.max(0, Math.min(100, percent))}%`;
statusText.textContent = text;
}
function setBusy(isBusy) {
captureBtn.disabled = isBusy;
selectBtn.disabled = isBusy;
captureBtn.classList.toggle("loading", isBusy);
selectBtn.classList.toggle("loading", isBusy);
captureBtn.classList.remove("success", "error");
btnText.textContent = isBusy ? "采集中..." : "开始采集";
selectBtnText.textContent = isBusy ? "正在打开浮窗..." : "打开页面浮窗";
}
function setResult(kind, text) {
captureBtn.classList.remove("loading", "success", "error");
captureBtn.classList.add(kind);
btnText.textContent = text;
}
async function syncSettingsFromUI() {
await saveSettings({
captureMode: captureModeSelect.value,
useProxy: proxyToggle.checked,
concurrency: concurrencySelect.value,
captureWidth: normalizeCaptureWidth(captureWidthInput.value, null)
});
}
function sanitizeCaptureWidthInput({ clamp = false } = {}) {
const digits = captureWidthInput.value.replace(/\D+/g, "");
captureWidthInput.value = digits;
const width = normalizeCaptureWidth(digits, null);
const rawNumber = Number.parseInt(digits, 10);
let invalid = Boolean(digits) && Number.isFinite(rawNumber) &&
(rawNumber < MIN_CAPTURE_WIDTH || rawNumber > MAX_CAPTURE_WIDTH);
captureWidthInput.classList.toggle("invalid", invalid);
if (clamp && digits) {
captureWidthInput.value = String(width);
invalid = false;
captureWidthInput.classList.remove("invalid");
}
return width;
}
function readSettingsFromUI() {
return normalizeSettings({
captureMode: captureModeSelect.value,
useProxy: proxyToggle.checked,
concurrency: concurrencySelect.value,
captureWidth: sanitizeCaptureWidthInput({ clamp: true })
});
}
async function startCapture() {
setBusy(true);
setProgress(12, "准备当前网页...");
const settings = readSettingsFromUI();
await saveSettings(settings);
try {
setProgress(28, "注入采集脚本...");
const response = await chrome.runtime.sendMessage({
type: "PIXSO_CAPTURE_START",
options: settings
});
if (!response || !response.ok) {
throw new Error(response?.error || "采集失败");
}
const viewportText = response.actualViewportWidth
? `已按 ${response.actualViewportWidth}px 采集`
: "已按当前页面尺寸采集";
setProgress(100, `${viewportText}${response.filename}`);
setResult("success", "采集完成");
setTimeout(() => window.close(), 900);
} catch (error) {
setProgress(0, error.message || String(error));
setResult("error", "采集失败");
setTimeout(() => {
setBusy(false);
status.hidden = true;
progressFill.style.width = "0";
}, 2600);
}
}
async function startFloatingToolbar() {
setBusy(true);
setProgress(18, "正在打开页面采集浮窗...");
const settings = readSettingsFromUI();
await saveSettings(settings);
try {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab?.id) throw new Error("没有可选择的当前标签页");
await chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ["element-picker.js"]
});
await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: pickerSettings => window.__webToPixsoShowCaptureToolbar?.(pickerSettings),
args: [settings]
});
setProgress(100, "请在页面浮窗中选择整页或元素采集。");
setTimeout(() => window.close(), 500);
} catch (error) {
setProgress(0, error.message || String(error));
setResult("error", "采集失败");
setTimeout(() => {
setBusy(false);
status.hidden = true;
progressFill.style.width = "0";
}, 2600);
}
}
document.addEventListener("DOMContentLoaded", async () => {
const settings = await getSettings();
const currentWidth = await getCurrentViewportWidth();
captureModeSelect.value = settings.captureMode;
proxyToggle.checked = settings.useProxy;
concurrencySelect.value = settings.concurrency;
captureWidthInput.value = String(settings.captureWidth || currentWidth || "");
});
captureModeSelect.addEventListener("change", syncSettingsFromUI);
proxyToggle.addEventListener("change", syncSettingsFromUI);
concurrencySelect.addEventListener("change", syncSettingsFromUI);
captureWidthInput.addEventListener("input", () => {
sanitizeCaptureWidthInput();
syncSettingsFromUI();
});
captureWidthInput.addEventListener("blur", () => {
sanitizeCaptureWidthInput({ clamp: true });
syncSettingsFromUI();
});
captureBtn.addEventListener("click", startCapture);
selectBtn.addEventListener("click", startFloatingToolbar);
closeBtn.addEventListener("click", () => window.close());
helpLink?.addEventListener("click", async event => {
event.preventDefault();
await chrome.tabs.create({ url: HELP_URL });
window.close();
});