feat: 加入 web-to-pixso 插件源码
Chrome 扩展,用于从网页提取 DOM 树为 JSON 格式。 核心文件:capture.js(76KB,提取逻辑)
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
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();
|
||||
});
|
||||
Reference in New Issue
Block a user