@icen.ai/ui 0.8.1 → 0.9.0
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/AGENTS.md +49 -6
- package/README.md +12 -9
- package/dist/behaviors/ai-branch.d.mts +19 -0
- package/dist/behaviors/ai-branch.mjs +217 -0
- package/dist/behaviors/ai-chat.d.mts +8 -6
- package/dist/behaviors/ai-chat.mjs +68 -8
- package/dist/behaviors/ai-composer.mjs +108 -20
- package/dist/behaviors/ai-feedback.d.mts +26 -0
- package/dist/behaviors/ai-feedback.mjs +303 -0
- package/dist/behaviors/ai-panel.d.mts +15 -1
- package/dist/behaviors/ai-panel.mjs +26 -0
- package/dist/behaviors/ai-threads.d.mts +38 -0
- package/dist/behaviors/ai-threads.mjs +319 -0
- package/dist/behaviors/ai-tool.mjs +5 -0
- package/dist/behaviors/ai-tools.mjs +5 -0
- package/dist/behaviors/charts.d.mts +103 -1
- package/dist/behaviors/charts.mjs +795 -0
- package/dist/behaviors/events.d.mts +193 -0
- package/dist/behaviors/kb-access.d.mts +34 -0
- package/dist/behaviors/kb-access.mjs +569 -0
- package/dist/behaviors/kb-acl.d.mts +50 -0
- package/dist/behaviors/kb-acl.mjs +405 -0
- package/dist/behaviors/kb-answer.d.mts +38 -0
- package/dist/behaviors/kb-answer.mjs +2055 -0
- package/dist/behaviors/kb-audit.d.mts +23 -0
- package/dist/behaviors/kb-audit.mjs +375 -0
- package/dist/behaviors/kb-canvas.d.mts +44 -0
- package/dist/behaviors/kb-canvas.mjs +589 -0
- package/dist/behaviors/kb-chain.d.mts +15 -0
- package/dist/behaviors/kb-chain.mjs +270 -0
- package/dist/behaviors/kb-checkpoint.d.mts +16 -0
- package/dist/behaviors/kb-checkpoint.mjs +342 -0
- package/dist/behaviors/kb-chunks.d.mts +31 -0
- package/dist/behaviors/kb-chunks.mjs +475 -0
- package/dist/behaviors/kb-citation.d.mts +38 -0
- package/dist/behaviors/kb-citation.mjs +677 -0
- package/dist/behaviors/kb-clarify.d.mts +20 -0
- package/dist/behaviors/kb-clarify.mjs +248 -0
- package/dist/behaviors/kb-conflict.d.mts +22 -0
- package/dist/behaviors/kb-conflict.mjs +284 -0
- package/dist/behaviors/kb-connector.d.mts +17 -0
- package/dist/behaviors/kb-connector.mjs +375 -0
- package/dist/behaviors/kb-core.d.mts +529 -0
- package/dist/behaviors/kb-core.mjs +890 -0
- package/dist/behaviors/kb-eval.d.mts +22 -0
- package/dist/behaviors/kb-eval.mjs +408 -0
- package/dist/behaviors/kb-explain.d.mts +11 -0
- package/dist/behaviors/kb-explain.mjs +243 -0
- package/dist/behaviors/kb-filter.d.mts +31 -0
- package/dist/behaviors/kb-filter.mjs +587 -0
- package/dist/behaviors/kb-gap.d.mts +27 -0
- package/dist/behaviors/kb-gap.mjs +273 -0
- package/dist/behaviors/kb-hittest.d.mts +34 -0
- package/dist/behaviors/kb-hittest.mjs +409 -0
- package/dist/behaviors/kb-metadata.d.mts +29 -0
- package/dist/behaviors/kb-metadata.mjs +326 -0
- package/dist/behaviors/kb-passage.d.mts +41 -0
- package/dist/behaviors/kb-passage.mjs +529 -0
- package/dist/behaviors/kb-pipeline.d.mts +18 -0
- package/dist/behaviors/kb-pipeline.mjs +381 -0
- package/dist/behaviors/kb-qa.d.mts +26 -0
- package/dist/behaviors/kb-qa.mjs +332 -0
- package/dist/behaviors/kb-rerank.d.mts +20 -0
- package/dist/behaviors/kb-rerank.mjs +315 -0
- package/dist/behaviors/kb-retrieval.d.mts +49 -0
- package/dist/behaviors/kb-retrieval.mjs +746 -0
- package/dist/behaviors/kb-review.d.mts +44 -0
- package/dist/behaviors/kb-review.mjs +451 -0
- package/dist/behaviors/kb-sandbox.d.mts +33 -0
- package/dist/behaviors/kb-sandbox.mjs +325 -0
- package/dist/behaviors/kb-segment.d.mts +32 -0
- package/dist/behaviors/kb-segment.mjs +499 -0
- package/dist/behaviors/kb-sources.d.mts +27 -0
- package/dist/behaviors/kb-sources.mjs +373 -0
- package/dist/behaviors/kb-sql.d.mts +43 -0
- package/dist/behaviors/kb-sql.mjs +948 -0
- package/dist/behaviors/kb-trace.d.mts +16 -0
- package/dist/behaviors/kb-trace.mjs +414 -0
- package/dist/behaviors/kb-visibility.d.mts +44 -0
- package/dist/behaviors/kb-visibility.mjs +403 -0
- package/dist/behaviors/kb-who-can.d.mts +32 -0
- package/dist/behaviors/kb-who-can.mjs +379 -0
- package/dist/components/ai-branch.mjs +3 -0
- package/dist/components/ai-chat.css +295 -0
- package/dist/components/ai-feedback.mjs +3 -0
- package/dist/components/ai-panel.css +217 -0
- package/dist/components/ai-threads.mjs +3 -0
- package/dist/components/chart-graph.css +57 -0
- package/dist/components/chart-graph.mjs +4 -0
- package/dist/components/chart-map.mjs +3 -0
- package/dist/components/chart-scatter.mjs +1 -1
- package/dist/components/charts.css +32 -0
- package/dist/components/charts.mjs +1 -0
- package/dist/components/kb-access.mjs +4 -0
- package/dist/components/kb-acl.mjs +4 -0
- package/dist/components/kb-agent.css +481 -0
- package/dist/components/kb-answer.mjs +4 -0
- package/dist/components/kb-audit.mjs +4 -0
- package/dist/components/kb-canvas.mjs +4 -0
- package/dist/components/kb-chain.mjs +4 -0
- package/dist/components/kb-checkpoint.mjs +4 -0
- package/dist/components/kb-chunks.mjs +4 -0
- package/dist/components/kb-citation.mjs +4 -0
- package/dist/components/kb-clarify.mjs +4 -0
- package/dist/components/kb-conflict.mjs +4 -0
- package/dist/components/kb-connector.mjs +4 -0
- package/dist/components/kb-data.css +649 -0
- package/dist/components/kb-eval.mjs +4 -0
- package/dist/components/kb-explain.mjs +4 -0
- package/dist/components/kb-filter.mjs +4 -0
- package/dist/components/kb-gap.mjs +4 -0
- package/dist/components/kb-ground.css +566 -0
- package/dist/components/kb-hittest.mjs +4 -0
- package/dist/components/kb-ingest.css +898 -0
- package/dist/components/kb-metadata.mjs +4 -0
- package/dist/components/kb-ops.css +548 -0
- package/dist/components/kb-passage.mjs +4 -0
- package/dist/components/kb-perm.css +444 -0
- package/dist/components/kb-pipeline.mjs +4 -0
- package/dist/components/kb-qa.mjs +4 -0
- package/dist/components/kb-rerank.mjs +4 -0
- package/dist/components/kb-retrieval.mjs +4 -0
- package/dist/components/kb-review.mjs +4 -0
- package/dist/components/kb-sandbox.mjs +4 -0
- package/dist/components/kb-search.css +940 -0
- package/dist/components/kb-segment.mjs +4 -0
- package/dist/components/kb-sources.mjs +4 -0
- package/dist/components/kb-sql.mjs +4 -0
- package/dist/components/kb-trace.mjs +4 -0
- package/dist/components/kb-visibility.mjs +4 -0
- package/dist/components/kb-who-can.mjs +4 -0
- package/dist/components/kb.css +252 -0
- package/dist/components/kb.mjs +9 -0
- package/dist/index.d.mts +37 -2
- package/dist/index.mjs +8555 -205
- package/dist/registry.json +325 -3
- package/dist/ui.css +5451 -45
- package/docs/research/2026-10-03-ai-kb-components.md +392 -0
- package/docs/research/2026-10-03-kb-permissions.md +992 -0
- package/docs/spec/kb-family.md +443 -0
- package/package.json +2 -2
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
// src/behaviors/ai-core.ts
|
|
2
|
+
var svg = (body) => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${body}</svg>`;
|
|
3
|
+
var ICON_TERMINAL = svg('<path d="m4 17 6-6-6-6"/><path d="M12 19h8"/>');
|
|
4
|
+
var ICON_FILE = svg(
|
|
5
|
+
'<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"/>'
|
|
6
|
+
);
|
|
7
|
+
var ICON_FILE_PEN = svg(
|
|
8
|
+
'<path d="M12.5 22H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8l5 5v3.5"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M19 6.5 16.5 4"/><path d="M21.378 16.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"/>'
|
|
9
|
+
);
|
|
10
|
+
var ICON_FILE_PLUS = svg(
|
|
11
|
+
'<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="M9 15h6"/><path d="M12 12v6"/>'
|
|
12
|
+
);
|
|
13
|
+
var ICON_TRASH = svg(
|
|
14
|
+
'<path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M10 11v6"/><path d="M14 11v6"/>'
|
|
15
|
+
);
|
|
16
|
+
var ICON_SEARCH = svg('<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>');
|
|
17
|
+
var ICON_FOLDER_SEARCH = svg(
|
|
18
|
+
'<path d="M11 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v4"/><circle cx="17" cy="17" r="3"/><path d="m21 21-1.9-1.9"/>'
|
|
19
|
+
);
|
|
20
|
+
var ICON_SCAN_SEARCH = svg(
|
|
21
|
+
'<path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><circle cx="12" cy="12" r="3"/><path d="m16 16-1.9-1.9"/>'
|
|
22
|
+
);
|
|
23
|
+
var ICON_GLOBE = svg(
|
|
24
|
+
'<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/>'
|
|
25
|
+
);
|
|
26
|
+
var ICON_DOWNLOAD = svg(
|
|
27
|
+
'<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/><path d="M12 15V3"/>'
|
|
28
|
+
);
|
|
29
|
+
var ICON_BLOCKS = svg(
|
|
30
|
+
'<rect width="7" height="7" x="4" y="4" rx="1"/><rect width="7" height="7" x="13" y="4" rx="1"/><rect width="7" height="7" x="4" y="13" rx="1"/><rect width="7" height="7" x="13" y="13" rx="1"/>'
|
|
31
|
+
);
|
|
32
|
+
var ICON_ZAP = svg(
|
|
33
|
+
'<path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/>'
|
|
34
|
+
);
|
|
35
|
+
var ICON_LIST = svg(
|
|
36
|
+
'<path d="M3 12h.01"/><path d="M3 18h.01"/><path d="M3 6h.01"/><path d="M8 12h13"/><path d="M8 18h13"/><path d="M8 6h13"/>'
|
|
37
|
+
);
|
|
38
|
+
var ICON_LIST_CHECKS = svg(
|
|
39
|
+
'<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"/>'
|
|
40
|
+
);
|
|
41
|
+
var ICON_BOT = svg(
|
|
42
|
+
'<path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/>'
|
|
43
|
+
);
|
|
44
|
+
var ICON_CHART = svg(
|
|
45
|
+
'<path d="M3 3v18h18"/><path d="M7 15l4-6 3 4 5-8"/>'
|
|
46
|
+
);
|
|
47
|
+
var ICON_FILE_TEXT = svg(
|
|
48
|
+
'<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"/>'
|
|
49
|
+
);
|
|
50
|
+
function isObj(v) {
|
|
51
|
+
return typeof v === "object" && v !== null;
|
|
52
|
+
}
|
|
53
|
+
function pickStr(input, keys) {
|
|
54
|
+
if (!isObj(input)) return void 0;
|
|
55
|
+
for (const k of keys) {
|
|
56
|
+
const v = input[k];
|
|
57
|
+
if (typeof v === "string" && v) return v;
|
|
58
|
+
}
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
61
|
+
function pickNum(src, keys) {
|
|
62
|
+
if (!isObj(src)) return void 0;
|
|
63
|
+
for (const k of keys) {
|
|
64
|
+
const v = src[k];
|
|
65
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
66
|
+
}
|
|
67
|
+
return void 0;
|
|
68
|
+
}
|
|
69
|
+
function firstLine(s, max = 80) {
|
|
70
|
+
const line = s.split("\n", 1)[0]?.trim() ?? "";
|
|
71
|
+
return line.length > max ? `${line.slice(0, max)}\u2026` : line;
|
|
72
|
+
}
|
|
73
|
+
function filePathOf(input) {
|
|
74
|
+
return pickStr(input, ["file_path", "path", "file", "filename"]);
|
|
75
|
+
}
|
|
76
|
+
var summarizePath = (input) => filePathOf(input);
|
|
77
|
+
function summarizeRead(input) {
|
|
78
|
+
const p = filePathOf(input);
|
|
79
|
+
if (!p) return void 0;
|
|
80
|
+
const offset = pickNum(input, ["offset", "start_line"]);
|
|
81
|
+
if (offset == null) return p;
|
|
82
|
+
const limit = pickNum(input, ["limit", "end_line"]);
|
|
83
|
+
return limit != null ? `${p}:${offset}-${offset + limit}` : `${p}:${offset}`;
|
|
84
|
+
}
|
|
85
|
+
function summarizeShell(input) {
|
|
86
|
+
const cmd = typeof input === "string" ? input : pickStr(input, ["command", "cmd", "script"]);
|
|
87
|
+
return cmd ? firstLine(cmd) : void 0;
|
|
88
|
+
}
|
|
89
|
+
function summarizeGrep(input) {
|
|
90
|
+
const pattern = pickStr(input, ["pattern", "regex", "query"]);
|
|
91
|
+
const p = filePathOf(input);
|
|
92
|
+
if (pattern) return p ? `${firstLine(pattern, 40)} \xB7 ${p}` : firstLine(pattern);
|
|
93
|
+
return p;
|
|
94
|
+
}
|
|
95
|
+
var sum = (fn) => {
|
|
96
|
+
return (input) => fn(input) ?? "";
|
|
97
|
+
};
|
|
98
|
+
var BUILTIN_KINDS = {
|
|
99
|
+
shell: { label: "\u547D\u4EE4", icon: ICON_TERMINAL, tint: "accent", summarize: sum(summarizeShell) },
|
|
100
|
+
read: { label: "\u8BFB\u53D6", icon: ICON_FILE, tint: "info", summarize: sum(summarizeRead) },
|
|
101
|
+
edit: { label: "\u7F16\u8F91", icon: ICON_FILE_PEN, tint: "warning", summarize: sum(summarizePath) },
|
|
102
|
+
write: { label: "\u5199\u5165", icon: ICON_FILE_PLUS, tint: "warning", summarize: sum(summarizePath) },
|
|
103
|
+
rm: { label: "\u5220\u9664", icon: ICON_TRASH, tint: "error", summarize: sum(summarizePath) },
|
|
104
|
+
grep: { label: "\u641C\u7D22", icon: ICON_SEARCH, tint: "info", summarize: sum(summarizeGrep) },
|
|
105
|
+
glob: { label: "\u5339\u914D", icon: ICON_FOLDER_SEARCH, tint: "info", summarize: sum((i) => pickStr(i, ["pattern", "glob", "path"])) },
|
|
106
|
+
browser: { label: "\u6D4F\u89C8", icon: ICON_GLOBE, tint: "accent", summarize: sum((i) => pickStr(i, ["url", "uri", "target"])) },
|
|
107
|
+
search: { label: "\u68C0\u7D22", icon: ICON_SCAN_SEARCH, tint: "accent", summarize: sum((i) => pickStr(i, ["query", "q", "pattern"])) },
|
|
108
|
+
fetch: { label: "\u8BF7\u6C42", icon: ICON_DOWNLOAD, tint: "info", summarize: sum((i) => pickStr(i, ["url", "uri", "endpoint"])) },
|
|
109
|
+
mcp: { label: "MCP", icon: ICON_BLOCKS, tint: "muted", summarize: sum((i) => pickStr(i, ["tool", "method", "name"])) },
|
|
110
|
+
skill: { label: "\u6280\u80FD", icon: ICON_ZAP, tint: "accent", summarize: sum((i) => pickStr(i, ["skill", "name", "command"])) },
|
|
111
|
+
todo: {
|
|
112
|
+
label: "\u5F85\u529E",
|
|
113
|
+
icon: ICON_LIST,
|
|
114
|
+
tint: "info",
|
|
115
|
+
summarize: sum((input) => {
|
|
116
|
+
const items = input && typeof input === "object" && Array.isArray(input.items) ? input.items : Array.isArray(input) ? input : [];
|
|
117
|
+
const done = items.filter((it) => it?.status === "done").length;
|
|
118
|
+
return items.length > 0 ? `${done}/${items.length} \u5B8C\u6210` : "";
|
|
119
|
+
})
|
|
120
|
+
},
|
|
121
|
+
plan: { label: "\u8BA1\u5212", icon: ICON_LIST_CHECKS, tint: "info" },
|
|
122
|
+
subagent: { label: "\u5B50\u4EE3\u7406", icon: ICON_BOT, tint: "accent", summarize: sum((i) => pickStr(i, ["task", "description", "prompt", "message", "subagent_type"])) },
|
|
123
|
+
note: { label: "\u7B14\u8BB0", icon: ICON_FILE_TEXT, tint: "muted" },
|
|
124
|
+
chart: { label: "\u56FE\u8868", icon: ICON_CHART, tint: "info", summarize: sum((i) => pickStr(i, ["title", "name"])) }
|
|
125
|
+
};
|
|
126
|
+
var kindRegistry = new Map(Object.entries(BUILTIN_KINDS));
|
|
127
|
+
function h(tag, className, text) {
|
|
128
|
+
const node = document.createElement(tag);
|
|
129
|
+
if (className) node.className = className;
|
|
130
|
+
if (text != null) node.textContent = text;
|
|
131
|
+
return node;
|
|
132
|
+
}
|
|
133
|
+
function svgIcon(svg2) {
|
|
134
|
+
if (typeof document === "undefined" || typeof DOMParser === "undefined") return null;
|
|
135
|
+
let parsed;
|
|
136
|
+
try {
|
|
137
|
+
parsed = new DOMParser().parseFromString(svg2, "image/svg+xml");
|
|
138
|
+
} catch {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
const root = parsed.documentElement;
|
|
142
|
+
if (!root || root.tagName.toLowerCase() !== "svg" || parsed.querySelector("parsererror")) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
const sanitize = (el) => {
|
|
146
|
+
for (const attr of Array.from(el.attributes)) {
|
|
147
|
+
const name = attr.name.toLowerCase();
|
|
148
|
+
if (name.startsWith("on")) el.removeAttribute(attr.name);
|
|
149
|
+
if ((name === "href" || name === "xlink:href") && attr.value.trim().toLowerCase().startsWith("javascript:")) {
|
|
150
|
+
el.removeAttribute(attr.name);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
for (const child of Array.from(el.children)) {
|
|
154
|
+
const tag = child.tagName.toLowerCase();
|
|
155
|
+
if (tag === "script" || tag === "foreignobject") {
|
|
156
|
+
child.remove();
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
sanitize(child);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
sanitize(root);
|
|
163
|
+
return document.importNode(root, true);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// src/behaviors/kb-core.ts
|
|
167
|
+
var ic = (label, d) => ({ label, icon: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">${d}</svg>` });
|
|
168
|
+
var BUILTIN_SOURCE_TYPES = {
|
|
169
|
+
doc: ic("\u6587\u6863", '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/>'),
|
|
170
|
+
web: ic("\u7F51\u9875", '<circle cx="12" cy="12" r="10"/><path d="M2 12h20"/><path d="M12 2a15 15 0 0 1 0 20 15 15 0 0 1 0-20z"/>'),
|
|
171
|
+
wiki: ic("\u77E5\u8BC6\u5E93", '<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/>'),
|
|
172
|
+
sheet: ic("\u8868\u683C", '<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M3 15h18M9 3v18M15 3v18"/>'),
|
|
173
|
+
db: ic("\u6570\u636E\u5E93", '<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14c0 1.7 4 3 9 3s9-1.3 9-3V5"/><path d="M3 12c0 1.7 4 3 9 3s9-1.3 9-3"/>'),
|
|
174
|
+
ticket: ic("\u5DE5\u5355", '<path d="M3 9V7a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v2a2 2 0 0 0 0 6v2a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-2a2 2 0 0 0 0-6z"/><path d="M13 5v14"/>'),
|
|
175
|
+
code: ic("\u4EE3\u7801", '<path d="m16 18 6-6-6-6"/><path d="m8 6-6 6 6 6"/>'),
|
|
176
|
+
api: ic("\u63A5\u53E3", '<path d="M4 7a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v3a2 2 0 0 0 0 4v3a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-3a2 2 0 0 0 0-4z"/>'),
|
|
177
|
+
image: ic("\u56FE\u7247", '<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-4.6-4.6a2 2 0 0 0-2.8 0L3 21"/>'),
|
|
178
|
+
mail: ic("\u90AE\u4EF6", '<rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-10 7L2 7"/>')
|
|
179
|
+
};
|
|
180
|
+
var sourceTypeRegistry = new Map(Object.entries(BUILTIN_SOURCE_TYPES));
|
|
181
|
+
var KB_ROLES = ["viewer", "commenter", "editor", "owner"];
|
|
182
|
+
function kbRoleLabel(r) {
|
|
183
|
+
return r === "viewer" ? "\u53EF\u67E5\u770B" : r === "commenter" ? "\u53EF\u8BC4\u8BBA" : r === "editor" ? "\u53EF\u7F16\u8F91" : r === "owner" ? "\u8D1F\u8D23\u4EBA" : "\u65E0\u6743\u9650";
|
|
184
|
+
}
|
|
185
|
+
var KB_ACCESS_STATES = [
|
|
186
|
+
"idle",
|
|
187
|
+
"requested",
|
|
188
|
+
"pending_review",
|
|
189
|
+
"granted",
|
|
190
|
+
"expiring",
|
|
191
|
+
"expired",
|
|
192
|
+
"denied",
|
|
193
|
+
"revoked"
|
|
194
|
+
];
|
|
195
|
+
function kbAccessStateLabel(s) {
|
|
196
|
+
return {
|
|
197
|
+
idle: "\u672A\u7533\u8BF7",
|
|
198
|
+
requested: "\u5DF2\u63D0\u4EA4",
|
|
199
|
+
pending_review: "\u5F85\u5BA1\u6279",
|
|
200
|
+
granted: "\u5DF2\u5F00\u901A",
|
|
201
|
+
expiring: "\u5373\u5C06\u5230\u671F",
|
|
202
|
+
expired: "\u5DF2\u8FC7\u671F",
|
|
203
|
+
denied: "\u672A\u901A\u8FC7",
|
|
204
|
+
revoked: "\u5DF2\u6536\u56DE"
|
|
205
|
+
}[s] ?? s;
|
|
206
|
+
}
|
|
207
|
+
function normalizeAccessRequest(raw) {
|
|
208
|
+
const r = raw;
|
|
209
|
+
const state = KB_ACCESS_STATES.includes(r.state) ? r.state : "idle";
|
|
210
|
+
const str = (v) => typeof v === "string" && v ? v : void 0;
|
|
211
|
+
return {
|
|
212
|
+
id: String(r.id ?? ""),
|
|
213
|
+
requester: String(r.requester ?? ""),
|
|
214
|
+
resource: String(r.resource ?? ""),
|
|
215
|
+
role: KB_ROLES.includes(r.role) ? r.role : "viewer",
|
|
216
|
+
reason: str(r.reason),
|
|
217
|
+
state,
|
|
218
|
+
submittedAt: str(r.submittedAt) ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
219
|
+
approverNote: str(r.approverNote),
|
|
220
|
+
decidedBy: str(r.decidedBy),
|
|
221
|
+
decidedAt: str(r.decidedAt),
|
|
222
|
+
expiresAt: str(r.expiresAt)
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function relativeTime(input, now = /* @__PURE__ */ new Date()) {
|
|
226
|
+
const t = input instanceof Date ? input.getTime() : typeof input === "number" ? input : Date.parse(input);
|
|
227
|
+
if (!Number.isFinite(t)) return typeof input === "string" ? input : "\u2014";
|
|
228
|
+
const diff = now.getTime() - t;
|
|
229
|
+
const MIN = 6e4, HOUR = 36e5, DAY = 864e5;
|
|
230
|
+
if (diff < MIN) return "\u521A\u521A";
|
|
231
|
+
if (diff < HOUR) return `${Math.floor(diff / MIN)} \u5206\u949F\u524D`;
|
|
232
|
+
if (diff < DAY) return `${Math.floor(diff / HOUR)} \u5C0F\u65F6\u524D`;
|
|
233
|
+
if (diff < 30 * DAY) return `${Math.floor(diff / DAY)} \u5929\u524D`;
|
|
234
|
+
const d = new Date(t);
|
|
235
|
+
const ymd = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
236
|
+
return ymd;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/behaviors/events.ts
|
|
240
|
+
function emitIcen(source, type, detail) {
|
|
241
|
+
return source.dispatchEvent(
|
|
242
|
+
new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/behaviors/kb-access.ts
|
|
247
|
+
var ico = (d) => `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">${d}</svg>`;
|
|
248
|
+
var ICON_LOCK = ico('<rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>');
|
|
249
|
+
var ICON_USER_SWITCH = ico('<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="m16 11 2 2 4-4"/>');
|
|
250
|
+
var EXPIRING_WINDOW_MS = 7 * 864e5;
|
|
251
|
+
var REQUESTABLE_ROLES = ["viewer", "commenter", "editor"];
|
|
252
|
+
var MIN_MS = 6e4;
|
|
253
|
+
var HOUR_MS = 36e5;
|
|
254
|
+
var DAY_MS = 864e5;
|
|
255
|
+
function untilText(at, now) {
|
|
256
|
+
if (!at) return "";
|
|
257
|
+
const t = Date.parse(at);
|
|
258
|
+
if (!Number.isFinite(t)) return "";
|
|
259
|
+
const diff = t - now.getTime();
|
|
260
|
+
if (diff <= 0) return "\u5DF2\u5230\u671F";
|
|
261
|
+
if (diff < HOUR_MS) return `${Math.max(1, Math.floor(diff / MIN_MS))} \u5206\u949F\u540E`;
|
|
262
|
+
if (diff < DAY_MS) return `${Math.floor(diff / HOUR_MS)} \u5C0F\u65F6\u540E`;
|
|
263
|
+
return `${Math.floor(diff / DAY_MS)} \u5929\u540E`;
|
|
264
|
+
}
|
|
265
|
+
function parseTs(at) {
|
|
266
|
+
if (!at) return null;
|
|
267
|
+
const t = Date.parse(at);
|
|
268
|
+
return Number.isFinite(t) ? t : null;
|
|
269
|
+
}
|
|
270
|
+
function createKbAccess(el, opts) {
|
|
271
|
+
const host = el;
|
|
272
|
+
if (typeof document === "undefined") {
|
|
273
|
+
const snapshot = normalizeAccessRequest(opts.request);
|
|
274
|
+
return {
|
|
275
|
+
setState: () => void 0,
|
|
276
|
+
getState: () => ({ ...snapshot }),
|
|
277
|
+
destroy: () => void 0
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
host.__icenKbAccess?.destroy();
|
|
281
|
+
const mode = opts.mode === "approver" ? "approver" : "requester";
|
|
282
|
+
let req = normalizeAccessRequest(opts.request);
|
|
283
|
+
let destroyed = false;
|
|
284
|
+
let formOpen = false;
|
|
285
|
+
let formRole = REQUESTABLE_ROLES.includes(req.role) ? req.role : "viewer";
|
|
286
|
+
let formDuration = "";
|
|
287
|
+
let formReason = "";
|
|
288
|
+
const deriveExpiry = () => {
|
|
289
|
+
if (req.state !== "granted" && req.state !== "expiring") return;
|
|
290
|
+
const t = parseTs(req.expiresAt);
|
|
291
|
+
if (t == null) return;
|
|
292
|
+
const now = Date.now();
|
|
293
|
+
if (t <= now) {
|
|
294
|
+
req = { ...req, state: "expired" };
|
|
295
|
+
emitIcen(host, "icen:kb-access-expire", { id: req.id });
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (t - now <= EXPIRING_WINDOW_MS && req.state === "granted") {
|
|
299
|
+
req = { ...req, state: "expiring" };
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
const submitForm = (form) => {
|
|
303
|
+
const reasonEl = form.querySelector(".kb-access-reason");
|
|
304
|
+
const durationEl = form.querySelector(".kb-access-duration");
|
|
305
|
+
const reason = reasonEl?.value.trim() ?? "";
|
|
306
|
+
const days = Number(durationEl?.value ?? "");
|
|
307
|
+
const expiresAt = Number.isFinite(days) && days > 0 ? new Date(Date.now() + days * DAY_MS).toISOString() : void 0;
|
|
308
|
+
formReason = reason;
|
|
309
|
+
formDuration = durationEl?.value ?? "";
|
|
310
|
+
req = normalizeAccessRequest({
|
|
311
|
+
...req,
|
|
312
|
+
role: formRole,
|
|
313
|
+
reason: reason || void 0,
|
|
314
|
+
expiresAt,
|
|
315
|
+
state: "requested",
|
|
316
|
+
submittedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
317
|
+
});
|
|
318
|
+
formOpen = false;
|
|
319
|
+
emitIcen(host, "icen:kb-access-request", { request: { ...req } });
|
|
320
|
+
opts.onRequest?.({ ...req });
|
|
321
|
+
render();
|
|
322
|
+
};
|
|
323
|
+
const decide = (decision, role) => {
|
|
324
|
+
req = normalizeAccessRequest({
|
|
325
|
+
...req,
|
|
326
|
+
state: decision === "approve" ? "granted" : "denied",
|
|
327
|
+
role: decision === "approve" && role && KB_ROLES.includes(role) ? role : req.role,
|
|
328
|
+
decidedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
329
|
+
});
|
|
330
|
+
emitIcen(host, "icen:kb-access-decide", {
|
|
331
|
+
id: req.id,
|
|
332
|
+
decision,
|
|
333
|
+
role: decision === "approve" ? req.role : void 0
|
|
334
|
+
});
|
|
335
|
+
opts.onDecide?.(req.id, decision, decision === "approve" ? req.role : void 0);
|
|
336
|
+
render();
|
|
337
|
+
};
|
|
338
|
+
const ghostBtn = (label, attr, title) => {
|
|
339
|
+
const btn = h("button", "kb-access-btn kb-access-btn--ghost", label);
|
|
340
|
+
btn.type = "button";
|
|
341
|
+
btn.setAttribute(attr, "");
|
|
342
|
+
if (title) btn.setAttribute("aria-label", title);
|
|
343
|
+
return btn;
|
|
344
|
+
};
|
|
345
|
+
const buildDeniedCard = () => {
|
|
346
|
+
const card = h("div", "kb-access-denied");
|
|
347
|
+
const title = h("div", "kb-access-title");
|
|
348
|
+
const lock = svgIcon(ICON_LOCK);
|
|
349
|
+
if (lock) title.appendChild(lock);
|
|
350
|
+
title.appendChild(h("span", void 0, req.resource || "\u53D7\u9650\u5185\u5BB9"));
|
|
351
|
+
card.appendChild(title);
|
|
352
|
+
card.appendChild(h("p", "kb-access-why", opts.deniedHint ?? "\u6B64\u5185\u5BB9\u4EC5\u5BF9\u6388\u6743\u8005\u5F00\u653E"));
|
|
353
|
+
card.appendChild(h("div", "kb-access-owner", `\u8D1F\u8D23\u4EBA\uFF1A${req.approverNote || "\u5185\u5BB9\u8D1F\u8D23\u4EBA"}`));
|
|
354
|
+
const actions = h("div", "kb-access-actions");
|
|
355
|
+
const primary = h("button", "kb-access-btn", "\u7533\u8BF7\u8BBF\u95EE");
|
|
356
|
+
primary.type = "button";
|
|
357
|
+
primary.dataset.accessRequest = "";
|
|
358
|
+
primary.setAttribute("aria-label", `\u7533\u8BF7\u8BBF\u95EE ${req.resource || "\u8BE5\u5185\u5BB9"}`);
|
|
359
|
+
primary.addEventListener("click", () => {
|
|
360
|
+
formOpen = !formOpen;
|
|
361
|
+
render();
|
|
362
|
+
});
|
|
363
|
+
actions.appendChild(primary);
|
|
364
|
+
const switchBtn = ghostBtn("\u5207\u6362\u8EAB\u4EFD", "data-access-switch", "\u5207\u6362\u8EAB\u4EFD\u540E\u91CD\u65B0\u68C0\u67E5\u6743\u9650");
|
|
365
|
+
const switchIcon = svgIcon(ICON_USER_SWITCH);
|
|
366
|
+
if (switchIcon) switchBtn.prepend(switchIcon);
|
|
367
|
+
switchBtn.addEventListener("click", () => opts.onSwitchIdentity?.());
|
|
368
|
+
actions.appendChild(switchBtn);
|
|
369
|
+
card.appendChild(actions);
|
|
370
|
+
return card;
|
|
371
|
+
};
|
|
372
|
+
const buildForm = () => {
|
|
373
|
+
const form = h("form", "kb-access-form");
|
|
374
|
+
form.hidden = !formOpen;
|
|
375
|
+
const reason = document.createElement("textarea");
|
|
376
|
+
reason.className = "kb-access-reason";
|
|
377
|
+
reason.value = formReason;
|
|
378
|
+
reason.placeholder = "\u8BF4\u660E\u4E1A\u52A1\u539F\u56E0\uFF08\u5BA1\u6279\u4EBA\u53EF\u89C1\uFF09";
|
|
379
|
+
reason.setAttribute("aria-label", "\u7533\u8BF7\u7406\u7531");
|
|
380
|
+
reason.rows = 3;
|
|
381
|
+
form.appendChild(reason);
|
|
382
|
+
const roles = h("div", "kb-access-roles");
|
|
383
|
+
roles.setAttribute("role", "group");
|
|
384
|
+
roles.setAttribute("aria-label", "\u7533\u8BF7\u89D2\u8272");
|
|
385
|
+
for (const r of REQUESTABLE_ROLES) {
|
|
386
|
+
const chip = h("button", `kb-access-role${formRole === r ? " is-on" : ""}`, kbRoleLabel(r));
|
|
387
|
+
chip.type = "button";
|
|
388
|
+
chip.dataset.role = r;
|
|
389
|
+
chip.setAttribute("aria-pressed", String(formRole === r));
|
|
390
|
+
chip.addEventListener("click", () => {
|
|
391
|
+
formRole = r;
|
|
392
|
+
render();
|
|
393
|
+
});
|
|
394
|
+
roles.appendChild(chip);
|
|
395
|
+
}
|
|
396
|
+
form.appendChild(roles);
|
|
397
|
+
const durationRow = h("label", "kb-access-duration-row");
|
|
398
|
+
durationRow.appendChild(h("span", void 0, "\u8BBF\u95EE\u65F6\u957F"));
|
|
399
|
+
const duration = h("select", "kb-access-duration");
|
|
400
|
+
for (const [value, label] of [
|
|
401
|
+
["", "\u6C38\u4E45"],
|
|
402
|
+
["7", "7 \u5929"],
|
|
403
|
+
["30", "30 \u5929"]
|
|
404
|
+
]) {
|
|
405
|
+
const o = h("option", void 0, label);
|
|
406
|
+
o.value = value;
|
|
407
|
+
duration.appendChild(o);
|
|
408
|
+
}
|
|
409
|
+
duration.value = formDuration;
|
|
410
|
+
duration.setAttribute("aria-label", "\u8BBF\u95EE\u65F6\u957F");
|
|
411
|
+
durationRow.appendChild(duration);
|
|
412
|
+
form.appendChild(durationRow);
|
|
413
|
+
form.appendChild(h("div", "kb-meta kb-access-route", `\u8BF7\u6C42\u5C06\u53D1\u9001\u7ED9\uFF1A${req.approverNote || "\u5185\u5BB9\u8D1F\u8D23\u4EBA"}`));
|
|
414
|
+
const submit = h("button", "kb-access-submit", "\u63D0\u4EA4\u7533\u8BF7");
|
|
415
|
+
submit.type = "submit";
|
|
416
|
+
submit.dataset.accessSubmit = "";
|
|
417
|
+
form.appendChild(submit);
|
|
418
|
+
form.addEventListener("submit", (e) => {
|
|
419
|
+
e.preventDefault();
|
|
420
|
+
submitForm(form);
|
|
421
|
+
});
|
|
422
|
+
return form;
|
|
423
|
+
};
|
|
424
|
+
const buildStateRow = () => {
|
|
425
|
+
const row = h("div", "kb-access-state");
|
|
426
|
+
const chip = h("span", "kb-chip", kbAccessStateLabel(req.state));
|
|
427
|
+
chip.dataset.state = req.state;
|
|
428
|
+
row.appendChild(chip);
|
|
429
|
+
const countdown = untilText(req.expiresAt, /* @__PURE__ */ new Date());
|
|
430
|
+
if ((req.state === "granted" || req.state === "expiring") && countdown) {
|
|
431
|
+
row.appendChild(h("span", "kb-num kb-access-countdown", `${countdown}\u5230\u671F`));
|
|
432
|
+
}
|
|
433
|
+
if (req.state === "requested" || req.state === "pending_review") {
|
|
434
|
+
row.appendChild(h("span", "kb-meta", `\u7B49\u5F85 ${req.approverNote || "\u5185\u5BB9\u8D1F\u8D23\u4EBA"} \u5BA1\u6279`));
|
|
435
|
+
return row;
|
|
436
|
+
}
|
|
437
|
+
if (req.state === "expiring") {
|
|
438
|
+
const renew = h("button", "kb-access-act", "\u7533\u8BF7\u7EED\u671F");
|
|
439
|
+
renew.type = "button";
|
|
440
|
+
renew.dataset.accessRenew = "";
|
|
441
|
+
renew.addEventListener("click", () => {
|
|
442
|
+
formOpen = true;
|
|
443
|
+
render();
|
|
444
|
+
});
|
|
445
|
+
row.appendChild(renew);
|
|
446
|
+
return row;
|
|
447
|
+
}
|
|
448
|
+
if (req.state === "expired") {
|
|
449
|
+
const reapply = h("button", "kb-access-act", "\u91CD\u65B0\u7533\u8BF7");
|
|
450
|
+
reapply.type = "button";
|
|
451
|
+
reapply.dataset.accessReapply = "";
|
|
452
|
+
reapply.addEventListener("click", () => {
|
|
453
|
+
formOpen = true;
|
|
454
|
+
render();
|
|
455
|
+
});
|
|
456
|
+
row.appendChild(reapply);
|
|
457
|
+
return row;
|
|
458
|
+
}
|
|
459
|
+
if (req.state === "denied") {
|
|
460
|
+
row.appendChild(h("span", "kb-meta", "\u672A\u901A\u8FC7\u2014\u2014\u53EF\u8C03\u6574\u7406\u7531\u540E\u91CD\u65B0\u7533\u8BF7"));
|
|
461
|
+
const reapply = h("button", "kb-access-act", "\u91CD\u65B0\u7533\u8BF7");
|
|
462
|
+
reapply.type = "button";
|
|
463
|
+
reapply.dataset.accessReapply = "";
|
|
464
|
+
reapply.addEventListener("click", () => {
|
|
465
|
+
formOpen = true;
|
|
466
|
+
render();
|
|
467
|
+
});
|
|
468
|
+
row.appendChild(reapply);
|
|
469
|
+
return row;
|
|
470
|
+
}
|
|
471
|
+
if (req.state === "revoked") {
|
|
472
|
+
row.appendChild(h("span", "kb-meta", "\u8BBF\u95EE\u5DF2\u88AB\u6536\u56DE"));
|
|
473
|
+
const reapply = h("button", "kb-access-act", "\u91CD\u65B0\u7533\u8BF7");
|
|
474
|
+
reapply.type = "button";
|
|
475
|
+
reapply.dataset.accessReapply = "";
|
|
476
|
+
reapply.addEventListener("click", () => {
|
|
477
|
+
formOpen = true;
|
|
478
|
+
render();
|
|
479
|
+
});
|
|
480
|
+
row.appendChild(reapply);
|
|
481
|
+
}
|
|
482
|
+
return row;
|
|
483
|
+
};
|
|
484
|
+
const buildQueue = () => {
|
|
485
|
+
const queue = h("div", "kb-access-queue");
|
|
486
|
+
const card = h("div", "kb-access-req");
|
|
487
|
+
card.appendChild(h("span", "kb-access-req-user", req.requester || "\u672A\u77E5\u8BF7\u6C42\u4EBA"));
|
|
488
|
+
card.appendChild(h("span", "kb-access-req-resource", req.resource || "\u53D7\u9650\u5185\u5BB9"));
|
|
489
|
+
card.appendChild(h("span", "kb-chip kb-access-req-role", kbRoleLabel(req.role)));
|
|
490
|
+
if (req.reason) card.appendChild(h("span", "kb-meta kb-access-req-reason", req.reason));
|
|
491
|
+
card.appendChild(h("span", "kb-num kb-access-req-at", relativeTime(req.submittedAt)));
|
|
492
|
+
const pending = req.state === "idle" || req.state === "requested" || req.state === "pending_review";
|
|
493
|
+
if (pending) {
|
|
494
|
+
const actions = h("span", "kb-access-req-actions");
|
|
495
|
+
const roleSel = h("select", "kb-access-approve-role");
|
|
496
|
+
roleSel.setAttribute("aria-label", "\u6279\u51C6\u89D2\u8272");
|
|
497
|
+
for (const r of REQUESTABLE_ROLES) {
|
|
498
|
+
const o = h("option", void 0, `\u6279\u51C6\u4E3A${kbRoleLabel(r)}`);
|
|
499
|
+
o.value = r;
|
|
500
|
+
roleSel.appendChild(o);
|
|
501
|
+
}
|
|
502
|
+
roleSel.value = REQUESTABLE_ROLES.includes(req.role) ? req.role : "viewer";
|
|
503
|
+
actions.appendChild(roleSel);
|
|
504
|
+
const approve = ghostBtn("\u6279\u51C6", "data-access-approve", "\u6279\u51C6\u8BE5\u8BBF\u95EE\u7533\u8BF7");
|
|
505
|
+
approve.addEventListener("click", () => decide("approve", roleSel.value));
|
|
506
|
+
actions.appendChild(approve);
|
|
507
|
+
const note = document.createElement("textarea");
|
|
508
|
+
note.className = "kb-access-note";
|
|
509
|
+
note.placeholder = "\u7ED9\u8BF7\u6C42\u4EBA\u7684\u7559\u8A00\uFF08\u53EF\u9009\uFF09";
|
|
510
|
+
note.rows = 1;
|
|
511
|
+
note.setAttribute("aria-label", "\u62D2\u7EDD\u7559\u8A00\uFF08\u53EF\u9009\uFF09");
|
|
512
|
+
actions.appendChild(note);
|
|
513
|
+
const deny = ghostBtn("\u62D2\u7EDD", "data-access-deny", "\u62D2\u7EDD\u8BE5\u8BBF\u95EE\u7533\u8BF7");
|
|
514
|
+
deny.addEventListener("click", () => decide("deny"));
|
|
515
|
+
actions.appendChild(deny);
|
|
516
|
+
card.appendChild(actions);
|
|
517
|
+
} else {
|
|
518
|
+
const decided = req.state === "granted" || req.state === "expiring" ? `\u5DF2\u6279\u51C6 \xB7 ${kbRoleLabel(req.role)}` : kbAccessStateLabel(req.state);
|
|
519
|
+
const echo = h("span", "kb-meta kb-access-req-decided", decided);
|
|
520
|
+
if (req.decidedAt) echo.appendChild(h("span", "kb-num", ` \xB7 ${relativeTime(req.decidedAt)}`));
|
|
521
|
+
card.appendChild(echo);
|
|
522
|
+
}
|
|
523
|
+
queue.appendChild(card);
|
|
524
|
+
return queue;
|
|
525
|
+
};
|
|
526
|
+
const render = () => {
|
|
527
|
+
if (destroyed) return;
|
|
528
|
+
deriveExpiry();
|
|
529
|
+
const doc = host.ownerDocument;
|
|
530
|
+
const active = doc.activeElement;
|
|
531
|
+
const focusInForm = active instanceof HTMLElement && host.contains(active) && active.closest(".kb-access-form") != null;
|
|
532
|
+
const prevReason = host.querySelector(".kb-access-reason");
|
|
533
|
+
if (prevReason) formReason = prevReason.value;
|
|
534
|
+
const prevDuration = host.querySelector(".kb-access-duration");
|
|
535
|
+
if (prevDuration) formDuration = prevDuration.value;
|
|
536
|
+
host.className = mode === "approver" ? "kb-access kb-access--approver" : "kb-access";
|
|
537
|
+
host.dataset.state = req.state;
|
|
538
|
+
host.replaceChildren();
|
|
539
|
+
if (mode === "approver") {
|
|
540
|
+
host.append(buildQueue(), buildStateRow());
|
|
541
|
+
} else {
|
|
542
|
+
const grantedLike = req.state === "granted" || req.state === "expiring";
|
|
543
|
+
if (!grantedLike) host.appendChild(buildDeniedCard());
|
|
544
|
+
const form = buildForm();
|
|
545
|
+
host.appendChild(form);
|
|
546
|
+
host.appendChild(buildStateRow());
|
|
547
|
+
if (formOpen && focusInForm) form.querySelector(".kb-access-reason")?.focus();
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
const setState = (next) => {
|
|
551
|
+
if (destroyed) return;
|
|
552
|
+
req = normalizeAccessRequest(typeof next === "string" ? { ...req, state: next } : { ...req, ...next });
|
|
553
|
+
render();
|
|
554
|
+
};
|
|
555
|
+
const destroy = () => {
|
|
556
|
+
if (destroyed) return;
|
|
557
|
+
destroyed = true;
|
|
558
|
+
host.replaceChildren();
|
|
559
|
+
host.classList.remove("kb-access", "kb-access--approver");
|
|
560
|
+
delete host.dataset.state;
|
|
561
|
+
delete host.__icenKbAccess;
|
|
562
|
+
};
|
|
563
|
+
host.__icenKbAccess = { destroy };
|
|
564
|
+
render();
|
|
565
|
+
return { setState, getState: () => ({ ...req }), destroy };
|
|
566
|
+
}
|
|
567
|
+
export {
|
|
568
|
+
createKbAccess
|
|
569
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { KbAclEntry, KbSubject } from './kb-core.js';
|
|
2
|
+
import './ai-core.js';
|
|
3
|
+
|
|
4
|
+
/** kb-acl 面板数据快照(renderKbAcl 入参;normalizeAclModel 宽进严出) */
|
|
5
|
+
interface KbAclModel {
|
|
6
|
+
/** 资源名(标题栏呈现) */
|
|
7
|
+
resource: string;
|
|
8
|
+
/** 父级链(近 → 远,面包式「继承自 A / B」) */
|
|
9
|
+
parentChain: string[];
|
|
10
|
+
/** 已停止继承(banner 警示态 + 头部按钮变「恢复继承」) */
|
|
11
|
+
broken: boolean;
|
|
12
|
+
/** 子项脱离继承的例外摘要(is-partial 态文案);null/缺省 = 无例外 */
|
|
13
|
+
exceptions?: {
|
|
14
|
+
label: string;
|
|
15
|
+
count: number;
|
|
16
|
+
} | null;
|
|
17
|
+
/** 直接授权行(deny 行渲染时置顶) */
|
|
18
|
+
entries: KbAclEntry[];
|
|
19
|
+
/** 链接访问行(subject.kind = link) */
|
|
20
|
+
links: KbAclEntry[];
|
|
21
|
+
/** 不去重暴露面计数(「对 N 人可见」过度授权警示);缺省/null = 不渲染 */
|
|
22
|
+
exposureCount?: number | null;
|
|
23
|
+
}
|
|
24
|
+
/** renderKbAcl 的回调通道(与 icen:kb-acl-* 事件双通道) */
|
|
25
|
+
interface KbAclRenderOptions {
|
|
26
|
+
/** 「授予访问」点击(data-acl-grant) */
|
|
27
|
+
onGrant?: () => void;
|
|
28
|
+
/** 行移除点击(data-acl-remove;system 行 CSS 隐藏 + 委托二次拒绝,不会触发) */
|
|
29
|
+
onRemove?: (subject: KbSubject) => void;
|
|
30
|
+
/** 「停止继承」(data-acl-break) */
|
|
31
|
+
onBreak?: () => void;
|
|
32
|
+
/** 「恢复继承」(data-acl-restore) */
|
|
33
|
+
onRestore?: () => void;
|
|
34
|
+
}
|
|
35
|
+
/** 素对象 → KbAclModel(宽进严出:entries/links 逐条 normalizeAclEntry) */
|
|
36
|
+
declare function normalizeAclModel(raw: KbAclModel | Record<string, unknown>): KbAclModel;
|
|
37
|
+
/**
|
|
38
|
+
* 渲染文档权限面板(快照;返回挂载容器 el)。结构见文件头 DOM 契约;
|
|
39
|
+
* 行级交互不在此绑定——统一走 initKbAcl 的 root 级委托(幂等,返回销毁)。
|
|
40
|
+
*/
|
|
41
|
+
declare function renderKbAcl(el: HTMLElement, model: KbAclModel | Record<string, unknown>, opts?: KbAclRenderOptions): HTMLElement;
|
|
42
|
+
/**
|
|
43
|
+
* 初始化 kb-acl 面板交互(root 级 click 委托,默认 document;幂等标记 __icenKbAclInit,
|
|
44
|
+
* 返回销毁函数,销毁后可重新 init)。命中 data-acl-grant / data-acl-remove /
|
|
45
|
+
* data-acl-break / data-acl-restore 时派 icen:kb-acl-* 事件并回调 renderKbAcl 登记的 opts
|
|
46
|
+
* (双通道)。is-system 行的移除钮在委托层二次拒绝(CSS 隐藏是第一重)。
|
|
47
|
+
*/
|
|
48
|
+
declare function initKbAcl(root?: ParentNode): () => void;
|
|
49
|
+
|
|
50
|
+
export { type KbAclModel, type KbAclRenderOptions, initKbAcl, normalizeAclModel, renderKbAcl };
|