(() => { const bridge = window.__annotationSubmissionBridge; if (!bridge?.pageName || typeof bridge.read !== "function") return; const endpoint = `/api/annotations/${encodeURIComponent(bridge.pageName)}/submissions`; const toolbar = document.querySelector(".toolbar"); if (!toolbar) return; const style = document.createElement("style"); style.textContent = ` .annotation-submit-status { color: #687385; font-size: 12px; white-space: nowrap; } .annotation-history-dialog { width: min(680px, calc(100vw - 32px)); max-height: min(720px, calc(100vh - 32px)); padding: 0; border: 1px solid #d9dee7; border-radius: 8px; box-shadow: 0 24px 80px rgba(15, 23, 42, .2); } .annotation-history-dialog::backdrop { background: rgba(15, 23, 42, .32); } .annotation-history-head { position: sticky; top: 0; z-index: 1; display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 16px 18px; border-bottom: 1px solid #e5e9ef; background: #fff; } .annotation-history-head h2 { margin: 0; font-size: 17px; } .annotation-history-close { width: 32px; height: 32px; border: 0; border-radius: 5px; background: #f1f4f8; font-size: 20px; color: #556274; } .annotation-history-list { margin: 0; padding: 0; list-style: none; overflow: auto; } .annotation-history-item { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 14px; align-items: center; padding: 14px 18px; border-bottom: 1px solid #edf0f4; } .annotation-history-item:last-child { border-bottom: 0; } .annotation-history-title { font-weight: 650; font-size: 14px; } .annotation-history-meta { margin-top: 5px; color: #687385; font-size: 12px; } .annotation-history-summary { margin-top: 7px; display: flex; flex-wrap: wrap; gap: 10px; color: #4b5666; font-size: 12px; } .annotation-history-actions { display: flex; gap: 7px; } .annotation-history-empty { padding: 40px 18px; text-align: center; color: #687385; } @media (max-width: 820px) { .annotation-submit-status { display: none; } } `; document.head.appendChild(style); const historyButton = button("提交记录", "secondary"); const submitButton = button("提交", "primary"); const status = document.createElement("span"); status.className = "annotation-submit-status"; toolbar.append(status, historyButton, submitButton); const dialog = document.createElement("dialog"); dialog.className = "annotation-history-dialog"; dialog.innerHTML = `

提交记录

    `; document.body.appendChild(dialog); dialog.querySelector(".annotation-history-close").onclick = () => dialog.close(); dialog.addEventListener("click", (event) => { if (event.target === dialog) dialog.close(); }); let history = []; historyButton.onclick = async () => { dialog.showModal(); await refreshHistory(); }; submitButton.onclick = async () => { submitButton.disabled = true; submitButton.textContent = "提交中"; try { const response = await fetch(endpoint, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ annotations: bridge.read() }), }); const payload = await response.json(); if (!response.ok) throw new Error(payload.error || "提交失败"); history = [ summaryOf(payload.submission), ...history.filter((item) => item.id !== payload.submission.id), ]; setStatus(payload.submission); setRevisionUrl(payload.submission.id); bridge.notify?.(`已提交第 ${payload.submission.version} 版`); } catch (error) { bridge.notify?.(error instanceof Error ? error.message : "提交失败"); } finally { submitButton.disabled = false; submitButton.textContent = "提交"; } }; async function refreshHistory() { const list = dialog.querySelector(".annotation-history-list"); list.innerHTML = '
  1. 正在读取
  2. '; try { const payload = await fetchJson(endpoint); history = payload.history || []; renderHistory(); } catch (error) { list.innerHTML = `
  3. ${escapeHtml(error instanceof Error ? error.message : "读取失败")}
  4. `; } } function renderHistory() { const list = dialog.querySelector(".annotation-history-list"); if (!history.length) { list.innerHTML = '
  5. 还没有提交记录
  6. '; return; } list.innerHTML = ""; for (const item of history) { const li = document.createElement("li"); li.className = "annotation-history-item"; li.innerHTML = `
    第 ${item.version} 版 · ${escapeHtml(item.submittedBy?.username || "未知用户")}
    ${formatTime(item.submittedAt)}
    已标 ${item.summary?.completed || 0}/${item.summary?.total || 0} G ${item.summary?.G || 0} S ${item.summary?.S || 0} B ${item.summary?.B || 0}
    `; li.querySelector('[data-action="copy"]').onclick = async () => { const url = revisionUrl(item.id); try { await navigator.clipboard.writeText(url); bridge.notify?.("版本链接已复制"); } catch { window.prompt("复制版本链接", url); } }; li.querySelector('[data-action="view"]').onclick = async () => { try { const payload = await fetchJson( `${endpoint}?revision=${encodeURIComponent(item.id)}`, ); bridge.apply(payload.submission.annotations || []); dialog.close(); setStatus(payload.submission, "正在查看"); setRevisionUrl(payload.submission.id); bridge.notify?.(`已打开第 ${payload.submission.version} 版`); } catch (error) { bridge.notify?.(error instanceof Error ? error.message : "读取失败"); } }; list.appendChild(li); } } async function initialize() { try { const requestedRevision = new URLSearchParams(window.location.search).get( "revision", ); if (requestedRevision) { const payload = await fetchJson( `${endpoint}?revision=${encodeURIComponent(requestedRevision)}`, ); bridge.apply(payload.submission.annotations || []); setStatus(payload.submission, "正在查看"); return; } const payload = await fetchJson(endpoint); history = payload.history || []; if (payload.latest) { bridge.applyMissing(payload.latest.annotations || []); setStatus(payload.latest); } else { status.textContent = "尚未提交"; } } catch { status.textContent = "提交记录暂不可用"; } } function setStatus(submission, prefix = "最新") { status.textContent = `${prefix}:第 ${submission.version} 版 · ${submission.submittedBy?.username || "未知用户"}`; } function revisionUrl(revisionId) { const url = new URL(window.location.href); url.searchParams.set("revision", revisionId); return url.toString(); } function setRevisionUrl(revisionId) { window.history.replaceState(null, "", revisionUrl(revisionId)); } function button(label, variant) { const element = document.createElement("button"); element.type = "button"; element.className = `btn ${variant}`; element.textContent = label; return element; } async function fetchJson(url) { const response = await fetch(url, { cache: "no-store" }); const payload = await response.json(); if (!response.ok) throw new Error(payload.error || "请求失败"); return payload; } function summaryOf(submission) { const { annotations: _annotations, ...summary } = submission; return summary; } function formatTime(value) { return new Intl.DateTimeFormat("zh-CN", { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hour12: false, }).format(new Date(value)); } function escapeHtml(value) { return String(value ?? "").replace( /[&<>"']/g, (character) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'", })[character], ); } void initialize(); })();