paseo-req-kit 1.14.0 → 1.15.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/docs/guide//347/273/264/346/212/244/350/200/205/346/214/207/345/215/227.md +2 -0
- package/engine/src/review-assets/app.css +153 -234
- package/engine/src/review-assets/app.html +44 -32
- package/engine/src/review-assets/app.js +11 -7
- package/engine/src/review-assets/check.css +11 -56
- package/engine/src/review-assets/check.html +48 -22
- package/engine/src/review-assets/check.js +169 -86
- package/engine/src/review-assets/code.css +15 -92
- package/engine/src/review-assets/code.html +41 -26
- package/engine/src/review-assets/code.js +196 -101
- package/engine/src/review-assets/rk-icons.js +98 -0
- package/engine/src/review-assets/rk-tokens.css +252 -0
- package/engine/src/review-assets/rk.css +252 -0
- package/engine/src/review.ts +6 -4
- package/package.json +1 -1
- package/paseo-plugin/package.json +1 -1
- package/site-dist/404.html +2 -2
- package/site-dist/assets/{app.VQ-QxRPH.js → app.D5s_yGHZ.js} +1 -1
- package/site-dist/assets/changelog.md.RJaBnmnw.js +1 -0
- package/site-dist/assets/chunks/@localSearchIndexroot.Dric-MbI.js +1 -0
- package/site-dist/assets/chunks/{VPLocalSearchBox.CT4Vlj76.js → VPLocalSearchBox.B4CyDsTr.js} +1 -1
- package/site-dist/assets/chunks/{theme.B_UmHHou.js → theme.CCocenmB.js} +2 -2
- package/site-dist/assets/{guide_getting-started.md.CZM53rO_.js → guide_getting-started.md.wFOkpMSx.js} +1 -1
- package/site-dist/assets/guide_maintainer.md.mFZprBj1.js +1 -0
- package/site-dist/assets/{guide_maintainer.md.C1v7EpSY.lean.js → guide_maintainer.md.mFZprBj1.lean.js} +1 -1
- package/site-dist/changelog.html +5 -5
- package/site-dist/guide/getting-started.html +5 -5
- package/site-dist/guide/how-it-works.html +4 -4
- package/site-dist/guide/index.html +4 -4
- package/site-dist/guide/install.html +4 -4
- package/site-dist/guide/maintainer.html +5 -5
- package/site-dist/guide/phases.html +4 -4
- package/site-dist/guide/project.html +4 -4
- package/site-dist/guide/user.html +4 -4
- package/site-dist/hashmap.json +1 -1
- package/site-dist/index.html +4 -4
- package/site-dist/reference/adr/0001-step-agents-under-origin-chat.html +4 -4
- package/site-dist/reference/adr/0002-no-directory-switch-in-chat.html +4 -4
- package/site-dist/reference/adr/0003-script-engine-not-hub.html +4 -4
- package/site-dist/reference/adr/0004-result-files.html +4 -4
- package/site-dist/reference/adr/0005-no-default-branch-local-commits-only.html +4 -4
- package/site-dist/reference/adr/0006-skill-install-targets.html +4 -4
- package/site-dist/reference/adr/0007-own-interview-skill.html +4 -4
- package/site-dist/reference/adr/0008-entries-do-not-call-entries.html +4 -4
- package/site-dist/reference/adr/0009-versioned-install-layout.html +4 -4
- package/site-dist/reference/adr/0010-generated-feature-overview.html +4 -4
- package/site-dist/reference/adr/0011-resume-after-completion.html +4 -4
- package/site-dist/reference/adr/0012-review-panel.html +4 -4
- package/site-dist/reference/adr/0013-no-release-tooling.html +4 -4
- package/site-dist/reference/adr/0014-notifier-slot.html +4 -4
- package/site-dist/reference/adr/0015-incremental-continue.html +4 -4
- package/site-dist/reference/adr/0016-upgrade-from-tags.html +4 -4
- package/site-dist/reference/adr/0017-use-paseo-capabilities.html +4 -4
- package/site-dist/reference/adr/0018-own-worktree-option.html +4 -4
- package/site-dist/reference/adr/0019-parallel-orchestration.html +4 -4
- package/site-dist/reference/adr/0020-scheduling-through-the-chat.html +4 -4
- package/site-dist/reference/adr/0021-npm-version-manager.html +4 -4
- package/site-dist/reference/adr/0022-develop-and-verify-phases.html +4 -4
- package/site-dist/reference/adr/0023-types-gate-slices.html +4 -4
- package/site-dist/reference/adr/0024-confirm-early-review-axes.html +4 -4
- package/site-dist/reference/adr/0025-joint-requirements.html +4 -4
- package/site-dist/reference/adr/0026-machine-records-and-api.html +4 -4
- package/site-dist/reference/adr/0027-paseo-plugin.html +4 -4
- package/site-dist/reference/adr/0028-external-integrations.html +4 -4
- package/site-dist/reference/adr/0029-requirements-from-cards.html +4 -4
- package/site-dist/reference/adr/0030-card-intake-process.html +4 -4
- package/site-dist/reference/adr/0031-fewer-interventions.html +4 -4
- package/site-dist/reference/adr/0032-npm-only-install.html +4 -4
- package/site-dist/reference/adr/0033-prototype-review-and-ui-check.html +4 -4
- package/site-dist/reference/adr/index.html +4 -4
- package/site-dist/reference/commands.html +4 -4
- package/site-dist/reference/config.html +4 -4
- package/site-dist/reference/contracts/cli-json.html +4 -4
- package/site-dist/reference/contracts/feature-files.html +4 -4
- package/site-dist/reference/contracts/index.html +4 -4
- package/site-dist/reference/contracts/notifier-event.html +4 -4
- package/site-dist/reference/contracts/result-file.html +4 -4
- package/site-dist/reference/contracts/spec.html +4 -4
- package/site-dist/reference/contracts/state.html +4 -4
- package/site-dist/reference/contracts/usage-record.html +4 -4
- package/site-dist/reference/contracts/verify.html +4 -4
- package/site-dist/reference/scope.html +4 -4
- package/site-dist/assets/changelog.md.CIKYJXpF.js +0 -1
- package/site-dist/assets/chunks/@localSearchIndexroot.h7oQE_np.js +0 -1
- package/site-dist/assets/guide_maintainer.md.C1v7EpSY.js +0 -1
- /package/site-dist/assets/{changelog.md.CIKYJXpF.lean.js → changelog.md.RJaBnmnw.lean.js} +0 -0
- /package/site-dist/assets/{guide_getting-started.md.CZM53rO_.lean.js → guide_getting-started.md.wFOkpMSx.lean.js} +0 -0
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
// The code review page (1.14). Plain script, no framework: the server serves it
|
|
2
|
-
// API (api/info, api/draft, api/submit). Everything shown is set with textContent.
|
|
1
|
+
// The code review page (1.14; design system 1.15). Plain script, no framework: the server serves it
|
|
2
|
+
// next to its JSON API (api/info, api/draft, api/submit). Everything shown is set with textContent.
|
|
3
3
|
(function () {
|
|
4
4
|
"use strict";
|
|
5
5
|
var state = { info: null, comments: [], decisions: {}, reviewer: "", reviewers: "", note: "", saveTimer: null, busy: false, open: null };
|
|
6
6
|
var $ = function (id) {
|
|
7
7
|
return document.getElementById(id);
|
|
8
8
|
};
|
|
9
|
+
var icon = function (name, cls) {
|
|
10
|
+
return window.RkIcon ? window.RkIcon(name, cls) : null;
|
|
11
|
+
};
|
|
9
12
|
|
|
10
13
|
function el(tag, attrs, children) {
|
|
11
14
|
var node = document.createElement(tag);
|
|
12
15
|
Object.keys(attrs || {}).forEach(function (k) {
|
|
13
|
-
if (attrs[k] === null || attrs[k] === undefined) return;
|
|
16
|
+
if (attrs[k] === null || attrs[k] === undefined || attrs[k] === false) return;
|
|
14
17
|
if (k === "text") node.textContent = attrs[k];
|
|
15
18
|
else if (k === "className") node.className = attrs[k];
|
|
16
19
|
else if (k.indexOf("on") === 0) node.addEventListener(k.slice(2), attrs[k]);
|
|
@@ -22,6 +25,10 @@
|
|
|
22
25
|
return node;
|
|
23
26
|
}
|
|
24
27
|
|
|
28
|
+
function badge(kind, iconName, text) {
|
|
29
|
+
return el("span", { className: "rk-badge rk-badge--" + kind }, [iconName ? icon(iconName) : null, text]);
|
|
30
|
+
}
|
|
31
|
+
|
|
25
32
|
function toast(text) {
|
|
26
33
|
var t = $("toast");
|
|
27
34
|
t.textContent = text;
|
|
@@ -50,11 +57,22 @@
|
|
|
50
57
|
.filter(Boolean);
|
|
51
58
|
}
|
|
52
59
|
|
|
60
|
+
function hhmm() {
|
|
61
|
+
var d = new Date();
|
|
62
|
+
return (d.getHours() < 10 ? "0" : "") + d.getHours() + ":" + (d.getMinutes() < 10 ? "0" : "") + d.getMinutes();
|
|
63
|
+
}
|
|
64
|
+
|
|
53
65
|
function save() {
|
|
54
66
|
if (state.info.readOnly) return;
|
|
55
67
|
clearTimeout(state.saveTimer);
|
|
56
68
|
state.saveTimer = setTimeout(function () {
|
|
57
|
-
api("api/draft", "PUT", { comments: state.comments, decisions: state.decisions, reviewer: state.reviewer, reviewers: state.reviewers, note: state.note })
|
|
69
|
+
api("api/draft", "PUT", { comments: state.comments, decisions: state.decisions, reviewer: state.reviewer, reviewers: state.reviewers, note: state.note })
|
|
70
|
+
.then(function () {
|
|
71
|
+
$("saved").textContent = "已自动保存 " + hhmm();
|
|
72
|
+
})
|
|
73
|
+
.catch(function () {
|
|
74
|
+
$("saved").textContent = "没保存上,网络恢复后再改一下会重试";
|
|
75
|
+
});
|
|
58
76
|
}, 600);
|
|
59
77
|
count();
|
|
60
78
|
}
|
|
@@ -75,19 +93,28 @@
|
|
|
75
93
|
}).length;
|
|
76
94
|
var fix = fixes().length;
|
|
77
95
|
var total = comments + fix + (state.note.trim() ? 1 : 0);
|
|
78
|
-
$("count").textContent =
|
|
96
|
+
$("count").textContent = total ? total + " 条修改意见" : "还没有修改意见";
|
|
97
|
+
$("countsub").textContent = state.info.readOnly
|
|
98
|
+
? "这是只读链接,只能查看"
|
|
99
|
+
: state.info.running
|
|
100
|
+
? "流水线正在运行,结束后才能提交评审"
|
|
101
|
+
: "代码行上的意见 " + comments + " 条,AI 意见标为要改 " + fix + " 条";
|
|
79
102
|
var blocked = state.info.readOnly || state.info.running || state.busy;
|
|
80
103
|
$("changes").disabled = blocked || total === 0;
|
|
81
|
-
$("changes").
|
|
104
|
+
$("changes").title = total === 0 ? "写一条意见,或把 AI 意见标为要改,才能退回修改" : "";
|
|
105
|
+
$("changes").lastChild.textContent = total ? "退回修改(" + total + " 条)" : "退回修改";
|
|
82
106
|
$("approve").disabled = blocked;
|
|
83
107
|
}
|
|
84
108
|
|
|
85
|
-
var
|
|
86
|
-
var
|
|
109
|
+
var SEVERITY = { "阻塞": ["failed", "octagon-alert"], "重要": ["warning", "triangle-alert"], "建议": ["muted", null], "待核实": ["ai", "circle-dashed"] };
|
|
110
|
+
var STATUS = { fixed: ["success", "circle-check", "已修复"], rejected: ["outline", null, "修复时判为不成立"], wontfix: ["outline", null, "决定不修复"], open: ["outline", null, "还开着"] };
|
|
87
111
|
|
|
88
|
-
function findingNode(f,
|
|
112
|
+
function findingNode(f, inline) {
|
|
89
113
|
var d = state.decisions[f.id] || {};
|
|
90
|
-
var
|
|
114
|
+
var sev = SEVERITY[f.severity] || ["muted", null];
|
|
115
|
+
var st = STATUS[f.status] || STATUS.open;
|
|
116
|
+
var note = el("input", { className: "rk-input", placeholder: "补充一句(可不填)", value: d.note || "", "aria-label": "补充说明", hidden: d.mark === "fix" ? null : "" });
|
|
117
|
+
note.style.flex = "1 1 200px";
|
|
91
118
|
note.addEventListener("input", function (e) {
|
|
92
119
|
state.decisions[f.id] = { mark: "fix", note: e.target.value };
|
|
93
120
|
save();
|
|
@@ -102,43 +129,47 @@
|
|
|
102
129
|
};
|
|
103
130
|
}
|
|
104
131
|
var where = f.file ? f.file + (f.line ? ":" + f.line : "") : "";
|
|
105
|
-
return el("div", { className: "finding " + (
|
|
106
|
-
el("div", { className: "
|
|
107
|
-
el("span", { className: "
|
|
108
|
-
el("
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
132
|
+
return el("div", { className: "rk-finding rk-finding--ai" + (d.mark === "ok" ? " is-ok" : "") }, [
|
|
133
|
+
el("div", { className: "rk-finding__head" }, [
|
|
134
|
+
el("span", { className: "rk-finding__who" }, [icon("sparkles", "ic ic-sm"), "AI 评审"]),
|
|
135
|
+
el("span", { className: "rk-finding__id", text: f.id + (f.round ? " · 第 " + f.round + " 轮" : "") }),
|
|
136
|
+
badge(sev[0], sev[1], f.severity),
|
|
137
|
+
badge(st[0], st[1], st[2]),
|
|
138
|
+
!inline && where
|
|
139
|
+
? el("a", { href: "#", className: "rk-finding__where", text: where, onclick: function (e) {
|
|
140
|
+
e.preventDefault();
|
|
141
|
+
jump(f.file, f.line);
|
|
142
|
+
} })
|
|
143
|
+
: null,
|
|
114
144
|
]),
|
|
115
|
-
el("div", { className: "
|
|
116
|
-
f.suggestion ? el("div", { className: "
|
|
145
|
+
el("div", { className: "rk-finding__body", text: f.title }),
|
|
146
|
+
f.suggestion ? el("div", { className: "rk-finding__fix", text: "建议:" + f.suggestion }) : null,
|
|
117
147
|
state.info.readOnly
|
|
118
148
|
? null
|
|
119
|
-
: el("div", { className: "
|
|
120
|
-
el("
|
|
121
|
-
|
|
149
|
+
: el("div", { className: "rk-finding__acts" }, [
|
|
150
|
+
el("div", { className: "rk-seg", role: "group", "aria-label": "这条意见要不要改" }, [
|
|
151
|
+
el("button", { type: "button", className: "is-fix", "aria-pressed": String(d.mark === "fix"), text: "要改", onclick: mark("fix") }),
|
|
152
|
+
el("button", { type: "button", className: "is-ok", "aria-pressed": String(d.mark === "ok"), text: "不用改", onclick: mark("ok") }),
|
|
153
|
+
]),
|
|
154
|
+
note,
|
|
122
155
|
]),
|
|
123
|
-
note,
|
|
124
156
|
]);
|
|
125
157
|
}
|
|
126
158
|
|
|
127
159
|
function jump(file, line) {
|
|
128
160
|
var target = document.querySelector('[data-file="' + CSS.escape(file) + '"]' + (line ? '[data-n="' + line + '"]' : ""));
|
|
129
161
|
if (!target) target = document.querySelector('[data-file-head="' + CSS.escape(file) + '"]');
|
|
130
|
-
if (target)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
162
|
+
if (!target) return;
|
|
163
|
+
var box = target.closest(".rk-diff");
|
|
164
|
+
if (box && box.classList.contains("folded")) box.classList.remove("folded");
|
|
165
|
+
target.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
166
|
+
target.classList.add("rk-flash");
|
|
167
|
+
setTimeout(function () {
|
|
168
|
+
target.classList.remove("rk-flash");
|
|
169
|
+
}, 1600);
|
|
139
170
|
}
|
|
140
171
|
|
|
141
|
-
function
|
|
172
|
+
function commentRow(file, line) {
|
|
142
173
|
var existing = state.comments.find(function (c) {
|
|
143
174
|
return c.file === file && c.line === line;
|
|
144
175
|
});
|
|
@@ -146,38 +177,58 @@
|
|
|
146
177
|
existing = { file: file, line: line, text: "" };
|
|
147
178
|
state.comments.push(existing);
|
|
148
179
|
}
|
|
149
|
-
var ta = el("textarea", { rows: "2", placeholder: "这一行哪里不对、应该怎么改" });
|
|
180
|
+
var ta = el("textarea", { className: "rk-input", rows: "2", placeholder: "这一行哪里不对、应该怎么改", "aria-label": "你的意见" });
|
|
150
181
|
ta.value = existing.text;
|
|
182
|
+
ta.disabled = !!state.info.readOnly;
|
|
151
183
|
ta.addEventListener("input", function (e) {
|
|
152
184
|
existing.text = e.target.value;
|
|
153
185
|
save();
|
|
154
186
|
});
|
|
155
|
-
var remove = el("button", {
|
|
187
|
+
var remove = el("button", { type: "button", className: "rk-btn rk-btn--ghost rk-btn--sm", onclick: function () {
|
|
156
188
|
state.comments = state.comments.filter(function (c) {
|
|
157
189
|
return c !== existing;
|
|
158
190
|
});
|
|
159
191
|
state.open = null;
|
|
160
192
|
render();
|
|
161
193
|
save();
|
|
162
|
-
} });
|
|
163
|
-
|
|
194
|
+
} }, [icon("trash-2", "ic ic-sm"), "删除"]);
|
|
195
|
+
var card = el("div", { className: "rk-finding rk-finding--you" }, [
|
|
196
|
+
el("div", { className: "rk-finding__head" }, [el("span", { className: "rk-finding__who" }, [icon("message-square-plus", "ic ic-sm"), "你的意见"]), el("span", { className: "rk-finding__where", text: file + ":" + line }), state.info.readOnly ? null : remove]),
|
|
197
|
+
ta,
|
|
198
|
+
]);
|
|
199
|
+
if (state.open === file + ":" + line && !existing.text) setTimeout(function () {
|
|
200
|
+
ta.focus();
|
|
201
|
+
});
|
|
202
|
+
return el("tr", { className: "note" }, [el("td", { colspan: "3" }, [card])]);
|
|
164
203
|
}
|
|
165
204
|
|
|
205
|
+
var FILE_STATUS = { added: "新增", deleted: "删除", modified: "修改", renamed: "改名" };
|
|
206
|
+
|
|
166
207
|
function renderFile(f, findingsByFile) {
|
|
167
208
|
var mine = findingsByFile[f.path] || [];
|
|
168
209
|
var owners = (state.info.reviewers.byFile || {})[f.path] || [];
|
|
169
|
-
var
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
el("span", { className: "
|
|
173
|
-
el("span", { className: "
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
owners.length ? el("span", { className: "
|
|
210
|
+
var box;
|
|
211
|
+
var head = el("div", { className: "rk-diff__head", role: "button", tabindex: "0", "aria-label": "展开或折叠 " + f.path, "data-file-head": f.path }, [
|
|
212
|
+
icon("chevron-down"),
|
|
213
|
+
el("span", { className: "rk-diff__path", text: f.path + (f.oldPath ? "(原 " + f.oldPath + ")" : "") }),
|
|
214
|
+
el("span", { className: "rk-diff__n" }, [el("span", { className: "a", text: "+" + f.additions }), " ", el("span", { className: "r", text: "−" + f.deletions })]),
|
|
215
|
+
badge("muted", null, FILE_STATUS[f.status] || f.status),
|
|
216
|
+
f.doc ? badge("muted", "file-text", "需求文档") : null,
|
|
217
|
+
mine.length ? badge("ai", "sparkles", "AI 意见 " + mine.length) : null,
|
|
218
|
+
el("span", { className: "rk-top__spacer" }),
|
|
219
|
+
owners.length ? el("span", { className: "rk-cap", text: "审核人 " + owners.join(" ") }) : null,
|
|
179
220
|
]);
|
|
180
|
-
|
|
221
|
+
function toggle() {
|
|
222
|
+
box.classList.toggle("folded");
|
|
223
|
+
}
|
|
224
|
+
head.addEventListener("click", toggle);
|
|
225
|
+
head.addEventListener("keydown", function (e) {
|
|
226
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
227
|
+
e.preventDefault();
|
|
228
|
+
toggle();
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
var body = el("div", { className: "rk-diff__body" });
|
|
181
232
|
var unplaced = mine.filter(function (x) {
|
|
182
233
|
return !x.line || !f.hunks.some(function (h) {
|
|
183
234
|
return h.lines.some(function (l) {
|
|
@@ -185,19 +236,23 @@
|
|
|
185
236
|
});
|
|
186
237
|
});
|
|
187
238
|
});
|
|
188
|
-
unplaced.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
239
|
+
if (unplaced.length) {
|
|
240
|
+
var wrap = el("div", { className: "rk-col", style: "padding:12px 16px" });
|
|
241
|
+
unplaced.forEach(function (x) {
|
|
242
|
+
wrap.appendChild(findingNode(x, true));
|
|
243
|
+
});
|
|
244
|
+
body.appendChild(wrap);
|
|
245
|
+
}
|
|
246
|
+
if (f.binary) body.appendChild(el("div", { className: "rk-cap", style: "padding:12px 16px", text: "二进制文件,不显示内容。" }));
|
|
192
247
|
f.hunks.forEach(function (h) {
|
|
193
|
-
var table = el("table"
|
|
194
|
-
table.appendChild(el("tr", { className: "hunk" }, [el("td", {
|
|
248
|
+
var table = el("table");
|
|
249
|
+
table.appendChild(el("tr", { className: "hunk" }, [el("td", { className: "n" }), el("td", { className: "n" }), el("td", { text: h.header })]));
|
|
195
250
|
h.lines.forEach(function (l) {
|
|
196
251
|
var line = l.n || l.o;
|
|
197
|
-
var row = el("tr", { className: "line " + (l.t === "+" ? "add" : l.t === "-" ? "del" : ""), "data-file": f.path, "data-n": l.n || "", title: state.info.readOnly ? "" : "点这一行写意见" }, [
|
|
198
|
-
el("td", { className: "
|
|
199
|
-
el("td", { className: "
|
|
200
|
-
el("td", { className: "
|
|
252
|
+
var row = el("tr", { className: "line " + (l.t === "+" ? "add" : l.t === "-" ? "del" : "ctx"), "data-file": f.path, "data-n": l.n || "", title: state.info.readOnly ? "" : "点这一行写意见" }, [
|
|
253
|
+
el("td", { className: "n", text: l.o === null ? "" : String(l.o) }),
|
|
254
|
+
el("td", { className: "n", text: l.n === null ? "" : String(l.n) }),
|
|
255
|
+
el("td", { className: "s", text: l.s }),
|
|
201
256
|
]);
|
|
202
257
|
if (!state.info.readOnly) {
|
|
203
258
|
row.addEventListener("click", function () {
|
|
@@ -211,20 +266,27 @@
|
|
|
211
266
|
return x.line && l.n === x.line;
|
|
212
267
|
})
|
|
213
268
|
.forEach(function (x) {
|
|
214
|
-
table.appendChild(el("tr", { className: "
|
|
269
|
+
table.appendChild(el("tr", { className: "note" }, [el("td", { colspan: "3" }, [findingNode(x, true)])]));
|
|
215
270
|
});
|
|
216
271
|
var hasComment = state.comments.some(function (c) {
|
|
217
272
|
return c.file === f.path && c.line === line;
|
|
218
273
|
});
|
|
219
|
-
if (hasComment || state.open === f.path + ":" + line) table.appendChild(
|
|
274
|
+
if (hasComment || state.open === f.path + ":" + line) table.appendChild(commentRow(f.path, line));
|
|
220
275
|
});
|
|
221
276
|
body.appendChild(table);
|
|
222
277
|
});
|
|
223
|
-
if (f.truncated) body.appendChild(el("div", { className: "
|
|
224
|
-
|
|
278
|
+
if (f.truncated) body.appendChild(el("div", { className: "rk-cap", style: "padding:12px 16px", text: "改动太多,只显示了一部分。完整改动请在开发工具里看。" }));
|
|
279
|
+
box = el("section", { className: "rk-diff" + (f.doc && !mine.length ? " folded" : ""), id: "file-" + f.path }, [head, body]);
|
|
225
280
|
return box;
|
|
226
281
|
}
|
|
227
282
|
|
|
283
|
+
function navLink(label, iconName, n, cls, onclick) {
|
|
284
|
+
return el("a", { href: "#", className: cls || "", onclick: function (e) {
|
|
285
|
+
e.preventDefault();
|
|
286
|
+
onclick();
|
|
287
|
+
} }, [icon(iconName), el("span", { text: label }), n ? el("span", { className: "cr-files-n" + (/^AI/.test(n) ? " has-ai" : ""), text: n }) : null]);
|
|
288
|
+
}
|
|
289
|
+
|
|
228
290
|
function render() {
|
|
229
291
|
var info = state.info;
|
|
230
292
|
var byFile = {};
|
|
@@ -234,48 +296,74 @@
|
|
|
234
296
|
})) (byFile[x.file] = byFile[x.file] || []).push(x);
|
|
235
297
|
});
|
|
236
298
|
var folded = {};
|
|
237
|
-
document.querySelectorAll(".
|
|
299
|
+
document.querySelectorAll("#diffs .rk-diff").forEach(function (n) {
|
|
238
300
|
folded[n.id] = n.classList.contains("folded");
|
|
239
301
|
});
|
|
240
302
|
var diffs = $("diffs");
|
|
241
303
|
var y = window.scrollY;
|
|
242
304
|
diffs.textContent = "";
|
|
243
|
-
if (!info.files.length)
|
|
305
|
+
if (!info.files.length) {
|
|
306
|
+
diffs.appendChild(el("div", { className: "rk-card rk-card--sm" }, [el("div", { className: "rk-empty" }, [icon("git-branch", "ic ic-xl"), el("h3", { text: info.base.commit ? "没有改动" : "找不到改动的起点" }), el("p", { text: info.base.commit ? "这个需求的分支和它分出来的地方一样。" : "找不到这个需求是从哪个提交开始的,没法列出改动。" })])]));
|
|
307
|
+
}
|
|
244
308
|
info.files.forEach(function (f) {
|
|
245
309
|
var node = renderFile(f, byFile);
|
|
246
310
|
if (node.id in folded) node.classList.toggle("folded", folded[node.id]);
|
|
247
311
|
diffs.appendChild(node);
|
|
248
312
|
});
|
|
249
|
-
|
|
313
|
+
|
|
314
|
+
// AI findings: a summary card; the ones not on a changed line, and the serious ones, listed here too.
|
|
250
315
|
var ai = $("ai");
|
|
251
316
|
ai.textContent = "";
|
|
252
317
|
var elsewhere = info.findings.filter(function (x) {
|
|
253
318
|
return !byFile[x.file] || byFile[x.file].indexOf(x) < 0;
|
|
254
319
|
});
|
|
255
|
-
if (info.findings.length) {
|
|
320
|
+
if (info.findings.length || info.rounds.length) {
|
|
256
321
|
ai.hidden = false;
|
|
257
322
|
var open = info.findings.filter(function (x) {
|
|
258
323
|
return x.status === "open";
|
|
259
324
|
}).length;
|
|
260
|
-
|
|
261
|
-
|
|
325
|
+
var fixed = info.findings.filter(function (x) {
|
|
326
|
+
return x.status === "fixed";
|
|
327
|
+
}).length;
|
|
328
|
+
var last = info.rounds.length ? info.rounds[info.rounds.length - 1] : null;
|
|
329
|
+
ai.appendChild(
|
|
330
|
+
el("div", { className: "rk-row", style: "justify-content:space-between" }, [
|
|
331
|
+
el("span", { className: "rk-card__label rk-card__label--ai" }, [icon("sparkles", "ic ic-sm"), "AI 评审 · " + info.rounds.length + " 轮"]),
|
|
332
|
+
el("span", { className: "rk-row", style: "gap:8px" }, [fixed ? badge("success", "circle-check", "已修复 " + fixed) : null, open ? badge("outline", null, "还开着 " + open) : null, last ? badge(last.verdict === "pass" ? "ai" : "warning", last.verdict === "pass" ? "sparkles" : "triangle-alert", last.verdict === "pass" ? "最后一轮:可以合并" : "最后一轮:需要修改") : null]),
|
|
333
|
+
]),
|
|
334
|
+
);
|
|
335
|
+
ai.appendChild(el("p", { text: info.findings.length ? "意见放在下面对应的代码行旁边。阻塞、重要的未处理意见建议标为要改;拿不准的请开发同事看一下。" : "AI 评审没有留下结构化的意见,详细内容见 code-review.md。" }));
|
|
262
336
|
info.findings.forEach(function (x) {
|
|
263
|
-
if (elsewhere.indexOf(x) >= 0 || x.severity === "阻塞" || x.severity === "重要") ai.appendChild(findingNode(x, false));
|
|
337
|
+
if (elsewhere.indexOf(x) >= 0 || ((x.severity === "阻塞" || x.severity === "重要") && x.status === "open")) ai.appendChild(findingNode(x, false));
|
|
264
338
|
});
|
|
265
|
-
} else if (info.rounds.length) {
|
|
266
|
-
ai.hidden = false;
|
|
267
|
-
ai.appendChild(el("h2", { text: "AI 评审" }));
|
|
268
|
-
ai.appendChild(el("p", { className: "muted", text: "AI 评审没有留下结构化的意见,详细内容见 code-review.md。" }));
|
|
269
339
|
}
|
|
340
|
+
|
|
270
341
|
var nav = $("files");
|
|
271
342
|
nav.textContent = "";
|
|
272
|
-
nav.appendChild(el("div", { className: "
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
343
|
+
nav.appendChild(el("div", { className: "rk-side__group", text: "概要" }));
|
|
344
|
+
if (!$("summary").hidden) nav.appendChild(navLink("这次改了什么", "list-checks", null, "", function () {
|
|
345
|
+
$("summary").scrollIntoView({ block: "start", behavior: "smooth" });
|
|
346
|
+
}));
|
|
347
|
+
if (!ai.hidden) nav.appendChild(navLink("AI 评审意见", "sparkles", info.findings.length ? String(info.findings.length) : null, "", function () {
|
|
348
|
+
ai.scrollIntoView({ block: "start", behavior: "smooth" });
|
|
349
|
+
}));
|
|
350
|
+
var code = info.files.filter(function (f) {
|
|
351
|
+
return !f.doc;
|
|
352
|
+
});
|
|
353
|
+
var docs = info.files.filter(function (f) {
|
|
354
|
+
return f.doc;
|
|
355
|
+
});
|
|
356
|
+
[["代码 · " + code.length + " 个文件", code, "file-code"], ["需求文档 · " + docs.length + " 个", docs, "file-text"]].forEach(function (g) {
|
|
357
|
+
if (!g[1].length) return;
|
|
358
|
+
nav.appendChild(el("div", { className: "rk-side__group", text: g[0] }));
|
|
359
|
+
g[1].forEach(function (f) {
|
|
360
|
+
var n = (byFile[f.path] || []).length;
|
|
361
|
+
nav.appendChild(navLink(f.doc ? f.path.split("/").pop() : f.path.split("/").slice(-2).join("/"), g[2], n ? "AI " + n : "+" + f.additions, f.doc ? "is-doc" : "", function () {
|
|
362
|
+
jump(f.path, null);
|
|
363
|
+
}));
|
|
364
|
+
});
|
|
278
365
|
});
|
|
366
|
+
window.RkFill && window.RkFill();
|
|
279
367
|
window.scrollTo(0, y);
|
|
280
368
|
count();
|
|
281
369
|
}
|
|
@@ -309,11 +397,23 @@
|
|
|
309
397
|
});
|
|
310
398
|
}
|
|
311
399
|
|
|
400
|
+
function setBanner(kind, iconName, text) {
|
|
401
|
+
var banner = $("status");
|
|
402
|
+
banner.hidden = false;
|
|
403
|
+
banner.className = "rk-banner rk-banner--" + kind;
|
|
404
|
+
banner.textContent = "";
|
|
405
|
+
banner.appendChild(icon(iconName));
|
|
406
|
+
banner.appendChild(el("span", { text: text }));
|
|
407
|
+
}
|
|
408
|
+
|
|
312
409
|
function load() {
|
|
313
410
|
return api("api/info").then(function (info) {
|
|
314
411
|
state.info = info;
|
|
412
|
+
document.title = "代码审查 · " + info.title;
|
|
413
|
+
$("title").textContent = info.title;
|
|
315
414
|
$("project").textContent = info.featureDir;
|
|
316
|
-
$("
|
|
415
|
+
$("project").hidden = false;
|
|
416
|
+
$("readonly").hidden = !info.readOnly;
|
|
317
417
|
var adds = 0,
|
|
318
418
|
dels = 0;
|
|
319
419
|
info.files.forEach(function (f) {
|
|
@@ -322,39 +422,34 @@
|
|
|
322
422
|
dels += f.deletions;
|
|
323
423
|
}
|
|
324
424
|
});
|
|
325
|
-
var last = info.rounds.length ? info.rounds[info.rounds.length - 1] : null;
|
|
326
425
|
$("sub").textContent =
|
|
327
426
|
(info.branch ? "分支 " + info.branch : "") +
|
|
328
427
|
(info.base.ref ? ",相对 " + info.base.ref : "") +
|
|
329
|
-
":改了 " +
|
|
428
|
+
":改了 " +
|
|
429
|
+
info.files.filter(function (f) {
|
|
330
430
|
return !f.doc;
|
|
331
|
-
}).length +
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
banner.hidden = true;
|
|
335
|
-
banner.className = "banner";
|
|
431
|
+
}).length +
|
|
432
|
+
" 个代码文件(+" + adds + " −" + dels + ")。";
|
|
433
|
+
$("status").hidden = true;
|
|
336
434
|
if (info.running) {
|
|
337
|
-
|
|
338
|
-
banner.textContent = "流水线正在运行,改动还会变化。可以先看,结束后再提交评审。";
|
|
435
|
+
setBanner("run", "loader-circle", "流水线正在运行,改动还会变化。可以先看,结束后再提交评审。");
|
|
339
436
|
} else if (info.human) {
|
|
340
|
-
banner.hidden = false;
|
|
341
437
|
var h = info.human;
|
|
342
438
|
if (h.status === "approved") {
|
|
343
|
-
|
|
344
|
-
banner.textContent = (h.stale ? "评审通过后代码又有改动,需要重新评审。" : "已评审通过") + "(" + h.reviewer + "," + h.at.slice(0, 10) + (h.reviewers.length ? ",审核人 " + h.reviewers.join(" ") : "") + ")";
|
|
439
|
+
setBanner(h.stale ? "warn" : "ok", h.stale ? "triangle-alert" : "circle-check", (h.stale ? "评审通过后代码又有改动,需要重新评审。" : "已评审通过") + "(" + h.reviewer + "," + h.at.slice(0, 10) + (h.reviewers.length ? ",审核人 " + h.reviewers.join(" ") : "") + ")");
|
|
345
440
|
} else {
|
|
346
|
-
|
|
347
|
-
banner.textContent = "上次退回了 " + h.items.length + " 条意见(" + h.reviewer + "," + h.at.slice(0, 10) + ")" + (h.consumedAttempt ? ",第 " + h.consumedAttempt + " 次尝试在处理" : ",还没开始修改");
|
|
441
|
+
setBanner("warn", "undo-2", "上次退回了 " + h.items.length + " 条意见(" + h.reviewer + "," + h.at.slice(0, 10) + ")" + (h.consumedAttempt ? ",第 " + h.consumedAttempt + " 次尝试在处理" : ",还没开始修改"));
|
|
348
442
|
}
|
|
349
443
|
}
|
|
350
444
|
var sum = $("summary");
|
|
351
445
|
sum.textContent = "";
|
|
446
|
+
sum.hidden = !info.summary.length;
|
|
352
447
|
if (info.summary.length) {
|
|
353
|
-
sum.
|
|
354
|
-
sum.appendChild(el("
|
|
355
|
-
info.summary.
|
|
356
|
-
|
|
357
|
-
});
|
|
448
|
+
sum.appendChild(el("span", { className: "rk-card__label" }, [icon("list-checks", "ic ic-sm"), "提测说明"]));
|
|
449
|
+
sum.appendChild(el("h3", { style: "font-size:17px;line-height:26px", text: "这次改了什么" }));
|
|
450
|
+
sum.appendChild(el("ul", { className: "cr-summary" }, info.summary.map(function (l) {
|
|
451
|
+
return el("li", { text: l.replace(/^\s*[-*]\s+/, "") });
|
|
452
|
+
})));
|
|
358
453
|
}
|
|
359
454
|
var d = info.draft || {};
|
|
360
455
|
state.comments = Array.isArray(d.comments) ? d.comments : [];
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/* Copied from design/examples/icons.js by scripts/tokens-build.mjs. Do not edit here. */
|
|
2
|
+
// Lucide icons (stroke 1.75 via the .ic class) used by the req-kit examples: <svg class="ic" data-i="sparkles"></svg>.
|
|
3
|
+
// Inline so the pages open offline (the review pages load a copy as rk-icons.js; see scripts/tokens-build.mjs).
|
|
4
|
+
(() => {
|
|
5
|
+
const I = {
|
|
6
|
+
check: '<path d="M20 6 9 17l-5-5"/>',
|
|
7
|
+
sparkles: '<path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/><path d="M20 3v4"/><path d="M22 5h-4"/><path d="M4 17v2"/><path d="M5 18H3"/>',
|
|
8
|
+
"circle-check": '<circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/>',
|
|
9
|
+
"circle-x": '<circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/>',
|
|
10
|
+
"circle-dashed": '<path d="M10.1 2.182a10 10 0 0 1 3.8 0"/><path d="M13.9 21.818a10 10 0 0 1-3.8 0"/><path d="M17.609 3.721a10 10 0 0 1 2.69 2.7"/><path d="M2.182 13.9a10 10 0 0 1 0-3.8"/><path d="M20.279 17.609a10 10 0 0 1-2.7 2.69"/><path d="M21.818 10.1a10 10 0 0 1 0 3.8"/><path d="M3.721 6.391a10 10 0 0 1 2.7-2.69"/><path d="M6.391 20.279a10 10 0 0 1-2.69-2.7"/>',
|
|
11
|
+
"circle-dot": '<circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="1"/>',
|
|
12
|
+
"loader-circle": '<path d="M21 12a9 9 0 1 1-6.219-8.56"/>',
|
|
13
|
+
"file-text": '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/>',
|
|
14
|
+
"file-code": '<path d="M10 12.5 8 15l2 2.5"/><path d="m14 12.5 2 2.5-2 2.5"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/>',
|
|
15
|
+
"messages-square": '<path d="M14 9a2 2 0 0 1-2 2H6l-4 4V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2z"/><path d="M18 9h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1"/>',
|
|
16
|
+
"message-square-plus": '<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/><path d="M12 7v6"/><path d="M9 10h6"/>',
|
|
17
|
+
"pen-tool": '<path d="M15.707 21.293a1 1 0 0 1-1.414 0l-1.586-1.586a1 1 0 0 1 0-1.414l5.586-5.586a1 1 0 0 1 1.414 0l1.586 1.586a1 1 0 0 1 0 1.414z"/><path d="m18 13-1.375-6.874a1 1 0 0 0-.746-.776L3.235 2.028a1 1 0 0 0-1.207 1.207L5.35 15.879a1 1 0 0 0 .776.746L13 18"/><path d="m2.3 2.3 7.286 7.286"/><circle cx="11" cy="11" r="2"/>',
|
|
18
|
+
workflow: '<rect width="8" height="8" x="3" y="3" rx="2"/><path d="M7 11v4a2 2 0 0 0 2 2h4"/><rect width="8" height="8" x="13" y="13" rx="2"/>',
|
|
19
|
+
"shield-check": '<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/><path d="m9 12 2 2 4-4"/>',
|
|
20
|
+
"git-pull-request": '<circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><path d="M13 6h3a2 2 0 0 1 2 2v7"/><line x1="6" x2="6" y1="9" y2="21"/>',
|
|
21
|
+
"git-branch": '<line x1="6" x2="6" y1="3" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/>',
|
|
22
|
+
"clipboard-check": '<rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><path d="m9 14 2 2 4-4"/>',
|
|
23
|
+
play: '<polygon points="6 3 20 12 6 21 6 3"/>',
|
|
24
|
+
inbox: '<polyline points="22 12 16 12 14 15 10 15 8 12 2 12"/><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/>',
|
|
25
|
+
"undo-2": '<path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11"/>',
|
|
26
|
+
"octagon-alert": '<path d="M12 16h.01"/><path d="M12 8v4"/><path d="M15.312 2a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586l-4.688-4.688A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2z"/>',
|
|
27
|
+
"triangle-alert": '<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/>',
|
|
28
|
+
"chart-column": '<path d="M3 3v16a2 2 0 0 0 2 2h16"/><path d="M18 17V9"/><path d="M13 17V5"/><path d="M8 17v-3"/>',
|
|
29
|
+
"chevron-down": '<path d="m6 9 6 6 6-6"/>',
|
|
30
|
+
"chevron-right": '<path d="m9 18 6-6-6-6"/>',
|
|
31
|
+
"arrow-right": '<path d="M5 12h14"/><path d="m12 5 7 7-7 7"/>',
|
|
32
|
+
copy: '<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>',
|
|
33
|
+
sun: '<circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/>',
|
|
34
|
+
lightbulb: '<path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"/><path d="M9 18h6"/><path d="M10 22h4"/>',
|
|
35
|
+
info: '<circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/>',
|
|
36
|
+
clock: '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
|
|
37
|
+
user: '<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>',
|
|
38
|
+
users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
|
|
39
|
+
search: '<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>',
|
|
40
|
+
terminal: '<polyline points="4 17 10 11 4 5"/><line x1="12" x2="20" y1="19" y2="19"/>',
|
|
41
|
+
lock: '<rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
|
|
42
|
+
"list-checks": '<path d="m3 17 2 2 4-4"/><path d="m3 7 2 2 4-4"/><path d="M13 6h8"/><path d="M13 12h8"/><path d="M13 18h8"/>',
|
|
43
|
+
"mouse-pointer-click": '<path d="M14 4.1 12 6"/><path d="m5.1 8-2.9-.8"/><path d="m6 12-1.9 2"/><path d="M7.2 2.2 8 5.1"/><path d="M9.037 9.69a.498.498 0 0 1 .653-.653l11 4.5a.5.5 0 0 1-.074.949l-4.349 1.041a1 1 0 0 0-.74.739l-1.04 4.35a.5.5 0 0 1-.95.074z"/>',
|
|
44
|
+
history: '<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M12 7v5l4 2"/>',
|
|
45
|
+
"trash-2": '<path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/>',
|
|
46
|
+
moon: '<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>',
|
|
47
|
+
"external-link": '<path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>',
|
|
48
|
+
folder: '<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/>',
|
|
49
|
+
};
|
|
50
|
+
// The req-kit mark: <svg data-logo></svg> (brand-edge: lime on dark grounds, the deeper lime on light ones; the hollow nodes take --rk-canvas or the element's --ground).
|
|
51
|
+
const LOGO = '<path d="M16 54 L40 77 L82 30" fill="none" stroke="var(--rk-brand-edge)" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/><g fill="var(--ground, var(--rk-canvas))" stroke="var(--rk-brand-edge)" stroke-width="6"><circle cx="16" cy="54" r="7"/><circle cx="40" cy="77" r="7"/></g><circle cx="82" cy="30" r="12" fill="var(--rk-brand-edge)"/><circle cx="82" cy="30" r="19" fill="none" stroke="var(--rk-brand-edge)" stroke-width="2" opacity=".45"/>';
|
|
52
|
+
const fill = () => {
|
|
53
|
+
for (const el of document.querySelectorAll("svg[data-logo]")) {
|
|
54
|
+
el.setAttribute("viewBox", "0 0 100 100");
|
|
55
|
+
el.setAttribute("aria-hidden", "true");
|
|
56
|
+
el.innerHTML = LOGO;
|
|
57
|
+
}
|
|
58
|
+
for (const el of document.querySelectorAll("svg[data-i]")) {
|
|
59
|
+
const p = I[el.dataset.i];
|
|
60
|
+
if (!p) continue;
|
|
61
|
+
el.setAttribute("viewBox", "0 0 24 24");
|
|
62
|
+
el.setAttribute("aria-hidden", "true");
|
|
63
|
+
el.innerHTML = p;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
// For pages that build their DOM in script: RkIcon("sparkles", "ic ic-sm") returns an <svg>.
|
|
67
|
+
window.RkIcon = (name, cls) => {
|
|
68
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
69
|
+
svg.setAttribute("class", cls || "ic");
|
|
70
|
+
svg.setAttribute("viewBox", "0 0 24 24");
|
|
71
|
+
svg.setAttribute("aria-hidden", "true");
|
|
72
|
+
svg.innerHTML = I[name] || "";
|
|
73
|
+
return svg;
|
|
74
|
+
};
|
|
75
|
+
window.RkFill = fill;
|
|
76
|
+
// A button with data-rk-theme switches between dark and light and remembers the choice.
|
|
77
|
+
const KEY = "rk-theme";
|
|
78
|
+
try {
|
|
79
|
+
const saved = localStorage.getItem(KEY);
|
|
80
|
+
if (saved === "dark" || saved === "light") document.documentElement.dataset.theme = saved;
|
|
81
|
+
} catch {
|
|
82
|
+
/* no storage: follow the system */
|
|
83
|
+
}
|
|
84
|
+
document.addEventListener("click", (e) => {
|
|
85
|
+
const b = e.target.closest && e.target.closest("[data-rk-theme]");
|
|
86
|
+
if (!b) return;
|
|
87
|
+
const r = document.documentElement;
|
|
88
|
+
const dark = r.dataset.theme ? r.dataset.theme === "dark" : !matchMedia("(prefers-color-scheme: light)").matches;
|
|
89
|
+
r.dataset.theme = dark ? "light" : "dark";
|
|
90
|
+
try {
|
|
91
|
+
localStorage.setItem(KEY, r.dataset.theme);
|
|
92
|
+
} catch {
|
|
93
|
+
/* not remembered */
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", fill);
|
|
97
|
+
else fill();
|
|
98
|
+
})();
|