@mrweicodes/dsh-permgate 1.3.9
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/LICENSE +21 -0
- package/README.md +211 -0
- package/client.js +1867 -0
- package/cordis.patch.yml +35 -0
- package/index.js +2451 -0
- package/package.json +43 -0
package/client.js
ADDED
|
@@ -0,0 +1,1867 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "dsh-permgate",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let React = require("react");
|
|
8
|
+
|
|
9
|
+
const CSS = ".pg-modal { position: fixed; top: 16px; right: 16px; z-index: 9999; width: 420px; max-width: 92vw; max-height: 82vh; overflow-y: auto; background: var(--dsw-alias-bg-overlay, #ffffff); color: var(--dsw-alias-label-primary, #24292f); border: 1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.4)); border-radius: 10px; box-shadow: 0 10px 36px rgba(0,0,0,0.32); padding: 14px 16px; font-family: system-ui, -apple-system, \"Segoe UI\", \"PingFang SC\", \"Microsoft YaHei\", sans-serif; } .pg-modal-title { font-size: 14px; font-weight: 600; margin-bottom: 6px; } .pg-modal-req { font-size: 12px; margin-bottom: 4px; } .pg-modal-body { font-size: 12px; color: var(--dsw-alias-label-secondary, #57606a); margin-bottom: 8px; } .pg-intent { font-size: 12px; background: var(--dsw-alias-bg-layer-1, #f6f8fa); border: 1px solid var(--dsw-alias-border-l1, #d0d7de); border-left: 3px solid var(--dsw-alias-brand-primary, #1f6feb); border-radius: 6px; padding: 6px 8px; color: var(--dsw-alias-label-secondary, #57606a); margin-bottom: 6px; line-height: 1.5; word-break: break-word; } .pg-intent-tag { display: inline-block; font-weight: 700; color: var(--dsw-alias-brand-primary, #1f6feb); margin-right: 6px; font-size: 11px; } .pg-args { font-size: 11px; background: var(--dsw-alias-bg-layer-1, #f6f8fa); border: 1px solid var(--dsw-alias-border-l1, #d0d7de); border-left: 3px solid rgba(128,128,128,0.65); border-radius: 6px; padding: 5px 8px; color: var(--dsw-alias-label-secondary, #57606a); margin-bottom: 8px; line-height: 1.5; word-break: break-all; font-family: ui-monospace, Consolas, monospace; } .pg-args-tag { display: inline-block; font-weight: 700; color: rgba(128,128,128,0.95); margin-right: 6px; font-size: 11px; font-family: system-ui, -apple-system, \"Segoe UI\", \"PingFang SC\", \"Microsoft YaHei\", sans-serif; } .pg-args-row { display: flex; gap: 6px; padding: 1px 0; } .pg-args-label { font-weight: 600; flex: 0 0 auto; } .pg-args-val { word-break: break-all; flex: 1; } .pg-cand-head { font-size: 12px; font-weight: 600; margin: 6px 0 4px; } .pg-cand-hint { font-size: 11px; color: var(--dsw-alias-label-secondary, #8c959f); margin-bottom: 4px; } .pg-cand { display: flex; align-items: center; gap: 6px; padding: 4px 0; border-bottom: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,0.2)); } .pg-cand-label { flex: 1; font-family: ui-monospace, Consolas, monospace; font-size: 11px; word-break: break-all; } .pg-radio { display: inline-flex; align-items: center; gap: 3px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.4)); background: transparent; color: var(--dsw-alias-label-secondary, #57606a); font-size: 11px; font-family: inherit; cursor: pointer; transition: all 0.15s ease; } .pg-radio:hover { border-color: var(--dsw-alias-brand-primary, #1f6feb); } .pg-radio-allow { border-color: var(--dsw-alias-state-success-primary, #1a7f37); color: var(--dsw-alias-state-success-primary, #1a7f37); background: rgba(26, 127, 55, 0.12); font-weight: 600; } .pg-radio-deny { border-color: var(--dsw-alias-state-error-primary, #cf222e); color: var(--dsw-alias-state-error-primary, #cf222e); background: rgba(207, 34, 46, 0.12); font-weight: 600; } .pg-footer { display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px; } .pg-action { display: inline-flex; align-items: center; padding: 5px 16px; border-radius: 6px; border: 1px solid; font-size: 12px; font-family: inherit; font-weight: 600; cursor: pointer; transition: filter 0.15s ease, box-shadow 0.15s ease, transform 0.05s ease; } .pg-action:hover { filter: brightness(1.07); box-shadow: 0 2px 8px rgba(0,0,0,0.18); } .pg-action:active { transform: translateY(1px); box-shadow: none; } .pg-action:disabled { opacity: 0.5; cursor: not-allowed; filter: none; box-shadow: none; } .pg-action-allow { border-color: rgba(26, 127, 55, 0.45); color: #1a7f37; background: rgba(26, 127, 55, 0.12); } @supports (color: color-mix(in srgb, red 10%, blue)) { .pg-action-allow { border-color: color-mix(in srgb, var(--dsw-alias-state-success-primary, #1a7f37) 45%, transparent); color: var(--dsw-alias-state-success-primary, #1a7f37); background: color-mix(in srgb, var(--dsw-alias-state-success-primary, #1a7f37) 12%, transparent); } } .pg-action-deny { border-color: rgba(207, 34, 46, 0.45); color: #cf222e; background: rgba(207, 34, 46, 0.12); } @supports (color: color-mix(in srgb, red 10%, blue)) { .pg-action-deny { border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary, #cf222e) 45%, transparent); color: var(--dsw-alias-state-error-primary, #cf222e); background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #cf222e) 12%, transparent); } } .pg-btn { padding: 4px 10px; border-radius: 6px; border: 1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.4)); background: transparent; color: var(--dsw-alias-label-primary, #24292f); cursor: pointer; font-size: 13px; font-family: inherit; transition: border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease, transform 0.05s ease; } .pg-btn:hover { border-color: var(--dsw-alias-brand-primary, #1f6feb); background: rgba(31, 111, 235, 0.08); } .pg-btn:active { transform: translateY(1px); } .pg-btn:disabled { opacity: 0.5; cursor: not-allowed; } .pg-btn-danger:hover { border-color: var(--dsw-alias-state-error-primary, #cf222e); color: var(--dsw-alias-state-error-primary, #cf222e); background: rgba(207, 34, 46, 0.08); } .pg-btn-on { border-color: var(--dsw-alias-state-success-primary, #1a7f37); color: var(--dsw-alias-state-success-primary, #1a7f37); background: rgba(26, 127, 55, 0.12); font-weight: 600; } .pg-btn-on:hover { border-color: var(--dsw-alias-state-success-primary, #1a7f37); color: var(--dsw-alias-state-success-primary, #1a7f37); background: rgba(26, 127, 55, 0.2); } .pg-btn-off { border-color: var(--dsw-alias-state-error-primary, #cf222e); color: var(--dsw-alias-state-error-primary, #cf222e); background: rgba(207, 34, 46, 0.1); font-weight: 600; } .pg-btn-off:hover { border-color: var(--dsw-alias-state-error-primary, #cf222e); color: var(--dsw-alias-state-error-primary, #cf222e); background: rgba(207, 34, 46, 0.16); } .pg-btn-confirm { border-color: var(--dsw-alias-state-error-primary, #cf222e); color: #ffffff; background: var(--dsw-alias-state-error-primary, #cf222e); } .pg-btn-confirm:hover { border-color: var(--dsw-alias-state-error-primary, #cf222e); color: #ffffff; background: var(--dsw-alias-state-error-primary, #cf222e); filter: brightness(1.12); } .pg-field { padding: 4px 8px; border-radius: 6px; border: 1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.4)); background: var(--dsw-alias-bg-layer-1, #f6f8fa); color: var(--dsw-alias-label-primary, #24292f); color-scheme: light dark; font-size: 13px; font-family: inherit; transition: border-color 0.15s ease, box-shadow 0.15s ease; } .pg-field:hover { border-color: var(--dsw-alias-brand-primary, #1f6feb); } .pg-field:focus { outline: none; border-color: var(--dsw-alias-brand-primary, #1f6feb); box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.22); } .pg-field:disabled { opacity: 0.5; cursor: not-allowed; } .pg-field option { color: var(--dsw-alias-label-primary, #24292f); background: var(--dsw-alias-bg-overlay, #ffffff); } .pg-tab { padding: 6px 16px; border-radius: 6px 6px 0 0; border: none; border-bottom: 2px solid transparent; background: transparent; color: var(--dsw-alias-label-primary, #24292f); cursor: pointer; font-size: 13px; font-family: inherit; margin-right: 4px; transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease; } .pg-tab:hover { background: rgba(128, 128, 128, 0.12); } .pg-tab-on { border-bottom-color: var(--dsw-alias-brand-primary, #1f6feb); color: var(--dsw-alias-brand-primary, #1f6feb); font-weight: 600; }";
|
|
10
|
+
|
|
11
|
+
const DIFF_CSS = '.pg-link { display: inline-flex; align-items: center; gap: 4px; background: var(--dsw-alias-bg-layer-1, #f0f2f5); color: var(--dsw-alias-label-primary, #24292f); border: 1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.45)); border-radius: 6px; padding: 3px 12px; cursor: pointer; font-size: 12px; font-weight: 500; margin-bottom: 6px; } .pg-link:hover { border-color: #1f6feb; color: #1f6feb; }';
|
|
12
|
+
|
|
13
|
+
// dsh-file-review 风格 unified diff + 右侧对比抽屉(浅/深色主题 token 跟随)
|
|
14
|
+
const DIFF2_CSS = '.pg2-block { position: relative; margin-top: 6px; margin-bottom: 8px; border: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,0.3)); border-radius: 8px; overflow: hidden; background: var(--dsw-alias-bg-layer-1, rgba(0,0,0,0.03)); } .pg2-header { display: flex; align-items: center; gap: 8px; min-height: 32px; padding: 0 8px; border-bottom: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,0.2)); font-family: ui-monospace, Consolas, monospace; font-size: 11px; } .pg2-status { color: var(--dsw-alias-state-success-primary, #1a7f37); font-weight: 700; } .pg2-status-read { color: var(--dsw-alias-brand-primary, #1f6feb); font-weight: 700; } .pg2-path { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: pointer; color: inherit; } .pg2-path:hover { color: var(--dsw-alias-brand-primary, #1f6feb); text-decoration: underline; } .pg2-added { margin-left: auto; color: var(--dsw-alias-state-success-primary, #1a7f37); } .pg2-removed { color: var(--dsw-alias-state-error-primary, #cf222e); } .pg2-copy { border: 0; background: transparent; color: var(--dsw-alias-label-secondary, #57606a); cursor: pointer; font-size: 11px; font-family: system-ui, -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; padding: 0 2px; margin-left: 6px; flex: 0 0 auto; } .pg2-copy:hover { color: var(--dsw-alias-brand-primary, #1f6feb); } .pg2-body { overflow: auto; max-height: 240px; font-family: ui-monospace, Consolas, monospace; font-size: 11px; line-height: 1.5; } .pg2-row { display: grid; grid-template-columns: 40px 40px 22px minmax(max-content, 1fr); min-width: max-content; white-space: pre; } .pg2-old, .pg2-new { padding: 0 6px; border-right: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,0.2)); color: var(--dsw-alias-label-tertiary, #8c959f); text-align: right; user-select: none; } .pg2-sign { text-align: center; user-select: none; } .pg2-text { padding-right: 12px; } .pg2-del { color: var(--dsw-alias-state-error-primary, #cf222e); background: rgba(207, 34, 46, 0.10); } .pg2-add { color: var(--dsw-alias-state-success-primary, #1a7f37); background: rgba(26, 127, 55, 0.10); } .pg2-ctx { color: var(--dsw-alias-label-primary, #24292f); } .pg2-gap { display: block; width: 100%; border: 0; border-top: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,0.2)); border-bottom: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,0.2)); background: var(--dsw-alias-bg-layer-2, rgba(128,128,128,0.07)); color: var(--dsw-alias-label-secondary, #57606a); cursor: pointer; font-size: 11px; font-family: ui-monospace, Consolas, monospace; text-align: left; padding: 4px 8px 4px 104px; } .pg2-gap:hover { color: var(--dsw-alias-label-primary, #24292f); } .pg2-gap-more { padding: 4px 8px 4px 104px; color: var(--dsw-alias-label-secondary, #57606a); font-size: 11px; font-family: ui-monospace, Consolas, monospace; } .pg2-fadd { color: #2e7d32; } .pg2-fdel { color: #c62828; } .pg2-fctx { color: rgba(128,128,128,0.7); } .pg2-foot { padding: 4px 8px; border-top: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,0.2)); font-size: 11px; color: rgba(128,128,128,0.8); } .pg2-rrow { display: grid; grid-template-columns: 40px minmax(max-content, 1fr); min-width: max-content; white-space: pre; } .pg2-rnum { padding: 0 6px; border-right: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,0.2)); color: var(--dsw-alias-label-tertiary, #8c959f); text-align: right; user-select: none; } .pg2-load { padding: 10px 12px; font-size: 12px; color: var(--dsw-alias-label-secondary, #57606a); } .pg2-err { padding: 10px 12px; font-size: 12px; color: var(--dsw-alias-state-error-primary, #cf222e); } .pg-path-link { color: var(--dsw-alias-brand-primary, #1f6feb); cursor: pointer; word-break: break-all; flex: 1; } .pg-path-link:hover { text-decoration: underline; } .pg2-drawer { position: fixed; top: 0; right: 0; bottom: 0; left: auto; width: 42vw; min-width: 480px; max-width: 96vw; z-index: 10000; background: var(--dsw-alias-bg-overlay, #ffffff); color: var(--dsw-alias-label-primary, #24292f); border-left: 1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.4)); box-shadow: -8px 0 28px rgba(0,0,0,0.25); display: flex; flex-direction: column; } .pg2-drawer-head { display: flex; align-items: center; gap: 8px; padding: 10px 12px; border-bottom: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,0.25)); min-height: 44px; } .pg2-drawer-file { font-family: ui-monospace, Consolas, monospace; font-size: 12px; word-break: break-all; flex: 1; min-width: 0; } .pg2-drawer-close { border: 1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.4)); background: transparent; color: var(--dsw-alias-label-secondary, #57606a); border-radius: 6px; width: 26px; height: 26px; cursor: pointer; font-size: 13px; line-height: 1; flex: 0 0 auto; } .pg2-drawer-close:hover { color: var(--dsw-alias-brand-primary, #1f6feb); border-color: var(--dsw-alias-brand-primary, #1f6feb); } .pg2-drawer-body { flex: 1; overflow: hidden; padding: 10px 12px; display: flex; flex-direction: column; min-height: 0; } .pg2-drawer-body .pg2-block { margin: 0; flex: 1; display: flex; flex-direction: column; min-height: 0; } .pg2-drawer-body .pg2-body { flex: 1; overflow: auto; max-height: none; min-height: 0; } .pg2-drawer-body .pg2-load, .pg2-drawer-body .pg2-err { flex: 1; display: flex; align-items: center; justify-content: center; } .pg2-drawer-resize { position: absolute; top: 0; bottom: 0; left: -4px; width: 8px; cursor: col-resize; z-index: 2; } .pg2-drawer-resize:hover, .pg2-drawer-resize.drag { background: rgba(31, 111, 235, 0.18); } .pg2-drawer-open { border: 1px solid var(--dsw-alias-brand-primary, #1f6feb); background: transparent; color: var(--dsw-alias-brand-primary, #1f6feb); border-radius: 6px; padding: 2px 10px; height: 26px; cursor: pointer; font-size: 12px; flex: 0 0 auto; } .pg2-drawer-open:hover { background: rgba(31, 111, 235, 0.10); } .pg2-edit-msg { font-size: 12px; color: var(--dsw-alias-label-secondary, #57606a); margin-right: 6px; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .pg2-edit-msg.err { color: var(--dsw-alias-state-error-primary, #cf222e); } .pg2-expand { border: 0; background: transparent; color: var(--dsw-alias-brand-primary, #1f6feb); cursor: pointer; font-size: 11px; font-family: system-ui, -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; padding: 0 2px; margin-left: 6px; flex: 0 0 auto; } .pg2-expand:hover { text-decoration: underline; } .pg2-romit { padding: 3px 8px 3px 48px; color: var(--dsw-alias-label-secondary, #57606a); font-size: 11px; font-family: ui-monospace, Consolas, monospace; font-style: italic; } .pg2-text .token.comment, .pg2-text .token.prolog, .pg2-text .token.doctype, .pg2-text .token.cdata { color: #8c959f; font-style: italic; } .pg2-text .token.punctuation { color: #57606a; } .pg2-text .token.property, .pg2-text .token.tag, .pg2-text .token.constant, .pg2-text .token.symbol, .pg2-text .token.deleted { color: #cf222e; } .pg2-text .token.boolean, .pg2-text .token.number { color: #9a6700; } .pg2-text .token.selector, .pg2-text .token.attr-name, .pg2-text .token.string, .pg2-text .token.char, .pg2-text .token.builtin, .pg2-text .token.inserted { color: #1a7f37; } .pg2-text .token.operator, .pg2-text .token.entity, .pg2-text .token.url { color: #8250df; } .pg2-text .token.atrule, .pg2-text .token.attr-value, .pg2-text .token.keyword { color: #1f6feb; } .pg2-text .token.function, .pg2-text .token.class-name { color: #8250df; } .pg2-text .token.regex, .pg2-text .token.important, .pg2-text .token.variable { color: #953800; }';
|
|
15
|
+
|
|
16
|
+
// ── Prism 语法高亮(vendored,prismjs 1.29.0,MIT © Lea Verou,https://prismjs.com)──
|
|
17
|
+
// PRISM_SRC 为 prism-core + 常用语言语法(components/prism-*.min.js)的拼接源码,
|
|
18
|
+
// 由构建脚本 JSON 转义后填入下方占位符。运行时代码经过 new Function 执行(非严格模式),
|
|
19
|
+
// 语言文件引用的 Prism 变量解析到同一函数作用域;CSP 等场景禁用时 PG_PRISM 为 null,
|
|
20
|
+
// 高亮静默降级为纯文本,不影响其余功能。
|
|
21
|
+
const PRISM_SRC = "var _self=\"undefined\"!=typeof window?window:\"undefined\"!=typeof WorkerGlobalScope\u0026\u0026self instanceof WorkerGlobalScope?self:{},Prism=function(e){var n=/(?:^|\\s)lang(?:uage)?-([\\w-]+)(?=\\s|$)/i,t=0,r={},a={manual:e.Prism\u0026\u0026e.Prism.manual,disableWorkerMessageHandler:e.Prism\u0026\u0026e.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof i?new i(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/\u0026/g,\"\u0026amp;\").replace(/\u003c/g,\"\u0026lt;\").replace(/\\u00a0/g,\" \")},type:function(e){return Object.prototype.toString.call(e).slice(8,-1)},objId:function(e){return e.__id||Object.defineProperty(e,\"__id\",{value:++t}),e.__id},clone:function e(n,t){var r,i;switch(t=t||{},a.util.type(n)){case\"Object\":if(i=a.util.objId(n),t[i])return t[i];for(var l in r={},t[i]=r,n)n.hasOwnProperty(l)\u0026\u0026(r[l]=e(n[l],t));return r;case\"Array\":return i=a.util.objId(n),t[i]?t[i]:(r=[],t[i]=r,n.forEach((function(n,a){r[a]=e(n,t)})),r);default:return n}},getLanguage:function(e){for(;e;){var t=n.exec(e.className);if(t)return t[1].toLowerCase();e=e.parentElement}return\"none\"},setLanguage:function(e,t){e.className=e.className.replace(RegExp(n,\"gi\"),\"\"),e.classList.add(\"language-\"+t)},currentScript:function(){if(\"undefined\"==typeof document)return null;if(\"currentScript\"in document)return document.currentScript;try{throw new Error}catch(r){var e=(/at [^(\\r\\n]*\\((.*):[^:]+:[^:]+\\)$/i.exec(r.stack)||[])[1];if(e){var n=document.getElementsByTagName(\"script\");for(var t in n)if(n[t].src==e)return n[t]}return null}},isActive:function(e,n,t){for(var r=\"no-\"+n;e;){var a=e.classList;if(a.contains(n))return!0;if(a.contains(r))return!1;e=e.parentElement}return!!t}},languages:{plain:r,plaintext:r,text:r,txt:r,extend:function(e,n){var t=a.util.clone(a.languages[e]);for(var r in n)t[r]=n[r];return t},insertBefore:function(e,n,t,r){var i=(r=r||a.languages)[e],l={};for(var o in i)if(i.hasOwnProperty(o)){if(o==n)for(var s in t)t.hasOwnProperty(s)\u0026\u0026(l[s]=t[s]);t.hasOwnProperty(o)||(l[o]=i[o])}var u=r[e];return r[e]=l,a.languages.DFS(a.languages,(function(n,t){t===u\u0026\u0026n!=e\u0026\u0026(this[n]=l)})),l},DFS:function e(n,t,r,i){i=i||{};var l=a.util.objId;for(var o in n)if(n.hasOwnProperty(o)){t.call(n,o,n[o],r||o);var s=n[o],u=a.util.type(s);\"Object\"!==u||i[l(s)]?\"Array\"!==u||i[l(s)]||(i[l(s)]=!0,e(s,t,o,i)):(i[l(s)]=!0,e(s,t,null,i))}}},plugins:{},highlightAll:function(e,n){a.highlightAllUnder(document,e,n)},highlightAllUnder:function(e,n,t){var r={callback:t,container:e,selector:\u0027code[class*=\"language-\"], [class*=\"language-\"] code, code[class*=\"lang-\"], [class*=\"lang-\"] code\u0027};a.hooks.run(\"before-highlightall\",r),r.elements=Array.prototype.slice.apply(r.container.querySelectorAll(r.selector)),a.hooks.run(\"before-all-elements-highlight\",r);for(var i,l=0;i=r.elements[l++];)a.highlightElement(i,!0===n,r.callback)},highlightElement:function(n,t,r){var i=a.util.getLanguage(n),l=a.languages[i];a.util.setLanguage(n,i);var o=n.parentElement;o\u0026\u0026\"pre\"===o.nodeName.toLowerCase()\u0026\u0026a.util.setLanguage(o,i);var s={element:n,language:i,grammar:l,code:n.textContent};function u(e){s.highlightedCode=e,a.hooks.run(\"before-insert\",s),s.element.innerHTML=s.highlightedCode,a.hooks.run(\"after-highlight\",s),a.hooks.run(\"complete\",s),r\u0026\u0026r.call(s.element)}if(a.hooks.run(\"before-sanity-check\",s),(o=s.element.parentElement)\u0026\u0026\"pre\"===o.nodeName.toLowerCase()\u0026\u0026!o.hasAttribute(\"tabindex\")\u0026\u0026o.setAttribute(\"tabindex\",\"0\"),!s.code)return a.hooks.run(\"complete\",s),void(r\u0026\u0026r.call(s.element));if(a.hooks.run(\"before-highlight\",s),s.grammar)if(t\u0026\u0026e.Worker){var c=new Worker(a.filename);c.onmessage=function(e){u(e.data)},c.postMessage(JSON.stringify({language:s.language,code:s.code,immediateClose:!0}))}else u(a.highlight(s.code,s.grammar,s.language));else u(a.util.encode(s.code))},highlight:function(e,n,t){var r={code:e,grammar:n,language:t};if(a.hooks.run(\"before-tokenize\",r),!r.grammar)throw new Error(\u0027The language \"\u0027+r.language+\u0027\" has no grammar.\u0027);return r.tokens=a.tokenize(r.code,r.grammar),a.hooks.run(\"after-tokenize\",r),i.stringify(a.util.encode(r.tokens),r.language)},tokenize:function(e,n){var t=n.rest;if(t){for(var r in t)n[r]=t[r];delete n.rest}var a=new s;return u(a,a.head,e),o(e,a,n,a.head,0),function(e){for(var n=[],t=e.head.next;t!==e.tail;)n.push(t.value),t=t.next;return n}(a)},hooks:{all:{},add:function(e,n){var t=a.hooks.all;t[e]=t[e]||[],t[e].push(n)},run:function(e,n){var t=a.hooks.all[e];if(t\u0026\u0026t.length)for(var r,i=0;r=t[i++];)r(n)}},Token:i};function i(e,n,t,r){this.type=e,this.content=n,this.alias=t,this.length=0|(r||\"\").length}function l(e,n,t,r){e.lastIndex=n;var a=e.exec(t);if(a\u0026\u0026r\u0026\u0026a[1]){var i=a[1].length;a.index+=i,a[0]=a[0].slice(i)}return a}function o(e,n,t,r,s,g){for(var f in t)if(t.hasOwnProperty(f)\u0026\u0026t[f]){var h=t[f];h=Array.isArray(h)?h:[h];for(var d=0;d\u003ch.length;++d){if(g\u0026\u0026g.cause==f+\",\"+d)return;var v=h[d],p=v.inside,m=!!v.lookbehind,y=!!v.greedy,k=v.alias;if(y\u0026\u0026!v.pattern.global){var x=v.pattern.toString().match(/[imsuy]*$/)[0];v.pattern=RegExp(v.pattern.source,x+\"g\")}for(var b=v.pattern||v,w=r.next,A=s;w!==n.tail\u0026\u0026!(g\u0026\u0026A\u003e=g.reach);A+=w.value.length,w=w.next){var E=w.value;if(n.length\u003ee.length)return;if(!(E instanceof i)){var P,L=1;if(y){if(!(P=l(b,A,e,m))||P.index\u003e=e.length)break;var S=P.index,O=P.index+P[0].length,j=A;for(j+=w.value.length;S\u003e=j;)j+=(w=w.next).value.length;if(A=j-=w.value.length,w.value instanceof i)continue;for(var C=w;C!==n.tail\u0026\u0026(j\u003cO||\"string\"==typeof C.value);C=C.next)L++,j+=C.value.length;L--,E=e.slice(A,j),P.index-=A}else if(!(P=l(b,0,E,m)))continue;S=P.index;var N=P[0],_=E.slice(0,S),M=E.slice(S+N.length),W=A+E.length;g\u0026\u0026W\u003eg.reach\u0026\u0026(g.reach=W);var z=w.prev;if(_\u0026\u0026(z=u(n,z,_),A+=_.length),c(n,z,L),w=u(n,z,new i(f,p?a.tokenize(N,p):N,k,N)),M\u0026\u0026u(n,w,M),L\u003e1){var I={cause:f+\",\"+d,reach:W};o(e,n,t,w.prev,A,I),g\u0026\u0026I.reach\u003eg.reach\u0026\u0026(g.reach=I.reach)}}}}}}function s(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function u(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function c(e,n,t){for(var r=n.next,a=0;a\u003ct\u0026\u0026r!==e.tail;a++)r=r.next;n.next=r,r.prev=n,e.length-=a}if(e.Prism=a,i.stringify=function e(n,t){if(\"string\"==typeof n)return n;if(Array.isArray(n)){var r=\"\";return n.forEach((function(n){r+=e(n,t)})),r}var i={type:n.type,content:e(n.content,t),tag:\"span\",classes:[\"token\",n.type],attributes:{},language:t},l=n.alias;l\u0026\u0026(Array.isArray(l)?Array.prototype.push.apply(i.classes,l):i.classes.push(l)),a.hooks.run(\"wrap\",i);var o=\"\";for(var s in i.attributes)o+=\" \"+s+\u0027=\"\u0027+(i.attributes[s]||\"\").replace(/\"/g,\"\u0026quot;\")+\u0027\"\u0027;return\"\u003c\"+i.tag+\u0027 class=\"\u0027+i.classes.join(\" \")+\u0027\"\u0027+o+\"\u003e\"+i.content+\"\u003c/\"+i.tag+\"\u003e\"},!e.document)return e.addEventListener?(a.disableWorkerMessageHandler||e.addEventListener(\"message\",(function(n){var t=JSON.parse(n.data),r=t.language,i=t.code,l=t.immediateClose;e.postMessage(a.highlight(i,a.languages[r],r)),l\u0026\u0026e.close()}),!1),a):a;var g=a.util.currentScript();function f(){a.manual||a.highlightAll()}if(g\u0026\u0026(a.filename=g.src,g.hasAttribute(\"data-manual\")\u0026\u0026(a.manual=!0)),!a.manual){var h=document.readyState;\"loading\"===h||\"interactive\"===h\u0026\u0026g\u0026\u0026g.defer?document.addEventListener(\"DOMContentLoaded\",f):window.requestAnimationFrame?window.requestAnimationFrame(f):window.setTimeout(f,16)}return a}(_self);\"undefined\"!=typeof module\u0026\u0026module.exports\u0026\u0026(module.exports=Prism),\"undefined\"!=typeof global\u0026\u0026(global.Prism=Prism);\nPrism.languages.markup={comment:{pattern:/\u003c!--(?:(?!\u003c!--)[\\s\\S])*?--\u003e/,greedy:!0},prolog:{pattern:/\u003c\\?[\\s\\S]+?\\?\u003e/,greedy:!0},doctype:{pattern:/\u003c!DOCTYPE(?:[^\u003e\"\u0027[\\]]|\"[^\"]*\"|\u0027[^\u0027]*\u0027)+(?:\\[(?:[^\u003c\"\u0027\\]]|\"[^\"]*\"|\u0027[^\u0027]*\u0027|\u003c(?!!--)|\u003c!--(?:[^-]|-(?!-\u003e))*--\u003e)*\\]\\s*)?\u003e/i,greedy:!0,inside:{\"internal-subset\":{pattern:/(^[^\\[]*\\[)[\\s\\S]+(?=\\]\u003e$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/\"[^\"]*\"|\u0027[^\u0027]*\u0027/,greedy:!0},punctuation:/^\u003c!|\u003e$|[[\\]]/,\"doctype-tag\":/^DOCTYPE/i,name:/[^\\s\u003c\u003e\u0027\"]+/}},cdata:{pattern:/\u003c!\\[CDATA\\[[\\s\\S]*?\\]\\]\u003e/i,greedy:!0},tag:{pattern:/\u003c\\/?(?!\\d)[^\\s\u003e\\/=$\u003c%]+(?:\\s(?:\\s*[^\\s\u003e\\/=]+(?:\\s*=\\s*(?:\"[^\"]*\"|\u0027[^\u0027]*\u0027|[^\\s\u0027\"\u003e=]+(?=[\\s\u003e]))|(?=[\\s/\u003e])))+)?\\s*\\/?\u003e/,greedy:!0,inside:{tag:{pattern:/^\u003c\\/?[^\\s\u003e\\/]+/,inside:{punctuation:/^\u003c\\/?/,namespace:/^[^\\s\u003e\\/:]+:/}},\"special-attr\":[],\"attr-value\":{pattern:/=\\s*(?:\"[^\"]*\"|\u0027[^\u0027]*\u0027|[^\\s\u0027\"\u003e=]+)/,inside:{punctuation:[{pattern:/^=/,alias:\"attr-equals\"},{pattern:/^(\\s*)[\"\u0027]|[\"\u0027]$/,lookbehind:!0}]}},punctuation:/\\/?\u003e/,\"attr-name\":{pattern:/[^\\s\u003e\\/]+/,inside:{namespace:/^[^\\s\u003e\\/:]+:/}}}},entity:[{pattern:/\u0026[\\da-z]{1,8};/i,alias:\"named-entity\"},/\u0026#x?[\\da-f]{1,8};/i]},Prism.languages.markup.tag.inside[\"attr-value\"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside[\"internal-subset\"].inside=Prism.languages.markup,Prism.hooks.add(\"wrap\",(function(a){\"entity\"===a.type\u0026\u0026(a.attributes.title=a.content.replace(/\u0026amp;/,\"\u0026\"))})),Object.defineProperty(Prism.languages.markup.tag,\"addInlined\",{value:function(a,e){var s={};s[\"language-\"+e]={pattern:/(^\u003c!\\[CDATA\\[)[\\s\\S]+?(?=\\]\\]\u003e$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^\u003c!\\[CDATA\\[|\\]\\]\u003e$/i;var t={\"included-cdata\":{pattern:/\u003c!\\[CDATA\\[[\\s\\S]*?\\]\\]\u003e/i,inside:s}};t[\"language-\"+e]={pattern:/[\\s\\S]+/,inside:Prism.languages[e]};var n={};n[a]={pattern:RegExp(\"(\u003c__[^\u003e]*\u003e)(?:\u003c!\\\\[CDATA\\\\[(?:[^\\\\]]|\\\\](?!\\\\]\u003e))*\\\\]\\\\]\u003e|(?!\u003c!\\\\[CDATA\\\\[)[^])*?(?=\u003c/__\u003e)\".replace(/__/g,(function(){return a})),\"i\"),lookbehind:!0,greedy:!0,inside:t},Prism.languages.insertBefore(\"markup\",\"cdata\",n)}}),Object.defineProperty(Prism.languages.markup.tag,\"addAttribute\",{value:function(a,e){Prism.languages.markup.tag.inside[\"special-attr\"].push({pattern:RegExp(\"(^|[\\\"\u0027\\\\s])(?:\"+a+\")\\\\s*=\\\\s*(?:\\\"[^\\\"]*\\\"|\u0027[^\u0027]*\u0027|[^\\\\s\u0027\\\"\u003e=]+(?=[\\\\s\u003e]))\",\"i\"),lookbehind:!0,inside:{\"attr-name\":/^[^\\s=]+/,\"attr-value\":{pattern:/=[\\s\\S]+/,inside:{value:{pattern:/(^=\\s*([\"\u0027]|(?![\"\u0027])))\\S[\\s\\S]*(?=\\2$)/,lookbehind:!0,alias:[e,\"language-\"+e],inside:Prism.languages[e]},punctuation:[{pattern:/^=/,alias:\"attr-equals\"},/\"|\u0027/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend(\"markup\",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml;\n!function(s){var e=/(?:\"(?:\\\\(?:\\r\\n|[\\s\\S])|[^\"\\\\\\r\\n])*\"|\u0027(?:\\\\(?:\\r\\n|[\\s\\S])|[^\u0027\\\\\\r\\n])*\u0027)/;s.languages.css={comment:/\\/\\*[\\s\\S]*?\\*\\//,atrule:{pattern:RegExp(\"@[\\\\w-](?:[^;{\\\\s\\\"\u0027]|\\\\s+(?!\\\\s)|\"+e.source+\")*?(?:;|(?=\\\\s*\\\\{))\"),inside:{rule:/^@[\\w-]+/,\"selector-function-argument\":{pattern:/(\\bselector\\s*\\(\\s*(?![\\s)]))(?:[^()\\s]|\\s+(?![\\s)])|\\((?:[^()]|\\([^()]*\\))*\\))+(?=\\s*\\))/,lookbehind:!0,alias:\"selector\"},keyword:{pattern:/(^|[^\\w-])(?:and|not|only|or)(?![\\w-])/,lookbehind:!0}}},url:{pattern:RegExp(\"\\\\burl\\\\((?:\"+e.source+\"|(?:[^\\\\\\\\\\r\\n()\\\"\u0027]|\\\\\\\\[^])*)\\\\)\",\"i\"),greedy:!0,inside:{function:/^url/i,punctuation:/^\\(|\\)$/,string:{pattern:RegExp(\"^\"+e.source+\"$\"),alias:\"url\"}}},selector:{pattern:RegExp(\"(^|[{}\\\\s])[^{}\\\\s](?:[^{};\\\"\u0027\\\\s]|\\\\s+(?![\\\\s{])|\"+e.source+\")*(?=\\\\s*\\\\{)\"),lookbehind:!0},string:{pattern:e,greedy:!0},property:{pattern:/(^|[^-\\w\\xA0-\\uFFFF])(?!\\s)[-_a-z\\xA0-\\uFFFF](?:(?!\\s)[-\\w\\xA0-\\uFFFF])*(?=\\s*:)/i,lookbehind:!0},important:/!important\\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\\()/i,lookbehind:!0},punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var t=s.languages.markup;t\u0026\u0026(t.tag.addInlined(\"style\",\"css\"),t.tag.addAttribute(\"style\",\"css\"))}(Prism);\nPrism.languages.clike={comment:[{pattern:/(^|[^\\\\])\\/\\*[\\s\\S]*?(?:\\*\\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\\\:])\\/\\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/([\"\u0027])(?:\\\\(?:\\r\\n|[\\s\\S])|(?!\\1)[^\\\\\\r\\n])*\\1/,greedy:!0},\"class-name\":{pattern:/(\\b(?:class|extends|implements|instanceof|interface|new|trait)\\s+|\\bcatch\\s+\\()[\\w.\\\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\\\]/}},keyword:/\\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\\b/,boolean:/\\b(?:false|true)\\b/,function:/\\b\\w+(?=\\()/,number:/\\b0x[\\da-f]+\\b|(?:\\b\\d+(?:\\.\\d*)?|\\B\\.\\d+)(?:e[+-]?\\d+)?/i,operator:/[\u003c\u003e]=?|[!=]=?=?|--?|\\+\\+?|\u0026\u0026?|\\|\\|?|[?*/~^%]/,punctuation:/[{}[\\];(),.:]/};\nPrism.languages.javascript=Prism.languages.extend(\"clike\",{\"class-name\":[Prism.languages.clike[\"class-name\"],{pattern:/(^|[^$\\w\\xA0-\\uFFFF])(?!\\s)[_$A-Z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*(?=\\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\\})\\s*)catch\\b/,lookbehind:!0},{pattern:/(^|[^.]|\\.\\.\\.\\s*)\\b(?:as|assert(?=\\s*\\{)|async(?=\\s*(?:function\\b|\\(|[$\\w\\xA0-\\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\\s*(?:\\{|$))|for|from(?=\\s*(?:[\u0027\"]|$))|function|(?:get|set)(?=\\s*(?:[#\\[$\\w\\xA0-\\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\\b/,lookbehind:!0}],function:/#?(?!\\s)[_$a-zA-Z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*(?=\\s*(?:\\.\\s*(?:apply|bind|call)\\s*)?\\()/,number:{pattern:RegExp(\"(^|[^\\\\w$])(?:NaN|Infinity|0[bB][01]+(?:_[01]+)*n?|0[oO][0-7]+(?:_[0-7]+)*n?|0[xX][\\\\dA-Fa-f]+(?:_[\\\\dA-Fa-f]+)*n?|\\\\d+(?:_\\\\d+)*n|(?:\\\\d+(?:_\\\\d+)*(?:\\\\.(?:\\\\d+(?:_\\\\d+)*)?)?|\\\\.\\\\d+(?:_\\\\d+)*)(?:[Ee][+-]?\\\\d+(?:_\\\\d+)*)?)(?![\\\\w$])\"),lookbehind:!0},operator:/--|\\+\\+|\\*\\*=?|=\u003e|\u0026\u0026=?|\\|\\|=?|[!=]==|\u003c\u003c=?|\u003e\u003e\u003e?=?|[-+*/%\u0026|^!=\u003c\u003e]=?|\\.{3}|\\?\\?=?|\\?\\.?|[~:]/}),Prism.languages.javascript[\"class-name\"][0].pattern=/(\\b(?:class|extends|implements|instanceof|interface|new)\\s+)[\\w.\\\\]+/,Prism.languages.insertBefore(\"javascript\",\"keyword\",{regex:{pattern:RegExp(\"((?:^|[^$\\\\w\\\\xA0-\\\\uFFFF.\\\"\u0027\\\\])\\\\s]|\\\\b(?:return|yield))\\\\s*)/(?:(?:\\\\[(?:[^\\\\]\\\\\\\\\\r\\n]|\\\\\\\\.)*\\\\]|\\\\\\\\.|[^/\\\\\\\\\\\\[\\r\\n])+/[dgimyus]{0,7}|(?:\\\\[(?:[^[\\\\]\\\\\\\\\\r\\n]|\\\\\\\\.|\\\\[(?:[^[\\\\]\\\\\\\\\\r\\n]|\\\\\\\\.|\\\\[(?:[^[\\\\]\\\\\\\\\\r\\n]|\\\\\\\\.)*\\\\])*\\\\])*\\\\]|\\\\\\\\.|[^/\\\\\\\\\\\\[\\r\\n])+/[dgimyus]{0,7}v[dgimyus]{0,7})(?=(?:\\\\s|/\\\\*(?:[^*]|\\\\*(?!/))*\\\\*/)*(?:$|[\\r\\n,.;:})\\\\]]|//))\"),lookbehind:!0,greedy:!0,inside:{\"regex-source\":{pattern:/^(\\/)[\\s\\S]+(?=\\/[a-z]*$)/,lookbehind:!0,alias:\"language-regex\",inside:Prism.languages.regex},\"regex-delimiter\":/^\\/|\\/$/,\"regex-flags\":/^[a-z]+$/}},\"function-variable\":{pattern:/#?(?!\\s)[_$a-zA-Z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*(?=\\s*[=:]\\s*(?:async\\s*)?(?:\\bfunction\\b|(?:\\((?:[^()]|\\([^()]*\\))*\\)|(?!\\s)[_$a-zA-Z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*)\\s*=\u003e))/,alias:\"function\"},parameter:[{pattern:/(function(?:\\s+(?!\\s)[_$a-zA-Z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*)?\\s*\\(\\s*)(?!\\s)(?:[^()\\s]|\\s+(?![\\s)])|\\([^()]*\\))+(?=\\s*\\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(^|[^$\\w\\xA0-\\uFFFF])(?!\\s)[_$a-z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*(?=\\s*=\u003e)/i,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(\\(\\s*)(?!\\s)(?:[^()\\s]|\\s+(?![\\s)])|\\([^()]*\\))+(?=\\s*\\)\\s*=\u003e)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\\b|\\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\\w\\xA0-\\uFFFF]))(?:(?!\\s)[_$a-zA-Z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*\\s*)\\(\\s*|\\]\\s*\\(\\s*)(?!\\s)(?:[^()\\s]|\\s+(?![\\s)])|\\([^()]*\\))+(?=\\s*\\)\\s*\\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\\b[A-Z](?:[A-Z_]|\\dx?)*\\b/}),Prism.languages.insertBefore(\"javascript\",\"string\",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:\"comment\"},\"template-string\":{pattern:/`(?:\\\\[\\s\\S]|\\$\\{(?:[^{}]|\\{(?:[^{}]|\\{[^}]*\\})*\\})+\\}|(?!\\$\\{)[^\\\\`])*`/,greedy:!0,inside:{\"template-punctuation\":{pattern:/^`|`$/,alias:\"string\"},interpolation:{pattern:/((?:^|[^\\\\])(?:\\\\{2})*)\\$\\{(?:[^{}]|\\{(?:[^{}]|\\{[^}]*\\})*\\})+\\}/,lookbehind:!0,inside:{\"interpolation-punctuation\":{pattern:/^\\$\\{|\\}$/,alias:\"punctuation\"},rest:Prism.languages.javascript}},string:/[\\s\\S]+/}},\"string-property\":{pattern:/((?:^|[,{])[ \\t]*)([\"\u0027])(?:\\\\(?:\\r\\n|[\\s\\S])|(?!\\2)[^\\\\\\r\\n])*\\2(?=\\s*:)/m,lookbehind:!0,greedy:!0,alias:\"property\"}}),Prism.languages.insertBefore(\"javascript\",\"operator\",{\"literal-property\":{pattern:/((?:^|[,{])[ \\t]*)(?!\\s)[_$a-zA-Z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*(?=\\s*:)/m,lookbehind:!0,alias:\"property\"}}),Prism.languages.markup\u0026\u0026(Prism.languages.markup.tag.addInlined(\"script\",\"javascript\"),Prism.languages.markup.tag.addAttribute(\"on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)\",\"javascript\")),Prism.languages.js=Prism.languages.javascript;\n!function(e){e.languages.typescript=e.languages.extend(\"javascript\",{\"class-name\":{pattern:/(\\b(?:class|extends|implements|instanceof|interface|new|type)\\s+)(?!keyof\\b)(?!\\s)[_$a-zA-Z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*(?:\\s*\u003c(?:[^\u003c\u003e]|\u003c(?:[^\u003c\u003e]|\u003c[^\u003c\u003e]*\u003e)*\u003e)*\u003e)?/,lookbehind:!0,greedy:!0,inside:null},builtin:/\\b(?:Array|Function|Promise|any|boolean|console|never|number|string|symbol|unknown)\\b/}),e.languages.typescript.keyword.push(/\\b(?:abstract|declare|is|keyof|readonly|require)\\b/,/\\b(?:asserts|infer|interface|module|namespace|type)\\b(?=\\s*(?:[{_$a-zA-Z\\xA0-\\uFFFF]|$))/,/\\btype\\b(?=\\s*(?:[\\{*]|$))/),delete e.languages.typescript.parameter,delete e.languages.typescript[\"literal-property\"];var s=e.languages.extend(\"typescript\",{});delete s[\"class-name\"],e.languages.typescript[\"class-name\"].inside=s,e.languages.insertBefore(\"typescript\",\"function\",{decorator:{pattern:/@[$\\w\\xA0-\\uFFFF]+/,inside:{at:{pattern:/^@/,alias:\"operator\"},function:/^[\\s\\S]+/}},\"generic-function\":{pattern:/#?(?!\\s)[_$a-zA-Z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*\\s*\u003c(?:[^\u003c\u003e]|\u003c(?:[^\u003c\u003e]|\u003c[^\u003c\u003e]*\u003e)*\u003e)*\u003e(?=\\s*\\()/,greedy:!0,inside:{function:/^#?(?!\\s)[_$a-zA-Z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*/,generic:{pattern:/\u003c[\\s\\S]+/,alias:\"class-name\",inside:s}}}}),e.languages.ts=e.languages.typescript}(Prism);\nPrism.languages.json={property:{pattern:/(^|[^\\\\])\"(?:\\\\.|[^\\\\\"\\r\\n])*\"(?=\\s*:)/,lookbehind:!0,greedy:!0},string:{pattern:/(^|[^\\\\])\"(?:\\\\.|[^\\\\\"\\r\\n])*\"(?!\\s*:)/,lookbehind:!0,greedy:!0},comment:{pattern:/\\/\\/.*|\\/\\*[\\s\\S]*?(?:\\*\\/|$)/,greedy:!0},number:/-?\\b\\d+(?:\\.\\d+)?(?:e[+-]?\\d+)?\\b/i,punctuation:/[{}[\\],]/,operator:/:/,boolean:/\\b(?:false|true)\\b/,null:{pattern:/\\bnull\\b/,alias:\"keyword\"}},Prism.languages.webmanifest=Prism.languages.json;\n!function(e){var n=/[*\u0026][^\\s[\\]{},]+/,r=/!(?:\u003c[\\w\\-%#;/?:@\u0026=+$,.!~*\u0027()[\\]]+\u003e|(?:[a-zA-Z\\d-]*!)?[\\w\\-%#;/?:@\u0026=+$.~*\u0027()]+)?/,t=\"(?:\"+r.source+\"(?:[ \\t]+\"+n.source+\")?|\"+n.source+\"(?:[ \\t]+\"+r.source+\")?)\",a=\"(?:[^\\\\s\\\\x00-\\\\x08\\\\x0e-\\\\x1f!\\\"#%\u0026\u0027*,\\\\-:\u003e?@[\\\\]`{|}\\\\x7f-\\\\x84\\\\x86-\\\\x9f\\\\ud800-\\\\udfff\\\\ufffe\\\\uffff]|[?:-]\u003cPLAIN\u003e)(?:[ \\t]*(?:(?![#:])\u003cPLAIN\u003e|:\u003cPLAIN\u003e))*\".replace(/\u003cPLAIN\u003e/g,(function(){return\"[^\\\\s\\\\x00-\\\\x08\\\\x0e-\\\\x1f,[\\\\]{}\\\\x7f-\\\\x84\\\\x86-\\\\x9f\\\\ud800-\\\\udfff\\\\ufffe\\\\uffff]\"})),d=\"\\\"(?:[^\\\"\\\\\\\\\\r\\n]|\\\\\\\\.)*\\\"|\u0027(?:[^\u0027\\\\\\\\\\r\\n]|\\\\\\\\.)*\u0027\";function o(e,n){n=(n||\"\").replace(/m/g,\"\")+\"m\";var r=\"([:\\\\-,[{]\\\\s*(?:\\\\s\u003c\u003cprop\u003e\u003e[ \\t]+)?)(?:\u003c\u003cvalue\u003e\u003e)(?=[ \\t]*(?:$|,|\\\\]|\\\\}|(?:[\\r\\n]\\\\s*)?#))\".replace(/\u003c\u003cprop\u003e\u003e/g,(function(){return t})).replace(/\u003c\u003cvalue\u003e\u003e/g,(function(){return e}));return RegExp(r,n)}e.languages.yaml={scalar:{pattern:RegExp(\"([\\\\-:]\\\\s*(?:\\\\s\u003c\u003cprop\u003e\u003e[ \\t]+)?[|\u003e])[ \\t]*(?:((?:\\r?\\n|\\r)[ \\t]+)\\\\S[^\\r\\n]*(?:\\\\2[^\\r\\n]+)*)\".replace(/\u003c\u003cprop\u003e\u003e/g,(function(){return t}))),lookbehind:!0,alias:\"string\"},comment:/#.*/,key:{pattern:RegExp(\"((?:^|[:\\\\-,[{\\r\\n?])[ \\t]*(?:\u003c\u003cprop\u003e\u003e[ \\t]+)?)\u003c\u003ckey\u003e\u003e(?=\\\\s*:\\\\s)\".replace(/\u003c\u003cprop\u003e\u003e/g,(function(){return t})).replace(/\u003c\u003ckey\u003e\u003e/g,(function(){return\"(?:\"+a+\"|\"+d+\")\"}))),lookbehind:!0,greedy:!0,alias:\"atrule\"},directive:{pattern:/(^[ \\t]*)%.+/m,lookbehind:!0,alias:\"important\"},datetime:{pattern:o(\"\\\\d{4}-\\\\d\\\\d?-\\\\d\\\\d?(?:[tT]|[ \\t]+)\\\\d\\\\d?:\\\\d{2}:\\\\d{2}(?:\\\\.\\\\d*)?(?:[ \\t]*(?:Z|[-+]\\\\d\\\\d?(?::\\\\d{2})?))?|\\\\d{4}-\\\\d{2}-\\\\d{2}|\\\\d\\\\d?:\\\\d{2}(?::\\\\d{2}(?:\\\\.\\\\d*)?)?\"),lookbehind:!0,alias:\"number\"},boolean:{pattern:o(\"false|true\",\"i\"),lookbehind:!0,alias:\"important\"},null:{pattern:o(\"null|~\",\"i\"),lookbehind:!0,alias:\"important\"},string:{pattern:o(d),lookbehind:!0,greedy:!0},number:{pattern:o(\"[+-]?(?:0x[\\\\da-f]+|0o[0-7]+|(?:\\\\d+(?:\\\\.\\\\d*)?|\\\\.\\\\d+)(?:e[+-]?\\\\d+)?|\\\\.inf|\\\\.nan)\",\"i\"),lookbehind:!0},tag:r,important:n,punctuation:/---|[:[\\]{}\\-,|\u003e?]|\\.\\.\\./},e.languages.yml=e.languages.yaml}(Prism);\n!function(n){function e(n){return n=n.replace(/\u003cinner\u003e/g,(function(){return\"(?:\\\\\\\\.|[^\\\\\\\\\\n\\r]|(?:\\n|\\r\\n?)(?![\\r\\n]))\"})),RegExp(\"((?:^|[^\\\\\\\\])(?:\\\\\\\\{2})*)(?:\"+n+\")\")}var t=\"(?:\\\\\\\\.|``(?:[^`\\r\\n]|`(?!`))+``|`[^`\\r\\n]+`|[^\\\\\\\\|\\r\\n`])+\",a=\"\\\\|?__(?:\\\\|__)+\\\\|?(?:(?:\\n|\\r\\n?)|(?![^]))\".replace(/__/g,(function(){return t})),i=\"\\\\|?[ \\t]*:?-{3,}:?[ \\t]*(?:\\\\|[ \\t]*:?-{3,}:?[ \\t]*)+\\\\|?(?:\\n|\\r\\n?)\";n.languages.markdown=n.languages.extend(\"markup\",{}),n.languages.insertBefore(\"markdown\",\"prolog\",{\"front-matter-block\":{pattern:/(^(?:\\s*[\\r\\n])?)---(?!.)[\\s\\S]*?[\\r\\n]---(?!.)/,lookbehind:!0,greedy:!0,inside:{punctuation:/^---|---$/,\"front-matter\":{pattern:/\\S+(?:\\s+\\S+)*/,alias:[\"yaml\",\"language-yaml\"],inside:n.languages.yaml}}},blockquote:{pattern:/^\u003e(?:[\\t ]*\u003e)*/m,alias:\"punctuation\"},table:{pattern:RegExp(\"^\"+a+i+\"(?:\"+a+\")*\",\"m\"),inside:{\"table-data-rows\":{pattern:RegExp(\"^(\"+a+i+\")(?:\"+a+\")*$\"),lookbehind:!0,inside:{\"table-data\":{pattern:RegExp(t),inside:n.languages.markdown},punctuation:/\\|/}},\"table-line\":{pattern:RegExp(\"^(\"+a+\")\"+i+\"$\"),lookbehind:!0,inside:{punctuation:/\\||:?-{3,}:?/}},\"table-header-row\":{pattern:RegExp(\"^\"+a+\"$\"),inside:{\"table-header\":{pattern:RegExp(t),alias:\"important\",inside:n.languages.markdown},punctuation:/\\|/}}}},code:[{pattern:/((?:^|\\n)[ \\t]*\\n|(?:^|\\r\\n?)[ \\t]*\\r\\n?)(?: {4}|\\t).+(?:(?:\\n|\\r\\n?)(?: {4}|\\t).+)*/,lookbehind:!0,alias:\"keyword\"},{pattern:/^```[\\s\\S]*?^```$/m,greedy:!0,inside:{\"code-block\":{pattern:/^(```.*(?:\\n|\\r\\n?))[\\s\\S]+?(?=(?:\\n|\\r\\n?)^```$)/m,lookbehind:!0},\"code-language\":{pattern:/^(```).+/,lookbehind:!0},punctuation:/```/}}],title:[{pattern:/\\S.*(?:\\n|\\r\\n?)(?:==+|--+)(?=[ \\t]*$)/m,alias:\"important\",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\\s*)#.+/m,lookbehind:!0,alias:\"important\",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\\s*)([*-])(?:[\\t ]*\\2){2,}(?=\\s*$)/m,lookbehind:!0,alias:\"punctuation\"},list:{pattern:/(^\\s*)(?:[*+-]|\\d+\\.)(?=[\\t ].)/m,lookbehind:!0,alias:\"punctuation\"},\"url-reference\":{pattern:/!?\\[[^\\]]+\\]:[\\t ]+(?:\\S+|\u003c(?:\\\\.|[^\u003e\\\\])+\u003e)(?:[\\t ]+(?:\"(?:\\\\.|[^\"\\\\])*\"|\u0027(?:\\\\.|[^\u0027\\\\])*\u0027|\\((?:\\\\.|[^)\\\\])*\\)))?/,inside:{variable:{pattern:/^(!?\\[)[^\\]]+/,lookbehind:!0},string:/(?:\"(?:\\\\.|[^\"\\\\])*\"|\u0027(?:\\\\.|[^\u0027\\\\])*\u0027|\\((?:\\\\.|[^)\\\\])*\\))$/,punctuation:/^[\\[\\]!:]|[\u003c\u003e]/},alias:\"url\"},bold:{pattern:e(\"\\\\b__(?:(?!_)\u003cinner\u003e|_(?:(?!_)\u003cinner\u003e)+_)+__\\\\b|\\\\*\\\\*(?:(?!\\\\*)\u003cinner\u003e|\\\\*(?:(?!\\\\*)\u003cinner\u003e)+\\\\*)+\\\\*\\\\*\"),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^..)[\\s\\S]+(?=..$)/,lookbehind:!0,inside:{}},punctuation:/\\*\\*|__/}},italic:{pattern:e(\"\\\\b_(?:(?!_)\u003cinner\u003e|__(?:(?!_)\u003cinner\u003e)+__)+_\\\\b|\\\\*(?:(?!\\\\*)\u003cinner\u003e|\\\\*\\\\*(?:(?!\\\\*)\u003cinner\u003e)+\\\\*\\\\*)+\\\\*\"),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^.)[\\s\\S]+(?=.$)/,lookbehind:!0,inside:{}},punctuation:/[*_]/}},strike:{pattern:e(\"(~~?)(?:(?!~)\u003cinner\u003e)+\\\\2\"),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^~~?)[\\s\\S]+(?=\\1$)/,lookbehind:!0,inside:{}},punctuation:/~~?/}},\"code-snippet\":{pattern:/(^|[^\\\\`])(?:``[^`\\r\\n]+(?:`[^`\\r\\n]+)*``(?!`)|`[^`\\r\\n]+`(?!`))/,lookbehind:!0,greedy:!0,alias:[\"code\",\"keyword\"]},url:{pattern:e(\u0027!?\\\\[(?:(?!\\\\])\u003cinner\u003e)+\\\\](?:\\\\([^\\\\s)]+(?:[\\t ]+\"(?:\\\\\\\\.|[^\"\\\\\\\\])*\")?\\\\)|[ \\t]?\\\\[(?:(?!\\\\])\u003cinner\u003e)+\\\\])\u0027),lookbehind:!0,greedy:!0,inside:{operator:/^!/,content:{pattern:/(^\\[)[^\\]]+(?=\\])/,lookbehind:!0,inside:{}},variable:{pattern:/(^\\][ \\t]?\\[)[^\\]]+(?=\\]$)/,lookbehind:!0},url:{pattern:/(^\\]\\()[^\\s)]+/,lookbehind:!0},string:{pattern:/(^[ \\t]+)\"(?:\\\\.|[^\"\\\\])*\"(?=\\)$)/,lookbehind:!0}}}}),[\"url\",\"bold\",\"italic\",\"strike\"].forEach((function(e){[\"url\",\"bold\",\"italic\",\"strike\",\"code-snippet\"].forEach((function(t){e!==t\u0026\u0026(n.languages.markdown[e].inside.content.inside[t]=n.languages.markdown[t])}))})),n.hooks.add(\"after-tokenize\",(function(n){\"markdown\"!==n.language\u0026\u0026\"md\"!==n.language||function n(e){if(e\u0026\u0026\"string\"!=typeof e)for(var t=0,a=e.length;t\u003ca;t++){var i=e[t];if(\"code\"===i.type){var r=i.content[1],o=i.content[3];if(r\u0026\u0026o\u0026\u0026\"code-language\"===r.type\u0026\u0026\"code-block\"===o.type\u0026\u0026\"string\"==typeof r.content){var l=r.content.replace(/\\b#/g,\"sharp\").replace(/\\b\\+\\+/g,\"pp\"),s=\"language-\"+(l=(/[a-z][\\w-]*/i.exec(l)||[\"\"])[0].toLowerCase());o.alias?\"string\"==typeof o.alias?o.alias=[o.alias,s]:o.alias.push(s):o.alias=[s]}}else n(i.content)}}(n.tokens)})),n.hooks.add(\"wrap\",(function(e){if(\"code-block\"===e.type){for(var t=\"\",a=0,i=e.classes.length;a\u003ci;a++){var s=e.classes[a],d=/language-(.+)/.exec(s);if(d){t=d[1];break}}var p=n.languages[t];if(p)e.content=n.highlight(e.content.replace(r,\"\").replace(/\u0026(\\w{1,8}|#x?[\\da-f]{1,8});/gi,(function(n,e){var t;return\"#\"===(e=e.toLowerCase())[0]?(t=\"x\"===e[1]?parseInt(e.slice(2),16):Number(e.slice(1)),l(t)):o[e]||n})),p,t);else if(t\u0026\u0026\"none\"!==t\u0026\u0026n.plugins.autoloader){var u=\"md-\"+(new Date).valueOf()+\"-\"+Math.floor(1e16*Math.random());e.attributes.id=u,n.plugins.autoloader.loadLanguages(t,(function(){var e=document.getElementById(u);e\u0026\u0026(e.innerHTML=n.highlight(e.textContent,n.languages[t],t))}))}}}));var r=RegExp(n.languages.markup.tag.pattern.source,\"gi\"),o={amp:\"\u0026\",lt:\"\u003c\",gt:\"\u003e\",quot:\u0027\"\u0027},l=String.fromCodePoint||String.fromCharCode;n.languages.md=n.languages.markdown}(Prism);\n!function(e){var t=\"\\\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\\\b\",a={pattern:/(^([\"\u0027]?)\\w+\\2)[ \\t]+\\S.*/,lookbehind:!0,alias:\"punctuation\",inside:null},n={bash:a,environment:{pattern:RegExp(\"\\\\$\"+t),alias:\"constant\"},variable:[{pattern:/\\$?\\(\\([\\s\\S]+?\\)\\)/,greedy:!0,inside:{variable:[{pattern:/(^\\$\\(\\([\\s\\S]+)\\)\\)/,lookbehind:!0},/^\\$\\(\\(/],number:/\\b0x[\\dA-Fa-f]+\\b|(?:\\b\\d+(?:\\.\\d*)?|\\B\\.\\d+)(?:[Ee]-?\\d+)?/,operator:/--|\\+\\+|\\*\\*=?|\u003c\u003c=?|\u003e\u003e=?|\u0026\u0026|\\|\\||[=!+\\-*/%\u003c\u003e^\u0026|]=?|[?~:]/,punctuation:/\\(\\(?|\\)\\)?|,|;/}},{pattern:/\\$\\((?:\\([^)]+\\)|[^()])+\\)|`[^`]+`/,greedy:!0,inside:{variable:/^\\$\\(|^`|\\)$|`$/}},{pattern:/\\$\\{[^}]+\\}/,greedy:!0,inside:{operator:/:[-=?+]?|[!\\/]|##?|%%?|\\^\\^?|,,?/,punctuation:/[\\[\\]]/,environment:{pattern:RegExp(\"(\\\\{)\"+t),lookbehind:!0,alias:\"constant\"}}},/\\$(?:\\w+|[#?*!@$])/],entity:/\\\\(?:[abceEfnrtv\\\\\"]|O?[0-7]{1,3}|U[0-9a-fA-F]{8}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{1,2})/};e.languages.bash={shebang:{pattern:/^#!\\s*\\/.*/,alias:\"important\"},comment:{pattern:/(^|[^\"{\\\\$])#.*/,lookbehind:!0},\"function-name\":[{pattern:/(\\bfunction\\s+)[\\w-]+(?=(?:\\s*\\(?:\\s*\\))?\\s*\\{)/,lookbehind:!0,alias:\"function\"},{pattern:/\\b[\\w-]+(?=\\s*\\(\\s*\\)\\s*\\{)/,alias:\"function\"}],\"for-or-select\":{pattern:/(\\b(?:for|select)\\s+)\\w+(?=\\s+in\\s)/,alias:\"variable\",lookbehind:!0},\"assign-left\":{pattern:/(^|[\\s;|\u0026]|[\u003c\u003e]\\()\\w+(?:\\.\\w+)*(?=\\+?=)/,inside:{environment:{pattern:RegExp(\"(^|[\\\\s;|\u0026]|[\u003c\u003e]\\\\()\"+t),lookbehind:!0,alias:\"constant\"}},alias:\"variable\",lookbehind:!0},parameter:{pattern:/(^|\\s)-{1,2}(?:\\w+:[+-]?)?\\w+(?:\\.\\w+)*(?=[=\\s]|$)/,alias:\"variable\",lookbehind:!0},string:[{pattern:/((?:^|[^\u003c])\u003c\u003c-?\\s*)(\\w+)\\s[\\s\\S]*?(?:\\r?\\n|\\r)\\2/,lookbehind:!0,greedy:!0,inside:n},{pattern:/((?:^|[^\u003c])\u003c\u003c-?\\s*)([\"\u0027])(\\w+)\\2\\s[\\s\\S]*?(?:\\r?\\n|\\r)\\3/,lookbehind:!0,greedy:!0,inside:{bash:a}},{pattern:/(^|[^\\\\](?:\\\\\\\\)*)\"(?:\\\\[\\s\\S]|\\$\\([^)]+\\)|\\$(?!\\()|`[^`]+`|[^\"\\\\`$])*\"/,lookbehind:!0,greedy:!0,inside:n},{pattern:/(^|[^$\\\\])\u0027[^\u0027]*\u0027/,lookbehind:!0,greedy:!0},{pattern:/\\$\u0027(?:[^\u0027\\\\]|\\\\[\\s\\S])*\u0027/,greedy:!0,inside:{entity:n.entity}}],environment:{pattern:RegExp(\"\\\\$?\"+t),alias:\"constant\"},variable:n.variable,function:{pattern:/(^|[\\s;|\u0026]|[\u003c\u003e]\\()(?:add|apropos|apt|apt-cache|apt-get|aptitude|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cargo|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|composer|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|docker|docker-compose|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|java|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|node|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|podman|podman-compose|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|sysctl|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vcpkg|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\\s;|\u0026])/,lookbehind:!0},keyword:{pattern:/(^|[\\s;|\u0026]|[\u003c\u003e]\\()(?:case|do|done|elif|else|esac|fi|for|function|if|in|select|then|until|while)(?=$|[)\\s;|\u0026])/,lookbehind:!0},builtin:{pattern:/(^|[\\s;|\u0026]|[\u003c\u003e]\\()(?:\\.|:|alias|bind|break|builtin|caller|cd|command|continue|declare|echo|enable|eval|exec|exit|export|getopts|hash|help|let|local|logout|mapfile|printf|pwd|read|readarray|readonly|return|set|shift|shopt|source|test|times|trap|type|typeset|ulimit|umask|unalias|unset)(?=$|[)\\s;|\u0026])/,lookbehind:!0,alias:\"class-name\"},boolean:{pattern:/(^|[\\s;|\u0026]|[\u003c\u003e]\\()(?:false|true)(?=$|[)\\s;|\u0026])/,lookbehind:!0},\"file-descriptor\":{pattern:/\\B\u0026\\d\\b/,alias:\"important\"},operator:{pattern:/\\d?\u003c\u003e|\u003e\\||\\+=|=[=~]?|!=?|\u003c\u003c[\u003c-]?|[\u0026\\d]?\u003e\u003e|\\d[\u003c\u003e]\u0026?|[\u003c\u003e][\u0026=]?|\u0026[\u003e\u0026]?|\\|[\u0026|]?/,inside:{\"file-descriptor\":{pattern:/^\\d/,alias:\"important\"}}},punctuation:/\\$?\\(\\(?|\\)\\)?|\\.\\.|[{}[\\];\\\\]/,number:{pattern:/(^|\\s)(?:[1-9]\\d*|0)(?:[.,]\\d+)?\\b/,lookbehind:!0}},a.inside=e.languages.bash;for(var s=[\"comment\",\"function-name\",\"for-or-select\",\"assign-left\",\"parameter\",\"string\",\"environment\",\"function\",\"keyword\",\"builtin\",\"boolean\",\"file-descriptor\",\"operator\",\"punctuation\",\"number\"],o=n.variable[1].inside,i=0;i\u003cs.length;i++)o[s[i]]=e.languages.bash[s[i]];e.languages.sh=e.languages.bash,e.languages.shell=e.languages.bash}(Prism);\n!function(e){var r=/%%?[~:\\w]+%?|!\\S+!/,t={pattern:/\\/[a-z?]+(?=[ :]|$):?|-[a-z]\\b|--[a-z-]+\\b/im,alias:\"attr-name\",inside:{punctuation:/:/}},n=/\"(?:[\\\\\"]\"|[^\"])*\"(?!\")/,i=/(?:\\b|-)\\d+\\b/;e.languages.batch={comment:[/^::.*/m,{pattern:/((?:^|[\u0026(])[ \\t]*)rem\\b(?:[^^\u0026)\\r\\n]|\\^(?:\\r\\n|[\\s\\S]))*/im,lookbehind:!0}],label:{pattern:/^:.*/m,alias:\"property\"},command:[{pattern:/((?:^|[\u0026(])[ \\t]*)for(?: \\/[a-z?](?:[ :](?:\"[^\"]*\"|[^\\s\"/]\\S*))?)* \\S+ in \\([^)]+\\) do/im,lookbehind:!0,inside:{keyword:/\\b(?:do|in)\\b|^for\\b/i,string:n,parameter:t,variable:r,number:i,punctuation:/[()\u0027,]/}},{pattern:/((?:^|[\u0026(])[ \\t]*)if(?: \\/[a-z?](?:[ :](?:\"[^\"]*\"|[^\\s\"/]\\S*))?)* (?:not )?(?:cmdextversion \\d+|defined \\w+|errorlevel \\d+|exist \\S+|(?:\"[^\"]*\"|(?!\")(?:(?!==)\\S)+)?(?:==| (?:equ|geq|gtr|leq|lss|neq) )(?:\"[^\"]*\"|[^\\s\"]\\S*))/im,lookbehind:!0,inside:{keyword:/\\b(?:cmdextversion|defined|errorlevel|exist|not)\\b|^if\\b/i,string:n,parameter:t,variable:r,number:i,operator:/\\^|==|\\b(?:equ|geq|gtr|leq|lss|neq)\\b/i}},{pattern:/((?:^|[\u0026()])[ \\t]*)else\\b/im,lookbehind:!0,inside:{keyword:/^else\\b/i}},{pattern:/((?:^|[\u0026(])[ \\t]*)set(?: \\/[a-z](?:[ :](?:\"[^\"]*\"|[^\\s\"/]\\S*))?)* (?:[^^\u0026)\\r\\n]|\\^(?:\\r\\n|[\\s\\S]))*/im,lookbehind:!0,inside:{keyword:/^set\\b/i,string:n,parameter:t,variable:[r,/\\w+(?=(?:[*\\/%+\\-\u0026^|]|\u003c\u003c|\u003e\u003e)?=)/],number:i,operator:/[*\\/%+\\-\u0026^|]=?|\u003c\u003c=?|\u003e\u003e=?|[!~_=]/,punctuation:/[()\u0027,]/}},{pattern:/((?:^|[\u0026(])[ \\t]*@?)\\w+\\b(?:\"(?:[\\\\\"]\"|[^\"])*\"(?!\")|[^\"^\u0026)\\r\\n]|\\^(?:\\r\\n|[\\s\\S]))*/m,lookbehind:!0,inside:{keyword:/^\\w+\\b/,string:n,parameter:t,label:{pattern:/(^\\s*):\\S+/m,lookbehind:!0,alias:\"property\"},variable:r,number:i,operator:/\\^/}}],operator:/[\u0026@]/,punctuation:/[()\u0027]/}}(Prism);\n!function(e){var i=e.languages.powershell={comment:[{pattern:/(^|[^`])\u003c#[\\s\\S]*?#\u003e/,lookbehind:!0},{pattern:/(^|[^`])#.*/,lookbehind:!0}],string:[{pattern:/\"(?:`[\\s\\S]|[^`\"])*\"/,greedy:!0,inside:null},{pattern:/\u0027(?:[^\u0027]|\u0027\u0027)*\u0027/,greedy:!0}],namespace:/\\[[a-z](?:\\[(?:\\[[^\\]]*\\]|[^\\[\\]])*\\]|[^\\[\\]])*\\]/i,boolean:/\\$(?:false|true)\\b/i,variable:/\\$\\w+\\b/,function:[/\\b(?:Add|Approve|Assert|Backup|Block|Checkpoint|Clear|Close|Compare|Complete|Compress|Confirm|Connect|Convert|ConvertFrom|ConvertTo|Copy|Debug|Deny|Disable|Disconnect|Dismount|Edit|Enable|Enter|Exit|Expand|Export|Find|ForEach|Format|Get|Grant|Group|Hide|Import|Initialize|Install|Invoke|Join|Limit|Lock|Measure|Merge|Move|New|Open|Optimize|Out|Ping|Pop|Protect|Publish|Push|Read|Receive|Redo|Register|Remove|Rename|Repair|Request|Reset|Resize|Resolve|Restart|Restore|Resume|Revoke|Save|Search|Select|Send|Set|Show|Skip|Sort|Split|Start|Step|Stop|Submit|Suspend|Switch|Sync|Tee|Test|Trace|Unblock|Undo|Uninstall|Unlock|Unprotect|Unpublish|Unregister|Update|Use|Wait|Watch|Where|Write)-[a-z]+\\b/i,/\\b(?:ac|cat|chdir|clc|cli|clp|clv|compare|copy|cp|cpi|cpp|cvpa|dbp|del|diff|dir|ebp|echo|epal|epcsv|epsn|erase|fc|fl|ft|fw|gal|gbp|gc|gci|gcs|gdr|gi|gl|gm|gp|gps|group|gsv|gu|gv|gwmi|iex|ii|ipal|ipcsv|ipsn|irm|iwmi|iwr|kill|lp|ls|measure|mi|mount|move|mp|mv|nal|ndr|ni|nv|ogv|popd|ps|pushd|pwd|rbp|rd|rdr|ren|ri|rm|rmdir|rni|rnp|rp|rv|rvpa|rwmi|sal|saps|sasv|sbp|sc|select|set|shcm|si|sl|sleep|sls|sort|sp|spps|spsv|start|sv|swmi|tee|trcm|type|write)\\b/i],keyword:/\\b(?:Begin|Break|Catch|Class|Continue|Data|Define|Do|DynamicParam|Else|ElseIf|End|Exit|Filter|Finally|For|ForEach|From|Function|If|InlineScript|Parallel|Param|Process|Return|Sequence|Switch|Throw|Trap|Try|Until|Using|Var|While|Workflow)\\b/i,operator:{pattern:/(^|\\W)(?:!|-(?:b?(?:and|x?or)|as|(?:Not)?(?:Contains|In|Like|Match)|eq|ge|gt|is(?:Not)?|Join|le|lt|ne|not|Replace|sh[lr])\\b|-[-=]?|\\+[+=]?|[*\\/%]=?)/i,lookbehind:!0},punctuation:/[|{}[\\];(),.]/};i.string[0].inside={function:{pattern:/(^|[^`])\\$\\((?:\\$\\([^\\r\\n()]*\\)|(?!\\$\\()[^\\r\\n)])*\\)/,lookbehind:!0,inside:i},boolean:i.boolean,variable:i.variable}}(Prism);\nPrism.languages.c=Prism.languages.extend(\"clike\",{comment:{pattern:/\\/\\/(?:[^\\r\\n\\\\]|\\\\(?:\\r\\n?|\\n|(?![\\r\\n])))*|\\/\\*[\\s\\S]*?(?:\\*\\/|$)/,greedy:!0},string:{pattern:/\"(?:\\\\(?:\\r\\n|[\\s\\S])|[^\"\\\\\\r\\n])*\"/,greedy:!0},\"class-name\":{pattern:/(\\b(?:enum|struct)\\s+(?:__attribute__\\s*\\(\\([\\s\\S]*?\\)\\)\\s*)?)\\w+|\\b[a-z]\\w*_t\\b/,lookbehind:!0},keyword:/\\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|__attribute__|asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|inline|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|typeof|union|unsigned|void|volatile|while)\\b/,function:/\\b[a-z_]\\w*(?=\\s*\\()/i,number:/(?:\\b0x(?:[\\da-f]+(?:\\.[\\da-f]*)?|\\.[\\da-f]+)(?:p[+-]?\\d+)?|(?:\\b\\d+(?:\\.\\d*)?|\\B\\.\\d+)(?:e[+-]?\\d+)?)[ful]{0,4}/i,operator:/\u003e\u003e=?|\u003c\u003c=?|-\u003e|([-+\u0026|:])\\1|[?:~]|[-+*/%\u0026|^!=\u003c\u003e]=?/}),Prism.languages.insertBefore(\"c\",\"string\",{char:{pattern:/\u0027(?:\\\\(?:\\r\\n|[\\s\\S])|[^\u0027\\\\\\r\\n]){0,32}\u0027/,greedy:!0}}),Prism.languages.insertBefore(\"c\",\"string\",{macro:{pattern:/(^[\\t ]*)#\\s*[a-z](?:[^\\r\\n\\\\/]|\\/(?!\\*)|\\/\\*(?:[^*]|\\*(?!\\/))*\\*\\/|\\\\(?:\\r\\n|[\\s\\S]))*/im,lookbehind:!0,greedy:!0,alias:\"property\",inside:{string:[{pattern:/^(#\\s*include\\s*)\u003c[^\u003e]+\u003e/,lookbehind:!0},Prism.languages.c.string],char:Prism.languages.c.char,comment:Prism.languages.c.comment,\"macro-name\":[{pattern:/(^#\\s*define\\s+)\\w+\\b(?!\\()/i,lookbehind:!0},{pattern:/(^#\\s*define\\s+)\\w+\\b(?=\\()/i,lookbehind:!0,alias:\"function\"}],directive:{pattern:/^(#\\s*)[a-z]+/,lookbehind:!0,alias:\"keyword\"},\"directive-hash\":/^#/,punctuation:/##|\\\\(?=[\\r\\n])/,expression:{pattern:/\\S[\\s\\S]*/,inside:Prism.languages.c}}}}),Prism.languages.insertBefore(\"c\",\"function\",{constant:/\\b(?:EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|__DATE__|__FILE__|__LINE__|__TIMESTAMP__|__TIME__|__func__|stderr|stdin|stdout)\\b/}),delete Prism.languages.c.boolean;\n!function(e){var t=/\\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|char8_t|class|co_await|co_return|co_yield|compl|concept|const|const_cast|consteval|constexpr|constinit|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|final|float|for|friend|goto|if|import|inline|int|int16_t|int32_t|int64_t|int8_t|long|module|mutable|namespace|new|noexcept|nullptr|operator|override|private|protected|public|register|reinterpret_cast|requires|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|uint16_t|uint32_t|uint64_t|uint8_t|union|unsigned|using|virtual|void|volatile|wchar_t|while)\\b/,n=\"\\\\b(?!\u003ckeyword\u003e)\\\\w+(?:\\\\s*\\\\.\\\\s*\\\\w+)*\\\\b\".replace(/\u003ckeyword\u003e/g,(function(){return t.source}));e.languages.cpp=e.languages.extend(\"c\",{\"class-name\":[{pattern:RegExp(\"(\\\\b(?:class|concept|enum|struct|typename)\\\\s+)(?!\u003ckeyword\u003e)\\\\w+\".replace(/\u003ckeyword\u003e/g,(function(){return t.source}))),lookbehind:!0},/\\b[A-Z]\\w*(?=\\s*::\\s*\\w+\\s*\\()/,/\\b[A-Z_]\\w*(?=\\s*::\\s*~\\w+\\s*\\()/i,/\\b\\w+(?=\\s*\u003c(?:[^\u003c\u003e]|\u003c(?:[^\u003c\u003e]|\u003c[^\u003c\u003e]*\u003e)*\u003e)*\u003e\\s*::\\s*\\w+\\s*\\()/],keyword:t,number:{pattern:/(?:\\b0b[01\u0027]+|\\b0x(?:[\\da-f\u0027]+(?:\\.[\\da-f\u0027]*)?|\\.[\\da-f\u0027]+)(?:p[+-]?[\\d\u0027]+)?|(?:\\b[\\d\u0027]+(?:\\.[\\d\u0027]*)?|\\B\\.[\\d\u0027]+)(?:e[+-]?[\\d\u0027]+)?)[ful]{0,4}/i,greedy:!0},operator:/\u003e\u003e=?|\u003c\u003c=?|-\u003e|--|\\+\\+|\u0026\u0026|\\|\\||[?:~]|\u003c=\u003e|[-+*/%\u0026|^!=\u003c\u003e]=?|\\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\\b/,boolean:/\\b(?:false|true)\\b/}),e.languages.insertBefore(\"cpp\",\"string\",{module:{pattern:RegExp(\u0027(\\\\b(?:import|module)\\\\s+)(?:\"(?:\\\\\\\\(?:\\r\\n|[^])|[^\"\\\\\\\\\\r\\n])*\"|\u003c[^\u003c\u003e\\r\\n]*\u003e|\u0027+\"\u003cmod-name\u003e(?:\\\\s*:\\\\s*\u003cmod-name\u003e)?|:\\\\s*\u003cmod-name\u003e\".replace(/\u003cmod-name\u003e/g,(function(){return n}))+\")\"),lookbehind:!0,greedy:!0,inside:{string:/^[\u003c\"][\\s\\S]+/,operator:/:/,punctuation:/\\./}},\"raw-string\":{pattern:/R\"([^()\\\\ ]{0,16})\\([\\s\\S]*?\\)\\1\"/,alias:\"string\",greedy:!0}}),e.languages.insertBefore(\"cpp\",\"keyword\",{\"generic-function\":{pattern:/\\b(?!operator\\b)[a-z_]\\w*\\s*\u003c(?:[^\u003c\u003e]|\u003c[^\u003c\u003e]*\u003e)*\u003e(?=\\s*\\()/i,inside:{function:/^\\w+/,generic:{pattern:/\u003c[\\s\\S]+/,alias:\"class-name\",inside:e.languages.cpp}}}}),e.languages.insertBefore(\"cpp\",\"operator\",{\"double-colon\":{pattern:/::/,alias:\"punctuation\"}}),e.languages.insertBefore(\"cpp\",\"class-name\",{\"base-clause\":{pattern:/(\\b(?:class|struct)\\s+\\w+\\s*:\\s*)[^;{}\"\u0027\\s]+(?:\\s+[^;{}\"\u0027\\s]+)*(?=\\s*[;{])/,lookbehind:!0,greedy:!0,inside:e.languages.extend(\"cpp\",{})}}),e.languages.insertBefore(\"inside\",\"double-colon\",{\"class-name\":/\\b[a-z_]\\w*\\b(?!\\s*::)/i},e.languages.cpp[\"base-clause\"])}(Prism);\n!function(e){function n(e,n){return e.replace(/\u003c\u003c(\\d+)\u003e\u003e/g,(function(e,s){return\"(?:\"+n[+s]+\")\"}))}function s(e,s,a){return RegExp(n(e,s),a||\"\")}function a(e,n){for(var s=0;s\u003cn;s++)e=e.replace(/\u003c\u003cself\u003e\u003e/g,(function(){return\"(?:\"+e+\")\"}));return e.replace(/\u003c\u003cself\u003e\u003e/g,\"[^\\\\s\\\\S]\")}var t=\"bool byte char decimal double dynamic float int long object sbyte short string uint ulong ushort var void\",r=\"class enum interface record struct\",i=\"add alias and ascending async await by descending from(?=\\\\s*(?:\\\\w|$)) get global group into init(?=\\\\s*;) join let nameof not notnull on or orderby partial remove select set unmanaged value when where with(?=\\\\s*{)\",o=\"abstract as base break case catch checked const continue default delegate do else event explicit extern finally fixed for foreach goto if implicit in internal is lock namespace new null operator out override params private protected public readonly ref return sealed sizeof stackalloc static switch this throw try typeof unchecked unsafe using virtual volatile while yield\";function l(e){return\"\\\\b(?:\"+e.trim().replace(/ /g,\"|\")+\")\\\\b\"}var d=l(r),p=RegExp(l(t+\" \"+r+\" \"+i+\" \"+o)),c=l(r+\" \"+i+\" \"+o),u=l(t+\" \"+r+\" \"+o),g=a(\"\u003c(?:[^\u003c\u003e;=+\\\\-*/%\u0026|^]|\u003c\u003cself\u003e\u003e)*\u003e\",2),b=a(\"\\\\((?:[^()]|\u003c\u003cself\u003e\u003e)*\\\\)\",2),h=\"@?\\\\b[A-Za-z_]\\\\w*\\\\b\",f=n(\"\u003c\u003c0\u003e\u003e(?:\\\\s*\u003c\u003c1\u003e\u003e)?\",[h,g]),m=n(\"(?!\u003c\u003c0\u003e\u003e)\u003c\u003c1\u003e\u003e(?:\\\\s*\\\\.\\\\s*\u003c\u003c1\u003e\u003e)*\",[c,f]),k=\"\\\\[\\\\s*(?:,\\\\s*)*\\\\]\",y=n(\"\u003c\u003c0\u003e\u003e(?:\\\\s*(?:\\\\?\\\\s*)?\u003c\u003c1\u003e\u003e)*(?:\\\\s*\\\\?)?\",[m,k]),w=n(\"[^,()\u003c\u003e[\\\\];=+\\\\-*/%\u0026|^]|\u003c\u003c0\u003e\u003e|\u003c\u003c1\u003e\u003e|\u003c\u003c2\u003e\u003e\",[g,b,k]),v=n(\"\\\\(\u003c\u003c0\u003e\u003e+(?:,\u003c\u003c0\u003e\u003e+)+\\\\)\",[w]),x=n(\"(?:\u003c\u003c0\u003e\u003e|\u003c\u003c1\u003e\u003e)(?:\\\\s*(?:\\\\?\\\\s*)?\u003c\u003c2\u003e\u003e)*(?:\\\\s*\\\\?)?\",[v,m,k]),$={keyword:p,punctuation:/[\u003c\u003e()?,.:[\\]]/},_=\"\u0027(?:[^\\r\\n\u0027\\\\\\\\]|\\\\\\\\.|\\\\\\\\[Uux][\\\\da-fA-F]{1,8})\u0027\",B=\u0027\"(?:\\\\\\\\.|[^\\\\\\\\\"\\r\\n])*\"\u0027;e.languages.csharp=e.languages.extend(\"clike\",{string:[{pattern:s(\"(^|[^$\\\\\\\\])\u003c\u003c0\u003e\u003e\",[\u0027@\"(?:\"\"|\\\\\\\\[^]|[^\\\\\\\\\"])*\"(?!\")\u0027]),lookbehind:!0,greedy:!0},{pattern:s(\"(^|[^@$\\\\\\\\])\u003c\u003c0\u003e\u003e\",[B]),lookbehind:!0,greedy:!0}],\"class-name\":[{pattern:s(\"(\\\\busing\\\\s+static\\\\s+)\u003c\u003c0\u003e\u003e(?=\\\\s*;)\",[m]),lookbehind:!0,inside:$},{pattern:s(\"(\\\\busing\\\\s+\u003c\u003c0\u003e\u003e\\\\s*=\\\\s*)\u003c\u003c1\u003e\u003e(?=\\\\s*;)\",[h,x]),lookbehind:!0,inside:$},{pattern:s(\"(\\\\busing\\\\s+)\u003c\u003c0\u003e\u003e(?=\\\\s*=)\",[h]),lookbehind:!0},{pattern:s(\"(\\\\b\u003c\u003c0\u003e\u003e\\\\s+)\u003c\u003c1\u003e\u003e\",[d,f]),lookbehind:!0,inside:$},{pattern:s(\"(\\\\bcatch\\\\s*\\\\(\\\\s*)\u003c\u003c0\u003e\u003e\",[m]),lookbehind:!0,inside:$},{pattern:s(\"(\\\\bwhere\\\\s+)\u003c\u003c0\u003e\u003e\",[h]),lookbehind:!0},{pattern:s(\"(\\\\b(?:is(?:\\\\s+not)?|as)\\\\s+)\u003c\u003c0\u003e\u003e\",[y]),lookbehind:!0,inside:$},{pattern:s(\"\\\\b\u003c\u003c0\u003e\u003e(?=\\\\s+(?!\u003c\u003c1\u003e\u003e|with\\\\s*\\\\{)\u003c\u003c2\u003e\u003e(?:\\\\s*[=,;:{)\\\\]]|\\\\s+(?:in|when)\\\\b))\",[x,u,h]),inside:$}],keyword:p,number:/(?:\\b0(?:x[\\da-f_]*[\\da-f]|b[01_]*[01])|(?:\\B\\.\\d+(?:_+\\d+)*|\\b\\d+(?:_+\\d+)*(?:\\.\\d+(?:_+\\d+)*)?)(?:e[-+]?\\d+(?:_+\\d+)*)?)(?:[dflmu]|lu|ul)?\\b/i,operator:/\u003e\u003e=?|\u003c\u003c=?|[-=]\u003e|([-+\u0026|])\\1|~|\\?\\?=?|[-+*/%\u0026|^!=\u003c\u003e]=?/,punctuation:/\\?\\.?|::|[{}[\\];(),.:]/}),e.languages.insertBefore(\"csharp\",\"number\",{range:{pattern:/\\.\\./,alias:\"operator\"}}),e.languages.insertBefore(\"csharp\",\"punctuation\",{\"named-parameter\":{pattern:s(\"([(,]\\\\s*)\u003c\u003c0\u003e\u003e(?=\\\\s*:)\",[h]),lookbehind:!0,alias:\"punctuation\"}}),e.languages.insertBefore(\"csharp\",\"class-name\",{namespace:{pattern:s(\"(\\\\b(?:namespace|using)\\\\s+)\u003c\u003c0\u003e\u003e(?:\\\\s*\\\\.\\\\s*\u003c\u003c0\u003e\u003e)*(?=\\\\s*[;{])\",[h]),lookbehind:!0,inside:{punctuation:/\\./}},\"type-expression\":{pattern:s(\"(\\\\b(?:default|sizeof|typeof)\\\\s*\\\\(\\\\s*(?!\\\\s))(?:[^()\\\\s]|\\\\s(?!\\\\s)|\u003c\u003c0\u003e\u003e)*(?=\\\\s*\\\\))\",[b]),lookbehind:!0,alias:\"class-name\",inside:$},\"return-type\":{pattern:s(\"\u003c\u003c0\u003e\u003e(?=\\\\s+(?:\u003c\u003c1\u003e\u003e\\\\s*(?:=\u003e|[({]|\\\\.\\\\s*this\\\\s*\\\\[)|this\\\\s*\\\\[))\",[x,m]),inside:$,alias:\"class-name\"},\"constructor-invocation\":{pattern:s(\"(\\\\bnew\\\\s+)\u003c\u003c0\u003e\u003e(?=\\\\s*[[({])\",[x]),lookbehind:!0,inside:$,alias:\"class-name\"},\"generic-method\":{pattern:s(\"\u003c\u003c0\u003e\u003e\\\\s*\u003c\u003c1\u003e\u003e(?=\\\\s*\\\\()\",[h,g]),inside:{function:s(\"^\u003c\u003c0\u003e\u003e\",[h]),generic:{pattern:RegExp(g),alias:\"class-name\",inside:$}}},\"type-list\":{pattern:s(\"\\\\b((?:\u003c\u003c0\u003e\u003e\\\\s+\u003c\u003c1\u003e\u003e|record\\\\s+\u003c\u003c1\u003e\u003e\\\\s*\u003c\u003c5\u003e\u003e|where\\\\s+\u003c\u003c2\u003e\u003e)\\\\s*:\\\\s*)(?:\u003c\u003c3\u003e\u003e|\u003c\u003c4\u003e\u003e|\u003c\u003c1\u003e\u003e\\\\s*\u003c\u003c5\u003e\u003e|\u003c\u003c6\u003e\u003e)(?:\\\\s*,\\\\s*(?:\u003c\u003c3\u003e\u003e|\u003c\u003c4\u003e\u003e|\u003c\u003c6\u003e\u003e))*(?=\\\\s*(?:where|[{;]|=\u003e|$))\",[d,f,h,x,p.source,b,\"\\\\bnew\\\\s*\\\\(\\\\s*\\\\)\"]),lookbehind:!0,inside:{\"record-arguments\":{pattern:s(\"(^(?!new\\\\s*\\\\()\u003c\u003c0\u003e\u003e\\\\s*)\u003c\u003c1\u003e\u003e\",[f,b]),lookbehind:!0,greedy:!0,inside:e.languages.csharp},keyword:p,\"class-name\":{pattern:RegExp(x),greedy:!0,inside:$},punctuation:/[,()]/}},preprocessor:{pattern:/(^[\\t ]*)#.*/m,lookbehind:!0,alias:\"property\",inside:{directive:{pattern:/(#)\\b(?:define|elif|else|endif|endregion|error|if|line|nullable|pragma|region|undef|warning)\\b/,lookbehind:!0,alias:\"keyword\"}}}});var E=B+\"|\"+_,R=n(\"/(?![*/])|//[^\\r\\n]*[\\r\\n]|/\\\\*(?:[^*]|\\\\*(?!/))*\\\\*/|\u003c\u003c0\u003e\u003e\",[E]),z=a(n(\"[^\\\"\u0027/()]|\u003c\u003c0\u003e\u003e|\\\\(\u003c\u003cself\u003e\u003e*\\\\)\",[R]),2),S=\"\\\\b(?:assembly|event|field|method|module|param|property|return|type)\\\\b\",j=n(\"\u003c\u003c0\u003e\u003e(?:\\\\s*\\\\(\u003c\u003c1\u003e\u003e*\\\\))?\",[m,z]);e.languages.insertBefore(\"csharp\",\"class-name\",{attribute:{pattern:s(\"((?:^|[^\\\\s\\\\w\u003e)?])\\\\s*\\\\[\\\\s*)(?:\u003c\u003c0\u003e\u003e\\\\s*:\\\\s*)?\u003c\u003c1\u003e\u003e(?:\\\\s*,\\\\s*\u003c\u003c1\u003e\u003e)*(?=\\\\s*\\\\])\",[S,j]),lookbehind:!0,greedy:!0,inside:{target:{pattern:s(\"^\u003c\u003c0\u003e\u003e(?=\\\\s*:)\",[S]),alias:\"keyword\"},\"attribute-arguments\":{pattern:s(\"\\\\(\u003c\u003c0\u003e\u003e*\\\\)\",[z]),inside:e.languages.csharp},\"class-name\":{pattern:RegExp(m),inside:{punctuation:/\\./}},punctuation:/[:,]/}}});var A=\":[^}\\r\\n]+\",F=a(n(\"[^\\\"\u0027/()]|\u003c\u003c0\u003e\u003e|\\\\(\u003c\u003cself\u003e\u003e*\\\\)\",[R]),2),P=n(\"\\\\{(?!\\\\{)(?:(?![}:])\u003c\u003c0\u003e\u003e)*\u003c\u003c1\u003e\u003e?\\\\}\",[F,A]),U=a(n(\"[^\\\"\u0027/()]|/(?!\\\\*)|/\\\\*(?:[^*]|\\\\*(?!/))*\\\\*/|\u003c\u003c0\u003e\u003e|\\\\(\u003c\u003cself\u003e\u003e*\\\\)\",[E]),2),Z=n(\"\\\\{(?!\\\\{)(?:(?![}:])\u003c\u003c0\u003e\u003e)*\u003c\u003c1\u003e\u003e?\\\\}\",[U,A]);function q(n,a){return{interpolation:{pattern:s(\"((?:^|[^{])(?:\\\\{\\\\{)*)\u003c\u003c0\u003e\u003e\",[n]),lookbehind:!0,inside:{\"format-string\":{pattern:s(\"(^\\\\{(?:(?![}:])\u003c\u003c0\u003e\u003e)*)\u003c\u003c1\u003e\u003e(?=\\\\}$)\",[a,A]),lookbehind:!0,inside:{punctuation:/^:/}},punctuation:/^\\{|\\}$/,expression:{pattern:/[\\s\\S]+/,alias:\"language-csharp\",inside:e.languages.csharp}}},string:/[\\s\\S]+/}}e.languages.insertBefore(\"csharp\",\"string\",{\"interpolation-string\":[{pattern:s(\u0027(^|[^\\\\\\\\])(?:\\\\$@|@\\\\$)\"(?:\"\"|\\\\\\\\[^]|\\\\{\\\\{|\u003c\u003c0\u003e\u003e|[^\\\\\\\\{\"])*\"\u0027,[P]),lookbehind:!0,greedy:!0,inside:q(P,F)},{pattern:s(\u0027(^|[^@\\\\\\\\])\\\\$\"(?:\\\\\\\\.|\\\\{\\\\{|\u003c\u003c0\u003e\u003e|[^\\\\\\\\\"{])*\"\u0027,[Z]),lookbehind:!0,greedy:!0,inside:q(Z,U)}],char:{pattern:RegExp(_),greedy:!0}}),e.languages.dotnet=e.languages.cs=e.languages.csharp}(Prism);\n!function(e){var n=/\\b(?:abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|exports|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|module|native|new|non-sealed|null|open|opens|package|permits|private|protected|provides|public|record(?!\\s*[(){}[\\]\u003c\u003e=%~.:,;?+\\-*/\u0026|^])|requires|return|sealed|short|static|strictfp|super|switch|synchronized|this|throw|throws|to|transient|transitive|try|uses|var|void|volatile|while|with|yield)\\b/,t=\"(?:[a-z]\\\\w*\\\\s*\\\\.\\\\s*)*(?:[A-Z]\\\\w*\\\\s*\\\\.\\\\s*)*\",s={pattern:RegExp(\"(^|[^\\\\w.])\"+t+\"[A-Z](?:[\\\\d_A-Z]*[a-z]\\\\w*)?\\\\b\"),lookbehind:!0,inside:{namespace:{pattern:/^[a-z]\\w*(?:\\s*\\.\\s*[a-z]\\w*)*(?:\\s*\\.)?/,inside:{punctuation:/\\./}},punctuation:/\\./}};e.languages.java=e.languages.extend(\"clike\",{string:{pattern:/(^|[^\\\\])\"(?:\\\\.|[^\"\\\\\\r\\n])*\"/,lookbehind:!0,greedy:!0},\"class-name\":[s,{pattern:RegExp(\"(^|[^\\\\w.])\"+t+\"[A-Z]\\\\w*(?=\\\\s+\\\\w+\\\\s*[;,=()]|\\\\s*(?:\\\\[[\\\\s,]*\\\\]\\\\s*)?::\\\\s*new\\\\b)\"),lookbehind:!0,inside:s.inside},{pattern:RegExp(\"(\\\\b(?:class|enum|extends|implements|instanceof|interface|new|record|throws)\\\\s+)\"+t+\"[A-Z]\\\\w*\\\\b\"),lookbehind:!0,inside:s.inside}],keyword:n,function:[e.languages.clike.function,{pattern:/(::\\s*)[a-z_]\\w*/,lookbehind:!0}],number:/\\b0b[01][01_]*L?\\b|\\b0x(?:\\.[\\da-f_p+-]+|[\\da-f_]+(?:\\.[\\da-f_p+-]+)?)\\b|(?:\\b\\d[\\d_]*(?:\\.[\\d_]*)?|\\B\\.\\d[\\d_]*)(?:e[+-]?\\d[\\d_]*)?[dfl]?/i,operator:{pattern:/(^|[^.])(?:\u003c\u003c=?|\u003e\u003e\u003e?=?|-\u003e|--|\\+\\+|\u0026\u0026|\\|\\||::|[?:~]|[-+*/%\u0026|^!=\u003c\u003e]=?)/m,lookbehind:!0},constant:/\\b[A-Z][A-Z_\\d]+\\b/}),e.languages.insertBefore(\"java\",\"string\",{\"triple-quoted-string\":{pattern:/\"\"\"[ \\t]*[\\r\\n](?:(?:\"|\"\")?(?:\\\\.|[^\"\\\\]))*\"\"\"/,greedy:!0,alias:\"string\"},char:{pattern:/\u0027(?:\\\\.|[^\u0027\\\\\\r\\n]){1,6}\u0027/,greedy:!0}}),e.languages.insertBefore(\"java\",\"class-name\",{annotation:{pattern:/(^|[^.])@\\w+(?:\\s*\\.\\s*\\w+)*/,lookbehind:!0,alias:\"punctuation\"},generics:{pattern:/\u003c(?:[\\w\\s,.?]|\u0026(?!\u0026)|\u003c(?:[\\w\\s,.?]|\u0026(?!\u0026)|\u003c(?:[\\w\\s,.?]|\u0026(?!\u0026)|\u003c(?:[\\w\\s,.?]|\u0026(?!\u0026))*\u003e)*\u003e)*\u003e)*\u003e/,inside:{\"class-name\":s,keyword:n,punctuation:/[\u003c\u003e(),.:]/,operator:/[?\u0026|]/}},import:[{pattern:RegExp(\"(\\\\bimport\\\\s+)\"+t+\"(?:[A-Z]\\\\w*|\\\\*)(?=\\\\s*;)\"),lookbehind:!0,inside:{namespace:s.inside.namespace,punctuation:/\\./,operator:/\\*/,\"class-name\":/\\w+/}},{pattern:RegExp(\"(\\\\bimport\\\\s+static\\\\s+)\"+t+\"(?:\\\\w+|\\\\*)(?=\\\\s*;)\"),lookbehind:!0,alias:\"static\",inside:{namespace:s.inside.namespace,static:/\\b\\w+$/,punctuation:/\\./,operator:/\\*/,\"class-name\":/\\w+/}}],namespace:{pattern:RegExp(\"(\\\\b(?:exports|import(?:\\\\s+static)?|module|open|opens|package|provides|requires|to|transitive|uses|with)\\\\s+)(?!\u003ckeyword\u003e)[a-z]\\\\w*(?:\\\\.[a-z]\\\\w*)*\\\\.?\".replace(/\u003ckeyword\u003e/g,(function(){return n.source}))),lookbehind:!0,inside:{punctuation:/\\./}}})}(Prism);\nPrism.languages.go=Prism.languages.extend(\"clike\",{string:{pattern:/(^|[^\\\\])\"(?:\\\\.|[^\"\\\\\\r\\n])*\"|`[^`]*`/,lookbehind:!0,greedy:!0},keyword:/\\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\\b/,boolean:/\\b(?:_|false|iota|nil|true)\\b/,number:[/\\b0(?:b[01_]+|o[0-7_]+)i?\\b/i,/\\b0x(?:[a-f\\d_]+(?:\\.[a-f\\d_]*)?|\\.[a-f\\d_]+)(?:p[+-]?\\d+(?:_\\d+)*)?i?(?!\\w)/i,/(?:\\b\\d[\\d_]*(?:\\.[\\d_]*)?|\\B\\.\\d[\\d_]*)(?:e[+-]?[\\d_]+)?i?(?!\\w)/i],operator:/[*\\/%^!=]=?|\\+[=+]?|-[=-]?|\\|[=|]?|\u0026(?:=|\u0026|\\^=?)?|\u003e(?:\u003e=?|=)?|\u003c(?:\u003c=?|=|-)?|:=|\\.\\.\\./,builtin:/\\b(?:append|bool|byte|cap|close|complex|complex(?:64|128)|copy|delete|error|float(?:32|64)|u?int(?:8|16|32|64)?|imag|len|make|new|panic|print(?:ln)?|real|recover|rune|string|uintptr)\\b/}),Prism.languages.insertBefore(\"go\",\"string\",{char:{pattern:/\u0027(?:\\\\.|[^\u0027\\\\\\r\\n]){0,10}\u0027/,greedy:!0}}),delete Prism.languages.go[\"class-name\"];\n!function(e){for(var a=\"/\\\\*(?:[^*/]|\\\\*(?!/)|/(?!\\\\*)|\u003cself\u003e)*\\\\*/\",t=0;t\u003c2;t++)a=a.replace(/\u003cself\u003e/g,(function(){return a}));a=a.replace(/\u003cself\u003e/g,(function(){return\"[^\\\\s\\\\S]\"})),e.languages.rust={comment:[{pattern:RegExp(\"(^|[^\\\\\\\\])\"+a),lookbehind:!0,greedy:!0},{pattern:/(^|[^\\\\:])\\/\\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/b?\"(?:\\\\[\\s\\S]|[^\\\\\"])*\"|b?r(#*)\"(?:[^\"]|\"(?!\\1))*\"\\1/,greedy:!0},char:{pattern:/b?\u0027(?:\\\\(?:x[0-7][\\da-fA-F]|u\\{(?:[\\da-fA-F]_*){1,6}\\}|.)|[^\\\\\\r\\n\\t\u0027])\u0027/,greedy:!0},attribute:{pattern:/#!?\\[(?:[^\\[\\]\"]|\"(?:\\\\[\\s\\S]|[^\\\\\"])*\")*\\]/,greedy:!0,alias:\"attr-name\",inside:{string:null}},\"closure-params\":{pattern:/([=(,:]\\s*|\\bmove\\s*)\\|[^|]*\\||\\|[^|]*\\|(?=\\s*(?:\\{|-\u003e))/,lookbehind:!0,greedy:!0,inside:{\"closure-punctuation\":{pattern:/^\\||\\|$/,alias:\"punctuation\"},rest:null}},\"lifetime-annotation\":{pattern:/\u0027\\w+/,alias:\"symbol\"},\"fragment-specifier\":{pattern:/(\\$\\w+:)[a-z]+/,lookbehind:!0,alias:\"punctuation\"},variable:/\\$\\w+/,\"function-definition\":{pattern:/(\\bfn\\s+)\\w+/,lookbehind:!0,alias:\"function\"},\"type-definition\":{pattern:/(\\b(?:enum|struct|trait|type|union)\\s+)\\w+/,lookbehind:!0,alias:\"class-name\"},\"module-declaration\":[{pattern:/(\\b(?:crate|mod)\\s+)[a-z][a-z_\\d]*/,lookbehind:!0,alias:\"namespace\"},{pattern:/(\\b(?:crate|self|super)\\s*)::\\s*[a-z][a-z_\\d]*\\b(?:\\s*::(?:\\s*[a-z][a-z_\\d]*\\s*::)*)?/,lookbehind:!0,alias:\"namespace\",inside:{punctuation:/::/}}],keyword:[/\\b(?:Self|abstract|as|async|await|become|box|break|const|continue|crate|do|dyn|else|enum|extern|final|fn|for|if|impl|in|let|loop|macro|match|mod|move|mut|override|priv|pub|ref|return|self|static|struct|super|trait|try|type|typeof|union|unsafe|unsized|use|virtual|where|while|yield)\\b/,/\\b(?:bool|char|f(?:32|64)|[ui](?:8|16|32|64|128|size)|str)\\b/],function:/\\b[a-z_]\\w*(?=\\s*(?:::\\s*\u003c|\\())/,macro:{pattern:/\\b\\w+!/,alias:\"property\"},constant:/\\b[A-Z_][A-Z_\\d]+\\b/,\"class-name\":/\\b[A-Z]\\w*\\b/,namespace:{pattern:/(?:\\b[a-z][a-z_\\d]*\\s*::\\s*)*\\b[a-z][a-z_\\d]*\\s*::(?!\\s*\u003c)/,inside:{punctuation:/::/}},number:/\\b(?:0x[\\dA-Fa-f](?:_?[\\dA-Fa-f])*|0o[0-7](?:_?[0-7])*|0b[01](?:_?[01])*|(?:(?:\\d(?:_?\\d)*)?\\.)?\\d(?:_?\\d)*(?:[Ee][+-]?\\d+)?)(?:_?(?:f32|f64|[iu](?:8|16|32|64|size)?))?\\b/,boolean:/\\b(?:false|true)\\b/,punctuation:/-\u003e|\\.\\.=|\\.{1,3}|::|[{}[\\];(),:]/,operator:/[-+*\\/%!^]=?|=[=\u003e]?|\u0026[\u0026=]?|\\|[|=]?|\u003c\u003c?=?|\u003e\u003e?=?|[@?]/},e.languages.rust[\"closure-params\"].inside.rest=e.languages.rust,e.languages.rust.attribute.inside.string=e.languages.rust.string}(Prism);\nPrism.languages.python={comment:{pattern:/(^|[^\\\\])#.*/,lookbehind:!0,greedy:!0},\"string-interpolation\":{pattern:/(?:f|fr|rf)(?:(\"\"\"|\u0027\u0027\u0027)[\\s\\S]*?\\1|(\"|\u0027)(?:\\\\.|(?!\\2)[^\\\\\\r\\n])*\\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\\{\\{)*)\\{(?!\\{)(?:[^{}]|\\{(?!\\{)(?:[^{}]|\\{(?!\\{)(?:[^{}])+\\})+\\})+\\}/,lookbehind:!0,inside:{\"format-spec\":{pattern:/(:)[^:(){}]+(?=\\}$)/,lookbehind:!0},\"conversion-option\":{pattern://,alias:\"punctuation\"},rest:null}},string:/[\\s\\S]+/}},\"triple-quoted-string\":{pattern:/(?:[rub]|br|rb)?(\"\"\"|\u0027\u0027\u0027)[\\s\\S]*?\\1/i,greedy:!0,alias:\"string\"},string:{pattern:/(?:[rub]|br|rb)?(\"|\u0027)(?:\\\\.|(?!\\1)[^\\\\\\r\\n])*\\1/i,greedy:!0},function:{pattern:/((?:^|\\s)def[ \\t]+)[a-zA-Z_]\\w*(?=\\s*\\()/g,lookbehind:!0},\"class-name\":{pattern:/(\\bclass\\s+)\\w+/i,lookbehind:!0},decorator:{pattern:/(^[\\t ]*)@\\w+(?:\\.\\w+)*/m,lookbehind:!0,alias:[\"annotation\",\"punctuation\"],inside:{punctuation:/\\./}},keyword:/\\b(?:_(?=\\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\\b/,builtin:/\\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\\b/,boolean:/\\b(?:False|None|True)\\b/,number:/\\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\\b|(?:\\b\\d+(?:_\\d+)*(?:\\.(?:\\d+(?:_\\d+)*)?)?|\\B\\.\\d+(?:_\\d+)*)(?:e[+-]?\\d+(?:_\\d+)*)?j?(?!\\w)/i,operator:/[-+%=]=?|!=|:=|\\*\\*?=?|\\/\\/?=?|\u003c[\u003c=\u003e]?|\u003e[=\u003e]?|[\u0026|^~]/,punctuation:/[{}[\\];(),.:]/},Prism.languages.python[\"string-interpolation\"].inside.interpolation.inside.rest=Prism.languages.python,Prism.languages.py=Prism.languages.python;\nPrism.languages.sql={comment:{pattern:/(^|[^\\\\])(?:\\/\\*[\\s\\S]*?\\*\\/|(?:--|\\/\\/|#).*)/,lookbehind:!0},variable:[{pattern:/@([\"\u0027`])(?:\\\\[\\s\\S]|(?!\\1)[^\\\\])+\\1/,greedy:!0},/@[\\w.$]+/],string:{pattern:/(^|[^@\\\\])(\"|\u0027)(?:\\\\[\\s\\S]|(?!\\2)[^\\\\]|\\2\\2)*\\2/,greedy:!0,lookbehind:!0},identifier:{pattern:/(^|[^@\\\\])`(?:\\\\[\\s\\S]|[^`\\\\]|``)*`/,greedy:!0,lookbehind:!0,inside:{punctuation:/^`|`$/}},function:/\\b(?:AVG|COUNT|FIRST|FORMAT|LAST|LCASE|LEN|MAX|MID|MIN|MOD|NOW|ROUND|SUM|UCASE)(?=\\s*\\()/i,keyword:/\\b(?:ACTION|ADD|AFTER|ALGORITHM|ALL|ALTER|ANALYZE|ANY|APPLY|AS|ASC|AUTHORIZATION|AUTO_INCREMENT|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADED?|CASE|CHAIN|CHAR(?:ACTER|SET)?|CHECK(?:POINT)?|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMNS?|COMMENT|COMMIT(?:TED)?|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS(?:TABLE)?|CONTINUE|CONVERT|CREATE|CROSS|CURRENT(?:_DATE|_TIME|_TIMESTAMP|_USER)?|CURSOR|CYCLE|DATA(?:BASES?)?|DATE(?:TIME)?|DAY|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DELIMITERS?|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE|DROP|DUMMY|DUMP(?:FILE)?|DUPLICATE|ELSE(?:IF)?|ENABLE|ENCLOSED|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPED?|EXCEPT|EXEC(?:UTE)?|EXISTS|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR(?: EACH ROW)?|FORCE|FOREIGN|FREETEXT(?:TABLE)?|FROM|FULL|FUNCTION|GEOMETRY(?:COLLECTION)?|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|HOUR|IDENTITY(?:COL|_INSERT)?|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTERVAL|INTO|INVOKER|ISOLATION|ITERATE|JOIN|KEYS?|KILL|LANGUAGE|LAST|LEAVE|LEFT|LEVEL|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONG(?:BLOB|TEXT)|LOOP|MATCH(?:ED)?|MEDIUM(?:BLOB|INT|TEXT)|MERGE|MIDDLEINT|MINUTE|MODE|MODIFIES|MODIFY|MONTH|MULTI(?:LINESTRING|POINT|POLYGON)|NATIONAL|NATURAL|NCHAR|NEXT|NO|NONCLUSTERED|NULLIF|NUMERIC|OFF?|OFFSETS?|ON|OPEN(?:DATASOURCE|QUERY|ROWSET)?|OPTIMIZE|OPTION(?:ALLY)?|ORDER|OUT(?:ER|FILE)?|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREPARE|PREV|PRIMARY|PRINT|PRIVILEGES|PROC(?:EDURE)?|PUBLIC|PURGE|QUICK|RAISERROR|READS?|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEAT(?:ABLE)?|REPLACE|REPLICATION|REQUIRE|RESIGNAL|RESTORE|RESTRICT|RETURN(?:ING|S)?|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROW(?:COUNT|GUIDCOL|S)?|RTREE|RULE|SAVE(?:POINT)?|SCHEMA|SECOND|SELECT|SERIAL(?:IZABLE)?|SESSION(?:_USER)?|SET(?:USER)?|SHARE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|SQL|START(?:ING)?|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLES?|TABLESPACE|TEMP(?:ORARY|TABLE)?|TERMINATED|TEXT(?:SIZE)?|THEN|TIME(?:STAMP)?|TINY(?:BLOB|INT|TEXT)|TOP?|TRAN(?:SACTIONS?)?|TRIGGER|TRUNCATE|TSEQUAL|TYPES?|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNIQUE|UNLOCK|UNPIVOT|UNSIGNED|UPDATE(?:TEXT)?|USAGE|USE|USER|USING|VALUES?|VAR(?:BINARY|CHAR|CHARACTER|YING)|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH(?: ROLLUP|IN)?|WORK|WRITE(?:TEXT)?|YEAR)\\b/i,boolean:/\\b(?:FALSE|NULL|TRUE)\\b/i,number:/\\b0x[\\da-f]+\\b|\\b\\d+(?:\\.\\d*)?|\\B\\.\\d+\\b/i,operator:/[-+*\\/=%^~]|\u0026\u0026?|\\|\\|?|!=?|\u003c(?:=\u003e?|\u003c|\u003e)?|\u003e[\u003e=]?|\\b(?:AND|BETWEEN|DIV|ILIKE|IN|IS|LIKE|NOT|OR|REGEXP|RLIKE|SOUNDS LIKE|XOR)\\b/i,punctuation:/[;[\\]()`,.]/};\nPrism.languages.ini={comment:{pattern:/(^[ \\f\\t\\v]*)[#;][^\\n\\r]*/m,lookbehind:!0},section:{pattern:/(^[ \\f\\t\\v]*)\\[[^\\n\\r\\]]*\\]?/m,lookbehind:!0,inside:{\"section-name\":{pattern:/(^\\[[ \\f\\t\\v]*)[^ \\f\\t\\v\\]]+(?:[ \\f\\t\\v]+[^ \\f\\t\\v\\]]+)*/,lookbehind:!0,alias:\"selector\"},punctuation:/\\[|\\]/}},key:{pattern:/(^[ \\f\\t\\v]*)[^ \\f\\n\\r\\t\\v=]+(?:[ \\f\\t\\v]+[^ \\f\\n\\r\\t\\v=]+)*(?=[ \\f\\t\\v]*=)/m,lookbehind:!0,alias:\"attr-name\"},value:{pattern:/(=[ \\f\\t\\v]*)[^ \\f\\n\\r\\t\\v]+(?:[ \\f\\t\\v]+[^ \\f\\n\\r\\t\\v]+)*/,lookbehind:!0,alias:\"attr-value\",inside:{\"inner-value\":{pattern:/^(\"|\u0027).+(?=\\1$)/,lookbehind:!0}}},punctuation:/=/};\nPrism.languages.properties={comment:/^[ \\t]*[#!].*$/m,value:{pattern:/(^[ \\t]*(?:\\\\(?:\\r\\n|[\\s\\S])|[^\\\\\\s:=])+(?: *[=:] *(?! )| ))(?:\\\\(?:\\r\\n|[\\s\\S])|[^\\\\\\r\\n])+/m,lookbehind:!0,alias:\"attr-value\"},key:{pattern:/^[ \\t]*(?:\\\\(?:\\r\\n|[\\s\\S])|[^\\\\\\s:=])+(?= *[=:]| )/m,alias:\"attr-name\"},punctuation:/[=:]/};\n!function(e){function n(e){return e.replace(/__/g,(function(){return\"(?:[\\\\w-]+|\u0027[^\u0027\\n\\r]*\u0027|\\\"(?:\\\\\\\\.|[^\\\\\\\\\\\"\\r\\n])*\\\")\"}))}e.languages.toml={comment:{pattern:/#.*/,greedy:!0},table:{pattern:RegExp(n(\"(^[\\t ]*\\\\[\\\\s*(?:\\\\[\\\\s*)?)__(?:\\\\s*\\\\.\\\\s*__)*(?=\\\\s*\\\\])\"),\"m\"),lookbehind:!0,greedy:!0,alias:\"class-name\"},key:{pattern:RegExp(n(\"(^[\\t ]*|[{,]\\\\s*)__(?:\\\\s*\\\\.\\\\s*__)*(?=\\\\s*=)\"),\"m\"),lookbehind:!0,greedy:!0,alias:\"property\"},string:{pattern:/\"\"\"(?:\\\\[\\s\\S]|[^\\\\])*?\"\"\"|\u0027\u0027\u0027[\\s\\S]*?\u0027\u0027\u0027|\u0027[^\u0027\\n\\r]*\u0027|\"(?:\\\\.|[^\\\\\"\\r\\n])*\"/,greedy:!0},date:[{pattern:/\\b\\d{4}-\\d{2}-\\d{2}(?:[T\\s]\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:Z|[+-]\\d{2}:\\d{2})?)?\\b/i,alias:\"number\"},{pattern:/\\b\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?\\b/,alias:\"number\"}],number:/(?:\\b0(?:x[\\da-zA-Z]+(?:_[\\da-zA-Z]+)*|o[0-7]+(?:_[0-7]+)*|b[10]+(?:_[10]+)*))\\b|[-+]?\\b\\d+(?:_\\d+)*(?:\\.\\d+(?:_\\d+)*)?(?:[eE][+-]?\\d+(?:_\\d+)*)?\\b|[-+]?\\b(?:inf|nan)\\b/,boolean:/\\b(?:false|true)\\b/,punctuation:/[.,=[\\]{}]/}}(Prism);\n!function(e){var n={pattern:/((?:^|[^\\\\$])(?:\\\\{2})*)\\$(?:\\w+|\\{[^{}]*\\})/,lookbehind:!0,inside:{\"interpolation-punctuation\":{pattern:/^\\$\\{?|\\}$/,alias:\"punctuation\"},expression:{pattern:/[\\s\\S]+/,inside:null}}};e.languages.groovy=e.languages.extend(\"clike\",{string:{pattern:/\u0027\u0027\u0027(?:[^\\\\]|\\\\[\\s\\S])*?\u0027\u0027\u0027|\u0027(?:\\\\.|[^\\\\\u0027\\r\\n])*\u0027/,greedy:!0},keyword:/\\b(?:abstract|as|assert|boolean|break|byte|case|catch|char|class|const|continue|def|default|do|double|else|enum|extends|final|finally|float|for|goto|if|implements|import|in|instanceof|int|interface|long|native|new|package|private|protected|public|return|short|static|strictfp|super|switch|synchronized|this|throw|throws|trait|transient|try|void|volatile|while)\\b/,number:/\\b(?:0b[01_]+|0x[\\da-f_]+(?:\\.[\\da-f_p\\-]+)?|[\\d_]+(?:\\.[\\d_]+)?(?:e[+-]?\\d+)?)[glidf]?\\b/i,operator:{pattern:/(^|[^.])(?:~|==?~?|\\?[.:]?|\\*(?:[.=]|\\*=?)?|\\.[@\u0026]|\\.\\.\u003c|\\.\\.(?!\\.)|-[-=\u003e]?|\\+[+=]?|!=?|\u003c(?:\u003c=?|=\u003e?)?|\u003e(?:\u003e\u003e?=?|=)?|\u0026[\u0026=]?|\\|[|=]?|\\/=?|\\^=?|%=?)/,lookbehind:!0},punctuation:/\\.+|[{}[\\];(),:$]/}),e.languages.insertBefore(\"groovy\",\"string\",{shebang:{pattern:/#!.+/,alias:\"comment\",greedy:!0},\"interpolation-string\":{pattern:/\"\"\"(?:[^\\\\]|\\\\[\\s\\S])*?\"\"\"|([\"/])(?:\\\\.|(?!\\1)[^\\\\\\r\\n])*\\1|\\$\\/(?:[^/$]|\\$(?:[/$]|(?![/$]))|\\/(?!\\$))*\\/\\$/,greedy:!0,inside:{interpolation:n,string:/[\\s\\S]+/}}}),e.languages.insertBefore(\"groovy\",\"punctuation\",{\"spock-block\":/\\b(?:and|cleanup|expect|given|setup|then|when|where):/}),e.languages.insertBefore(\"groovy\",\"function\",{annotation:{pattern:/(^|[^.])@\\w+/,lookbehind:!0,alias:\"punctuation\"}}),n.inside.expression.inside=e.languages.groovy}(Prism);\n!function(t){var n=t.util.clone(t.languages.javascript),e=\"(?:\\\\{\u003cS\u003e*\\\\.{3}(?:[^{}]|\u003cBRACES\u003e)*\\\\})\";function a(t,n){return t=t.replace(/\u003cS\u003e/g,(function(){return\"(?:\\\\s|//.*(?!.)|/\\\\*(?:[^*]|\\\\*(?!/))\\\\*/)\"})).replace(/\u003cBRACES\u003e/g,(function(){return\"(?:\\\\{(?:\\\\{(?:\\\\{[^{}]*\\\\}|[^{}])*\\\\}|[^{}])*\\\\})\"})).replace(/\u003cSPREAD\u003e/g,(function(){return e})),RegExp(t,n)}e=a(e).source,t.languages.jsx=t.languages.extend(\"markup\",n),t.languages.jsx.tag.pattern=a(\"\u003c/?(?:[\\\\w.:-]+(?:\u003cS\u003e+(?:[\\\\w.:$-]+(?:=(?:\\\"(?:\\\\\\\\[^]|[^\\\\\\\\\\\"])*\\\"|\u0027(?:\\\\\\\\[^]|[^\\\\\\\\\u0027])*\u0027|[^\\\\s{\u0027\\\"/\u003e=]+|\u003cBRACES\u003e))?|\u003cSPREAD\u003e))*\u003cS\u003e*/?)?\u003e\"),t.languages.jsx.tag.inside.tag.pattern=/^\u003c\\/?[^\\s\u003e\\/]*/,t.languages.jsx.tag.inside[\"attr-value\"].pattern=/=(?!\\{)(?:\"(?:\\\\[\\s\\S]|[^\\\\\"])*\"|\u0027(?:\\\\[\\s\\S]|[^\\\\\u0027])*\u0027|[^\\s\u0027\"\u003e]+)/,t.languages.jsx.tag.inside.tag.inside[\"class-name\"]=/^[A-Z]\\w*(?:\\.[A-Z]\\w*)*$/,t.languages.jsx.tag.inside.comment=n.comment,t.languages.insertBefore(\"inside\",\"attr-name\",{spread:{pattern:a(\"\u003cSPREAD\u003e\"),inside:t.languages.jsx}},t.languages.jsx.tag),t.languages.insertBefore(\"inside\",\"special-attr\",{script:{pattern:a(\"=\u003cBRACES\u003e\"),alias:\"language-javascript\",inside:{\"script-punctuation\":{pattern:/^=(?=\\{)/,alias:\"punctuation\"},rest:t.languages.jsx}}},t.languages.jsx.tag);var s=function(t){return t?\"string\"==typeof t?t:\"string\"==typeof t.content?t.content:t.content.map(s).join(\"\"):\"\"},g=function(n){for(var e=[],a=0;a\u003cn.length;a++){var o=n[a],i=!1;if(\"string\"!=typeof o\u0026\u0026(\"tag\"===o.type\u0026\u0026o.content[0]\u0026\u0026\"tag\"===o.content[0].type?\"\u003c/\"===o.content[0].content[0].content?e.length\u003e0\u0026\u0026e[e.length-1].tagName===s(o.content[0].content[1])\u0026\u0026e.pop():\"/\u003e\"===o.content[o.content.length-1].content||e.push({tagName:s(o.content[0].content[1]),openedBraces:0}):e.length\u003e0\u0026\u0026\"punctuation\"===o.type\u0026\u0026\"{\"===o.content?e[e.length-1].openedBraces++:e.length\u003e0\u0026\u0026e[e.length-1].openedBraces\u003e0\u0026\u0026\"punctuation\"===o.type\u0026\u0026\"}\"===o.content?e[e.length-1].openedBraces--:i=!0),(i||\"string\"==typeof o)\u0026\u0026e.length\u003e0\u0026\u00260===e[e.length-1].openedBraces){var r=s(o);a\u003cn.length-1\u0026\u0026(\"string\"==typeof n[a+1]||\"plain-text\"===n[a+1].type)\u0026\u0026(r+=s(n[a+1]),n.splice(a+1,1)),a\u003e0\u0026\u0026(\"string\"==typeof n[a-1]||\"plain-text\"===n[a-1].type)\u0026\u0026(r=s(n[a-1])+r,n.splice(a-1,1),a--),n[a]=new t.Token(\"plain-text\",r,null,r)}o.content\u0026\u0026\"string\"!=typeof o.content\u0026\u0026g(o.content)}};t.hooks.add(\"after-tokenize\",(function(t){\"jsx\"!==t.language\u0026\u0026\"tsx\"!==t.language||g(t.tokens)}))}(Prism);\n!function(e){var a=e.util.clone(e.languages.typescript);e.languages.tsx=e.languages.extend(\"jsx\",a),delete e.languages.tsx.parameter,delete e.languages.tsx[\"literal-property\"];var t=e.languages.tsx.tag;t.pattern=RegExp(\"(^|[^\\\\w$]|(?=\u003c/))(?:\"+t.pattern.source+\")\",t.pattern.flags),t.lookbehind=!0}(Prism);\n!function(e){e.languages.diff={coord:[/^(?:\\*{3}|-{3}|\\+{3}).*$/m,/^@@.*@@$/m,/^\\d.*$/m]};var n={\"deleted-sign\":\"-\",\"deleted-arrow\":\"\u003c\",\"inserted-sign\":\"+\",\"inserted-arrow\":\"\u003e\",unchanged:\" \",diff:\"!\"};Object.keys(n).forEach((function(a){var i=n[a],r=[];/^\\w+$/.test(a)||r.push(/\\w+/.exec(a)[0]),\"diff\"===a\u0026\u0026r.push(\"bold\"),e.languages.diff[a]={pattern:RegExp(\"^(?:[\"+i+\"].*(?:\\r\\n?|\\n|(?![\\\\s\\\\S])))+\",\"m\"),alias:r,inside:{line:{pattern:/(.)(?=[\\s\\S]).*(?:\\r\\n?|\\n)?/,lookbehind:!0},prefix:{pattern:/[\\s\\S]/,alias:/\\w+/.exec(a)[0]}}}})),Object.defineProperty(e.languages.diff,\"PREFIXES\",{value:n})}(Prism);";
|
|
22
|
+
// Prism 惰性初始化:~75KB 源码的解析(正则编译)从模块加载的同步路径移走,
|
|
23
|
+
// 且仅在首个 diff/read payload 实际解析后才安排空闲期执行(schedulePrismIdle),
|
|
24
|
+
// 避免每次页面加载都无条件付出解析成本;首次实际使用时也会同步 ensurePrism()。
|
|
25
|
+
// Prism core 是 UMD 构建:内部自注册 _self.Prism(_self 解析为 window),尾部还
|
|
26
|
+
// 直接写 global.Prism(浏览器中 global === window)——直接在全局作用域执行会把
|
|
27
|
+
// vendored Prism 写入/覆盖宿主页面已有的 window.Prism。用局部空对象同时遮蔽
|
|
28
|
+
// window 与 global 两个参数(typeof 检查仍通过),写不到真全局。
|
|
29
|
+
let PG_PRISM = null;
|
|
30
|
+
let prismInitDone = false;
|
|
31
|
+
let prismIdleScheduled = false;
|
|
32
|
+
// 首个 diff/read payload 解析后安排空闲期初始化(无 requestIdleCallback 时
|
|
33
|
+
// setTimeout 兜底);幂等,仅调度一次。
|
|
34
|
+
function schedulePrismIdle() {
|
|
35
|
+
if (prismIdleScheduled) return;
|
|
36
|
+
prismIdleScheduled = true;
|
|
37
|
+
scheduleIdle(ensurePrism, 3000);
|
|
38
|
+
}
|
|
39
|
+
function ensurePrism() {
|
|
40
|
+
if (prismInitDone) return;
|
|
41
|
+
prismInitDone = true;
|
|
42
|
+
let prism = null;
|
|
43
|
+
try {
|
|
44
|
+
prism = new Function('window', 'global', PRISM_SRC + '\n;return typeof Prism !== "undefined" ? Prism : null;')({}, {});
|
|
45
|
+
} catch (e) { console.warn('[permgate] prism init via eval failed:', e); }
|
|
46
|
+
if (!prism && typeof document !== 'undefined') {
|
|
47
|
+
// CSP 禁 eval 时回退:内联 script 标签(宿主允许内联脚本时生效)。
|
|
48
|
+
// 内联脚本在全局作用域执行,Prism 会自注册 window.Prism——注入前保存宿主原值,
|
|
49
|
+
// 读取结果后立即恢复,避免覆盖其它模块已加载的 window.Prism;
|
|
50
|
+
// 结果经 window.__PG_PRISM__ 传出,读取后立即清理。
|
|
51
|
+
// data-manual 使 Prism core 尾部跳过自动高亮调度(highlightAll),
|
|
52
|
+
// 防止其改写宿主页面中 .language-* 元素(该调度持有闭包、事后恢复
|
|
53
|
+
// window.Prism 无法取消)。
|
|
54
|
+
let prevPrism;
|
|
55
|
+
try {
|
|
56
|
+
const sc = document.createElement('script');
|
|
57
|
+
prevPrism = (typeof window !== 'undefined' && 'Prism' in window) ? window.Prism : undefined;
|
|
58
|
+
sc.setAttribute('data-manual', '');
|
|
59
|
+
sc.textContent = '(function(){' + PRISM_SRC + '\nwindow.__PG_PRISM__ = typeof Prism !== "undefined" ? Prism : null;})();';
|
|
60
|
+
document.head.appendChild(sc);
|
|
61
|
+
prism = (typeof window !== 'undefined' && window.__PG_PRISM__) || null;
|
|
62
|
+
} catch (e2) { console.warn('[permgate] prism init via script failed:', e2); }
|
|
63
|
+
finally {
|
|
64
|
+
// 恢复宿主 window.Prism 与清理 __PG_PRISM__ 放在 finally:执行段内任一步抛错
|
|
65
|
+
// (如 document.head 为 null 时 appendChild 失败)也不会跳过恢复,
|
|
66
|
+
// 避免宿主 Prism 被 vendored 实例静默替换(global 存在的宿主环境)。
|
|
67
|
+
if (typeof window !== 'undefined') {
|
|
68
|
+
if ('__PG_PRISM__' in window) {
|
|
69
|
+
try { delete window.__PG_PRISM__ } catch (e3) { try { window.__PG_PRISM__ = null } catch (e5) {} }
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
if (prevPrism === undefined) delete window.Prism;
|
|
73
|
+
else window.Prism = prevPrism;
|
|
74
|
+
} catch (e4) {}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
PG_PRISM = prism;
|
|
79
|
+
}
|
|
80
|
+
// 扩展名 → Prism 语言
|
|
81
|
+
const PRISM_LANG_BY_EXT = {
|
|
82
|
+
js: 'javascript', mjs: 'javascript', cjs: 'javascript', jsx: 'jsx',
|
|
83
|
+
ts: 'typescript', tsx: 'tsx',
|
|
84
|
+
json: 'json', jsonc: 'json', yml: 'yaml', yaml: 'yaml',
|
|
85
|
+
md: 'markdown', markdown: 'markdown',
|
|
86
|
+
css: 'css', html: 'markup', htm: 'markup', xml: 'markup', svg: 'markup',
|
|
87
|
+
sh: 'bash', bash: 'bash', cmd: 'batch', bat: 'batch', ps1: 'powershell',
|
|
88
|
+
py: 'python', c: 'c', h: 'cpp', cpp: 'cpp', cc: 'cpp', cxx: 'cpp', hpp: 'cpp',
|
|
89
|
+
cs: 'csharp', java: 'java', go: 'go', rs: 'rust', sql: 'sql',
|
|
90
|
+
ini: 'ini', cfg: 'ini', conf: 'ini', toml: 'toml', properties: 'properties',
|
|
91
|
+
gradle: 'groovy', diff: 'diff', patch: 'diff',
|
|
92
|
+
};
|
|
93
|
+
function prismLangFor(file) {
|
|
94
|
+
const base = String(file || '').split(/[\\/]/).pop() || '';
|
|
95
|
+
const idx = base.lastIndexOf('.');
|
|
96
|
+
if (idx <= 0) return null;
|
|
97
|
+
const lang = PRISM_LANG_BY_EXT[base.slice(idx + 1).toLowerCase()];
|
|
98
|
+
return lang && PG_PRISM && PG_PRISM.languages[lang] ? lang : null;
|
|
99
|
+
}
|
|
100
|
+
// 语法高亮单侧文本上限(128KB):整段 tokenize 在 UI 主线程同步执行,超大文件降级纯文本
|
|
101
|
+
const HIGHLIGHT_MAX_CHARS = 131072
|
|
102
|
+
// 从 ops 重建完整 old/new 文本(gap 携带隐藏行;缺失时返回 null 放弃高亮),
|
|
103
|
+
// 并同步收集每行在文本中的位置对应的真实文件行号(oldNums/newNums),供客户端
|
|
104
|
+
// 按行号 Map 取高亮(changesOnly 过滤后文本非连续,不能再用「行号-首行」偏移索引)。
|
|
105
|
+
// 拼接过程中累计长度,任一侧一旦超过 HIGHLIGHT_MAX_CHARS 立即中止返回 null——
|
|
106
|
+
// 避免对超大 payload(gap 最多携带 10 万行隐藏行,见 index.js pushCtxRun)先整段
|
|
107
|
+
// 拼接再丢弃,白白付出主线程内存与拷贝成本。
|
|
108
|
+
function prismTextsFromOps(ops) {
|
|
109
|
+
const oldParts = [];
|
|
110
|
+
const newParts = [];
|
|
111
|
+
const oldNums = [];
|
|
112
|
+
const newNums = [];
|
|
113
|
+
let oldLen = 0;
|
|
114
|
+
let newLen = 0;
|
|
115
|
+
const over = () => oldLen > HIGHLIGHT_MAX_CHARS || newLen > HIGHLIGHT_MAX_CHARS
|
|
116
|
+
for (const op of ops || []) {
|
|
117
|
+
if (op.t === 'g') {
|
|
118
|
+
if (!op.lines) return null;
|
|
119
|
+
for (const l of op.lines) {
|
|
120
|
+
oldParts.push(l.s); newParts.push(l.s);
|
|
121
|
+
oldNums.push(l.o); newNums.push(l.n);
|
|
122
|
+
oldLen += l.s.length + 1; newLen += l.s.length + 1;
|
|
123
|
+
if (over()) return null;
|
|
124
|
+
}
|
|
125
|
+
} else if (op.t === 'a') { newParts.push(op.s); newNums.push(op.n); newLen += op.s.length + 1; if (over()) return null }
|
|
126
|
+
else if (op.t === 'd') { oldParts.push(op.s); oldNums.push(op.o); oldLen += op.s.length + 1; if (over()) return null }
|
|
127
|
+
else { oldParts.push(op.s); newParts.push(op.s); oldNums.push(op.o); newNums.push(op.n); oldLen += op.s.length + 1; newLen += op.s.length + 1; if (over()) return null }
|
|
128
|
+
}
|
|
129
|
+
return { oldText: oldParts.join('\n'), newText: newParts.join('\n'), oldNums, newNums };
|
|
130
|
+
}
|
|
131
|
+
// HTML 转义(高亮行与纯文本降级行共用同一规则,避免两处漂移导致转义不一致)
|
|
132
|
+
const escapeHtml = (s) => s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
133
|
+
// 整段 tokenize 后按行切分:跨行 token(字符串/块注释)在换行处关闭并在下一行重开 span,
|
|
134
|
+
// 每行得到独立完整的 HTML。返回行数组(行 i 对应文本第 i+1 行),失败返回 null。
|
|
135
|
+
function prismHighlightLines(text, lang) {
|
|
136
|
+
if (!PG_PRISM || !lang || !PG_PRISM.languages[lang]) return null;
|
|
137
|
+
try {
|
|
138
|
+
const tokens = PG_PRISM.tokenize(text, PG_PRISM.languages[lang]);
|
|
139
|
+
const lines = [];
|
|
140
|
+
let cur = [];
|
|
141
|
+
const open = [];
|
|
142
|
+
const nl = () => {
|
|
143
|
+
for (let i = 0; i < open.length; i++) cur.push('</span>');
|
|
144
|
+
lines.push(cur.join(''));
|
|
145
|
+
cur = [];
|
|
146
|
+
for (let i = 0; i < open.length; i++) cur.push('<span class="token ' + open[i] + '">');
|
|
147
|
+
};
|
|
148
|
+
const emitText = (s) => {
|
|
149
|
+
const parts = s.split('\n');
|
|
150
|
+
cur.push(escapeHtml(parts[0]));
|
|
151
|
+
for (let i = 1; i < parts.length; i++) { nl(); cur.push(escapeHtml(parts[i])) }
|
|
152
|
+
};
|
|
153
|
+
const walk = (toks) => {
|
|
154
|
+
for (const t of toks) {
|
|
155
|
+
if (typeof t === 'string') { emitText(t); continue }
|
|
156
|
+
const type = String(t.type);
|
|
157
|
+
cur.push('<span class="token ' + type + '">');
|
|
158
|
+
open.push(type);
|
|
159
|
+
if (typeof t.content === 'string') emitText(t.content);
|
|
160
|
+
else walk(t.content);
|
|
161
|
+
open.pop();
|
|
162
|
+
cur.push('</span>');
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
walk(tokens);
|
|
166
|
+
for (let i = 0; i < open.length; i++) cur.push('</span>');
|
|
167
|
+
lines.push(cur.join(''));
|
|
168
|
+
return lines;
|
|
169
|
+
} catch (e) { return null }
|
|
170
|
+
}
|
|
171
|
+
// 分块高亮:按行数与字符预算分块 tokenize,块间跨行 token 断开(与 changesOnly 相同
|
|
172
|
+
// 取舍);配合 idle 调度逐块让出主线程,避免整段文本一次 tokenize 长时间占用主线程。
|
|
173
|
+
// 返回逐步执行器:step() 处理下一块并返回是否还有剩余块,result 为累计的行 HTML。
|
|
174
|
+
const CHUNK_LINES = 400
|
|
175
|
+
const CHUNK_CHARS = 16384
|
|
176
|
+
function chunkHighlighter(text, lang) {
|
|
177
|
+
const lines = text.split('\n');
|
|
178
|
+
const out = [];
|
|
179
|
+
let pos = 0;
|
|
180
|
+
let failed = false;
|
|
181
|
+
// 任一行超过字符预算(length >= CHUNK_CHARS,与下方首行守卫同一阈值):
|
|
182
|
+
// 行首则整行按纯文本转义输出;位于块中段则提前断开块、把该行留给下一轮首行
|
|
183
|
+
// 守卫处理——保证任何一次 tokenize 的输入都不含超长行,避免同步阻塞主线程。
|
|
184
|
+
const step = () => {
|
|
185
|
+
if (failed || pos >= lines.length) return false;
|
|
186
|
+
let end = pos;
|
|
187
|
+
let bytes = 0;
|
|
188
|
+
while (end < lines.length && (end - pos) < CHUNK_LINES && bytes < CHUNK_CHARS) {
|
|
189
|
+
if (end > pos && lines[end].length >= CHUNK_CHARS) break;
|
|
190
|
+
bytes += lines[end].length + 1;
|
|
191
|
+
end++;
|
|
192
|
+
}
|
|
193
|
+
if (end === pos + 1 && bytes > CHUNK_CHARS) {
|
|
194
|
+
out.push(escapeHtml(lines[pos]));
|
|
195
|
+
pos = end;
|
|
196
|
+
return pos < lines.length;
|
|
197
|
+
}
|
|
198
|
+
const chunk = lines.slice(pos, end).join('\n');
|
|
199
|
+
const html = prismHighlightLines(chunk, lang);
|
|
200
|
+
if (html === null) { failed = true; return false }
|
|
201
|
+
for (const h of html) out.push(h);
|
|
202
|
+
pos = end;
|
|
203
|
+
return pos < lines.length;
|
|
204
|
+
};
|
|
205
|
+
return { step, result: out, failed: () => failed };
|
|
206
|
+
}
|
|
207
|
+
// idle 调度封装:优先 requestIdleCallback(带兜底超时),无则 setTimeout;返回取消函数。
|
|
208
|
+
function scheduleIdle(fn, timeout) {
|
|
209
|
+
if (typeof requestIdleCallback === 'function') {
|
|
210
|
+
const id = requestIdleCallback(fn, { timeout });
|
|
211
|
+
return () => { try { cancelIdleCallback(id) } catch (e) {} };
|
|
212
|
+
}
|
|
213
|
+
const id = setTimeout(fn, 0);
|
|
214
|
+
return () => clearTimeout(id);
|
|
215
|
+
}
|
|
216
|
+
// 高亮结果缓存:key 为 diffCache 数据对象(引用稳定)。同一数据在抽屉重开/
|
|
217
|
+
// 详情重展开(组件重挂载)时直接复用已完成的行 HTML,避免重复分块 tokenize。
|
|
218
|
+
const diffHlCache = new WeakMap();
|
|
219
|
+
const readHlCache = new WeakMap();
|
|
220
|
+
// 分块渐进高亮 hook(DiffBlock old/new 双侧与 ReadBlock 单侧共用同一实现,
|
|
221
|
+
// 避免两份状态机漂移):首次使用时同步 ensurePrism(惰性初始化),idle 调度
|
|
222
|
+
// 逐块 tokenize,完成前返回 null(首渲染纯文本、渐进补高亮);结果写入 cache
|
|
223
|
+
// (按 data 引用缓存),重挂载直接复用。
|
|
224
|
+
function useProgressiveHighlight(text, file, cache) {
|
|
225
|
+
const [lineHtml, setLineHtml] = React.useState(() => (cache && cache.text === text) ? cache.html : null);
|
|
226
|
+
React.useEffect(() => {
|
|
227
|
+
ensurePrism();
|
|
228
|
+
const lang = prismLangFor(file);
|
|
229
|
+
if (text == null || text.length > HIGHLIGHT_MAX_CHARS || !lang) { setLineHtml(null); return undefined }
|
|
230
|
+
if (cache && cache.text === text && cache.html) return undefined
|
|
231
|
+
let alive = true;
|
|
232
|
+
const H = chunkHighlighter(text, lang);
|
|
233
|
+
let cancel = null;
|
|
234
|
+
const work = () => {
|
|
235
|
+
if (!alive) return;
|
|
236
|
+
const more = H.step();
|
|
237
|
+
if (H.failed()) { setLineHtml(null); return }
|
|
238
|
+
if (more) { cancel = scheduleIdle(work, 2000); return }
|
|
239
|
+
if (cache) { cache.text = text; cache.html = H.result }
|
|
240
|
+
setLineHtml(H.result);
|
|
241
|
+
};
|
|
242
|
+
cancel = scheduleIdle(work, 2000);
|
|
243
|
+
return () => { alive = false; if (cancel) cancel() };
|
|
244
|
+
}, [text, file]);
|
|
245
|
+
return lineHtml;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const ROUTES = {
|
|
249
|
+
'permgate:status': ['GET', '/permgate/status'],
|
|
250
|
+
'permgate:pending': ['GET', '/permgate/pending'],
|
|
251
|
+
'permgate:file-diff': ['POST', '/permgate/file-diff'],
|
|
252
|
+
'permgate:decide': ['POST', '/permgate/decide'],
|
|
253
|
+
'permgate:set-sandbox': ['POST', '/permgate/set-sandbox'],
|
|
254
|
+
'permgate:set-categories': ['POST', '/permgate/set-categories'],
|
|
255
|
+
'permgate:set-category': ['POST', '/permgate/set-category'],
|
|
256
|
+
'permgate:set-quick': ['POST', '/permgate/set-quick'],
|
|
257
|
+
'permgate:add-exception': ['POST', '/permgate/add-exception'],
|
|
258
|
+
'permgate:remove-exception': ['POST', '/permgate/remove-exception'],
|
|
259
|
+
'permgate:add-rule': ['POST', '/permgate/add-rule'],
|
|
260
|
+
'permgate:remove-rule': ['POST', '/permgate/remove-rule'],
|
|
261
|
+
'permgate:reload': ['POST', '/permgate/reload'],
|
|
262
|
+
'permgate:open-config': ['POST', '/permgate/open-config'],
|
|
263
|
+
'permgate:open-file': ['POST', '/permgate/open-file'],
|
|
264
|
+
};
|
|
265
|
+
function call(method, args) {
|
|
266
|
+
const entry = ROUTES[method] || ['GET', '/permgate/status'];
|
|
267
|
+
const m = entry[0];
|
|
268
|
+
let path = entry[1];
|
|
269
|
+
// 附带当前 dsh 语言(zh/en),宿主按语言返回 reason 等文案;缺失/非法由宿主回退中文
|
|
270
|
+
const lang = (LC && LC.locale && typeof LC.locale.getLocale === 'function') ? LC.locale.getLocale().active : 'zh';
|
|
271
|
+
const opts = { method: m, headers: { accept: 'application/json' } };
|
|
272
|
+
const extra = args || {};
|
|
273
|
+
if (m === 'POST') {
|
|
274
|
+
opts.headers['content-type'] = 'application/json';
|
|
275
|
+
opts.body = JSON.stringify(Object.assign({}, extra, { lang }));
|
|
276
|
+
} else {
|
|
277
|
+
const q = ['lang=' + encodeURIComponent(lang)];
|
|
278
|
+
for (const k of Object.keys(extra)) {
|
|
279
|
+
const v = extra[k];
|
|
280
|
+
if (v !== undefined && v !== null && v !== '') q.push(k + '=' + encodeURIComponent(String(v)));
|
|
281
|
+
}
|
|
282
|
+
path = path + (path.indexOf('?') === -1 ? '?' : '&') + q.join('&');
|
|
283
|
+
}
|
|
284
|
+
return fetch(path, opts).then((r) => r.json());
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// SSE 事件订阅:/permgate/events 长连接推送(状态/待审批变化即时刷新,替代轮询)
|
|
288
|
+
let eventSource = null;
|
|
289
|
+
const eventListeners = new Set();
|
|
290
|
+
function notifyEvents(data) {
|
|
291
|
+
for (const fn of Array.from(eventListeners)) { try { fn(data); } catch (e) {} }
|
|
292
|
+
}
|
|
293
|
+
function ensureEventSource() {
|
|
294
|
+
if (eventSource) return;
|
|
295
|
+
eventSource = new EventSource('/permgate/events');
|
|
296
|
+
eventSource.onmessage = (ev) => {
|
|
297
|
+
let data = null;
|
|
298
|
+
try { data = JSON.parse(ev.data); } catch (e) {}
|
|
299
|
+
if (data) notifyEvents(data);
|
|
300
|
+
};
|
|
301
|
+
// 首连与断线重连成功都会触发:全量刷新兜底,收敛重连期间丢失的事件
|
|
302
|
+
eventSource.onopen = () => notifyEvents({ type: 'refresh' });
|
|
303
|
+
eventSource.onerror = () => { /* EventSource 自动重连 */ };
|
|
304
|
+
}
|
|
305
|
+
function subscribeEvents(fn) {
|
|
306
|
+
eventListeners.add(fn);
|
|
307
|
+
ensureEventSource();
|
|
308
|
+
return () => {
|
|
309
|
+
eventListeners.delete(fn);
|
|
310
|
+
if (eventListeners.size === 0 && eventSource) {
|
|
311
|
+
try { eventSource.close(); } catch (e) {}
|
|
312
|
+
eventSource = null;
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ── 权限选择器图标兼容层(参照 dsh-auto-approve 的 DOM best-effort 方案)──
|
|
318
|
+
// rc.6 的权限选择器不提供自定义 preset 图标 API(glyph 为内置三个 key 硬编码)。
|
|
319
|
+
// 这里用 MutationObserver 识别「自定义审查」的触发器与菜单项,注入盾牌+放大镜
|
|
320
|
+
// svg 徽标(-webkit-mask 跟随 currentColor)。依赖 DOM 结构与无障碍文案,
|
|
321
|
+
// dsh 升级后可能失效——仅影响图标显示,不影响审查功能。
|
|
322
|
+
const PG_STYLE = 'data-dsh-permgate-style';
|
|
323
|
+
const PG_CUSTOM_LABEL = '自定义审查';
|
|
324
|
+
const PG_NAME_ZH = '自定义审查';
|
|
325
|
+
const PG_NAME_EN = 'Custom Review';
|
|
326
|
+
const PG_DESC_ZH = '按分类逐项审查工具调用;被底层沙箱拒绝时弹出升级审批';
|
|
327
|
+
const PG_DESC_EN = 'Per-category tool review; sandbox denials trigger an upgrade approval';
|
|
328
|
+
const PG_PERMISSION_LABELS = new Set(['Read Only', 'Workspace Write', '自定义审查', 'Custom Review', 'Full access', '仅可查看', '工作区内修改', '完全权限']);
|
|
329
|
+
const PG_TRIGGER_PREFIXES = ['访问模式,当前:', 'Access mode, current: '];
|
|
330
|
+
const PG_TRIGGER_SELECTOR = ['button[aria-label^="访问模式,当前:"]', 'button[aria-label^="Access mode, current: "]'].join(',');
|
|
331
|
+
const PG_MASK = 'url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22 fill=%22none%22%3E%3Cpath d=%22M8.20554%200.899994L14.7901%203.36857V7.01026C14.7901%2012%2011.0466%2014.2103%208.20554%2015.3C5.36446%2014.2103%201.62012%2012%201.62012%207.01026V3.36857L8.20554%200.899994Z%22 stroke=%22black%22 stroke-width=%221.31831%22 stroke-linejoin=%22round%22/%3E%3Ccircle cx=%227%22 cy=%227%22 r=%222.9%22 stroke=%22black%22 stroke-width=%221.2%22/%3E%3Cpath d=%22M9.2%209.2L11.6%2011.6%22 stroke=%22black%22 stroke-width=%221.4%22 stroke-linecap=%22round%22/%3E%3C/svg%3E")';
|
|
332
|
+
const PG_CSS = '\n' +
|
|
333
|
+
// 触发器徽标:纯 CSS 按 aria-label 选择(平台 t() 本地化前缀 + 两种语言的
|
|
334
|
+
// 预设名,四个变体全覆盖)。无需 JS 打标,跟随语言/预设自动显示,重渲染不丢失。
|
|
335
|
+
'button[aria-label^="访问模式,当前:自定义审查"]::before,' +
|
|
336
|
+
'button[aria-label^="Access mode, current: 自定义审查"]::before,' +
|
|
337
|
+
'button[aria-label^="访问模式,当前:Custom Review"]::before,' +
|
|
338
|
+
'button[aria-label^="Access mode, current: Custom Review"]::before' +
|
|
339
|
+
' { content: ""; display: inline-block; flex: 0 0 auto; width: 14px; height: 14px; margin-right: 4px; background-color: currentColor; -webkit-mask: ' + PG_MASK + ' center / contain no-repeat; mask: ' + PG_MASK + ' center / contain no-repeat; }\n';
|
|
340
|
+
const pgNoop = () => {};
|
|
341
|
+
function pgTriggerLabel(value) {
|
|
342
|
+
return typeof value === 'string' && PG_TRIGGER_PREFIXES.some((p) => value.startsWith(p));
|
|
343
|
+
}
|
|
344
|
+
// 预设名/描述文本的语言化交换(best-effort DOM 层):平台权限选择器对
|
|
345
|
+
// preset name/description 无 i18n 钩子(内置项在两种语言下均为英文),
|
|
346
|
+
// 这里在 en 语言下把「自定义审查」替换为 Custom Review(含触发器
|
|
347
|
+
// aria-label),在 zh 语言下把残留的英文替换回中文。仅限定权限选择器
|
|
348
|
+
// 相关表面(触发器/按钮/菜单/列表项),绝不触碰聊天内容等任意文本。
|
|
349
|
+
function pgActiveLang() {
|
|
350
|
+
// 三级回退:locale 服务 → <html lang> → 平台触发器 aria-label 前缀
|
|
351
|
+
// (前缀是平台 t() 本地化渲染的,直接反映界面实际语言)
|
|
352
|
+
try {
|
|
353
|
+
const l = LC && LC.locale && typeof LC.locale.getLocale === 'function' ? LC.locale.getLocale().active : '';
|
|
354
|
+
if (l === 'en' || l === 'zh') return l;
|
|
355
|
+
} catch (e) {}
|
|
356
|
+
try {
|
|
357
|
+
const htmlLang = document.documentElement && document.documentElement.getAttribute && document.documentElement.getAttribute('lang');
|
|
358
|
+
if (htmlLang === 'en' || htmlLang === 'zh') return htmlLang;
|
|
359
|
+
} catch (e) {}
|
|
360
|
+
try {
|
|
361
|
+
const el = document.querySelector && document.querySelector(PG_TRIGGER_SELECTOR);
|
|
362
|
+
const label = el && el.getAttribute && el.getAttribute('aria-label');
|
|
363
|
+
if (typeof label === 'string') {
|
|
364
|
+
if (label.indexOf('Access mode') !== -1) return 'en';
|
|
365
|
+
if (label.indexOf('访问模式') !== -1) return 'zh';
|
|
366
|
+
}
|
|
367
|
+
} catch (e) {}
|
|
368
|
+
return 'zh';
|
|
369
|
+
}
|
|
370
|
+
function pgSwapText(el, from, to) {
|
|
371
|
+
try {
|
|
372
|
+
if (!el || typeof el.textContent !== 'string') return;
|
|
373
|
+
// 只原位修改直接文本子节点的值(nodeValue),绝不 textContent 赋值或
|
|
374
|
+
// 替换节点:React 持有元素/文本节点引用,整体替换会破坏其子树结构
|
|
375
|
+
// 导致渲染崩溃(曾因此打不开下拉框)。React 重渲染只会把同一文本
|
|
376
|
+
// 节点的值改回,观察器再换回来,结构始终完好。
|
|
377
|
+
for (const node of Array.from(el.childNodes || [])) {
|
|
378
|
+
if (node.nodeType !== 3) continue;
|
|
379
|
+
const v = node.nodeValue || '';
|
|
380
|
+
if (v.indexOf(from) === -1) continue;
|
|
381
|
+
const next = v.split(from).join(to);
|
|
382
|
+
if (next !== v) node.nodeValue = next;
|
|
383
|
+
}
|
|
384
|
+
} catch (e) {}
|
|
385
|
+
}
|
|
386
|
+
function pgSwapAttr(el, attr, from, to) {
|
|
387
|
+
try {
|
|
388
|
+
const v = el.getAttribute && el.getAttribute(attr);
|
|
389
|
+
if (typeof v !== 'string' || v.indexOf(from) === -1) return;
|
|
390
|
+
const next = v.split(from).join(to);
|
|
391
|
+
if (next !== v) el.setAttribute(attr, next);
|
|
392
|
+
} catch (e) {}
|
|
393
|
+
}
|
|
394
|
+
const PG_SWAP_SELECTOR = 'button,[role="menuitem"],[role="menuitemradio"],[role="menuitemcheckbox"],[role="option"],[role="menu"],[role="listbox"]';
|
|
395
|
+
// 就地替换某元素首个文本子节点的值(仅当其为唯一的直接文本子节点)
|
|
396
|
+
function pgSwapSingleText(el, value) {
|
|
397
|
+
try {
|
|
398
|
+
if (!el || typeof el.textContent !== 'string') return false;
|
|
399
|
+
let hit = null;
|
|
400
|
+
for (const node of Array.from(el.childNodes || [])) {
|
|
401
|
+
if (node.nodeType !== 3) continue;
|
|
402
|
+
const v = node.nodeValue || '';
|
|
403
|
+
if (v.trim() === '') continue;
|
|
404
|
+
if (hit !== null) return false; // 多个文本子节点,不整段替换
|
|
405
|
+
hit = node;
|
|
406
|
+
}
|
|
407
|
+
if (hit === null) return false;
|
|
408
|
+
if (hit.nodeValue !== value) { hit.nodeValue = value; return true; }
|
|
409
|
+
} catch (e) {}
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
function pgScanText(document) {
|
|
413
|
+
try {
|
|
414
|
+
const lang = pgActiveLang();
|
|
415
|
+
const from = lang === 'en' ? PG_NAME_ZH : PG_NAME_EN;
|
|
416
|
+
const to = lang === 'en' ? PG_NAME_EN : PG_NAME_ZH;
|
|
417
|
+
const fromDesc = lang === 'en' ? PG_DESC_ZH : PG_DESC_EN;
|
|
418
|
+
const toDesc = lang === 'en' ? PG_DESC_EN : PG_DESC_ZH;
|
|
419
|
+
const body = document.body;
|
|
420
|
+
if (!body) return;
|
|
421
|
+
// 轻量早退:目标名不在页面中就跳过(交换完成后通常立即命中此处,
|
|
422
|
+
// 使流式渲染期间的扫描成本≈一次 textContent 读取)
|
|
423
|
+
const bodyText = body.textContent || '';
|
|
424
|
+
if (bodyText.indexOf(from) === -1) return;
|
|
425
|
+
// 1) 触发器按钮:aria-label 按前缀切出名称段,仅当名称恰为 zh/en 名时
|
|
426
|
+
// 替换(绝不误改 Read Only 等其他预设名);可见文本整段就地替换
|
|
427
|
+
for (const el of Array.from(document.querySelectorAll(PG_TRIGGER_SELECTOR))) {
|
|
428
|
+
const label = el.getAttribute && el.getAttribute('aria-label');
|
|
429
|
+
if (typeof label === 'string') {
|
|
430
|
+
for (const p of PG_TRIGGER_PREFIXES) {
|
|
431
|
+
if (label.length > p.length && label.startsWith(p)) {
|
|
432
|
+
const name = label.slice(p.length);
|
|
433
|
+
if (name === PG_NAME_ZH && lang === 'en') {
|
|
434
|
+
try { el.setAttribute('aria-label', p + PG_NAME_EN); } catch (e) {}
|
|
435
|
+
} else if (name === PG_NAME_EN && lang === 'zh') {
|
|
436
|
+
try { el.setAttribute('aria-label', p + PG_NAME_ZH); } catch (e) {}
|
|
437
|
+
}
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
pgSwapAttr(el, 'title', from, to);
|
|
443
|
+
pgSwapAttr(el, 'title', fromDesc, toDesc);
|
|
444
|
+
// 可见文本:触发器自身及后代叶子中,文本恰为「非当前语言名」的整段替换
|
|
445
|
+
const nodes = [el];
|
|
446
|
+
try { nodes.push.apply(nodes, Array.from(el.querySelectorAll('*'))); } catch (e) {}
|
|
447
|
+
for (const n of nodes) {
|
|
448
|
+
if (n.children && n.children.length) continue;
|
|
449
|
+
const t = (n.textContent || '').trim();
|
|
450
|
+
if (t !== from) continue;
|
|
451
|
+
pgSwapSingleText(n, to);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
// 2) 按钮/菜单/列表项:子串交换(名称与描述)+ title 交换
|
|
455
|
+
for (const el of Array.from(document.querySelectorAll(PG_SWAP_SELECTOR))) {
|
|
456
|
+
pgSwapText(el, from, to);
|
|
457
|
+
pgSwapText(el, fromDesc, toDesc);
|
|
458
|
+
pgSwapAttr(el, 'title', from, to);
|
|
459
|
+
pgSwapAttr(el, 'title', fromDesc, toDesc);
|
|
460
|
+
}
|
|
461
|
+
// 3) 按钮/菜单表面的叶子后代:子串交换(菜单项 label/detail 可能
|
|
462
|
+
// 是独立叶子元素)
|
|
463
|
+
for (const el of Array.from(document.querySelectorAll('button *,[role="menu"] *,[role="listbox"] *,[role="combobox"] *'))) {
|
|
464
|
+
if (el.children && el.children.length) continue;
|
|
465
|
+
pgSwapText(el, from, to);
|
|
466
|
+
pgSwapText(el, fromDesc, toDesc);
|
|
467
|
+
}
|
|
468
|
+
} catch (e) {}
|
|
469
|
+
}
|
|
470
|
+
function pgTouchesSurface(node) {
|
|
471
|
+
try {
|
|
472
|
+
if (!node || node.nodeType !== 1) return false;
|
|
473
|
+
if (node.getAttribute && node.getAttribute('data-composer-seat') !== null) return true;
|
|
474
|
+
if (node.getAttribute && node.getAttribute('role') === 'menu') return true;
|
|
475
|
+
if (pgTriggerLabel(node.getAttribute && node.getAttribute('aria-label'))) return true;
|
|
476
|
+
if (typeof node.querySelector === 'function') {
|
|
477
|
+
if (node.querySelector('[data-composer-seat]') !== null) return true;
|
|
478
|
+
if (node.querySelector('[role="menu"]') !== null) return true;
|
|
479
|
+
if (node.querySelector(PG_TRIGGER_SELECTOR) !== null) return true;
|
|
480
|
+
}
|
|
481
|
+
return typeof node.closest === 'function' && node.closest('[data-composer-seat]') !== null;
|
|
482
|
+
} catch (e) { return false; }
|
|
483
|
+
}
|
|
484
|
+
function pgInsideComposer(node) {
|
|
485
|
+
try {
|
|
486
|
+
if (!node || node.nodeType !== 1) return false;
|
|
487
|
+
if (node.getAttribute && node.getAttribute('data-composer-seat') !== null) return true;
|
|
488
|
+
return typeof node.closest === 'function' && node.closest('[data-composer-seat]') !== null;
|
|
489
|
+
} catch (e) { return false; }
|
|
490
|
+
}
|
|
491
|
+
function pgRelevant(records) {
|
|
492
|
+
try {
|
|
493
|
+
for (const record of records) {
|
|
494
|
+
if (record.type === 'attributes') {
|
|
495
|
+
if (record.attributeName === 'aria-label') {
|
|
496
|
+
if (pgTriggerLabel(record.target && record.target.getAttribute && record.target.getAttribute('aria-label'))) return true;
|
|
497
|
+
}
|
|
498
|
+
if (record.attributeName === 'role' && pgTouchesSurface(record.target)) return true;
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
// React 更新既有文本节点走 nodeValue(characterData 突变),
|
|
502
|
+
// 不产生 childList 记录 —— en 下必须监听它才能把被改回的中文换回来
|
|
503
|
+
if (record.type === 'characterData') {
|
|
504
|
+
if (pgActiveLang() === 'en') return true;
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
if (record.type !== 'childList') continue;
|
|
508
|
+
// en 语言下需要全局扫描(设置页行、弹层菜单等不位于 composer seat)
|
|
509
|
+
if (pgActiveLang() === 'en') return true;
|
|
510
|
+
if (pgInsideComposer(record.target)) return true;
|
|
511
|
+
for (const node of record.addedNodes || []) { if (pgTouchesSurface(node)) return true; }
|
|
512
|
+
for (const node of record.removedNodes || []) { if (pgTouchesSurface(node)) return true; }
|
|
513
|
+
}
|
|
514
|
+
} catch (e) {}
|
|
515
|
+
return false;
|
|
516
|
+
}
|
|
517
|
+
function pgCustomItem(container) {
|
|
518
|
+
try {
|
|
519
|
+
const labels = new Set();
|
|
520
|
+
let custom = undefined;
|
|
521
|
+
// 兼容各代权限选择器结构:旧 Menu(menuitem*)、0.1.2 popupSelect
|
|
522
|
+
// (div[role="option"])。label 一律优先取首个非空 SPAN 子元素,
|
|
523
|
+
// 排除 detail/check 兄弟节点,避免文本并入误判;无 SPAN 时回退整项文本。
|
|
524
|
+
const items = Array.from(container.querySelectorAll('[role="menuitem"], [role="menuitemradio"], [role="menuitemcheckbox"], [role="option"]'));
|
|
525
|
+
for (const item of items) {
|
|
526
|
+
let label = '';
|
|
527
|
+
// 优先取第一个非空 SPAN 子元素(label),排除 detail/check 兄弟,
|
|
528
|
+
// 避免文本并入误判;无 SPAN 时回退整项文本(旧 Menu 按钮结构)
|
|
529
|
+
for (const child of Array.from(item.children || [])) {
|
|
530
|
+
if (child.tagName !== 'SPAN') continue;
|
|
531
|
+
const t = (child.textContent || '').trim();
|
|
532
|
+
if (t) { label = t; break; }
|
|
533
|
+
}
|
|
534
|
+
if (!label) label = item.textContent ? item.textContent.trim() : '';
|
|
535
|
+
if (!label || labels.has(label)) continue;
|
|
536
|
+
labels.add(label);
|
|
537
|
+
if (label === PG_CUSTOM_LABEL || label === PG_NAME_EN || label.indexOf(PG_CUSTOM_LABEL) !== -1 || label.indexOf(PG_NAME_EN) !== -1) custom = item;
|
|
538
|
+
}
|
|
539
|
+
if (!custom) return undefined;
|
|
540
|
+
let hits = 0;
|
|
541
|
+
for (const want of PG_PERMISSION_LABELS) {
|
|
542
|
+
if (labels.has(want)) hits++;
|
|
543
|
+
}
|
|
544
|
+
// 至少命中 3 项预设标签才认定是权限选择器(宽容标签变体;其他菜单
|
|
545
|
+
// 不可能同时含有自定义审查/多项预设名,误伤概率为零)
|
|
546
|
+
return hits >= 3 ? custom : undefined;
|
|
547
|
+
} catch (e) { return undefined; }
|
|
548
|
+
}
|
|
549
|
+
// ── 菜单项徽标:向 label span 注入内联样式图标元素。不依赖平台按钮布局
|
|
550
|
+
// (::before 在 flex/grid 布局下可能不可见),inline-block 与文本同行;
|
|
551
|
+
// React 不管理未知子元素,重渲染/切语言不丢失,卸载时统一清理。──
|
|
552
|
+
const pgMenuIcons = new Set();
|
|
553
|
+
const PG_MENU_ICON_ATTR = 'data-pg-menu-icon';
|
|
554
|
+
function pgEnsureMenuIcon(document, item) {
|
|
555
|
+
try {
|
|
556
|
+
let label = null;
|
|
557
|
+
for (const child of Array.from(item.children || [])) {
|
|
558
|
+
if (child.tagName !== 'SPAN') continue;
|
|
559
|
+
const t = (child.textContent || '').trim();
|
|
560
|
+
if (t === PG_CUSTOM_LABEL || t === PG_NAME_EN || t.indexOf(PG_CUSTOM_LABEL) !== -1 || t.indexOf(PG_NAME_EN) !== -1) { label = child; break; }
|
|
561
|
+
}
|
|
562
|
+
if (!label || label.querySelector('[' + PG_MENU_ICON_ATTR + ']')) return;
|
|
563
|
+
const icon = document.createElement('span');
|
|
564
|
+
icon.setAttribute(PG_MENU_ICON_ATTR, '1');
|
|
565
|
+
icon.setAttribute('aria-hidden', 'true');
|
|
566
|
+
icon.style.cssText = 'display:inline-block;flex:none;width:14px;height:14px;margin-right:6px;vertical-align:-2px;background-color:currentColor;-webkit-mask:' + PG_MASK + ' center/contain no-repeat;mask:' + PG_MASK + ' center/contain no-repeat;';
|
|
567
|
+
label.insertBefore(icon, label.firstChild);
|
|
568
|
+
pgMenuIcons.add(icon);
|
|
569
|
+
} catch (e) {}
|
|
570
|
+
}
|
|
571
|
+
function pgScanIcons(document) {
|
|
572
|
+
try {
|
|
573
|
+
// 菜单/选择器是 portal 渲染(不位于触发器旁边),全文档扫描所有
|
|
574
|
+
// role=menu(旧 Menu 组件)与 role=listbox(0.1.2 popupSelect);
|
|
575
|
+
// pgCustomItem 要求多项预设齐全才认,误伤其他菜单的概率为零
|
|
576
|
+
for (const menu of Array.from(document.querySelectorAll('[role="menu"], [role="listbox"]'))) {
|
|
577
|
+
const customItem = pgCustomItem(menu);
|
|
578
|
+
if (customItem !== undefined) pgEnsureMenuIcon(document, customItem);
|
|
579
|
+
}
|
|
580
|
+
} catch (e) {}
|
|
581
|
+
}
|
|
582
|
+
function pgScanAll(document) {
|
|
583
|
+
try { pgScanText(document); } catch (e) {}
|
|
584
|
+
try { pgScanIcons(document); } catch (e) {}
|
|
585
|
+
}
|
|
586
|
+
function pgInstallCompat() {
|
|
587
|
+
const document = window.document;
|
|
588
|
+
const Observer = window.MutationObserver;
|
|
589
|
+
const requestFrame = window.requestAnimationFrame;
|
|
590
|
+
const cancelFrame = window.cancelAnimationFrame;
|
|
591
|
+
if (!document || typeof Observer !== 'function' || typeof requestFrame !== 'function') return pgNoop;
|
|
592
|
+
let stopped = false;
|
|
593
|
+
let frame;
|
|
594
|
+
let styleNode = null;
|
|
595
|
+
try {
|
|
596
|
+
styleNode = document.createElement('style');
|
|
597
|
+
styleNode.setAttribute(PG_STYLE, 'client');
|
|
598
|
+
styleNode.textContent = PG_CSS;
|
|
599
|
+
(document.head || document.documentElement).appendChild(styleNode);
|
|
600
|
+
} catch (e) { styleNode = null; }
|
|
601
|
+
const scan = () => {
|
|
602
|
+
try { pgScanAll(document); } catch (e) {}
|
|
603
|
+
};
|
|
604
|
+
const runScan = () => {
|
|
605
|
+
frame = undefined;
|
|
606
|
+
if (stopped) return;
|
|
607
|
+
try { scan(); } catch (e) {}
|
|
608
|
+
};
|
|
609
|
+
const schedule = () => {
|
|
610
|
+
if (stopped || frame !== undefined) return;
|
|
611
|
+
try { frame = requestFrame.call(window, runScan); } catch (e) {}
|
|
612
|
+
};
|
|
613
|
+
let observer;
|
|
614
|
+
try {
|
|
615
|
+
observer = new Observer((records) => {
|
|
616
|
+
try { if (pgRelevant(records)) schedule(); } catch (e) {}
|
|
617
|
+
});
|
|
618
|
+
observer.observe(document.documentElement, {
|
|
619
|
+
subtree: true,
|
|
620
|
+
childList: true,
|
|
621
|
+
attributes: true,
|
|
622
|
+
characterData: true,
|
|
623
|
+
attributeFilter: ['aria-label', 'role'],
|
|
624
|
+
});
|
|
625
|
+
schedule();
|
|
626
|
+
} catch (e) {
|
|
627
|
+
try { if (observer) observer.disconnect(); } catch (e2) {}
|
|
628
|
+
if (styleNode && styleNode.parentElement) { try { styleNode.remove(); } catch (e2) {} }
|
|
629
|
+
return pgNoop;
|
|
630
|
+
}
|
|
631
|
+
// 事件驱动的兜底调度:点击/键盘聚焦任何元素后,下一帧扫描一次。
|
|
632
|
+
// 菜单由 portal 挂载,点击触发器的下一帧菜单必然已在 DOM 中 ——
|
|
633
|
+
// 即使 MutationObserver 失效,菜单项图标注入与文本交换仍然可靠。
|
|
634
|
+
const onInteraction = () => { schedule(); };
|
|
635
|
+
try {
|
|
636
|
+
document.addEventListener('click', onInteraction, true);
|
|
637
|
+
document.addEventListener('focusin', onInteraction, true);
|
|
638
|
+
} catch (e) {}
|
|
639
|
+
let offLocale = null;
|
|
640
|
+
try {
|
|
641
|
+
if (LC && LC.locale && typeof LC.locale.subscribe === 'function') {
|
|
642
|
+
offLocale = LC.locale.subscribe(() => schedule());
|
|
643
|
+
}
|
|
644
|
+
} catch (e) {}
|
|
645
|
+
return () => {
|
|
646
|
+
if (stopped) return;
|
|
647
|
+
stopped = true;
|
|
648
|
+
try { observer.disconnect(); } catch (e) {}
|
|
649
|
+
try { document.removeEventListener('click', onInteraction, true); } catch (e) {}
|
|
650
|
+
try { document.removeEventListener('focusin', onInteraction, true); } catch (e) {}
|
|
651
|
+
if (offLocale) { try { offLocale(); } catch (e) {} }
|
|
652
|
+
if (frame !== undefined && typeof cancelFrame === 'function') {
|
|
653
|
+
try { cancelFrame.call(window, frame); } catch (e) {}
|
|
654
|
+
}
|
|
655
|
+
frame = undefined;
|
|
656
|
+
for (const icon of Array.from(pgMenuIcons)) { try { icon.remove(); } catch (e) {} }
|
|
657
|
+
pgMenuIcons.clear();
|
|
658
|
+
if (styleNode && styleNode.parentElement) { try { styleNode.remove(); } catch (e) {} }
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const CATS = ['directory', 'command', 'read', 'edit', 'subagent', 'doomloop'];
|
|
663
|
+
const EXC_CATS = ['directory', 'command', 'read', 'edit'];
|
|
664
|
+
// 例外列表超过该数量默认折叠(展开/折叠按钮在标题行右侧)
|
|
665
|
+
const EXC_COLLAPSE_THRESHOLD = 6;
|
|
666
|
+
// 文件对比缓存:按审批 id 缓存 /permgate/file-diff 结果(弹窗详情与右侧抽屉共用)
|
|
667
|
+
const diffCache = new Map();
|
|
668
|
+
const diffFetching = new Set();
|
|
669
|
+
const ALL_MODES = ['ask', 'allow', 'deny', 'inherit'];
|
|
670
|
+
const MODES = ['ask', 'allow', 'deny'];
|
|
671
|
+
const MODE_COLORS = { ask: '#e65100', allow: '#2e7d32', deny: '#c62828', inherit: '#888' };
|
|
672
|
+
const QUICK_PRESET = ['web_search', 'skill', 'grep', 'glob', 'web_fetch'];
|
|
673
|
+
|
|
674
|
+
// ── 中英文适配:字典 + locale 绑定(跟随 dsh 语言设置自动切换)──────────
|
|
675
|
+
let LC = null; // apply 时注入的 ctx
|
|
676
|
+
const I18N = {
|
|
677
|
+
zh: {
|
|
678
|
+
'app.title': '权限审批',
|
|
679
|
+
'app.request': '{tool} 请求执行',
|
|
680
|
+
'app.intent': '意图说明',
|
|
681
|
+
'app.args': '参数',
|
|
682
|
+
'app.cand.head': '添加项目规则(当前项目)',
|
|
683
|
+
'app.cand.hint': '点亮条目后点击「允许 / 拒绝」,选中的条目自动加入当前项目例外,之后不再询问。',
|
|
684
|
+
'app.allow': '允许',
|
|
685
|
+
'app.deny': '拒绝',
|
|
686
|
+
'app.allowOne': '√ 允许',
|
|
687
|
+
'app.denyOne': '× 拒绝',
|
|
688
|
+
'app.denyReason': '拒绝意见',
|
|
689
|
+
'app.denyReasonPh': '选填:填写后作为拒绝理由告知 agent(默认为「用户拒绝」)',
|
|
690
|
+
'app.confirmDeny': '确认拒绝',
|
|
691
|
+
'app.detail': '详情',
|
|
692
|
+
'app.diffNew': '新增',
|
|
693
|
+
'app.diffMod': '修改',
|
|
694
|
+
'app.diffMore': '其余 {n} 行',
|
|
695
|
+
'app.diffLoading': '加载中…',
|
|
696
|
+
'app.diffErr': '无法生成对比',
|
|
697
|
+
'app.uiErr': '界面渲染出错',
|
|
698
|
+
'app.eolNote': '行尾已按 LF 归一化匹配(磁盘为 CRLF/CR 而参数为 LF):预览仅为意图展示,实际编辑可能因行尾不一致失败',
|
|
699
|
+
'app.diffGapShow': '显示 {n} 行未更改',
|
|
700
|
+
'app.diffGapHide': '隐藏 {n} 行',
|
|
701
|
+
'app.diffGapMore': '… {n} 行未更改',
|
|
702
|
+
'app.diffCopy': '复制',
|
|
703
|
+
'app.diffCopied': '已复制',
|
|
704
|
+
'app.diffExpand': '展开',
|
|
705
|
+
'app.diffCollapse': '收缩',
|
|
706
|
+
'app.diffRead': '读取',
|
|
707
|
+
'app.diffLines': '{n} 行',
|
|
708
|
+
'app.diffTopMore': '… 上方还有 {n} 行',
|
|
709
|
+
'app.diffBottomMore': '… 下方还有更多行',
|
|
710
|
+
'app.diffClose': '关闭',
|
|
711
|
+
'app.openFile': '打开文件',
|
|
712
|
+
'app.editOpened': '已用默认编辑器打开',
|
|
713
|
+
'app.diffResize': '拖动调整宽度',
|
|
714
|
+
'dock.title': '● 权限网关',
|
|
715
|
+
'settings.title': '权限网关',
|
|
716
|
+
'dock.refresh': '刷新',
|
|
717
|
+
'dock.loading': '加载中…',
|
|
718
|
+
'cat.directory': '目录访问(工作区外)',
|
|
719
|
+
'cat.command': '执行命令',
|
|
720
|
+
'cat.read': '读取文件',
|
|
721
|
+
'cat.edit': '编辑文件',
|
|
722
|
+
'cat.subagent': '启动子代理',
|
|
723
|
+
'cat.doomloop': '重复操作(Doom Loop)',
|
|
724
|
+
'catS.directory': '目录',
|
|
725
|
+
'catS.command': '命令',
|
|
726
|
+
'catS.read': '读取',
|
|
727
|
+
'catS.edit': '编辑',
|
|
728
|
+
'catS.subagent': '子代理',
|
|
729
|
+
'catS.doomloop': '循环',
|
|
730
|
+
'mode.ask': '询问',
|
|
731
|
+
'mode.allow': '允许',
|
|
732
|
+
'mode.deny': '拒绝',
|
|
733
|
+
'mode.inherit': '继承全局',
|
|
734
|
+
'panel.status': '状态',
|
|
735
|
+
'panel.sessionPerm': '当前会话权限',
|
|
736
|
+
'panel.customReview': '自定义审查',
|
|
737
|
+
'panel.none': '未选择',
|
|
738
|
+
'panel.active': '权限网关审查生效中',
|
|
739
|
+
'panel.inactive': '权限网关未生效:请在会话的权限选择中选择「自定义审查」',
|
|
740
|
+
'panel.sandboxEff': '底层沙箱(生效)',
|
|
741
|
+
'panel.sandboxDesc.ww': '(文件操作限制在工作区内)',
|
|
742
|
+
'panel.sandboxDesc.fa': '(文件操作不受工作区限制)',
|
|
743
|
+
'panel.effCats': '生效分类默认:',
|
|
744
|
+
'panel.config': '配置文件:',
|
|
745
|
+
'panel.project': '项目:',
|
|
746
|
+
'panel.stats': '最近统计:拦截 {d} · 审批 {a}',
|
|
747
|
+
'panel.loadErr': '加载错误:',
|
|
748
|
+
'panel.saveErr': '保存错误:',
|
|
749
|
+
'panel.tabGlobal': '全局',
|
|
750
|
+
'panel.tabProject': '项目',
|
|
751
|
+
'panel.editGlobal': '正在编辑全局设置(所有项目的默认)。',
|
|
752
|
+
'panel.editProject': '正在编辑当前项目({p}),项目设置优先于全局。',
|
|
753
|
+
'panel.sandbox': '底层沙箱',
|
|
754
|
+
'sandbox.ww': 'Workspace Write',
|
|
755
|
+
'sandbox.fa': 'Full access',
|
|
756
|
+
'sandbox.inherit': '继承全局',
|
|
757
|
+
'sandbox.desc.ww': '文件操作限制在工作区内',
|
|
758
|
+
'sandbox.desc.fa': '文件操作不受工作区限制',
|
|
759
|
+
'sandbox.desc.inherit': '跟随全局设置(当前全局:{g})',
|
|
760
|
+
'panel.quick': '快捷工具(其他工具快速设置)',
|
|
761
|
+
'panel.quickHint': '适用于网页搜索、技能加载、grep/glob 等未归类的工具;改动即时生效。',
|
|
762
|
+
'panel.quickAdd': '新增工具名(如 todo_write,支持通配)',
|
|
763
|
+
'panel.quickAddBtn': '添加(默认允许)',
|
|
764
|
+
'panel.rules': '自定义规则(通用匹配)',
|
|
765
|
+
'panel.rulesHint': '优先级:自定义规则 > 分类例外 > 分类默认/快捷工具。',
|
|
766
|
+
'panel.actionDeny': 'deny 拒绝',
|
|
767
|
+
'panel.actionAsk': 'ask 审批',
|
|
768
|
+
'panel.actionAllow': 'allow 放行',
|
|
769
|
+
'panel.toolPh': 'tool 通配(如 cordis_*)',
|
|
770
|
+
'panel.pathPh': 'path glob',
|
|
771
|
+
'panel.argsPh': 'args 子串',
|
|
772
|
+
'panel.reasonPh': '原因(可选)',
|
|
773
|
+
'panel.addToGlobal': '添加到全局',
|
|
774
|
+
'panel.addToProject': '添加到项目',
|
|
775
|
+
'panel.ruleList': '{t}规则({n})',
|
|
776
|
+
'panel.noRules': '暂无规则',
|
|
777
|
+
'panel.decisions': '最近决策',
|
|
778
|
+
'panel.noDecisions': '暂无决策记录',
|
|
779
|
+
'panel.reload': '重新加载配置文件',
|
|
780
|
+
'panel.openConfig': '打开',
|
|
781
|
+
'panel.openConfigDone': '已用默认编辑器打开配置文件',
|
|
782
|
+
'panel.saved': '已保存',
|
|
783
|
+
'panel.savedToGlobal': '已保存到全局',
|
|
784
|
+
'panel.savedToProject': '已保存到当前项目',
|
|
785
|
+
'panel.all': '(全部)',
|
|
786
|
+
'panel.sandboxSaved': '底层沙箱已保存({t}):{v}',
|
|
787
|
+
'panel.needValue': '请先输入例外匹配',
|
|
788
|
+
'panel.needTool': '请输入工具名',
|
|
789
|
+
'panel.excCount': '例外({n})',
|
|
790
|
+
'panel.excCmdHint': ':匹配命令子串/glob,优先于分类默认',
|
|
791
|
+
'panel.excPathHint': ':路径 glob,优先于分类默认',
|
|
792
|
+
'panel.excNone': '无例外',
|
|
793
|
+
'panel.excExpand': '展开',
|
|
794
|
+
'panel.excCollapse': '折叠',
|
|
795
|
+
'panel.excListLink': '例外列表',
|
|
796
|
+
'panel.excUnit': '条',
|
|
797
|
+
'panel.excPh': '命令子串/glob',
|
|
798
|
+
'panel.excPathPh': '路径 glob',
|
|
799
|
+
'panel.excReason': '拒绝原因',
|
|
800
|
+
'panel.excReasonPh': '选填:拒绝时显示的提示',
|
|
801
|
+
'panel.addExc': '添加例外',
|
|
802
|
+
'panel.confirmDel': '确认删除?',
|
|
803
|
+
'panel.cancel': '取消',
|
|
804
|
+
'panel.del': '删除',
|
|
805
|
+
'panel.allow': '允许',
|
|
806
|
+
'panel.deny': '拒绝',
|
|
807
|
+
},
|
|
808
|
+
en: {
|
|
809
|
+
'app.title': 'Permission Review',
|
|
810
|
+
'app.request': '{tool} requests to run',
|
|
811
|
+
'app.intent': 'Intent',
|
|
812
|
+
'app.args': 'Arguments',
|
|
813
|
+
'app.cand.head': 'Add project rule (current project)',
|
|
814
|
+
'app.cand.hint': 'Toggle items, then click Allow / Deny to add them to the current project exceptions permanently.',
|
|
815
|
+
'app.allow': 'Allow',
|
|
816
|
+
'app.deny': 'Deny',
|
|
817
|
+
'app.allowOne': '√ Allow',
|
|
818
|
+
'app.denyOne': '× Deny',
|
|
819
|
+
'app.denyReason': 'Rejection reason',
|
|
820
|
+
'app.denyReasonPh': 'Optional: sent to the agent as the denial reason (default: User denied)',
|
|
821
|
+
'app.confirmDeny': 'Confirm deny',
|
|
822
|
+
'app.detail': 'Details',
|
|
823
|
+
'app.diffNew': 'New file',
|
|
824
|
+
'app.diffMod': 'Modified',
|
|
825
|
+
'app.diffMore': '{n} more lines',
|
|
826
|
+
'app.diffLoading': 'Loading…',
|
|
827
|
+
'app.diffErr': 'Cannot build comparison',
|
|
828
|
+
'app.uiErr': 'UI render error',
|
|
829
|
+
'app.eolNote': 'Line endings normalized to LF for matching (CRLF/CR file with LF args): preview is indicative only; the actual edit may fail on line-ending mismatch',
|
|
830
|
+
'app.diffGapShow': 'Show {n} unchanged lines',
|
|
831
|
+
'app.diffGapHide': 'Hide {n} lines',
|
|
832
|
+
'app.diffGapMore': '… {n} unchanged lines',
|
|
833
|
+
'app.diffCopy': 'Copy',
|
|
834
|
+
'app.diffCopied': 'Copied',
|
|
835
|
+
'app.diffExpand': 'Expand',
|
|
836
|
+
'app.diffCollapse': 'Collapse',
|
|
837
|
+
'app.diffRead': 'Read',
|
|
838
|
+
'app.diffLines': '{n} lines',
|
|
839
|
+
'app.diffTopMore': '… {n} more lines above',
|
|
840
|
+
'app.diffBottomMore': '… more lines below',
|
|
841
|
+
'app.diffClose': 'Close',
|
|
842
|
+
'app.openFile': 'Open File',
|
|
843
|
+
'app.editOpened': 'Opened with default editor',
|
|
844
|
+
'app.diffResize': 'Drag to resize',
|
|
845
|
+
'dock.title': '● Permission Gate',
|
|
846
|
+
'settings.title': 'Permissions',
|
|
847
|
+
'dock.refresh': 'Refresh',
|
|
848
|
+
'dock.loading': 'Loading…',
|
|
849
|
+
'cat.directory': 'Directory access (outside workspace)',
|
|
850
|
+
'cat.command': 'Run command',
|
|
851
|
+
'cat.read': 'Read file',
|
|
852
|
+
'cat.edit': 'Edit file',
|
|
853
|
+
'cat.subagent': 'Spawn subagent',
|
|
854
|
+
'cat.doomloop': 'Doom Loop',
|
|
855
|
+
'catS.directory': 'Dir',
|
|
856
|
+
'catS.command': 'Cmd',
|
|
857
|
+
'catS.read': 'Read',
|
|
858
|
+
'catS.edit': 'Edit',
|
|
859
|
+
'catS.subagent': 'Sub',
|
|
860
|
+
'catS.doomloop': 'Loop',
|
|
861
|
+
'mode.ask': 'Ask',
|
|
862
|
+
'mode.allow': 'Allow',
|
|
863
|
+
'mode.deny': 'Deny',
|
|
864
|
+
'mode.inherit': 'Inherit global',
|
|
865
|
+
'panel.status': 'Status',
|
|
866
|
+
'panel.sessionPerm': 'Current session permission',
|
|
867
|
+
'panel.customReview': 'Custom Review',
|
|
868
|
+
'panel.none': 'None',
|
|
869
|
+
'panel.active': 'Permission gate is active',
|
|
870
|
+
'panel.inactive': 'Permission gate inactive: select "Custom Review" in the session permission picker',
|
|
871
|
+
'panel.sandboxEff': 'Sandbox (effective)',
|
|
872
|
+
'panel.sandboxDesc.ww': '(file operations restricted to the workspace)',
|
|
873
|
+
'panel.sandboxDesc.fa': '(file operations are not workspace-restricted)',
|
|
874
|
+
'panel.effCats': 'Effective category defaults: ',
|
|
875
|
+
'panel.config': 'Config: ',
|
|
876
|
+
'panel.project': 'Project: ',
|
|
877
|
+
'panel.stats': 'Recent: {d} denied · {a} asked',
|
|
878
|
+
'panel.loadErr': 'Load error: ',
|
|
879
|
+
'panel.saveErr': 'Save error: ',
|
|
880
|
+
'panel.tabGlobal': 'Global',
|
|
881
|
+
'panel.tabProject': 'Project',
|
|
882
|
+
'panel.editGlobal': 'Editing global settings (default for all projects).',
|
|
883
|
+
'panel.editProject': 'Editing current project ({p}); project settings override global.',
|
|
884
|
+
'panel.sandbox': 'Sandbox',
|
|
885
|
+
'sandbox.ww': 'Workspace Write',
|
|
886
|
+
'sandbox.fa': 'Full access',
|
|
887
|
+
'sandbox.inherit': 'Inherit global',
|
|
888
|
+
'sandbox.desc.ww': 'File operations are restricted to the workspace',
|
|
889
|
+
'sandbox.desc.fa': 'File operations are not workspace-restricted',
|
|
890
|
+
'sandbox.desc.inherit': 'Follows global (currently: {g})',
|
|
891
|
+
'panel.quick': 'Quick tools (other tools)',
|
|
892
|
+
'panel.quickHint': 'For uncategorized tools like web search, skills, grep/glob; changes apply immediately.',
|
|
893
|
+
'panel.quickAdd': 'New tool name (e.g. todo_write, wildcards allowed)',
|
|
894
|
+
'panel.quickAddBtn': 'Add (allow by default)',
|
|
895
|
+
'panel.rules': 'Custom rules (generic matching)',
|
|
896
|
+
'panel.rulesHint': 'Priority: custom rules > category exceptions > category default / quick tools.',
|
|
897
|
+
'panel.actionDeny': 'deny Deny',
|
|
898
|
+
'panel.actionAsk': 'ask Ask',
|
|
899
|
+
'panel.actionAllow': 'allow Allow',
|
|
900
|
+
'panel.toolPh': 'tool wildcard (e.g. cordis_*)',
|
|
901
|
+
'panel.pathPh': 'path glob',
|
|
902
|
+
'panel.argsPh': 'args substring',
|
|
903
|
+
'panel.reasonPh': 'reason (optional)',
|
|
904
|
+
'panel.addToGlobal': 'Add to Global',
|
|
905
|
+
'panel.addToProject': 'Add to Project',
|
|
906
|
+
'panel.ruleList': '{t} rules ({n})',
|
|
907
|
+
'panel.noRules': 'No rules',
|
|
908
|
+
'panel.decisions': 'Recent decisions',
|
|
909
|
+
'panel.noDecisions': 'No decisions yet',
|
|
910
|
+
'panel.reload': 'Reload config file',
|
|
911
|
+
'panel.openConfig': 'Open',
|
|
912
|
+
'panel.openConfigDone': 'Opened with default editor',
|
|
913
|
+
'panel.saved': 'Saved',
|
|
914
|
+
'panel.sandboxSaved': 'Sandbox saved ({t}): {v}',
|
|
915
|
+
'panel.needValue': 'Enter a match value first',
|
|
916
|
+
'panel.needTool': 'Enter a tool name',
|
|
917
|
+
'panel.excCount': 'Exceptions ({n})',
|
|
918
|
+
'panel.excCmdHint': ': command substring/glob, overrides category default',
|
|
919
|
+
'panel.excPathHint': ': path glob, overrides category default',
|
|
920
|
+
'panel.excNone': 'No exceptions',
|
|
921
|
+
'panel.excExpand': 'Expand',
|
|
922
|
+
'panel.excCollapse': 'Collapse',
|
|
923
|
+
'panel.excListLink': 'Exception list',
|
|
924
|
+
'panel.excUnit': 'items',
|
|
925
|
+
'panel.excPh': 'command substring/glob',
|
|
926
|
+
'panel.excPathPh': 'path glob',
|
|
927
|
+
'panel.excReason': 'Deny reason',
|
|
928
|
+
'panel.excReasonPh': 'optional: shown when denied',
|
|
929
|
+
'panel.addExc': 'Add exception',
|
|
930
|
+
'panel.confirmDel': 'Confirm delete?',
|
|
931
|
+
'panel.cancel': 'Cancel',
|
|
932
|
+
'panel.del': 'Delete',
|
|
933
|
+
'panel.allow': 'Allow',
|
|
934
|
+
'panel.deny': 'Deny',
|
|
935
|
+
},
|
|
936
|
+
};
|
|
937
|
+
let T = (key) => (I18N.zh[key] !== undefined ? I18N.zh[key] : key);
|
|
938
|
+
function useLocaleTick() {
|
|
939
|
+
const [, setTick] = React.useState(0);
|
|
940
|
+
React.useEffect(() => {
|
|
941
|
+
if (!LC || !LC.locale || typeof LC.locale.subscribe !== 'function') return undefined;
|
|
942
|
+
return LC.locale.subscribe(() => setTick((v) => v + 1));
|
|
943
|
+
}, []);
|
|
944
|
+
return T;
|
|
945
|
+
}
|
|
946
|
+
function catLabel(c) { return T('cat.' + c); }
|
|
947
|
+
function catShort(c) { return T('catS.' + c); }
|
|
948
|
+
function modeLabel(m) { return T('mode.' + m); }
|
|
949
|
+
const FONT = 'system-ui, -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif';
|
|
950
|
+
const card = { border: '1px solid rgba(128,128,128,0.35)', borderRadius: 8, padding: 12, marginBottom: 12 };
|
|
951
|
+
const rowStyle = { display: 'flex', alignItems: 'center', gap: 8, padding: '6px 0', borderBottom: '1px solid rgba(128,128,128,0.15)', flexWrap: 'wrap' };
|
|
952
|
+
const small = { fontSize: 12, color: 'rgba(128,128,128,0.95)', marginRight: 6 };
|
|
953
|
+
const h = () => ({ fontSize: 13, fontWeight: 600, margin: '0 0 8px' });
|
|
954
|
+
|
|
955
|
+
function ApprovalOverlay(props) {
|
|
956
|
+
const [pending, setPending] = React.useState([]);
|
|
957
|
+
const [busyId, setBusyId] = React.useState(null);
|
|
958
|
+
const [sel, setSel] = React.useState({});
|
|
959
|
+
const [openDetail, setOpenDetail] = React.useState({});
|
|
960
|
+
const [openArgs, setOpenArgs] = React.useState({});
|
|
961
|
+
const [denyText, setDenyText] = React.useState({});
|
|
962
|
+
const [denyMode, setDenyMode] = React.useState({});
|
|
963
|
+
const [, setTick] = React.useState(0);
|
|
964
|
+
useLocaleTick();
|
|
965
|
+
const poll = () => {
|
|
966
|
+
call('permgate:pending', {}).then((r) => setPending(Array.isArray(r) ? r : [])).catch(() => {});
|
|
967
|
+
};
|
|
968
|
+
React.useEffect(() => {
|
|
969
|
+
poll();
|
|
970
|
+
const off = subscribeEvents((ev) => {
|
|
971
|
+
if (ev.type === 'pending' || ev.type === 'refresh') poll();
|
|
972
|
+
});
|
|
973
|
+
return () => off();
|
|
974
|
+
}, []);
|
|
975
|
+
// 懒加载对比数据:详情默认展开的编辑/写入审批按需拉取(缓存按审批 id 复用)。
|
|
976
|
+
// 注意:必须在 `if (!pending.length) return null` 提前返回之前声明(hooks 顺序约束)。
|
|
977
|
+
React.useEffect(() => {
|
|
978
|
+
// 缓存随待审批池淘汰:已结算/已消失的审批 id 立即清理,避免长期会话无界增长
|
|
979
|
+
const aliveIds = new Set(pending.map((p) => p.id));
|
|
980
|
+
for (const key of diffCache.keys()) if (!aliveIds.has(key)) diffCache.delete(key);
|
|
981
|
+
for (const key of diffFetching) if (!aliveIds.has(key)) diffFetching.delete(key);
|
|
982
|
+
for (const p of pending) {
|
|
983
|
+
if (!p.hasDiff || diffCache.has(p.id) || diffFetching.has(p.id)) continue
|
|
984
|
+
const open = openDetail[p.id] === undefined ? true : openDetail[p.id] === true
|
|
985
|
+
if (!open) continue
|
|
986
|
+
diffFetching.add(p.id)
|
|
987
|
+
schedulePrismIdle()
|
|
988
|
+
call('permgate:file-diff', { id: p.id }).then((r) => {
|
|
989
|
+
diffCache.set(p.id, (r && typeof r === 'object') ? r : { ok: false, error: '' })
|
|
990
|
+
diffFetching.delete(p.id)
|
|
991
|
+
setTick((v) => v + 1)
|
|
992
|
+
}).catch(() => {
|
|
993
|
+
diffCache.set(p.id, { ok: false, error: '' })
|
|
994
|
+
diffFetching.delete(p.id)
|
|
995
|
+
setTick((v) => v + 1)
|
|
996
|
+
})
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
const decide = (id, action, rules, reason) => {
|
|
1000
|
+
setBusyId(id);
|
|
1001
|
+
call('permgate:decide', { id, action, rules, reason: reason || undefined }).then(poll).catch(() => {}).then(() => setBusyId(null));
|
|
1002
|
+
};
|
|
1003
|
+
if (!pending.length) return null;
|
|
1004
|
+
const pick = (candId, v) => {
|
|
1005
|
+
const next = Object.assign({}, sel);
|
|
1006
|
+
if (next[candId] === v) delete next[candId];
|
|
1007
|
+
else next[candId] = v;
|
|
1008
|
+
setSel(next);
|
|
1009
|
+
};
|
|
1010
|
+
const submit = (p, action) => {
|
|
1011
|
+
const rules = (p.candidates || []).filter((c) => sel[c.id]).map((c) => ({ value: c.value, kind: c.kind, decision: sel[c.id] }));
|
|
1012
|
+
decide(p.id, action, rules);
|
|
1013
|
+
};
|
|
1014
|
+
// 两段式拒绝:确认阶段提交(携带意见)或取消返回
|
|
1015
|
+
const confirmDeny = (p) => {
|
|
1016
|
+
const reason = (denyText[p.id] || '').trim();
|
|
1017
|
+
setDenyMode(Object.assign({}, denyMode, { [p.id]: false }));
|
|
1018
|
+
decide(p.id, 'deny', [], reason || undefined);
|
|
1019
|
+
};
|
|
1020
|
+
const cancelDeny = (p) => setDenyMode(Object.assign({}, denyMode, { [p.id]: false }));
|
|
1021
|
+
const radio = (p, c, v, label, cls) => React.createElement('button', {
|
|
1022
|
+
className: 'pg-radio' + (sel[c.id] === v ? ' ' + cls : ''),
|
|
1023
|
+
disabled: busyId === p.id,
|
|
1024
|
+
onClick: () => pick(c.id, v),
|
|
1025
|
+
}, label);
|
|
1026
|
+
// 编辑/写入审批(有 diff):详情默认展开、参数默认收起;无 diff 时参数照常显示
|
|
1027
|
+
const detailOpen = (p) => (openDetail[p.id] === undefined ? !!p.hasDiff : openDetail[p.id]);
|
|
1028
|
+
const argsOpen = (p) => (openArgs[p.id] === undefined ? !p.hasDiff : openArgs[p.id]);
|
|
1029
|
+
const toggle = (map, setMap, p, v) => setMap(Object.assign({}, map, { [p.id]: v }));
|
|
1030
|
+
// 点击文件名 → 打开右侧对比抽屉(父组件 OverlayRoot 持有 pin 状态)
|
|
1031
|
+
const openFile = (p, file) => {
|
|
1032
|
+
if (props && typeof props.onOpenFile === 'function') props.onOpenFile(p.id, file);
|
|
1033
|
+
};
|
|
1034
|
+
const argsBox = (p) => React.createElement('div', { className: 'pg-args' },
|
|
1035
|
+
React.createElement('span', { className: 'pg-args-tag' }, T('app.args')),
|
|
1036
|
+
(p.argLines && p.argLines.length)
|
|
1037
|
+
? p.argLines.map((l, i) => React.createElement('div', { key: i, className: 'pg-args-row' },
|
|
1038
|
+
React.createElement('span', { className: 'pg-args-label' }, l.label + ':'),
|
|
1039
|
+
l.path ? React.createElement('span', { className: 'pg-path-link', title: l.path, onClick: () => openFile(p, l.path) }, l.value)
|
|
1040
|
+
: React.createElement('span', { className: 'pg-args-val' }, l.value),
|
|
1041
|
+
))
|
|
1042
|
+
: React.createElement('span', null, p.args),
|
|
1043
|
+
);
|
|
1044
|
+
return React.createElement('div', null,
|
|
1045
|
+
pending.map((p) => React.createElement('div', { key: p.id, className: 'pg-modal' },
|
|
1046
|
+
React.createElement('div', { className: 'pg-modal-title' }, T('app.title')),
|
|
1047
|
+
React.createElement('div', { className: 'pg-modal-req' }, React.createElement('b', null, p.tool), ' ', T('app.request').replace('{tool}', '')),
|
|
1048
|
+
p.reason ? React.createElement('div', { className: 'pg-modal-body' }, p.reason) : null,
|
|
1049
|
+
p.intent ? React.createElement('div', { className: 'pg-intent' },
|
|
1050
|
+
React.createElement('span', { className: 'pg-intent-tag' }, T('app.intent')),
|
|
1051
|
+
React.createElement('span', null, p.intent),
|
|
1052
|
+
) : null,
|
|
1053
|
+
(p.args || (p.argLines && p.argLines.length)) ? (p.hasDiff
|
|
1054
|
+
? React.createElement('div', null,
|
|
1055
|
+
React.createElement('button', { className: 'pg-link', onClick: () => toggle(openArgs, setOpenArgs, p, !argsOpen(p)) },
|
|
1056
|
+
(argsOpen(p) ? '▾ ' : '▸ ') + T('app.args'),
|
|
1057
|
+
),
|
|
1058
|
+
argsOpen(p) ? argsBox(p) : null,
|
|
1059
|
+
)
|
|
1060
|
+
: argsBox(p)) : null,
|
|
1061
|
+
p.hasDiff ? React.createElement('div', null,
|
|
1062
|
+
React.createElement('button', { className: 'pg-link', onClick: () => toggle(openDetail, setOpenDetail, p, !detailOpen(p)) },
|
|
1063
|
+
(detailOpen(p) ? '▾ ' : '▸ ') + T('app.detail'),
|
|
1064
|
+
),
|
|
1065
|
+
detailOpen(p) ? (() => {
|
|
1066
|
+
const c = diffCache.get(p.id)
|
|
1067
|
+
if (!c) return React.createElement('div', { className: 'pg2-load' }, T('app.diffLoading'))
|
|
1068
|
+
if (!c.ok) return React.createElement('div', { className: 'pg2-err' }, (c.error || T('app.diffErr')))
|
|
1069
|
+
return c.kind === 'read'
|
|
1070
|
+
? React.createElement(PGErrorBoundary, { resetKey: payloadKey(c), fallback: T('app.diffErr') }, React.createElement(ReadBlockMemo, { data: c, onOpenFile: (f) => openFile(p, f) }))
|
|
1071
|
+
: React.createElement(PGErrorBoundary, { resetKey: payloadKey(c), fallback: T('app.diffErr') }, React.createElement(DiffBlockMemo, { data: c, onOpenFile: (f) => openFile(p, f), changesOnly: true }))
|
|
1072
|
+
})() : null,
|
|
1073
|
+
) : null,
|
|
1074
|
+
(p.candidates || []).length ? React.createElement('div', null,
|
|
1075
|
+
React.createElement('div', { className: 'pg-cand-head' }, T('app.cand.head')),
|
|
1076
|
+
React.createElement('div', { className: 'pg-cand-hint' }, T('app.cand.hint')),
|
|
1077
|
+
(p.candidates || []).map((c) => React.createElement('div', { key: c.id, className: 'pg-cand' },
|
|
1078
|
+
React.createElement('span', { className: 'pg-cand-label' }, c.label),
|
|
1079
|
+
radio(p, c, 'allow', T('app.allowOne'), 'pg-radio-allow'),
|
|
1080
|
+
radio(p, c, 'deny', T('app.denyOne'), 'pg-radio-deny'),
|
|
1081
|
+
)),
|
|
1082
|
+
) : null,
|
|
1083
|
+
// 两段式拒绝:平时只显示 拒绝/允许 两个按钮;点「拒绝」后展开
|
|
1084
|
+
// 意见输入框 + 「确认拒绝/取消」(Enter 确认、Esc 取消),
|
|
1085
|
+
// 输入框只在真要拒绝时出现,允许仍是一键。
|
|
1086
|
+
denyMode[p.id] ? React.createElement('div', null,
|
|
1087
|
+
React.createElement('div', { style: { margin: '8px 0 4px' } },
|
|
1088
|
+
React.createElement('div', { style: { fontSize: 12, fontWeight: 600, color: 'var(--dsw-alias-label-primary, #24292f)', marginBottom: 4 } }, T('app.denyReason')),
|
|
1089
|
+
React.createElement('input', { className: 'pg-field', autoFocus: true, style: { width: '100%', boxSizing: 'border-box' }, placeholder: T('app.denyReasonPh'), value: denyText[p.id] || '', onChange: (e) => setDenyText(Object.assign({}, denyText, { [p.id]: e.target.value })), onKeyDown: (e) => { if (e.key === 'Enter') { e.preventDefault(); confirmDeny(p); } else if (e.key === 'Escape') { e.preventDefault(); cancelDeny(p); } } }),
|
|
1090
|
+
),
|
|
1091
|
+
React.createElement('div', { className: 'pg-footer' },
|
|
1092
|
+
React.createElement('button', { className: 'pg-action pg-action-deny', disabled: busyId === p.id, onClick: () => confirmDeny(p) }, T('app.confirmDeny')),
|
|
1093
|
+
React.createElement('button', { className: 'pg-action', disabled: busyId === p.id, onClick: () => cancelDeny(p) }, T('panel.cancel')),
|
|
1094
|
+
),
|
|
1095
|
+
) : React.createElement('div', { className: 'pg-footer' },
|
|
1096
|
+
React.createElement('button', { className: 'pg-action pg-action-deny', disabled: busyId === p.id, onClick: () => setDenyMode(Object.assign({}, denyMode, { [p.id]: true })) }, T('app.deny')),
|
|
1097
|
+
React.createElement('button', { className: 'pg-action pg-action-allow', disabled: busyId === p.id, onClick: () => submit(p, 'allow') }, T('app.allow')),
|
|
1098
|
+
),
|
|
1099
|
+
)),
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// ── 文件对比(dsh-file-review 风格 unified diff + 右侧抽屉)─────────
|
|
1104
|
+
// 弹窗内联「详情」与右侧抽屉共用 DiffBlock/ReadBlock;数据来自
|
|
1105
|
+
// /permgate/file-diff(按审批 id 取,diffCache 缓存复用)。
|
|
1106
|
+
function diffOpsText(file, ops) {
|
|
1107
|
+
const out = [file];
|
|
1108
|
+
for (const op of ops || []) {
|
|
1109
|
+
if (op.t === 'g') {
|
|
1110
|
+
if (op.lines) { for (const l of op.lines) out.push(' ' + l.s) }
|
|
1111
|
+
else out.push('…');
|
|
1112
|
+
} else if (op.t === 'a') out.push('+ ' + op.s);
|
|
1113
|
+
else if (op.t === 'd') out.push('- ' + op.s);
|
|
1114
|
+
else out.push(' ' + op.s);
|
|
1115
|
+
}
|
|
1116
|
+
return out.join('\n');
|
|
1117
|
+
}
|
|
1118
|
+
function DiffBlock({ data, onOpenFile, onCollapse, changesOnly }) {
|
|
1119
|
+
const [copied, setCopied] = React.useState(false);
|
|
1120
|
+
const [gaps, setGaps] = React.useState({});
|
|
1121
|
+
useLocaleTick();
|
|
1122
|
+
// 语法高亮:从 ops 重建 old/new 文本后分块 tokenize,按真实文件行号 Map 取
|
|
1123
|
+
// 对应行的 HTML(edit 窗口化 diff 的 ops 行号是真实文件行号,窗口 base 可能 >1;
|
|
1124
|
+
// changesOnly 过滤后文本非连续,不能再用「行号-首行」偏移索引)。
|
|
1125
|
+
// 重建结果按 data 对象引用 + 视图模式缓存(diffHlCache:抽屉/弹窗共用同一
|
|
1126
|
+
// payload,但重建内容不同——changesOnly 只含改动行,full 含全部 gap/ctx 行)。
|
|
1127
|
+
const hlMode = changesOnly ? 'co' : 'full';
|
|
1128
|
+
const [hl, setHl] = React.useState(() => {
|
|
1129
|
+
const c = data ? diffHlCache.get(data) : null;
|
|
1130
|
+
return (c && c[hlMode]) || null;
|
|
1131
|
+
});
|
|
1132
|
+
React.useEffect(() => {
|
|
1133
|
+
ensurePrism();
|
|
1134
|
+
const lang = prismLangFor(data.file);
|
|
1135
|
+
if (data.fallback || !lang) { setHl(null); return undefined }
|
|
1136
|
+
const cached = data ? diffHlCache.get(data) : null;
|
|
1137
|
+
if (cached && cached[hlMode]) { setHl(cached[hlMode]); return undefined }
|
|
1138
|
+
// changesOnly(弹窗内联)只渲染改动行:重建文本仅含 a/d 行,不把 gap 携带的
|
|
1139
|
+
// 隐藏行(最多 10 万行)拼进文本,避免对永不展示的内容做拼接与分块 tokenize。
|
|
1140
|
+
const ops = changesOnly
|
|
1141
|
+
? (data.ops || []).filter((op) => op.t === 'a' || op.t === 'd')
|
|
1142
|
+
: (data.ops || []);
|
|
1143
|
+
const texts = prismTextsFromOps(ops);
|
|
1144
|
+
if (!texts) { setHl(null); return undefined }
|
|
1145
|
+
const hl = {
|
|
1146
|
+
old: { text: texts.oldText, html: null },
|
|
1147
|
+
new: { text: texts.newText, html: null },
|
|
1148
|
+
// 行号 → 重建文本行序号的索引(行号来自 ops,为真实文件行号)
|
|
1149
|
+
oldByNum: new Map(texts.oldNums.map((n, i) => [n, i])),
|
|
1150
|
+
newByNum: new Map(texts.newNums.map((n, i) => [n, i])),
|
|
1151
|
+
};
|
|
1152
|
+
if (data) {
|
|
1153
|
+
const entry = diffHlCache.get(data) || {};
|
|
1154
|
+
entry[hlMode] = hl;
|
|
1155
|
+
diffHlCache.set(data, entry);
|
|
1156
|
+
}
|
|
1157
|
+
setHl(hl);
|
|
1158
|
+
}, [data, hlMode]);
|
|
1159
|
+
// 双侧分块渐进高亮(idle 调度):首渲染纯文本、分块完成后补高亮。
|
|
1160
|
+
const oldHtml = useProgressiveHighlight(hl ? hl.old.text : null, data.file, hl ? hl.old : null);
|
|
1161
|
+
const newHtml = useProgressiveHighlight(hl ? hl.new.text : null, data.file, hl ? hl.new : null);
|
|
1162
|
+
// changesOnly 与抽屉统一走渐进高亮:首渲染纯文本、idle 分块完成后补高亮,
|
|
1163
|
+
// 避免弹窗打开时同步逐行 tokenize 阻塞主线程(maps 就绪前行数按纯文本渲染)。
|
|
1164
|
+
const rowText = (num, side, plain) => {
|
|
1165
|
+
const byNum = hl ? (side === 'old' ? hl.oldByNum : hl.newByNum) : null;
|
|
1166
|
+
const htmlArr = side === 'old' ? oldHtml : newHtml;
|
|
1167
|
+
const idx = (byNum && num !== null && num !== undefined) ? byNum.get(num) : undefined;
|
|
1168
|
+
const html = htmlArr && idx !== undefined ? htmlArr[idx] : undefined;
|
|
1169
|
+
if (html === undefined) return React.createElement('span', { className: 'pg2-text' }, plain);
|
|
1170
|
+
return React.createElement('span', { className: 'pg2-text', dangerouslySetInnerHTML: { __html: html } });
|
|
1171
|
+
};
|
|
1172
|
+
const onCopy = () => {
|
|
1173
|
+
if (copied) return;
|
|
1174
|
+
const text = data.fallback ? (data.lines || []).join('\n') : diffOpsText(data.file, data.ops || []);
|
|
1175
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
|
1176
|
+
navigator.clipboard.writeText(text).then(() => { setCopied(true); setTimeout(() => setCopied(false), 1000) }).catch(() => {});
|
|
1177
|
+
}
|
|
1178
|
+
};
|
|
1179
|
+
const kindLabel = data.kind === 'new' ? T('app.diffNew') : T('app.diffMod');
|
|
1180
|
+
const badge = data.kind === 'new' ? '+' : 'M';
|
|
1181
|
+
const copyBtn = React.createElement('button', { className: 'pg2-copy', onClick: onCopy }, copied ? T('app.diffCopied') : T('app.diffCopy'));
|
|
1182
|
+
// 「展开」按钮:仅弹窗内联上下文显示(onOpenFile 存在时),点击打开右侧栏面板
|
|
1183
|
+
const expandBtn = (typeof onOpenFile === 'function')
|
|
1184
|
+
? React.createElement('button', { className: 'pg2-expand', onClick: () => onOpenFile(data.file), title: T('app.diffExpand') }, T('app.diffExpand'))
|
|
1185
|
+
: null;
|
|
1186
|
+
// 「收缩」按钮:仅在侧边栏面板内显示(onCollapse 存在时),放在复制按钮旁边,点击回到权限弹窗
|
|
1187
|
+
const collapseBtn = (typeof onCollapse === 'function')
|
|
1188
|
+
? React.createElement('button', { className: 'pg2-expand', onClick: onCollapse, title: T('app.diffCollapse') }, T('app.diffCollapse'))
|
|
1189
|
+
: null;
|
|
1190
|
+
// 行尾归一化提示条:fallback 降级视图同样渲染(该视图下实际 edit 的原始字节
|
|
1191
|
+
// 字面匹配同样可能因 CRLF/CR 失败,提示不应随降级丢失)
|
|
1192
|
+
const eolNote = data.eolNormalized ? React.createElement('div', { className: 'pg2-eolnote', style: { padding: '4px 8px', fontSize: 11, color: 'var(--dsw-alias-state-warning-primary, #9a6700)' } }, T('app.eolNote')) : null;
|
|
1193
|
+
if (data.fallback) {
|
|
1194
|
+
return React.createElement('div', { className: 'pg2-block' },
|
|
1195
|
+
React.createElement('div', { className: 'pg2-header' },
|
|
1196
|
+
React.createElement('span', { className: 'pg2-status', title: kindLabel }, badge),
|
|
1197
|
+
React.createElement('span', { className: 'pg2-path', title: data.file, onClick: () => onOpenFile && onOpenFile(data.file) }, data.file),
|
|
1198
|
+
React.createElement('span', { className: 'pg2-added' }, '+' + data.added),
|
|
1199
|
+
React.createElement('span', { className: 'pg2-removed' }, '-' + data.removed),
|
|
1200
|
+
copyBtn,
|
|
1201
|
+
expandBtn,
|
|
1202
|
+
collapseBtn,
|
|
1203
|
+
),
|
|
1204
|
+
eolNote,
|
|
1205
|
+
React.createElement('div', { className: 'pg2-body' },
|
|
1206
|
+
(data.lines || []).map((l, i) => React.createElement('div', { key: i, className: l[0] === '+' ? 'pg2-fadd' : l[0] === '-' ? 'pg2-fdel' : 'pg2-fctx' }, l)),
|
|
1207
|
+
),
|
|
1208
|
+
data.truncated > 0 ? React.createElement('div', { className: 'pg2-foot' }, '… ' + T('app.diffMore').replace('{n}', String(data.truncated))) : null,
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
const rows = [];
|
|
1212
|
+
let gi = 0;
|
|
1213
|
+
let ri = 0;
|
|
1214
|
+
for (const op of data.ops || []) {
|
|
1215
|
+
// 权限弹窗内联视图(changesOnly):只显示修改行,不渲染上下文/gap
|
|
1216
|
+
if (changesOnly && op.t !== 'a' && op.t !== 'd') continue
|
|
1217
|
+
if (op.t === 'g') {
|
|
1218
|
+
const id = 'g' + (gi++);
|
|
1219
|
+
const expanded = !!gaps[id];
|
|
1220
|
+
if (op.lines) {
|
|
1221
|
+
rows.push(React.createElement('button', { key: 'r' + (ri++), className: 'pg2-gap', onClick: () => setGaps(Object.assign({}, gaps, { [id]: !expanded })) },
|
|
1222
|
+
(expanded ? '▾ ' : '↕ ') + (expanded ? T('app.diffGapHide') : T('app.diffGapShow')).replace('{n}', String(op.c)),
|
|
1223
|
+
));
|
|
1224
|
+
if (expanded) {
|
|
1225
|
+
for (const l of op.lines) {
|
|
1226
|
+
rows.push(React.createElement('div', { key: 'r' + (ri++), className: 'pg2-row pg2-ctx' },
|
|
1227
|
+
React.createElement('span', { className: 'pg2-old' }, String(l.o)),
|
|
1228
|
+
React.createElement('span', { className: 'pg2-new' }, String(l.n)),
|
|
1229
|
+
React.createElement('span', { className: 'pg2-sign' }, ' '),
|
|
1230
|
+
rowText(l.o, 'old', l.s),
|
|
1231
|
+
));
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
} else {
|
|
1235
|
+
rows.push(React.createElement('div', { key: 'r' + (ri++), className: 'pg2-gap-more' }, T('app.diffGapMore').replace('{n}', String(op.c))));
|
|
1236
|
+
}
|
|
1237
|
+
} else {
|
|
1238
|
+
const cls = op.t === 'a' ? 'pg2-add' : op.t === 'd' ? 'pg2-del' : 'pg2-ctx';
|
|
1239
|
+
const sign = op.t === 'a' ? '+' : op.t === 'd' ? '-' : ' ';
|
|
1240
|
+
rows.push(React.createElement('div', { key: 'r' + (ri++), className: 'pg2-row ' + cls },
|
|
1241
|
+
React.createElement('span', { className: 'pg2-old' }, op.o === null ? '' : String(op.o)),
|
|
1242
|
+
React.createElement('span', { className: 'pg2-new' }, op.n === null ? '' : String(op.n)),
|
|
1243
|
+
React.createElement('span', { className: 'pg2-sign' }, sign),
|
|
1244
|
+
rowText(op.t === 'a' ? op.n : op.o, op.t === 'a' ? 'new' : 'old', op.s),
|
|
1245
|
+
));
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
return React.createElement('div', { className: 'pg2-block' },
|
|
1249
|
+
React.createElement('div', { className: 'pg2-header' },
|
|
1250
|
+
React.createElement('span', { className: 'pg2-status', title: kindLabel }, badge),
|
|
1251
|
+
React.createElement('span', { className: 'pg2-path', title: data.file, onClick: () => onOpenFile && onOpenFile(data.file) }, data.file),
|
|
1252
|
+
React.createElement('span', { className: 'pg2-added' }, '+' + data.added),
|
|
1253
|
+
React.createElement('span', { className: 'pg2-removed' }, '-' + data.removed),
|
|
1254
|
+
copyBtn,
|
|
1255
|
+
expandBtn,
|
|
1256
|
+
collapseBtn,
|
|
1257
|
+
),
|
|
1258
|
+
eolNote,
|
|
1259
|
+
React.createElement('div', { className: 'pg2-body' }, rows),
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1262
|
+
function ReadBlock({ data, onOpenFile, onCollapse }) {
|
|
1263
|
+
const [copied, setCopied] = React.useState(false);
|
|
1264
|
+
useLocaleTick();
|
|
1265
|
+
const text = data.text || '';
|
|
1266
|
+
// 窗口化读取:行号从 startLine 起算,上下省略时显示灰色提示行
|
|
1267
|
+
const startLine = data.startLine || 1;
|
|
1268
|
+
const topOmitted = data.topOmitted === true;
|
|
1269
|
+
const bottomOmitted = data.bottomOmitted === true;
|
|
1270
|
+
const body = text.endsWith('\n') ? text.slice(0, -1) : text;
|
|
1271
|
+
const lines = body === '' ? [] : body.split('\n');
|
|
1272
|
+
const onCopy = () => {
|
|
1273
|
+
if (copied) return;
|
|
1274
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
|
1275
|
+
navigator.clipboard.writeText(text).then(() => { setCopied(true); setTimeout(() => setCopied(false), 1000) }).catch(() => {});
|
|
1276
|
+
}
|
|
1277
|
+
};
|
|
1278
|
+
const rows = [];
|
|
1279
|
+
let ri = 0;
|
|
1280
|
+
// 语法高亮:分块 tokenize 后按行取 HTML(超大文本降级纯文本,防主线程卡顿)。
|
|
1281
|
+
// 高亮结果按 data 引用缓存(readHlCache):抽屉重开/详情重展开(重挂载)
|
|
1282
|
+
// 直接复用已完成的行 HTML,不重复分块 tokenize。
|
|
1283
|
+
const [readHl] = React.useState(() => {
|
|
1284
|
+
if (!data) return null;
|
|
1285
|
+
let c = readHlCache.get(data);
|
|
1286
|
+
if (!c) { c = { text: null, html: null }; readHlCache.set(data, c) }
|
|
1287
|
+
return c;
|
|
1288
|
+
});
|
|
1289
|
+
// 分块渐进高亮(idle 调度,Prism 惰性初始化):首渲染纯文本、分块完成后补高亮。
|
|
1290
|
+
const lineHtml = useProgressiveHighlight(text, data.file, readHl);
|
|
1291
|
+
if (topOmitted) {
|
|
1292
|
+
rows.push(React.createElement('div', { key: 'r' + (ri++), className: 'pg2-romit' }, T('app.diffTopMore').replace('{n}', String(startLine - 1))));
|
|
1293
|
+
}
|
|
1294
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1295
|
+
rows.push(React.createElement('div', { key: 'r' + (ri++), className: 'pg2-rrow' },
|
|
1296
|
+
React.createElement('span', { className: 'pg2-rnum' }, String(startLine + i)),
|
|
1297
|
+
lineHtml && lineHtml[i] !== undefined
|
|
1298
|
+
? React.createElement('span', { className: 'pg2-text', dangerouslySetInnerHTML: { __html: lineHtml[i] } })
|
|
1299
|
+
: React.createElement('span', { className: 'pg2-text' }, lines[i]),
|
|
1300
|
+
));
|
|
1301
|
+
}
|
|
1302
|
+
if (bottomOmitted) {
|
|
1303
|
+
rows.push(React.createElement('div', { key: 'r' + (ri++), className: 'pg2-romit' }, T('app.diffBottomMore')));
|
|
1304
|
+
}
|
|
1305
|
+
return React.createElement('div', { className: 'pg2-block' },
|
|
1306
|
+
React.createElement('div', { className: 'pg2-header' },
|
|
1307
|
+
React.createElement('span', { className: 'pg2-status-read', title: T('app.diffRead') }, 'R'),
|
|
1308
|
+
React.createElement('span', { className: 'pg2-path', title: data.file, onClick: () => onOpenFile && onOpenFile(data.file) }, data.file),
|
|
1309
|
+
React.createElement('span', { className: 'pg2-added' }, T('app.diffLines').replace('{n}', String(lines.length) + (topOmitted || bottomOmitted ? '+' : ''))),
|
|
1310
|
+
React.createElement('button', { className: 'pg2-copy', onClick: onCopy }, copied ? T('app.diffCopied') : T('app.diffCopy')),
|
|
1311
|
+
(typeof onOpenFile === 'function')
|
|
1312
|
+
? React.createElement('button', { className: 'pg2-expand', onClick: () => onOpenFile(data.file), title: T('app.diffExpand') }, T('app.diffExpand'))
|
|
1313
|
+
: null,
|
|
1314
|
+
(typeof onCollapse === 'function')
|
|
1315
|
+
? React.createElement('button', { className: 'pg2-expand', onClick: onCollapse, title: T('app.diffCollapse') }, T('app.diffCollapse'))
|
|
1316
|
+
: null,
|
|
1317
|
+
),
|
|
1318
|
+
React.createElement('div', { className: 'pg2-body' }, rows),
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
1321
|
+
// 记忆化:ApprovalOverlay 的状态变化(如拒绝理由输入框每次按键)不应重渲染 diff 区域。
|
|
1322
|
+
// 回调 props 每次渲染都是新引用,比较时忽略,仅按数据与模式比较(diffCache 对象引用稳定)。
|
|
1323
|
+
const diffPropsEqual = (a, b) => a.data === b.data && a.changesOnly === b.changesOnly;
|
|
1324
|
+
const DiffBlockMemo = React.memo(DiffBlock, diffPropsEqual);
|
|
1325
|
+
const ReadBlockMemo = React.memo(ReadBlock, diffPropsEqual);
|
|
1326
|
+
function CompareDrawer({ pin, onClose }) {
|
|
1327
|
+
const [data, setData] = React.useState(null);
|
|
1328
|
+
const [err, setErr] = React.useState('');
|
|
1329
|
+
const [editMsg, setEditMsg] = React.useState(null);
|
|
1330
|
+
const [width, setWidth] = React.useState(null);
|
|
1331
|
+
useLocaleTick();
|
|
1332
|
+
React.useEffect(() => {
|
|
1333
|
+
setData(null);
|
|
1334
|
+
setErr('');
|
|
1335
|
+
// 与弹窗内联详情共用 diffCache:命中直接渲染(含失败态),未命中才请求并回写
|
|
1336
|
+
const cached = diffCache.get(pin.id);
|
|
1337
|
+
if (cached) {
|
|
1338
|
+
if (cached.ok) setData(cached);
|
|
1339
|
+
else setErr((cached.error) ? cached.error : T('app.diffErr'));
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
let alive = true;
|
|
1343
|
+
schedulePrismIdle()
|
|
1344
|
+
call('permgate:file-diff', { id: pin.id }).then((r) => {
|
|
1345
|
+
if (!alive) return;
|
|
1346
|
+
const norm = (r && typeof r === 'object') ? r : { ok: false, error: '' };
|
|
1347
|
+
diffCache.set(pin.id, norm);
|
|
1348
|
+
if (norm.ok) setData(norm);
|
|
1349
|
+
else setErr((norm.error) ? norm.error : T('app.diffErr'));
|
|
1350
|
+
}).catch(() => { if (alive) setErr(T('app.diffErr')) });
|
|
1351
|
+
return () => { alive = false };
|
|
1352
|
+
}, [pin.id]);
|
|
1353
|
+
// 左边缘拖动调整侧边栏宽度(面板固定在右侧,向左拖变宽)
|
|
1354
|
+
const onResizeStart = (e) => {
|
|
1355
|
+
if (e && e.preventDefault) e.preventDefault();
|
|
1356
|
+
const startX = e.clientX;
|
|
1357
|
+
const el = e.currentTarget ? e.currentTarget.parentElement : null;
|
|
1358
|
+
const startW = el ? el.getBoundingClientRect().width : (width || 600);
|
|
1359
|
+
let active = true;
|
|
1360
|
+
const onMove = (ev) => {
|
|
1361
|
+
if (!active) return;
|
|
1362
|
+
const maxW = (typeof window !== 'undefined' ? window.innerWidth : 1200) - 80;
|
|
1363
|
+
setWidth(Math.round(Math.min(Math.max(startW + (startX - ev.clientX), 480), maxW)));
|
|
1364
|
+
};
|
|
1365
|
+
const onUp = () => {
|
|
1366
|
+
active = false;
|
|
1367
|
+
if (typeof document !== 'undefined') {
|
|
1368
|
+
document.removeEventListener('mousemove', onMove);
|
|
1369
|
+
document.removeEventListener('mouseup', onUp);
|
|
1370
|
+
document.body.style.cursor = '';
|
|
1371
|
+
}
|
|
1372
|
+
};
|
|
1373
|
+
if (typeof document !== 'undefined') {
|
|
1374
|
+
document.body.style.cursor = 'col-resize';
|
|
1375
|
+
document.addEventListener('mousemove', onMove);
|
|
1376
|
+
document.addEventListener('mouseup', onUp);
|
|
1377
|
+
}
|
|
1378
|
+
};
|
|
1379
|
+
const onEdit = () => {
|
|
1380
|
+
setEditMsg(null);
|
|
1381
|
+
call('permgate:open-file', { id: pin.id }).then((r) => {
|
|
1382
|
+
setEditMsg(r && r.ok ? { ok: true, text: T('app.editOpened') } : { ok: false, text: (r && r.error) || T('app.diffErr') });
|
|
1383
|
+
setTimeout(() => setEditMsg(null), 2500);
|
|
1384
|
+
}).catch(() => {
|
|
1385
|
+
setEditMsg({ ok: false, text: T('app.diffErr') });
|
|
1386
|
+
setTimeout(() => setEditMsg(null), 2500);
|
|
1387
|
+
});
|
|
1388
|
+
};
|
|
1389
|
+
let body;
|
|
1390
|
+
if (data) {
|
|
1391
|
+
body = data.kind === 'read'
|
|
1392
|
+
? React.createElement(PGErrorBoundary, { fallback: T('app.diffErr') }, React.createElement(ReadBlockMemo, { data, onOpenFile: null, onCollapse: onClose }))
|
|
1393
|
+
: React.createElement(PGErrorBoundary, { fallback: T('app.diffErr') }, React.createElement(DiffBlockMemo, { data, onOpenFile: null, onCollapse: onClose }));
|
|
1394
|
+
} else if (err) {
|
|
1395
|
+
body = React.createElement('div', { className: 'pg2-err' }, err);
|
|
1396
|
+
} else {
|
|
1397
|
+
body = React.createElement('div', { className: 'pg2-load' }, T('app.diffLoading'));
|
|
1398
|
+
}
|
|
1399
|
+
return React.createElement('div', { className: 'pg2-drawer', style: width ? { width: width + 'px' } : undefined },
|
|
1400
|
+
React.createElement('div', { className: 'pg2-drawer-resize', onMouseDown: onResizeStart, title: T('app.diffResize') }),
|
|
1401
|
+
React.createElement('div', { className: 'pg2-drawer-head' },
|
|
1402
|
+
React.createElement('span', { className: 'pg2-drawer-file', title: pin.file }, pin.file),
|
|
1403
|
+
editMsg ? React.createElement('span', { className: 'pg2-edit-msg' + (editMsg.ok ? '' : ' err') }, editMsg.text) : null,
|
|
1404
|
+
React.createElement('button', { className: 'pg2-drawer-open', onClick: onEdit, title: T('app.openFile') }, T('app.openFile')),
|
|
1405
|
+
React.createElement('button', { className: 'pg2-drawer-close', onClick: onClose, title: T('app.diffClose') }, '✕'),
|
|
1406
|
+
),
|
|
1407
|
+
React.createElement('div', { className: 'pg2-drawer-body' }, body),
|
|
1408
|
+
);
|
|
1409
|
+
}
|
|
1410
|
+
// shell.overlay 入口:审批弹窗 + 右侧对比抽屉共享 pin 状态
|
|
1411
|
+
// 错误边界:任一子组件 render 崩溃只降级该区域,不影响审批弹窗/抽屉存活
|
|
1412
|
+
// 按 payload 对象引用生成稳定 resetKey(WeakMap:对象被回收后条目随之消失)
|
|
1413
|
+
const payloadKeys = new WeakMap();
|
|
1414
|
+
let payloadKeySeq = 0;
|
|
1415
|
+
function payloadKey(o) {
|
|
1416
|
+
let k = payloadKeys.get(o);
|
|
1417
|
+
if (k === undefined) { k = 'd' + (++payloadKeySeq); payloadKeys.set(o, k) }
|
|
1418
|
+
return k;
|
|
1419
|
+
}
|
|
1420
|
+
class PGErrorBoundary extends React.Component {
|
|
1421
|
+
constructor(props) {
|
|
1422
|
+
super(props);
|
|
1423
|
+
this.state = { err: null };
|
|
1424
|
+
}
|
|
1425
|
+
static getDerivedStateFromError(err) {
|
|
1426
|
+
return { err };
|
|
1427
|
+
}
|
|
1428
|
+
componentDidCatch(err) {
|
|
1429
|
+
console.error('[permgate] ui error:', err);
|
|
1430
|
+
}
|
|
1431
|
+
componentDidUpdate(prevProps) {
|
|
1432
|
+
// resetKey 变化(新 pin/新 payload 数据)时解除错误锁死:
|
|
1433
|
+
// 一次渲染崩溃不应让该子树对后续有效数据永久显示兜底错误。
|
|
1434
|
+
if (this.state.err && this.props.resetKey !== prevProps.resetKey) {
|
|
1435
|
+
this.setState({ err: null });
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
render() {
|
|
1439
|
+
if (this.state.err) {
|
|
1440
|
+
// 只显示通用 fallback,不把原始异常消息(可能含内部路径/文件片段)渲染进界面;
|
|
1441
|
+
// 完整错误已由 componentDidCatch 写入 console。
|
|
1442
|
+
return React.createElement('div', { className: 'pg2-err' }, this.props.fallback || T('app.diffErr'));
|
|
1443
|
+
}
|
|
1444
|
+
return this.props.children;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
function OverlayRoot() {
|
|
1448
|
+
const [pin, setPin] = React.useState(null);
|
|
1449
|
+
return React.createElement('div', null,
|
|
1450
|
+
React.createElement(PGErrorBoundary, { fallback: T('app.uiErr') },
|
|
1451
|
+
React.createElement(ApprovalOverlay, { onOpenFile: (id, file) => setPin({ id, file }) }),
|
|
1452
|
+
),
|
|
1453
|
+
pin ? React.createElement(PGErrorBoundary, { resetKey: pin.id, fallback: T('app.uiErr') },
|
|
1454
|
+
React.createElement(CompareDrawer, { pin, onClose: () => setPin(null) }),
|
|
1455
|
+
) : null,
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
function DockBar(props) {
|
|
1460
|
+
// 槽为 session 作用域:props.sessionId 是当前会话(标准 props),
|
|
1461
|
+
// 状态按会话查询,切换对话后各 DockBar 只反映自己的会话
|
|
1462
|
+
const sessionId = props && props.sessionId;
|
|
1463
|
+
const [status, setStatus] = React.useState(null);
|
|
1464
|
+
useLocaleTick();
|
|
1465
|
+
const refresh = () => { call('permgate:status', { sessionId: sessionId || undefined }).then(setStatus).catch(() => {}); };
|
|
1466
|
+
React.useEffect(() => {
|
|
1467
|
+
refresh();
|
|
1468
|
+
const off = subscribeEvents((ev) => {
|
|
1469
|
+
if (ev.type === 'status' || ev.type === 'refresh') refresh();
|
|
1470
|
+
});
|
|
1471
|
+
return () => off();
|
|
1472
|
+
}, []);
|
|
1473
|
+
// 仅当前会话选中「自定义审查」时显示分类徽标(只读展示,无开关)
|
|
1474
|
+
if (!status || status.activeForSession !== true) return null;
|
|
1475
|
+
const eff = status.effective || {};
|
|
1476
|
+
const chip = (label, m) => React.createElement('span', { key: label, style: { color: MODE_COLORS[m] || '#888' } }, label + ':' + modeLabel(m));
|
|
1477
|
+
return React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, fontSize: 12, color: 'rgba(128,128,128,0.95)', padding: '2px 0' } },
|
|
1478
|
+
React.createElement('span', { style: { fontWeight: 600 } }, T('dock.title')),
|
|
1479
|
+
React.createElement('span', { style: { display: 'flex', gap: 6, flexWrap: 'wrap' } },
|
|
1480
|
+
chip(catShort('directory'), eff.directory), chip(catShort('command'), eff.command), chip(catShort('read'), eff.read), chip(catShort('edit'), eff.edit), chip(catShort('subagent'), eff.subagent), chip(catShort('doomloop'), eff.doomloop),
|
|
1481
|
+
),
|
|
1482
|
+
React.createElement('span', { style: { cursor: 'pointer', padding: '0 4px' }, onClick: refresh, title: T('dock.refresh') }, '↻'),
|
|
1483
|
+
);
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
function Panel(props) {
|
|
1487
|
+
// settings.section 是 root 作用域槽:不直接给 sessionId,但注入 useSessions
|
|
1488
|
+
// 标准 hook。用它订阅当前活动会话(SessionListState.current),会话切换时
|
|
1489
|
+
// 选择器变化触发重渲染 + 重新查询,面板始终显示当前会话的项目配置。
|
|
1490
|
+
const sessionId = (props && typeof props.useSessions === 'function') ? props.useSessions((st) => (st ? st.current : undefined)) : undefined;
|
|
1491
|
+
const [status, setStatus] = React.useState(null);
|
|
1492
|
+
const [busy, setBusy] = React.useState(false);
|
|
1493
|
+
const [msg, setMsg] = React.useState('');
|
|
1494
|
+
const [tab, setTab] = React.useState('global');
|
|
1495
|
+
const [cats, setCats] = React.useState({ global: {}, project: {} });
|
|
1496
|
+
const [quickSel, setQuickSel] = React.useState({});
|
|
1497
|
+
const [exVals, setExVals] = React.useState({ directory: '', command: '', read: '', edit: '' });
|
|
1498
|
+
const [exAction, setExAction] = React.useState('allow');
|
|
1499
|
+
const [exReasonVal, setExReasonVal] = React.useState('');
|
|
1500
|
+
const [newTool, setNewTool] = React.useState('');
|
|
1501
|
+
const [form, setForm] = React.useState({ action: 'deny', tool: '', path: '', args: '', reason: '' });
|
|
1502
|
+
const [confirm, setConfirm] = React.useState(null);
|
|
1503
|
+
const [excCollapsed, setExcCollapsed] = React.useState({});
|
|
1504
|
+
|
|
1505
|
+
React.useEffect(() => {
|
|
1506
|
+
if (confirm === null) return undefined;
|
|
1507
|
+
const id = setTimeout(() => setConfirm(null), 4000);
|
|
1508
|
+
return () => clearTimeout(id);
|
|
1509
|
+
}, [confirm]);
|
|
1510
|
+
|
|
1511
|
+
const applyStatus = (s) => {
|
|
1512
|
+
if (!s) return;
|
|
1513
|
+
setStatus(s);
|
|
1514
|
+
const cs = { global: {}, project: {} };
|
|
1515
|
+
for (const t of ['global', 'project']) {
|
|
1516
|
+
const block = s.categories && s.categories[t] ? s.categories[t] : null;
|
|
1517
|
+
for (const c of CATS) {
|
|
1518
|
+
cs[t][c] = block && block[c] ? block[c].mode : (t === 'project' ? 'inherit' : 'ask');
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
setCats(cs);
|
|
1522
|
+
const qs = {};
|
|
1523
|
+
const gq = s.quickTools ? s.quickTools.global : {};
|
|
1524
|
+
const pq = s.quickTools ? s.quickTools.project : {};
|
|
1525
|
+
const names = {};
|
|
1526
|
+
for (const t of QUICK_PRESET) names[t] = 1;
|
|
1527
|
+
for (const k of Object.keys(gq)) names[k] = 1;
|
|
1528
|
+
for (const k of Object.keys(pq)) names[k] = 1;
|
|
1529
|
+
for (const t of Object.keys(names)) {
|
|
1530
|
+
qs[t] = { g: gq[t] || 'allow', p: pq[t] && pq[t] !== 'inherit' ? pq[t] : 'inherit' };
|
|
1531
|
+
}
|
|
1532
|
+
setQuickSel(qs);
|
|
1533
|
+
};
|
|
1534
|
+
|
|
1535
|
+
const refresh = () => {
|
|
1536
|
+
call('permgate:status', { sessionId: sessionId || undefined }).then(applyStatus).catch((e) => setMsg(String((e && e.message) || e)));
|
|
1537
|
+
};
|
|
1538
|
+
// 会话切换(useSessions 选择器变化)与 SSE 联动都会触发重查,
|
|
1539
|
+
// 保证切换会话后面板立即显示当前会话的项目配置
|
|
1540
|
+
React.useEffect(() => {
|
|
1541
|
+
refresh();
|
|
1542
|
+
const off = subscribeEvents((ev) => {
|
|
1543
|
+
if (ev.type === 'status' || ev.type === 'refresh') refresh();
|
|
1544
|
+
});
|
|
1545
|
+
return () => off();
|
|
1546
|
+
}, [sessionId]);
|
|
1547
|
+
|
|
1548
|
+
const invoke = (method, args, done) => {
|
|
1549
|
+
setBusy(true);
|
|
1550
|
+
setMsg('');
|
|
1551
|
+
call(method, Object.assign({}, args || {}, { sessionId: sessionId || undefined })).then((r) => {
|
|
1552
|
+
if (r && r.error) { setMsg(String(r.error)); return; }
|
|
1553
|
+
setMsg(T('panel.saved'));
|
|
1554
|
+
applyStatus(r && r.status ? r.status : r);
|
|
1555
|
+
if (done) done(r);
|
|
1556
|
+
}).catch((e) => setMsg(String((e && e.message) || e))).then(() => setBusy(false));
|
|
1557
|
+
};
|
|
1558
|
+
|
|
1559
|
+
const confirmDelete = (key, fn) => {
|
|
1560
|
+
if (busy) return;
|
|
1561
|
+
if (confirm === key) { setConfirm(null); fn(); }
|
|
1562
|
+
else setConfirm(key);
|
|
1563
|
+
};
|
|
1564
|
+
|
|
1565
|
+
const changeCat = (c) => (e) => {
|
|
1566
|
+
const mode = e.target.value;
|
|
1567
|
+
setCats(Object.assign({}, cats, { [tab]: Object.assign({}, cats[tab], { [c]: mode }) }));
|
|
1568
|
+
invoke('permgate:set-categories', { [tab]: { [c]: mode } }, () => setMsg(tab === 'global' ? T('panel.savedToGlobal') : T('panel.savedToProject')));
|
|
1569
|
+
};
|
|
1570
|
+
|
|
1571
|
+
const changeQuick = (tool) => (e) => {
|
|
1572
|
+
const mode = e.target.value;
|
|
1573
|
+
const key = tab === 'global' ? 'g' : 'p';
|
|
1574
|
+
setQuickSel(Object.assign({}, quickSel, { [tool]: Object.assign({}, quickSel[tool], { [key]: mode }) }));
|
|
1575
|
+
invoke('permgate:set-quick', { target: tab, tool, action: mode });
|
|
1576
|
+
};
|
|
1577
|
+
|
|
1578
|
+
const addQuick = () => {
|
|
1579
|
+
if (!newTool || !String(newTool).trim()) { setMsg(T('panel.needTool')); return; }
|
|
1580
|
+
invoke('permgate:set-quick', { target: tab, tool: String(newTool).trim(), action: 'allow' }, () => setNewTool(''));
|
|
1581
|
+
};
|
|
1582
|
+
|
|
1583
|
+
const addException = (c) => {
|
|
1584
|
+
const v = String(exVals[c] || '').trim();
|
|
1585
|
+
if (!v) { setMsg(T('panel.needValue')); return; }
|
|
1586
|
+
setExVals(Object.assign({}, exVals, { [c]: '' }));
|
|
1587
|
+
setExReasonVal('');
|
|
1588
|
+
invoke('permgate:add-exception', { target: tab, category: c, match: v, action: exAction, reason: exAction === 'deny' ? (String(exReasonVal || '').trim() || undefined) : undefined });
|
|
1589
|
+
};
|
|
1590
|
+
|
|
1591
|
+
const removeException = (c, id) => invoke('permgate:remove-exception', { target: tab, category: c, id });
|
|
1592
|
+
|
|
1593
|
+
const setFormKey = (key) => (e) => setForm(Object.assign({}, form, { [key]: e.target.value }));
|
|
1594
|
+
const addRule = () => invoke('permgate:add-rule', { target: tab, action: form.action, tool: form.tool || undefined, path: form.path || undefined, args: form.args || undefined, reason: form.reason || undefined });
|
|
1595
|
+
const removeRule = (id) => invoke('permgate:remove-rule', { target: tab, id });
|
|
1596
|
+
const reload = () => invoke('permgate:reload', {});
|
|
1597
|
+
const openConfig = () => invoke('permgate:open-config', {}, () => setMsg(T('panel.openConfigDone')));
|
|
1598
|
+
|
|
1599
|
+
const badge = (action) => React.createElement('span', { style: { color: MODE_COLORS[action] || '#888', fontWeight: 600, marginRight: 6 } }, modeLabel(action));
|
|
1600
|
+
|
|
1601
|
+
const sel = (v, onChange, options, disabled) => React.createElement('select', { value: v, onChange, className: 'pg-field', style: { padding: '2px 6px' }, disabled: !!disabled },
|
|
1602
|
+
options.map((m) => React.createElement('option', { key: m, value: m }, modeLabel(m))),
|
|
1603
|
+
);
|
|
1604
|
+
|
|
1605
|
+
const tabBtn = (t, label) => React.createElement('button', { className: 'pg-tab' + (tab === t ? ' pg-tab-on' : ''), onClick: () => setTab(t) }, label);
|
|
1606
|
+
|
|
1607
|
+
const catBlock = (c) => {
|
|
1608
|
+
const mode = cats[tab] ? (cats[tab][c] || (tab === 'project' ? 'inherit' : 'ask')) : (tab === 'project' ? 'inherit' : 'ask');
|
|
1609
|
+
const block = status && status.categories && status.categories[tab] ? status.categories[tab] : null;
|
|
1610
|
+
const excList = block && block[c] && Array.isArray(block[c].exceptions) ? block[c].exceptions : [];
|
|
1611
|
+
// 例外折叠:列表超过阈值默认收起(长列表不再撑爆面板),添加行始终可见
|
|
1612
|
+
const excCollapsedState = excCollapsed[c] === undefined ? excList.length > EXC_COLLAPSE_THRESHOLD : excCollapsed[c];
|
|
1613
|
+
const toggleExc = () => setExcCollapsed(Object.assign({}, excCollapsed, { [c]: !excCollapsedState }));
|
|
1614
|
+
return React.createElement('div', { key: c, style: { border: '1px solid rgba(128,128,128,0.3)', borderRadius: 8, padding: 10, marginBottom: 10 } },
|
|
1615
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 4 } },
|
|
1616
|
+
React.createElement('span', { style: { fontSize: 13, fontWeight: 600 } }, catLabel(c)),
|
|
1617
|
+
sel(mode, changeCat(c), tab === 'project' ? ALL_MODES : MODES, busy),
|
|
1618
|
+
),
|
|
1619
|
+
EXC_CATS.indexOf(c) !== -1 ? React.createElement('div', { style: { marginTop: 2 } },
|
|
1620
|
+
React.createElement('div', { style: { fontSize: 12, color: 'rgba(128,128,128,0.85)', marginBottom: 4 } },
|
|
1621
|
+
T('panel.excCount').replace('{n}', String(excList.length)) + (c === 'command' ? T('panel.excCmdHint') : T('panel.excPathHint')),
|
|
1622
|
+
),
|
|
1623
|
+
excCollapsedState
|
|
1624
|
+
? React.createElement('div', { style: { fontSize: 12, color: '#1f6feb', cursor: 'pointer', userSelect: 'none', padding: '2px 0' }, onClick: toggleExc, title: T('panel.excExpand') },
|
|
1625
|
+
'> ' + T('panel.excListLink') + '(' + String(excList.length) + ' ' + T('panel.excUnit') + ')',
|
|
1626
|
+
)
|
|
1627
|
+
: (excList.length ? React.createElement('div', null,
|
|
1628
|
+
// 展开/折叠开关固定在占位行位置:展开后原地变为「▾ 折叠」,条目列在下方
|
|
1629
|
+
React.createElement('div', { style: { fontSize: 12, color: '#1f6feb', cursor: 'pointer', userSelect: 'none', padding: '2px 0', marginBottom: 2 }, onClick: toggleExc, title: T('panel.excCollapse') },
|
|
1630
|
+
'▾ ' + T('panel.excCollapse'),
|
|
1631
|
+
),
|
|
1632
|
+
excList.map((r) => React.createElement('div', { key: r.id, style: rowStyle },
|
|
1633
|
+
badge(r.action),
|
|
1634
|
+
React.createElement('span', { style: { fontFamily: 'monospace', fontSize: 12 } }, r.path || r.match || ''),
|
|
1635
|
+
r.action === 'deny' && r.reason ? React.createElement('span', { style: { fontSize: 12, color: '#c62828', marginLeft: 6 } }, '「' + r.reason + '」') : null,
|
|
1636
|
+
React.createElement('div', { style: { display: 'flex', gap: 6, marginLeft: 'auto' } },
|
|
1637
|
+
React.createElement('button', { className: 'pg-btn pg-btn-danger' + (confirm === 'exc:' + c + ':' + r.id ? ' pg-btn-confirm' : ''), disabled: busy, onClick: () => confirmDelete('exc:' + c + ':' + r.id, () => removeException(c, r.id)) }, confirm === 'exc:' + c + ':' + r.id ? T('panel.confirmDel') : T('panel.del')),
|
|
1638
|
+
confirm === 'exc:' + c + ':' + r.id ? React.createElement('button', { className: 'pg-btn', disabled: busy, onClick: () => setConfirm(null) }, T('panel.cancel')) : null,
|
|
1639
|
+
),
|
|
1640
|
+
)),
|
|
1641
|
+
) : React.createElement('div', { style: { fontSize: 12, color: 'rgba(128,128,128,0.7)' } }, T('panel.excNone'))),
|
|
1642
|
+
React.createElement('div', { style: { display: 'flex', gap: 6, marginTop: 6, flexWrap: 'wrap' } },
|
|
1643
|
+
React.createElement('input', { className: 'pg-field', placeholder: c === 'command' ? T('panel.excPh') : T('panel.excPathPh'), value: exVals[c] || '', onChange: (e) => setExVals(Object.assign({}, exVals, { [c]: e.target.value })), disabled: busy }),
|
|
1644
|
+
React.createElement('select', { className: 'pg-field', style: { padding: '2px 6px' }, value: exAction, onChange: (e) => setExAction(e.target.value), disabled: busy },
|
|
1645
|
+
React.createElement('option', { value: 'allow' }, T('panel.allow')), React.createElement('option', { value: 'deny' }, T('panel.deny')),
|
|
1646
|
+
),
|
|
1647
|
+
exAction === 'deny' ? React.createElement('input', { className: 'pg-field', style: { maxWidth: 220 }, placeholder: T('panel.excReasonPh'), title: T('panel.excReason'), value: exReasonVal, onChange: (e) => setExReasonVal(e.target.value), disabled: busy }) : null,
|
|
1648
|
+
React.createElement('button', { className: 'pg-btn', disabled: busy, onClick: () => addException(c) }, T('panel.addExc')),
|
|
1649
|
+
),
|
|
1650
|
+
) : null,
|
|
1651
|
+
);
|
|
1652
|
+
};
|
|
1653
|
+
|
|
1654
|
+
const quickTools = Object.keys(quickSel);
|
|
1655
|
+
const quickRow = (t) => {
|
|
1656
|
+
const key = tab === 'global' ? 'g' : 'p';
|
|
1657
|
+
const mode = quickSel[t] ? (quickSel[t][key] || (tab === 'project' ? 'inherit' : 'allow')) : (tab === 'project' ? 'inherit' : 'allow');
|
|
1658
|
+
return React.createElement('div', { key: t, style: rowStyle },
|
|
1659
|
+
React.createElement('span', { style: { fontFamily: 'monospace', fontSize: 13, width: 150 } }, t),
|
|
1660
|
+
sel(mode, changeQuick(t), tab === 'project' ? ALL_MODES : MODES, busy),
|
|
1661
|
+
);
|
|
1662
|
+
};
|
|
1663
|
+
|
|
1664
|
+
const ruleRow = (r) => {
|
|
1665
|
+
const matchers = [];
|
|
1666
|
+
if (r.tool) matchers.push('tool=' + r.tool);
|
|
1667
|
+
if (r.path) matchers.push('path=' + r.path);
|
|
1668
|
+
if (r.args) matchers.push('args="' + r.args + '"');
|
|
1669
|
+
return React.createElement('div', { key: r.id, style: rowStyle },
|
|
1670
|
+
badge(r.action),
|
|
1671
|
+
React.createElement('span', { style: { fontFamily: 'monospace', fontSize: 12 } }, matchers.join(' ') || T('panel.all')),
|
|
1672
|
+
r.reason ? React.createElement('span', { style: { fontSize: 12, color: 'rgba(128,128,128,0.9)' } }, '(' + r.reason + ')') : null,
|
|
1673
|
+
React.createElement('div', { style: { display: 'flex', gap: 6, marginLeft: 'auto' } },
|
|
1674
|
+
React.createElement('button', { className: 'pg-btn pg-btn-danger' + (confirm === 'rule:' + r.id ? ' pg-btn-confirm' : ''), disabled: busy, onClick: () => confirmDelete('rule:' + r.id, () => removeRule(r.id)) }, confirm === 'rule:' + r.id ? T('panel.confirmDel') : T('panel.del')),
|
|
1675
|
+
confirm === 'rule:' + r.id ? React.createElement('button', { className: 'pg-btn', disabled: busy, onClick: () => setConfirm(null) }, T('panel.cancel')) : null,
|
|
1676
|
+
),
|
|
1677
|
+
);
|
|
1678
|
+
};
|
|
1679
|
+
|
|
1680
|
+
const customList = status && status.custom ? status.custom[tab] : null;
|
|
1681
|
+
const eff = status ? status.effective : null;
|
|
1682
|
+
const decisions = status && status.recentDecisions ? status.recentDecisions : [];
|
|
1683
|
+
const stats = status && status.stats ? status.stats : { deny: 0, ask: 0 };
|
|
1684
|
+
const sandbox = status && status.sandbox ? status.sandbox : { global: 'workspace-write', project: 'inherit', effective: 'workspace-write' };
|
|
1685
|
+
const presetName = status && status.preset ? status.preset : null;
|
|
1686
|
+
const activeForSession = status ? status.activeForSession === true : false;
|
|
1687
|
+
|
|
1688
|
+
const sandboxLabel = (m) => (m === 'workspace-write' ? T('sandbox.ww') : m === 'danger-full-access' ? T('sandbox.fa') : T('sandbox.inherit'));
|
|
1689
|
+
const sandboxVal = tab === 'global' ? sandbox.global : sandbox.project;
|
|
1690
|
+
const sandboxOptions = tab === 'global' ? ['workspace-write', 'danger-full-access'] : ['workspace-write', 'danger-full-access', 'inherit'];
|
|
1691
|
+
const changeSandbox = (e) => {
|
|
1692
|
+
const mode = e.target.value;
|
|
1693
|
+
invoke('permgate:set-sandbox', { target: tab, mode }, () => setMsg(T('panel.sandboxSaved').replace('{t}', T(tab === 'global' ? 'panel.tabGlobal' : 'panel.tabProject')).replace('{v}', sandboxLabel(mode))));
|
|
1694
|
+
};
|
|
1695
|
+
|
|
1696
|
+
return React.createElement('div', { style: { maxWidth: 760, fontFamily: FONT, color: 'var(--dsw-alias-label-primary, #24292f)' } },
|
|
1697
|
+
React.createElement('div', { style: card },
|
|
1698
|
+
React.createElement('div', { style: h() }, T('panel.status')),
|
|
1699
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8, flexWrap: 'wrap' } },
|
|
1700
|
+
React.createElement('span', { style: { fontSize: 13, fontWeight: 600 } }, T('panel.sessionPerm')),
|
|
1701
|
+
React.createElement('span', { style: { fontSize: 13, color: activeForSession ? '#2e7d32' : '#888', fontWeight: 600 } }, presetName === 'custom-review' ? T('panel.customReview') : (presetName || T('panel.none'))),
|
|
1702
|
+
React.createElement('span', { style: small }, activeForSession ? T('panel.active') : T('panel.inactive')),
|
|
1703
|
+
),
|
|
1704
|
+
React.createElement('div', { style: { fontSize: 13, marginBottom: 6 } },
|
|
1705
|
+
T('panel.sandboxEff') + ' ',
|
|
1706
|
+
React.createElement('span', { style: { color: sandbox.effective === 'danger-full-access' ? '#e65100' : '#2e7d32', fontWeight: 600 } }, sandboxLabel(sandbox.effective)),
|
|
1707
|
+
React.createElement('span', { style: small }, sandbox.effective === 'workspace-write' ? T('panel.sandboxDesc.ww') : T('panel.sandboxDesc.fa')),
|
|
1708
|
+
),
|
|
1709
|
+
React.createElement('div', { style: { fontSize: 13, marginBottom: 6 } },
|
|
1710
|
+
T('panel.effCats'),
|
|
1711
|
+
eff ? CATS.map((c) => React.createElement('span', { key: c, style: { color: MODE_COLORS[eff[c]] || '#888', fontWeight: 600, marginRight: 8 } }, (catLabel(c) + ':' + modeLabel(eff[c])))) : '…',
|
|
1712
|
+
),
|
|
1713
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' } },
|
|
1714
|
+
React.createElement('span', { style: small }, T('panel.config') + (status ? status.configPath : '…')),
|
|
1715
|
+
React.createElement('button', { className: 'pg-btn', disabled: busy, onClick: openConfig }, T('panel.openConfig')),
|
|
1716
|
+
React.createElement('button', { className: 'pg-btn', disabled: busy, onClick: reload }, T('panel.reload')),
|
|
1717
|
+
),
|
|
1718
|
+
React.createElement('div', { style: small }, T('panel.project') + (status ? status.projectKey : '…')),
|
|
1719
|
+
React.createElement('div', { style: small }, T('panel.stats').replace('{d}', String(stats.deny)).replace('{a}', String(stats.ask))),
|
|
1720
|
+
status && status.loadError ? React.createElement('div', { style: { color: '#c62828', fontSize: 12 } }, T('panel.loadErr') + status.loadError) : null,
|
|
1721
|
+
status && status.saveError ? React.createElement('div', { style: { color: '#c62828', fontSize: 12 } }, T('panel.saveErr') + status.saveError) : null,
|
|
1722
|
+
),
|
|
1723
|
+
React.createElement('div', { style: { display: 'flex', marginBottom: 12, borderBottom: '1px solid rgba(128,128,128,0.35)' } },
|
|
1724
|
+
tabBtn('global', T('panel.tabGlobal')),
|
|
1725
|
+
tabBtn('project', T('panel.tabProject')),
|
|
1726
|
+
),
|
|
1727
|
+
React.createElement('div', { style: { fontSize: 12, color: 'rgba(128,128,128,0.85)', marginBottom: 8 } },
|
|
1728
|
+
tab === 'global' ? T('panel.editGlobal') : T('panel.editProject').replace('{p}', status ? status.projectKey : ''),
|
|
1729
|
+
),
|
|
1730
|
+
React.createElement('div', { style: card },
|
|
1731
|
+
React.createElement('div', { style: h() }, T('panel.sandbox')),
|
|
1732
|
+
React.createElement('div', { style: { fontSize: 12, color: 'rgba(128,128,128,0.85)', marginBottom: 6 } },
|
|
1733
|
+
sandboxVal === 'inherit' ? T('sandbox.desc.inherit').replace('{g}', sandboxLabel(sandbox.global)) : (sandboxVal === 'workspace-write' ? T('sandbox.desc.ww') : T('sandbox.desc.fa')),
|
|
1734
|
+
),
|
|
1735
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' } },
|
|
1736
|
+
React.createElement('select', { className: 'pg-field', style: { padding: '2px 6px' }, value: sandboxVal, onChange: changeSandbox, disabled: busy },
|
|
1737
|
+
sandboxOptions.map((m) => React.createElement('option', { key: m, value: m }, sandboxLabel(m))),
|
|
1738
|
+
),
|
|
1739
|
+
),
|
|
1740
|
+
),
|
|
1741
|
+
CATS.map(catBlock),
|
|
1742
|
+
React.createElement('div', { style: card },
|
|
1743
|
+
React.createElement('div', { style: h() }, T('panel.quick')),
|
|
1744
|
+
React.createElement('div', { style: { fontSize: 12, color: 'rgba(128,128,128,0.8)', marginBottom: 6 } }, T('panel.quickHint')),
|
|
1745
|
+
quickTools.map(quickRow),
|
|
1746
|
+
React.createElement('div', { style: { display: 'flex', gap: 6, marginTop: 6, flexWrap: 'wrap' } },
|
|
1747
|
+
React.createElement('input', { className: 'pg-field', placeholder: T('panel.quickAdd'), value: newTool, onChange: (e) => setNewTool(e.target.value), disabled: busy }),
|
|
1748
|
+
React.createElement('button', { className: 'pg-btn', disabled: busy, onClick: addQuick }, T('panel.quickAddBtn')),
|
|
1749
|
+
),
|
|
1750
|
+
),
|
|
1751
|
+
React.createElement('div', { style: card },
|
|
1752
|
+
React.createElement('div', { style: h() }, T('panel.rules')),
|
|
1753
|
+
React.createElement('div', { style: { fontSize: 12, color: 'rgba(128,128,128,0.8)', marginBottom: 8 } }, T('panel.rulesHint')),
|
|
1754
|
+
React.createElement('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center', marginBottom: 8 } },
|
|
1755
|
+
React.createElement('select', { value: form.action, onChange: setFormKey('action'), className: 'pg-field', disabled: busy },
|
|
1756
|
+
React.createElement('option', { value: 'deny' }, T('panel.actionDeny')), React.createElement('option', { value: 'ask' }, T('panel.actionAsk')), React.createElement('option', { value: 'allow' }, T('panel.actionAllow')),
|
|
1757
|
+
),
|
|
1758
|
+
React.createElement('input', { placeholder: T('panel.toolPh'), value: form.tool, onChange: setFormKey('tool'), className: 'pg-field', disabled: busy }),
|
|
1759
|
+
React.createElement('input', { placeholder: T('panel.pathPh'), value: form.path, onChange: setFormKey('path'), className: 'pg-field', disabled: busy }),
|
|
1760
|
+
React.createElement('input', { placeholder: T('panel.argsPh'), value: form.args, onChange: setFormKey('args'), className: 'pg-field', disabled: busy }),
|
|
1761
|
+
React.createElement('input', { placeholder: T('panel.reasonPh'), value: form.reason, onChange: setFormKey('reason'), className: 'pg-field', disabled: busy }),
|
|
1762
|
+
React.createElement('button', { className: 'pg-btn', disabled: busy, onClick: addRule }, tab === 'global' ? T('panel.addToGlobal') : T('panel.addToProject')),
|
|
1763
|
+
),
|
|
1764
|
+
React.createElement('div', { style: { marginTop: 8 } },
|
|
1765
|
+
React.createElement('div', { style: { fontSize: 12, fontWeight: 600, marginBottom: 4 } }, T('panel.ruleList').replace('{t}', T(tab === 'global' ? 'panel.tabGlobal' : 'panel.tabProject')).replace('{n}', String(customList ? customList.length : 0))),
|
|
1766
|
+
customList && customList.length ? customList.map(ruleRow) : React.createElement('div', { style: { fontSize: 12, color: 'rgba(128,128,128,0.8)' } }, T('panel.noRules')),
|
|
1767
|
+
),
|
|
1768
|
+
),
|
|
1769
|
+
React.createElement('div', { style: card },
|
|
1770
|
+
React.createElement('div', { style: h() }, T('panel.decisions')),
|
|
1771
|
+
decisions.length ? decisions.map((d, i) => React.createElement('div', { key: i, style: rowStyle },
|
|
1772
|
+
badge(d.action),
|
|
1773
|
+
React.createElement('span', { style: { fontFamily: 'monospace', fontSize: 12 } }, d.tool),
|
|
1774
|
+
React.createElement('span', { style: { fontSize: 12, color: 'rgba(128,128,128,0.9)' } }, d.reason || ''),
|
|
1775
|
+
)) : React.createElement('div', { style: { fontSize: 12, color: 'rgba(128,128,128,0.8)' } }, T('panel.noDecisions')),
|
|
1776
|
+
),
|
|
1777
|
+
msg ? React.createElement('div', { style: { fontSize: 12, color: '#1e88e5', marginBottom: 8 } }, msg) : null,
|
|
1778
|
+
);
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
function apply(ctx) {
|
|
1782
|
+
LC = ctx;
|
|
1783
|
+
// 中英文适配:注册 permgate 字典并绑定翻译函数(t 调用时读取当前语言)。
|
|
1784
|
+
// register 与 bind 分开容错:register 失败(如重复激活)时 bind 仍要生效,
|
|
1785
|
+
// 否则 T 会退化为纯中文回退函数,整个插件界面卡在中文。
|
|
1786
|
+
try {
|
|
1787
|
+
if (ctx.locale && typeof ctx.locale.register === 'function') {
|
|
1788
|
+
ctx.locale.register('permgate', { zh: I18N.zh, en: I18N.en });
|
|
1789
|
+
}
|
|
1790
|
+
} catch (e) {}
|
|
1791
|
+
try {
|
|
1792
|
+
if (ctx.locale && typeof ctx.locale.bind === 'function') {
|
|
1793
|
+
T = ctx.locale.bind('permgate');
|
|
1794
|
+
}
|
|
1795
|
+
} catch (e) {}
|
|
1796
|
+
if (typeof document !== 'undefined' && document.querySelector('style[data-plugin-css="dsh-permgate"]') === null) {
|
|
1797
|
+
const tag = document.createElement('style');
|
|
1798
|
+
tag.dataset.plugin = 'dsh-permgate';
|
|
1799
|
+
tag.dataset.pluginCss = 'dsh-permgate';
|
|
1800
|
+
tag.textContent = CSS;
|
|
1801
|
+
document.head.appendChild(tag);
|
|
1802
|
+
ctx.effect(() => () => { try { tag.remove(); } catch (e) {} });
|
|
1803
|
+
}
|
|
1804
|
+
if (typeof document !== 'undefined' && document.querySelector('style[data-plugin-css="dsh-permgate-diff"]') === null) {
|
|
1805
|
+
const tag = document.createElement('style');
|
|
1806
|
+
tag.dataset.plugin = 'dsh-permgate';
|
|
1807
|
+
tag.dataset.pluginCss = 'dsh-permgate-diff';
|
|
1808
|
+
tag.textContent = DIFF_CSS;
|
|
1809
|
+
document.head.appendChild(tag);
|
|
1810
|
+
ctx.effect(() => () => { try { tag.remove(); } catch (e) {} });
|
|
1811
|
+
}
|
|
1812
|
+
if (typeof document !== 'undefined' && document.querySelector('style[data-plugin-css="dsh-permgate-diff2"]') === null) {
|
|
1813
|
+
const tag = document.createElement('style');
|
|
1814
|
+
tag.dataset.plugin = 'dsh-permgate';
|
|
1815
|
+
tag.dataset.pluginCss = 'dsh-permgate-diff2';
|
|
1816
|
+
tag.textContent = DIFF2_CSS;
|
|
1817
|
+
document.head.appendChild(tag);
|
|
1818
|
+
ctx.effect(() => () => { try { tag.remove(); } catch (e) {} });
|
|
1819
|
+
}
|
|
1820
|
+
// 权限选择器兼容层:图标徽标为纯 CSS(按 aria-label 选择器,零 JS 依赖,
|
|
1821
|
+
// 跟随语言自动显示);预设名文本交换由 MutationObserver 驱动(childList +
|
|
1822
|
+
// characterData + attributes 全覆盖)+ locale 订阅,无定时器。安装仅在
|
|
1823
|
+
// documentElement 就绪时进行,最多重试 10 次后放弃(有界,非常驻定时器)。
|
|
1824
|
+
ctx.effect(() => {
|
|
1825
|
+
let install = null;
|
|
1826
|
+
let tries = 0;
|
|
1827
|
+
let tid = null;
|
|
1828
|
+
const attempt = () => {
|
|
1829
|
+
if (install) return;
|
|
1830
|
+
if (typeof document !== 'undefined' && document.documentElement) {
|
|
1831
|
+
try { install = pgInstallCompat(); } catch (e) {}
|
|
1832
|
+
return;
|
|
1833
|
+
}
|
|
1834
|
+
tries += 1;
|
|
1835
|
+
if (tries < 10) {
|
|
1836
|
+
try { tid = setTimeout(attempt, 500); } catch (e) {}
|
|
1837
|
+
}
|
|
1838
|
+
};
|
|
1839
|
+
attempt();
|
|
1840
|
+
return () => {
|
|
1841
|
+
if (tid !== null) { try { clearTimeout(tid); } catch (e) {} }
|
|
1842
|
+
if (install) { try { install(); } catch (e) {} }
|
|
1843
|
+
};
|
|
1844
|
+
}, 'permgate: permission selector compat');
|
|
1845
|
+
const slots = ctx.slots;
|
|
1846
|
+
// 注意:槽注册必须直接传组件(不能包一层 () => createElement(X, null) 工厂 —
|
|
1847
|
+
// 那样会丢弃槽系统注入的 props,DockBar 就拿不到 sessionId,状态无法按会话区分)
|
|
1848
|
+
slots.inject('settings.section', () => slots.register(
|
|
1849
|
+
// label 用 thunk:每次投影时重新求值,跟随当前语言(locale.bind 读取活动语言)
|
|
1850
|
+
{ name: 'settings.section', id: 'permgate', order: 30, label: () => T('settings.title') },
|
|
1851
|
+
Panel,
|
|
1852
|
+
));
|
|
1853
|
+
slots.inject('conversation.composer.dock', () => slots.register(
|
|
1854
|
+
{ name: 'conversation.composer.dock', id: 'permgate', order: 10 },
|
|
1855
|
+
DockBar,
|
|
1856
|
+
));
|
|
1857
|
+
slots.inject('shell.overlay', () => slots.register(
|
|
1858
|
+
{ name: 'shell.overlay', id: 'permgate-approval', order: 100 },
|
|
1859
|
+
OverlayRoot,
|
|
1860
|
+
));
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
exports.apply = apply;
|
|
1864
|
+
exports.inject = ['slots', 'locale'];
|
|
1865
|
+
return module.exports;
|
|
1866
|
+
}
|
|
1867
|
+
});
|