cursor Init 完善多人协同changelog,以及godot相关基础skill和代码规范
This commit is contained in:
232
.cursor/skills/html-doc/assets/annotate.js
Normal file
232
.cursor/skills/html-doc/assets/annotate.js
Normal file
@@ -0,0 +1,232 @@
|
||||
/* ============================================================
|
||||
html-doc · 批注模块(annotate.js)
|
||||
让读者在文档里逐字段/逐区块填备注,自动暂存到 localStorage,
|
||||
一键导出「把备注嵌进 JSON 的 HTML 副本」发回,对方打开即可看到全部批注。
|
||||
|
||||
用法(生成单文件文档时,把本文件全部内联进 <script>):
|
||||
1) 表格模式:给 <table> 加 data-annotate="唯一名",会为每行追加一列「备注」。
|
||||
- 可选:给某个 <tr> 加 data-note-id="稳定key" 覆盖自动 key(防行序漂移)。
|
||||
2) 区块模式:给任意元素加 data-note-id="稳定key",其后会插入一个备注框。
|
||||
3) 命名空间:给 <body> 加 data-note-ns="文档唯一标识",隔离不同文档的暂存。
|
||||
|
||||
纯前端、零依赖、幂等(重复打开导出的副本不会重复注入)。
|
||||
============================================================ */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var NS = (document.body && document.body.dataset.noteNs) || document.title || "html-doc";
|
||||
var STORAGE_PREFIX = "htmldoc-note::" + NS + "::";
|
||||
var NOTE_COL_LABEL = "备注 / 批注";
|
||||
|
||||
// 1. 读取上一份文件嵌入的备注(对方发回来的文件里带着)
|
||||
var embedded = {};
|
||||
var embEl = document.getElementById("embedded-notes");
|
||||
if (embEl) {
|
||||
try { embedded = JSON.parse(embEl.textContent || "{}"); } catch (e) { embedded = {}; }
|
||||
}
|
||||
|
||||
// 2. 注入自带样式
|
||||
var style = document.createElement("style");
|
||||
style.id = "hd-note-style";
|
||||
style.textContent = [
|
||||
".hd-note-toolbar{position:sticky;top:0;z-index:30;display:flex;align-items:center;gap:10px;flex-wrap:wrap;",
|
||||
"background:var(--panel,#171a21);border:1px solid var(--border,#2a2f3a);border-radius:10px;padding:12px 16px;",
|
||||
"margin:0 0 24px;box-shadow:0 6px 18px rgba(0,0,0,.35)}",
|
||||
".hd-note-toolbar .nt-title{font-weight:600;font-size:14px}",
|
||||
".hd-note-toolbar .nt-count{color:var(--accent-2,#7ee0a2);font-size:12.5px}",
|
||||
".hd-note-toolbar .nt-hint{color:var(--text-dim,#9aa1ad);font-size:12.5px;flex-basis:100%;margin-top:2px}",
|
||||
".hd-note-btn{background:var(--panel-2,#1e222b);color:var(--text,#d7dbe2);border:1px solid var(--border,#2a2f3a);",
|
||||
"border-radius:7px;padding:6px 12px;font-size:13px;cursor:pointer;font-family:inherit}",
|
||||
".hd-note-btn:hover{border-color:var(--accent,#5aa9ff);color:var(--accent,#5aa9ff)}",
|
||||
".hd-note-btn.primary{background:rgba(90,169,255,.14);border-color:rgba(90,169,255,.5);color:var(--accent,#5aa9ff)}",
|
||||
".hd-note-btn.danger:hover{border-color:var(--danger,#ff6b6b);color:var(--danger,#ff6b6b)}",
|
||||
"th.hd-note-col,td.hd-note-cell{min-width:200px}",
|
||||
"textarea.hd-note{width:100%;background:var(--code-bg,#11141a);color:var(--text,#d7dbe2);",
|
||||
"border:1px solid var(--border,#2a2f3a);border-radius:6px;padding:6px 8px;font-family:inherit;font-size:13px;",
|
||||
"line-height:1.5;resize:vertical;min-height:38px}",
|
||||
"textarea.hd-note:focus{outline:none;border-color:var(--accent,#5aa9ff)}",
|
||||
"textarea.hd-note.filled{border-color:rgba(126,224,162,.5);background:rgba(126,224,162,.06)}",
|
||||
".hd-note-block{margin:8px 0 18px}",
|
||||
".hd-note-block .lbl{font-size:12px;color:var(--text-dim,#9aa1ad);margin-bottom:4px}"
|
||||
].join("");
|
||||
document.head.appendChild(style);
|
||||
|
||||
var entries = []; // {key, label, group, ta}
|
||||
|
||||
function restore(key) {
|
||||
if (Object.prototype.hasOwnProperty.call(embedded, key)) return embedded[key]; // 收到的文件优先显示对方批注
|
||||
var ls = localStorage.getItem(STORAGE_PREFIX + key);
|
||||
return ls !== null ? ls : "";
|
||||
}
|
||||
|
||||
function makeTextarea(key, label, group) {
|
||||
var ta = document.createElement("textarea");
|
||||
ta.className = "hd-note";
|
||||
ta.rows = 2;
|
||||
ta.placeholder = "在此填写备注…";
|
||||
ta.dataset.key = key;
|
||||
var val = restore(key);
|
||||
ta.value = val;
|
||||
if (val.trim()) ta.classList.add("filled");
|
||||
ta.addEventListener("input", function () {
|
||||
localStorage.setItem(STORAGE_PREFIX + key, ta.value);
|
||||
ta.classList.toggle("filled", ta.value.trim() !== "");
|
||||
updateCount();
|
||||
});
|
||||
entries.push({ key: key, label: label, group: group, ta: ta });
|
||||
return ta;
|
||||
}
|
||||
|
||||
// 3a. 表格模式:table[data-annotate]
|
||||
var annTables = document.querySelectorAll("table[data-annotate]");
|
||||
Array.prototype.forEach.call(annTables, function (table, ti) {
|
||||
var tableKey = table.getAttribute("data-annotate") || ("table" + ti);
|
||||
var section = table.closest("section");
|
||||
var h2 = section ? section.querySelector("h2.section, h2, h3") : null;
|
||||
var group = h2 ? h2.textContent.trim() : tableKey;
|
||||
|
||||
var headRow = table.querySelector("thead tr");
|
||||
if (headRow) {
|
||||
var th = document.createElement("th");
|
||||
th.className = "hd-note-col";
|
||||
th.textContent = NOTE_COL_LABEL;
|
||||
headRow.appendChild(th);
|
||||
}
|
||||
var rows = table.querySelectorAll("tbody tr");
|
||||
Array.prototype.forEach.call(rows, function (tr, idx) {
|
||||
var firstCell = tr.querySelector("td");
|
||||
var label = firstCell ? firstCell.textContent.trim() : ("行" + idx);
|
||||
var key = tr.getAttribute("data-note-id") || (tableKey + "::" + idx + "::" + label);
|
||||
var td = document.createElement("td");
|
||||
td.className = "hd-note-cell";
|
||||
td.appendChild(makeTextarea(key, label, group));
|
||||
tr.appendChild(td);
|
||||
});
|
||||
});
|
||||
|
||||
// 3b. 区块模式:任意 [data-note-id](表格行已在上面处理,这里排除)
|
||||
var blocks = document.querySelectorAll("[data-note-id]");
|
||||
Array.prototype.forEach.call(blocks, function (el) {
|
||||
if (el.tagName === "TR" || el.closest("table[data-annotate]")) return;
|
||||
var key = el.getAttribute("data-note-id");
|
||||
var section = el.closest("section");
|
||||
var h2 = section ? section.querySelector("h2.section, h2, h3") : null;
|
||||
var group = h2 ? h2.textContent.trim() : "区块批注";
|
||||
var label = (el.textContent || "").trim().slice(0, 40) || key;
|
||||
var wrap = document.createElement("div");
|
||||
wrap.className = "hd-note-block";
|
||||
var lbl = document.createElement("div");
|
||||
lbl.className = "lbl";
|
||||
lbl.textContent = NOTE_COL_LABEL;
|
||||
wrap.appendChild(lbl);
|
||||
wrap.appendChild(makeTextarea(key, label, group));
|
||||
if (el.nextSibling) el.parentNode.insertBefore(wrap, el.nextSibling);
|
||||
else el.parentNode.appendChild(wrap);
|
||||
});
|
||||
|
||||
if (!entries.length) return; // 没有任何批注目标就不加工具栏
|
||||
|
||||
// 4. 工具栏
|
||||
var bar = document.createElement("div");
|
||||
bar.className = "hd-note-toolbar";
|
||||
bar.innerHTML =
|
||||
'<span class="nt-title">批注模式</span>' +
|
||||
'<span class="nt-count" id="hdNoteCount">已填 0 条</span>' +
|
||||
'<button class="hd-note-btn primary" id="hdBtnExport">导出带备注的副本 (.html)</button>' +
|
||||
'<button class="hd-note-btn" id="hdBtnMd">复制备注为 Markdown</button>' +
|
||||
'<button class="hd-note-btn danger" id="hdBtnClear">清空本地备注</button>' +
|
||||
'<span class="nt-hint">填写会自动暂存到本机浏览器;填完点「导出带备注的副本」下载一份 HTML 发回即可,对方打开就能看到所有批注。</span>';
|
||||
var main = document.querySelector("main") || document.body;
|
||||
var head = main.querySelector("header.page-head");
|
||||
if (head && head.nextSibling) main.insertBefore(bar, head.nextSibling);
|
||||
else main.insertBefore(bar, main.firstChild);
|
||||
|
||||
function collect() {
|
||||
var out = {};
|
||||
entries.forEach(function (e) { if (e.ta.value.trim() !== "") out[e.key] = e.ta.value; });
|
||||
return out;
|
||||
}
|
||||
function updateCount() {
|
||||
var n = entries.filter(function (e) { return e.ta.value.trim() !== ""; }).length;
|
||||
var el = document.getElementById("hdNoteCount");
|
||||
if (el) el.textContent = "已填 " + n + " 条";
|
||||
}
|
||||
updateCount();
|
||||
|
||||
function pad(n) { return (n < 10 ? "0" : "") + n; }
|
||||
function stamp() {
|
||||
var d = new Date();
|
||||
return "" + d.getFullYear() + pad(d.getMonth() + 1) + pad(d.getDate()) + "-" + pad(d.getHours()) + pad(d.getMinutes());
|
||||
}
|
||||
function flash(btn, msg) {
|
||||
if (!btn) return;
|
||||
var old = btn.textContent;
|
||||
btn.textContent = msg;
|
||||
setTimeout(function () { btn.textContent = old; }, 1500);
|
||||
}
|
||||
|
||||
// 5. 导出:克隆文档 → 剔除注入的 UI → 仅嵌入备注 JSON(再次打开由本脚本幂等重建)
|
||||
function exportHtml() {
|
||||
var data = collect();
|
||||
var clone = document.documentElement.cloneNode(true);
|
||||
clone.querySelectorAll(".hd-note-col,.hd-note-cell,.hd-note-block,.hd-note-toolbar,#hd-note-style,#embedded-notes")
|
||||
.forEach(function (el) { el.parentNode.removeChild(el); });
|
||||
var s = document.createElement("script");
|
||||
s.type = "application/json";
|
||||
s.id = "embedded-notes";
|
||||
s.textContent = JSON.stringify(data, null, 2);
|
||||
clone.querySelector("body").appendChild(s);
|
||||
var html = "<!DOCTYPE html>\n" + clone.outerHTML;
|
||||
var blob = new Blob([html], { type: "text/html;charset=utf-8" });
|
||||
var a = document.createElement("a");
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = "notes-" + NS.replace(/[^a-zA-Z0-9_-]+/g, "_") + "-" + stamp() + ".html";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(a.href);
|
||||
}
|
||||
|
||||
// 6. 复制为 Markdown(按 group 分组)
|
||||
function toMarkdown() {
|
||||
var byGroup = {}, order = [];
|
||||
entries.forEach(function (e) {
|
||||
if (e.ta.value.trim() === "") return;
|
||||
if (!byGroup[e.group]) { byGroup[e.group] = []; order.push(e.group); }
|
||||
byGroup[e.group].push("- **" + e.label + "**: " + e.ta.value.replace(/\n+/g, " "));
|
||||
});
|
||||
if (!order.length) return "(暂无备注)";
|
||||
var lines = ["# " + NS + " · 批注", ""];
|
||||
order.forEach(function (g) { lines.push("## " + g); lines.push.apply(lines, byGroup[g]); lines.push(""); });
|
||||
return lines.join("\n");
|
||||
}
|
||||
function copyMd() {
|
||||
var md = toMarkdown();
|
||||
var done = function () { flash(document.getElementById("hdBtnMd"), "已复制 Markdown"); };
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(md).then(done, function () { fallbackCopy(md, done); });
|
||||
} else { fallbackCopy(md, done); }
|
||||
}
|
||||
function fallbackCopy(text, cb) {
|
||||
var t = document.createElement("textarea");
|
||||
t.value = text; t.style.position = "fixed"; t.style.opacity = "0";
|
||||
document.body.appendChild(t); t.select();
|
||||
try { document.execCommand("copy"); } catch (e) {}
|
||||
document.body.removeChild(t); if (cb) cb();
|
||||
}
|
||||
function clearAll() {
|
||||
if (!window.confirm("确定清空本机暂存的所有备注?(不影响已导出的文件)")) return;
|
||||
entries.forEach(function (e) {
|
||||
localStorage.removeItem(STORAGE_PREFIX + e.key);
|
||||
e.ta.value = ""; e.ta.classList.remove("filled");
|
||||
});
|
||||
updateCount();
|
||||
}
|
||||
|
||||
var be = document.getElementById("hdBtnExport");
|
||||
var bm = document.getElementById("hdBtnMd");
|
||||
var bc = document.getElementById("hdBtnClear");
|
||||
if (be) be.addEventListener("click", exportHtml);
|
||||
if (bm) bm.addEventListener("click", copyMd);
|
||||
if (bc) bc.addEventListener("click", clearAll);
|
||||
})();
|
||||
Reference in New Issue
Block a user