cursor Init 完善多人协同changelog,以及godot相关基础skill和代码规范

This commit is contained in:
Nostars Developer
2026-07-15 14:38:08 +08:00
parent fc8557f2a9
commit 6446261c69
56 changed files with 3523 additions and 1584 deletions

View File

@@ -0,0 +1,88 @@
---
name: html-doc
description: >-
生成人类可读的单文件 HTML 文档(参考手册、报告、字段说明、清单等),统一深色主题
与版式(侧边目录 + 表格 + callout + 徽标 + 代码块),并可选挂载标准化的「批注模块」——
让读者逐字段/逐区块填备注、自动暂存、一键导出带备注的 HTML 副本回传,作者打开即见全部批注。
当用户要「做一份给人看的 HTML 文档/参考手册/说明页/报告」,或提到「统一 HTML 样式」、
「让别人写批注/注释」、「可批注文档」时使用。
---
# html-doc
把「给人阅读的 HTML 文档」标准化:统一主题 + 统一版式 + 可选批注模块。**产出单文件自包含 HTML**CSS/JS 全内联,一个 `.html` 直接发人)。
## 资产
| 文件 | 作用 | 生成时怎么用 |
|------|------|------|
| `assets/theme.css` | 通用深色主题tokens + 版式 + 表格/callout/徽标/目录/代码块) | 内联进 `<head><style>` |
| `assets/annotate.js` | 标准化批注模块(表格列 / 区块框、localStorage 暂存、导出嵌 JSON、复制 Markdown | 需批注时内联进末尾 `<script>` |
| `templates/skeleton.html` | 文档骨架(侧栏目录 + 正文 + 占位符) | 复制后填内容 |
> 三者是唯一真相源;改主题/批注逻辑只改 assets再重新生成文档。
## 生成工作流
```
1. 明确文档类型与章节结构(参考手册 / 报告 / 清单…)
2. 复制 templates/skeleton.html替换占位符
{{TITLE}} {{SUBTITLE}} {{DOC_ID}}(批注命名空间,用短横线英文,如 csv-data-reference
{{TOC_LINKS}} {{CONTENT}} {{FOOTER}}
3. 把 assets/theme.css 全部内容内联到 <head> 的 <style> 里(单文件自包含)
4. 按「组件速查」填正文;每章一个 <section id="...">,与目录 <a href="#..."> 对应
5. 若需批注 → 见「批注模块」:给目标加 data 属性 + 内联 annotate.js
6. 用 Read/ReadLints 自查;中文内容务必用文件工具写(勿用 PowerShell 重定向,见 baserule
```
## 组件速查
章节标题:`<h2 class="section">`(自带顶部分隔线);子标题 `<h3>`
目录项:`<a href="#id">主名<span class="cn">副名</span></a>`;分隔 `<div class="sep"></div>`
表格:标准 `<table><thead><tr><th>…</thead><tbody>…`。类型列用 `<td class="type">`
徽标(语义色,放 `<span class="badge X">``accent` / `ok` / `warn` / `danger` / `muted`
提示框:
```html
<div class="callout warn"><div class="t">标题</div><p>正文</p></div>
```
变体:默认(蓝)/ `warn` / `danger` / `ok`
其他:`.tag`(等宽 pill`.legend`(图例行)、`ul.tight`(紧凑列表)、`.muted`(弱化文字)、`<footer>`
配色改动只动 `theme.css``:root` design tokens。文档专属徽标追加在内联 theme 之后。
## 批注模块(可选)
目的:读者填备注 → 导出带备注的 HTML 发回 → 作者打开即见。纯前端、零依赖、幂等。
**接入三步**
1. `<body data-note-ns="文档唯一标识">`(骨架已留 `{{DOC_ID}}`;隔离不同文档的本地暂存)
2. 标记批注目标(二选一或混用):
- 表格模式:`<table data-annotate="唯一名">` → 每行自动追加一列「备注」
- 区块模式:任意元素加 `data-note-id="唯一名"` → 其后插入一个备注框
- 防行序漂移:可给某 `<tr>``data-note-id="稳定key"` 覆盖自动 key
3.`assets/annotate.js` 全部内容内联进末尾 `<script>`
**运行行为**:页头下方出现工具栏(导出带备注副本 / 复制 Markdown / 清空 / 已填计数);输入自动存 localStorage导出时克隆文档、剔除注入 UI、仅把备注以 `<script id="embedded-notes" type="application/json">` 嵌入 → 对方打开由脚本幂等重建并回填。
**恢复优先级**:嵌入 JSON > localStorage保证收到的回传文件一定显示对方批注
## 反模式
- 别用外链 CSS/JS破坏「一个文件发人」——一律内联。
- 别硬编码猜测批注目标(如靠表头文字)——用 `data-annotate` / `data-note-id` 显式 opt-in。
- 别手改 `embedded-notes` JSON —— 由模块导出维护。
- 中文文本别用 PowerShell `Set-Content`/`echo >` 落盘GBK 会损坏),用文件工具写。
## 自迭代日志
本节记录使用本 Skill 过程中发现的必要检查项。
### 已知必要检查
1. **单文件自包含** — 生成后确认 `theme.css`(及批注时的 `annotate.js`)已真正内联,页面无 `<link>`/外链 `<script src>`,否则发给别人会样式/功能丢失。
2. **批注命名空间唯一**`data-note-ns` 必须每份文档不同,否则不同文档的 localStorage 暂存互相污染。

View 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);
})();

View File

@@ -0,0 +1,195 @@
/* ============================================================
html-doc · 通用深色主题
人类可读 HTML 文档的统一表现层。生成单文件文档时,把本文件
全部内容内联进 <style>。design tokens 集中在 :root改配色只动这里。
============================================================ */
:root {
--bg: #0f1115;
--panel: #171a21;
--panel-2: #1e222b;
--border: #2a2f3a;
--text: #d7dbe2;
--text-dim: #9aa1ad;
--accent: #5aa9ff;
--accent-2: #7ee0a2;
--warn: #ffb454;
--danger: #ff6b6b;
--code-bg: #11141a;
--mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
background: var(--bg);
color: var(--text);
font-family: var(--sans);
line-height: 1.6;
font-size: 15px;
}
/* ---------- 布局:侧边目录 + 正文 ---------- */
.layout { display: flex; align-items: flex-start; }
nav.toc {
position: sticky;
top: 0;
align-self: flex-start;
width: 260px;
height: 100vh;
overflow-y: auto;
background: var(--panel);
border-right: 1px solid var(--border);
padding: 20px 16px;
flex-shrink: 0;
}
nav.toc h2 {
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-dim);
margin: 0 0 12px;
}
nav.toc a {
display: block;
color: var(--text);
text-decoration: none;
padding: 6px 10px;
border-radius: 6px;
font-size: 14px;
margin-bottom: 2px;
}
nav.toc a:hover { background: var(--panel-2); color: var(--accent); }
nav.toc a .cn { color: var(--text-dim); font-size: 12px; margin-left: 6px; }
nav.toc .sep { height: 1px; background: var(--border); margin: 12px 4px; }
main {
flex: 1;
max-width: 1080px;
margin: 0 auto;
padding: 40px 48px 120px;
min-width: 0;
}
/* ---------- 标题与正文 ---------- */
header.page-head { margin-bottom: 32px; }
header.page-head h1 { font-size: 30px; margin: 0 0 8px; }
header.page-head p.sub { color: var(--text-dim); margin: 0; }
h2.section {
font-size: 24px;
margin: 48px 0 6px;
padding-top: 12px;
border-top: 1px solid var(--border);
}
h2.section:first-of-type { border-top: none; }
h3 { font-size: 19px; margin: 32px 0 10px; }
.file-title { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.file-title code { font-size: 18px; }
.cnname { color: var(--text-dim); font-size: 15px; }
p { margin: 10px 0; }
a.link { color: var(--accent); }
.muted { color: var(--text-dim); }
/* ---------- 代码 ---------- */
code {
font-family: var(--mono);
background: var(--code-bg);
border: 1px solid var(--border);
border-radius: 4px;
padding: 1px 5px;
font-size: 13px;
color: #e6c07b;
}
pre {
background: var(--code-bg);
border: 1px solid var(--border);
border-radius: 8px;
padding: 14px 16px;
overflow-x: auto;
font-family: var(--mono);
font-size: 13px;
line-height: 1.55;
}
pre code { background: none; border: none; padding: 0; color: var(--text); }
/* ---------- 表格 ---------- */
table {
width: 100%;
border-collapse: collapse;
margin: 14px 0 6px;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 8px;
overflow: hidden;
font-size: 14px;
}
thead th {
text-align: left;
background: var(--panel-2);
color: var(--text-dim);
font-weight: 600;
padding: 10px 12px;
border-bottom: 1px solid var(--border);
white-space: nowrap;
}
tbody td { padding: 9px 12px; border-bottom: 1px solid var(--border); vertical-align: top; }
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background: var(--panel-2); }
td.type { color: var(--warn); font-family: var(--mono); font-size: 12.5px; white-space: nowrap; }
/* ---------- 徽标(语义色)---------- */
.badge {
display: inline-block;
font-size: 11px;
padding: 1px 7px;
border-radius: 999px;
border: 1px solid var(--border);
white-space: nowrap;
line-height: 1.5;
}
.badge.accent { background: rgba(90,169,255,0.14); color: var(--accent); border-color: rgba(90,169,255,0.4); }
.badge.ok { background: rgba(126,224,162,0.12); color: var(--accent-2); border-color: rgba(126,224,162,0.35); }
.badge.warn { background: rgba(255,180,84,0.10); color: var(--warn); border-color: rgba(255,180,84,0.3); }
.badge.danger { background: rgba(255,107,107,0.12); color: var(--danger); border-color: rgba(255,107,107,0.4); }
.badge.muted { background: rgba(154,161,173,0.10); color: var(--text-dim); }
/* ---------- 提示框 ---------- */
.callout {
border-left: 3px solid var(--accent);
background: var(--panel);
border-radius: 0 8px 8px 0;
padding: 12px 16px;
margin: 16px 0;
}
.callout.warn { border-left-color: var(--warn); }
.callout.danger { border-left-color: var(--danger); }
.callout.ok { border-left-color: var(--accent-2); }
.callout .t { font-weight: 600; margin-bottom: 4px; }
/* ---------- 通用 pill / 标签 ---------- */
.tag {
display: inline-block;
background: var(--panel-2);
border: 1px solid var(--border);
border-radius: 6px;
padding: 3px 9px;
font-family: var(--mono);
font-size: 12.5px;
color: var(--accent);
margin: 4px 6px 4px 0;
}
/* ---------- 图例 / 列表 ---------- */
.legend { display: flex; gap: 14px; flex-wrap: wrap; margin: 10px 0 0; font-size: 13px; color: var(--text-dim); }
.legend span { display: inline-flex; align-items: center; gap: 6px; }
ul.tight { margin: 8px 0; padding-left: 22px; }
ul.tight li { margin: 3px 0; }
footer { margin-top: 60px; padding-top: 20px; border-top: 1px solid var(--border); color: var(--text-dim); font-size: 13px; }
@media (max-width: 860px) {
nav.toc { display: none; }
main { padding: 24px 18px 80px; }
}

View File

@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{TITLE}}</title>
<style>
/* [html-doc] 生成时:把 assets/theme.css 的全部内容内联到这里(保证单文件自包含)。
如需文档专属徽标/样式,追加在 theme.css 内容之后即可。 */
</style>
</head>
<body data-note-ns="{{DOC_ID}}">
<div class="layout">
<nav class="toc">
<h2>目录</h2>
<!-- {{TOC_LINKS}} 例:<a href="#sec1">章节一<span class="cn">副标题</span></a>
分隔线用 <div class="sep"></div> -->
</nav>
<main>
<header class="page-head">
<h1>{{TITLE}}</h1>
<p class="sub">{{SUBTITLE}}</p>
</header>
<!-- {{CONTENT}}
每个章节: <section id="sec1"><h2 class="section">标题</h2> ... </section>
组件速查见 SKILL.md「组件速查」。 -->
<footer>{{FOOTER}}</footer>
</main>
</div>
<!-- [html-doc] 需要批注功能时:
1) 给要批注的 <table> 加 data-annotate="唯一名";或给任意区块加 data-note-id="唯一名"
2) 把 assets/annotate.js 的全部内容内联到下面的 <script> 中并取消注释 -->
<!-- <script>/* INLINE assets/annotate.js */</script> -->
</body>
</html>