minecodex 2.0.55 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -26
- package/apps/dashboard/app.js +20 -14
- package/apps/dashboard/assets/chevron-left.svg +13 -0
- package/apps/dashboard/assets/chevron-right.svg +13 -0
- package/apps/dashboard/assets/feature-showcase.js +185 -0
- package/apps/dashboard/assets/pause.svg +14 -0
- package/apps/dashboard/assets/play.svg +13 -0
- package/apps/dashboard/assets/previews/composer-expand.jpg +0 -0
- package/apps/dashboard/assets/previews/composer-expand.mp4 +0 -0
- package/apps/dashboard/assets/previews/file-navigation.jpg +0 -0
- package/apps/dashboard/assets/previews/file-navigation.mp4 +0 -0
- package/apps/dashboard/assets/previews/files-auto-refresh.jpg +0 -0
- package/apps/dashboard/assets/previews/files-auto-refresh.mp4 +0 -0
- package/apps/dashboard/assets/previews/images.jpg +0 -0
- package/apps/dashboard/assets/previews/images.mp4 +0 -0
- package/apps/dashboard/assets/previews/manifest.json +67 -1
- package/apps/dashboard/assets/previews/markdown-reading.jpg +0 -0
- package/apps/dashboard/assets/previews/markdown-reading.mp4 +0 -0
- package/apps/dashboard/assets/previews/mermaid-diagrams.jpg +0 -0
- package/apps/dashboard/assets/previews/mermaid-diagrams.mp4 +0 -0
- package/apps/dashboard/assets/previews/model-slider.jpg +0 -0
- package/apps/dashboard/assets/previews/model-slider.mp4 +0 -0
- package/apps/dashboard/assets/previews/notes.jpg +0 -0
- package/apps/dashboard/assets/previews/notes.mp4 +0 -0
- package/apps/dashboard/assets/previews/quick-chat.jpg +0 -0
- package/apps/dashboard/assets/previews/quick-chat.mp4 +0 -0
- package/apps/dashboard/assets/previews/session-status.jpg +0 -0
- package/apps/dashboard/assets/previews/session-status.mp4 +0 -0
- package/apps/dashboard/assets/previews/sidebar-focus.jpg +0 -0
- package/apps/dashboard/assets/previews/sidebar-focus.mp4 +0 -0
- package/apps/dashboard/assets/previews/sites.jpg +0 -0
- package/apps/dashboard/assets/previews/sites.mp4 +0 -0
- package/apps/dashboard/assets/previews/usage-meter.jpg +0 -0
- package/apps/dashboard/assets/previews/usage-meter.mp4 +0 -0
- package/apps/dashboard/index.html +26 -2
- package/apps/dashboard/styles.css +58 -39
- package/assets/readme/hero-dark.svg +50 -0
- package/assets/readme/hero.svg +36 -79
- package/features/images/src/adapters/codex/rspack-modules.mjs +9 -3
- package/features/images/src/library-prompt-source.mjs +2 -2
- package/features/notes/codex-feature.json +1 -0
- package/features/quick-chat/src/adapters/codex/renderer.mjs +3 -1
- package/package.json +3 -3
- package/packages/cli/src/config.mjs +1 -2
- package/packages/cli/src/control-server.mjs +1 -1
- package/packages/plugin/assets/app.js +35 -35
- package/packages/runtime-host/src/codex-cdp.mjs +0 -7
- package/packages/runtime-host/src/codex-injection.mjs +50 -2114
- package/packages/runtime-host/src/codex-runtime.mjs +4 -203
- package/packages/runtime-host/src/feature-registry.mjs +10 -91
- package/packages/runtime-host/src/main.mjs +0 -1
- package/packages/ui/src/native/tokens.mjs +0 -28
- package/apps/dashboard/assets/feature-previews.js +0 -206
- package/features/images/src/adapters/codex/cloud-import.mjs +0 -52
- package/features/images/src/backend/cloud-library.mjs +0 -12
- package/features/images/src/chatgpt-image-source-page.mjs +0 -207
- package/features/images/src/chatgpt-image-source.mjs +0 -42
- package/features/images/src/http-server.mjs +0 -487
- package/features/images/src/main.mjs +0 -52
- package/features/images/src/save-as.mjs +0 -69
- package/features/images/web/app.js +0 -1780
- package/features/images/web/i18n.mjs +0 -132
- package/features/images/web/index.html +0 -187
- package/features/images/web/styles.css +0 -1114
- package/packages/runtime-host/src/host-actions.mjs +0 -221
|
@@ -11,82 +11,15 @@ import { MCP_NAVIGATION_ICONS } from "../../plugin/src/navigation-icons.mjs";
|
|
|
11
11
|
import { randomBytes } from "node:crypto";
|
|
12
12
|
import { CODEX_THEME_TOKENS, CODEX_THEME_TOKEN_ALIASES, MINECODEX_THEME_BINDINGS } from "../../ui/src/native/tokens.mjs";
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
export const RUNTIME_VERSION = 115;
|
|
15
16
|
export const HOST_BINDING_NAME = "__codexPersonalHostAction";
|
|
16
17
|
export const CSP_BOOTSTRAP_VERSION = 1;
|
|
17
18
|
export const CSP_BOOTSTRAP_KEY = "__mineCodexCspBootstrapVersion";
|
|
18
19
|
export const CODEX_APP_ORIGIN = "app://-";
|
|
19
|
-
|
|
20
|
-
export const SURFACE_FRAME_PREFIX = "minecodex-surface-";
|
|
21
|
-
export const LOOPBACK_SURFACE_HOSTS = new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
|
|
22
|
-
export const RIGHT_PANEL_ROUTE_KINDS = Object.freeze(["home", "local-thread"]);
|
|
23
|
-
|
|
24
|
-
export const RESPONSIVE_SUMMARY_LAYOUT = Object.freeze({
|
|
25
|
-
contentBaseWidth: 736,
|
|
26
|
-
overlayClearance: 180,
|
|
27
|
-
gutterClearance: 400,
|
|
28
|
-
panelWidth: 300,
|
|
29
|
-
panelInset: 16,
|
|
30
|
-
verticalInset: 12,
|
|
31
|
-
popoverOffset: 8,
|
|
32
|
-
popoverBottomInset: 6,
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
export function responsiveSummaryDisplayMode(mainContentTargetWidth, layout = RESPONSIVE_SUMMARY_LAYOUT) {
|
|
36
|
-
const clearance = (Number(mainContentTargetWidth) - layout.contentBaseWidth) / 2;
|
|
37
|
-
if (clearance < layout.overlayClearance) return "overlay";
|
|
38
|
-
if (clearance < layout.gutterClearance) return "shift";
|
|
39
|
-
return "gutter";
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function responsiveSummaryVisibleSurface({ displayMode, isPinned, isPopoverOpen }) {
|
|
43
|
-
if (displayMode !== "overlay" && isPinned) return "inline";
|
|
44
|
-
if (displayMode === "overlay" && isPopoverOpen) return "popover";
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function responsiveSummaryContentShift({ displayMode, isPinned }, layout = RESPONSIVE_SUMMARY_LAYOUT) {
|
|
49
|
-
return displayMode !== "overlay" && isPinned
|
|
50
|
-
? -(layout.panelWidth + layout.panelInset) / 2
|
|
51
|
-
: 0;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function declaredSurfaceUrls(feature) {
|
|
55
|
-
return new Set([
|
|
56
|
-
feature.surfaceUrl,
|
|
57
|
-
feature.pinnedSummary?.surfaceUrl,
|
|
58
|
-
...(feature.detailTabs ?? []).map((detail) => detail.surfaceUrl),
|
|
59
|
-
].filter(Boolean));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function requireDeclaredLoopbackSurface(feature, value) {
|
|
63
|
-
if (typeof value !== "string") {
|
|
64
|
-
throw Object.assign(new Error("Surface URL is not declared by this feature"), {
|
|
65
|
-
code: "SURFACE_URL_NOT_ALLOWED",
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
const url = new URL(value);
|
|
69
|
-
const matchesDeclaredDocument = Array.from(declaredSurfaceUrls(feature), (declared) => new URL(declared))
|
|
70
|
-
.some((declared) => (
|
|
71
|
-
declared.origin === url.origin
|
|
72
|
-
&& declared.username === url.username
|
|
73
|
-
&& declared.password === url.password
|
|
74
|
-
&& declared.pathname === url.pathname
|
|
75
|
-
&& declared.hash === url.hash
|
|
76
|
-
));
|
|
77
|
-
if (!matchesDeclaredDocument) {
|
|
78
|
-
throw Object.assign(new Error("Surface URL is not declared by this feature"), {
|
|
79
|
-
code: "SURFACE_URL_NOT_ALLOWED",
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
if (url.protocol !== "http:" || !LOOPBACK_SURFACE_HOSTS.has(url.hostname)) {
|
|
83
|
-
throw Object.assign(new Error("Surface URL must use an exact loopback HTTP origin"), {
|
|
84
|
-
code: "SURFACE_URL_NOT_ALLOWED",
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
return url;
|
|
88
|
-
}
|
|
20
|
+
const LOOPBACK_SERVICE_HOSTS = new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
|
|
89
21
|
|
|
22
|
+
// 带本机服务的 page-script 只能经 RuntimeHost 访问与 healthUrl 同源的 loopback HTTP 服务。
|
|
90
23
|
export function pageScriptServiceOrigin(feature) {
|
|
91
24
|
if (!feature.pageScript || !feature.surfaceUrl || !feature.healthUrl) return null;
|
|
92
25
|
const surfaceUrl = new URL(feature.surfaceUrl);
|
|
@@ -94,30 +27,11 @@ export function pageScriptServiceOrigin(feature) {
|
|
|
94
27
|
if (
|
|
95
28
|
surfaceUrl.origin !== healthUrl.origin
|
|
96
29
|
|| surfaceUrl.protocol !== "http:"
|
|
97
|
-
|| !
|
|
30
|
+
|| !LOOPBACK_SERVICE_HOSTS.has(surfaceUrl.hostname)
|
|
98
31
|
) return null;
|
|
99
32
|
return surfaceUrl.origin;
|
|
100
33
|
}
|
|
101
34
|
|
|
102
|
-
export function findFrameByName(frameTree, frameName) {
|
|
103
|
-
if (frameTree.frame?.name === frameName) return frameTree.frame;
|
|
104
|
-
for (const child of frameTree.childFrames ?? []) {
|
|
105
|
-
const match = findFrameByName(child, frameName);
|
|
106
|
-
if (match) return match;
|
|
107
|
-
}
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export function documentWithBase(html, surfaceUrl) {
|
|
112
|
-
const head = /<head(?:\s[^>]*)?>/i;
|
|
113
|
-
if (!head.test(html)) {
|
|
114
|
-
throw Object.assign(new Error("Surface document has no head element"), {
|
|
115
|
-
code: "SURFACE_DOCUMENT_INVALID",
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
return html.replace(head, (match) => `${match}<base href=${JSON.stringify(surfaceUrl)}>`);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
35
|
// createInjectionSource:产出注入 Codex Renderer 的自包含脚本字符串。
|
|
122
36
|
// 返回的脚本经 CDP 注入后独立执行(install 函数体必须自包含,不能引用模块外部标识符)。
|
|
123
37
|
export function createInjectionSource(features, {
|
|
@@ -146,25 +60,6 @@ export function createInjectionSource(features, {
|
|
|
146
60
|
const lifetime = new AbortController();
|
|
147
61
|
const codex = createCodexEnvironment();
|
|
148
62
|
const featureUI = installUI(lifetime.signal, {codex,mcpServer:"minecodex",mcpPluginId:"minecodex@minecodex-local",mcpNavigationIcons:config.mcpNavigationIcons,themeTokens:config.themeTokens,themeTokenAliases:config.themeTokenAliases,themeBindings:config.themeBindings});
|
|
149
|
-
const rightPanelRouteKinds = new Set(config.rightPanelRouteKinds);
|
|
150
|
-
|
|
151
|
-
const entryMarker = "data-codex-personal-feature";
|
|
152
|
-
const pageSurfaceMarker = "data-codex-personal-surface";
|
|
153
|
-
const pinnedSurfaceMarker = "data-codex-personal-pinned-summary";
|
|
154
|
-
const modalSurfaceMarker = "data-codex-personal-modal";
|
|
155
|
-
const promptPreviewMarker = "data-codex-personal-prompt-preview";
|
|
156
|
-
const nativeLabels = {
|
|
157
|
-
sites: ["sites", "站点"],
|
|
158
|
-
scheduled: ["scheduled", "计划任务"],
|
|
159
|
-
plugins: ["plugins", "插件"],
|
|
160
|
-
};
|
|
161
|
-
const nativeToolbarLabels = {
|
|
162
|
-
summary: ["Toggle summary", "切换摘要"],
|
|
163
|
-
pinnedSummary: ["Toggle pinned summary", "切换固定摘要"],
|
|
164
|
-
bottomPanel: ["Toggle bottom panel", "切换底部面板"],
|
|
165
|
-
sidePanel: ["Toggle side panel", "切换侧边面板"],
|
|
166
|
-
temporaryChat: ["temporary chat", "临时聊天", "临时对话"],
|
|
167
|
-
};
|
|
168
63
|
|
|
169
64
|
function normalizedLocale(value) {
|
|
170
65
|
return String(value ?? "").toLowerCase().startsWith("zh") ? "zh-CN" : "en";
|
|
@@ -179,26 +74,12 @@ export function createInjectionSource(features, {
|
|
|
179
74
|
}
|
|
180
75
|
|
|
181
76
|
// ========================================================================
|
|
182
|
-
// 配置派生与运行状态
|
|
77
|
+
// 配置派生与运行状态
|
|
183
78
|
// ========================================================================
|
|
184
79
|
|
|
185
80
|
const features = config.features.map((feature) => ({
|
|
186
81
|
...feature,
|
|
187
82
|
label: localizedText(feature.label, feature.labels),
|
|
188
|
-
entry: feature.entry ? {
|
|
189
|
-
...feature.entry,
|
|
190
|
-
ariaLabel: localizedText(feature.entry.ariaLabel, feature.entry.ariaLabels),
|
|
191
|
-
} : { kind: "sidebar" },
|
|
192
|
-
pinnedSummary: feature.pinnedSummary ? {
|
|
193
|
-
...feature.pinnedSummary,
|
|
194
|
-
label: localizedText(feature.pinnedSummary.label, feature.pinnedSummary.labels),
|
|
195
|
-
overlayLabel: localizedText(feature.pinnedSummary.overlayLabel, feature.pinnedSummary.overlayLabels),
|
|
196
|
-
pinnedLabel: localizedText(feature.pinnedSummary.pinnedLabel, feature.pinnedSummary.pinnedLabels),
|
|
197
|
-
} : null,
|
|
198
|
-
detailTabs: (feature.detailTabs ?? []).map((detail) => ({
|
|
199
|
-
...detail,
|
|
200
|
-
label: localizedText(detail.label, detail.labels),
|
|
201
|
-
})),
|
|
202
83
|
modelSelector: feature.modelSelector ? {
|
|
203
84
|
...feature.modelSelector,
|
|
204
85
|
strings: Object.fromEntries(Object.entries(feature.modelSelector.strings ?? {}).map(
|
|
@@ -209,42 +90,36 @@ export function createInjectionSource(features, {
|
|
|
209
90
|
}));
|
|
210
91
|
const selfCheck = createRendererSelfCheck(features, config.sessionId, featureUI.theme);
|
|
211
92
|
const featureById = new Map(features.map((feature) => [feature.id, feature]));
|
|
212
|
-
const sidebarFeatures = features.filter((feature) => feature.entry?.kind === "sidebar");
|
|
213
|
-
const toolbarFeatures = features.filter((feature) => feature.entry?.kind === "conversation-toolbar");
|
|
214
93
|
const modelSelectorFeature = features.find(
|
|
215
94
|
(feature) => feature.entry?.kind === "composer-model-selector",
|
|
216
95
|
) ?? null;
|
|
217
96
|
const pageScriptFeatures = features.filter((feature) => feature.pageScript);
|
|
218
|
-
const pageSurfaces = new Map();
|
|
219
|
-
const pinnedSurfaces = new Map();
|
|
220
|
-
const surfaceRecords = new Map();
|
|
221
|
-
const nativeDetailComponents = new Map();
|
|
222
|
-
const nativeDetailSurfaceKeys = new Map();
|
|
223
|
-
const nativeOpenTabSessions = new Set();
|
|
224
|
-
const summaryStates = new Map();
|
|
225
|
-
let activePageFeatureId = null;
|
|
226
|
-
let activePinnedFeatureId = null;
|
|
227
97
|
let ensureQueued = false;
|
|
228
98
|
let ensureFrame = null;
|
|
229
99
|
let ensureTimer = null;
|
|
230
|
-
let themeQueued = false;
|
|
231
|
-
let suppressedSelections = [];
|
|
232
100
|
let savedComposerRange = null;
|
|
233
101
|
let savedComposerThread = null;
|
|
234
|
-
let currentThread = null;
|
|
235
|
-
let currentSummaryDisplayMode = null;
|
|
236
|
-
let shiftedConversationOwners = [];
|
|
237
|
-
let mainContentObserver = null;
|
|
238
|
-
let toolbarReadiness = null;
|
|
239
102
|
const domObservers = [];
|
|
240
103
|
const pageScriptCleanups = [];
|
|
241
104
|
const pendingHostActions = new Map();
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
105
|
+
|
|
106
|
+
function createMarkupIcon(iconDefinition, className, size = 16) {
|
|
107
|
+
if (!iconDefinition?.markup) return null;
|
|
108
|
+
const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
109
|
+
icon.setAttribute("width", String(size));
|
|
110
|
+
icon.setAttribute("height", String(size));
|
|
111
|
+
icon.setAttribute("viewBox", iconDefinition.viewBox ?? "0 0 24 24");
|
|
112
|
+
icon.setAttribute("fill", "none");
|
|
113
|
+
icon.setAttribute("stroke", "currentColor");
|
|
114
|
+
icon.setAttribute("stroke-width", "1.5");
|
|
115
|
+
icon.setAttribute("stroke-linecap", "round");
|
|
116
|
+
icon.setAttribute("stroke-linejoin", "round");
|
|
117
|
+
icon.setAttribute("aria-hidden", "true");
|
|
118
|
+
if (className) icon.setAttribute("class", className);
|
|
119
|
+
icon.innerHTML = iconDefinition.markup;
|
|
120
|
+
return icon;
|
|
121
|
+
}
|
|
122
|
+
|
|
248
123
|
let modelSlider = {ensure() {}, dispose() {}};
|
|
249
124
|
try {
|
|
250
125
|
modelSlider = installModelSliderFeature(lifetime.signal, {
|
|
@@ -296,515 +171,20 @@ export function createInjectionSource(features, {
|
|
|
296
171
|
}
|
|
297
172
|
}
|
|
298
173
|
|
|
299
|
-
// ========================================================================
|
|
300
|
-
// Toolbar 入口定位与就绪 — 识别原生 Toolbar/Summary 控件并等待其就绪
|
|
301
|
-
// ========================================================================
|
|
302
|
-
|
|
303
|
-
function findEntry(featureId) {
|
|
304
|
-
return Array.from(document.querySelectorAll(`[${entryMarker}]`)).find(
|
|
305
|
-
(entry) => entry.getAttribute(entryMarker) === featureId,
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function findNativeEntry(key) {
|
|
310
|
-
const labels = nativeLabels[key] ?? [String(key).toLowerCase()];
|
|
311
|
-
return Array.from(document.querySelectorAll("a, button")).find((element) => (
|
|
312
|
-
labels.includes(element.textContent?.trim().toLowerCase())
|
|
313
|
-
));
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
function findReferenceEntry() {
|
|
317
|
-
return findNativeEntry("plugins");
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function isVisibleToolbarControl(element) {
|
|
321
|
-
if (!(element instanceof HTMLElement)) return false;
|
|
322
|
-
const style = getComputedStyle(element);
|
|
323
|
-
const rect = element.getBoundingClientRect();
|
|
324
|
-
return style.display !== "none"
|
|
325
|
-
&& style.visibility !== "hidden"
|
|
326
|
-
&& rect.width > 0
|
|
327
|
-
&& rect.height > 0;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function hasNativeToolbarLabel(button, key) {
|
|
331
|
-
const label = String(button?.getAttribute("aria-label") ?? "").trim().toLowerCase();
|
|
332
|
-
return nativeToolbarLabels[key].some((candidate) => (
|
|
333
|
-
key === "temporaryChat"
|
|
334
|
-
? label.includes(candidate.toLowerCase())
|
|
335
|
-
: label === candidate.toLowerCase()
|
|
336
|
-
));
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function nativeSummaryButton() {
|
|
340
|
-
return Array.from(document.querySelectorAll("button")).find((button) => (
|
|
341
|
-
(
|
|
342
|
-
hasNativeToolbarLabel(button, "summary")
|
|
343
|
-
|| hasNativeToolbarLabel(button, "pinnedSummary")
|
|
344
|
-
)
|
|
345
|
-
&& isVisibleToolbarControl(button)
|
|
346
|
-
)) ?? null;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
function nativeBottomPanelButton() {
|
|
350
|
-
return Array.from(document.querySelectorAll("button")).find((button) => {
|
|
351
|
-
if (!hasNativeToolbarLabel(button, "bottomPanel")) return false;
|
|
352
|
-
return isVisibleToolbarControl(button);
|
|
353
|
-
}) ?? null;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function nativeSidePanelButton() {
|
|
357
|
-
return Array.from(document.querySelectorAll("button")).find((button) => {
|
|
358
|
-
if (!hasNativeToolbarLabel(button, "sidePanel")) return false;
|
|
359
|
-
return isVisibleToolbarControl(button);
|
|
360
|
-
}) ?? null;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
function nativeTemporaryChatButton() {
|
|
364
|
-
return Array.from(document.querySelectorAll("button")).find((button) => {
|
|
365
|
-
if (!hasNativeToolbarLabel(button, "temporaryChat")) return false;
|
|
366
|
-
return isVisibleToolbarControl(button);
|
|
367
|
-
}) ?? null;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
function structuralToolbarAnchor() {
|
|
371
|
-
const buttons = Array.from(document.querySelectorAll("button"));
|
|
372
|
-
return buttons.find((button) => hasNativeToolbarLabel(button, "temporaryChat"))
|
|
373
|
-
?? buttons.find((button) => (
|
|
374
|
-
hasNativeToolbarLabel(button, "summary")
|
|
375
|
-
|| hasNativeToolbarLabel(button, "pinnedSummary")
|
|
376
|
-
))
|
|
377
|
-
?? buttons.find((button) => hasNativeToolbarLabel(button, "bottomPanel"))
|
|
378
|
-
?? buttons.find((button) => hasNativeToolbarLabel(button, "sidePanel"))
|
|
379
|
-
?? null;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
function stopToolbarReadiness() {
|
|
383
|
-
if (!toolbarReadiness) return;
|
|
384
|
-
toolbarReadiness.observer.disconnect();
|
|
385
|
-
if (toolbarReadiness.retryTimer != null) clearTimeout(toolbarReadiness.retryTimer);
|
|
386
|
-
if (toolbarReadiness.deadlineTimer != null) clearTimeout(toolbarReadiness.deadlineTimer);
|
|
387
|
-
toolbarReadiness = null;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
function ensureToolbarReadiness(candidate) {
|
|
391
|
-
const thread = threadIdentity();
|
|
392
|
-
if (toolbarReadiness?.candidate === candidate && toolbarReadiness.thread === thread) return;
|
|
393
|
-
stopToolbarReadiness();
|
|
394
|
-
const record = {
|
|
395
|
-
candidate,
|
|
396
|
-
thread,
|
|
397
|
-
attempts: 0,
|
|
398
|
-
retryTimer: null,
|
|
399
|
-
deadlineTimer: null,
|
|
400
|
-
observer: null,
|
|
401
|
-
};
|
|
402
|
-
const wake = () => {
|
|
403
|
-
if (toolbarReadiness !== record) return;
|
|
404
|
-
queueEnsure();
|
|
405
|
-
};
|
|
406
|
-
record.observer = new ResizeObserver(wake);
|
|
407
|
-
record.observer.observe(candidate);
|
|
408
|
-
const header = candidate.closest("header");
|
|
409
|
-
if (header) record.observer.observe(header);
|
|
410
|
-
const retry = () => {
|
|
411
|
-
if (toolbarReadiness !== record || record.attempts >= 8) return;
|
|
412
|
-
record.attempts += 1;
|
|
413
|
-
wake();
|
|
414
|
-
record.retryTimer = setTimeout(retry, 75);
|
|
415
|
-
};
|
|
416
|
-
record.retryTimer = setTimeout(retry, 75);
|
|
417
|
-
record.deadlineTimer = setTimeout(() => {
|
|
418
|
-
if (toolbarReadiness === record) stopToolbarReadiness();
|
|
419
|
-
}, 1_500);
|
|
420
|
-
toolbarReadiness = record;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
function toolbarControlRoot(button) {
|
|
424
|
-
return button?.parentElement?.parentElement ?? button?.parentElement ?? button ?? null;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
function nativeSummaryToolbarGroup(nativeButton) {
|
|
428
|
-
return toolbarControlRoot(nativeButton)?.parentElement ?? null;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
function conversationToolbarHost(nativeButton) {
|
|
432
|
-
const header = nativeButton?.closest("header");
|
|
433
|
-
if (!header) return null;
|
|
434
|
-
return Array.from(header.children).find((child) => {
|
|
435
|
-
if (!(child instanceof HTMLElement) || !isVisibleToolbarControl(child)) return false;
|
|
436
|
-
return Number.parseFloat(getComputedStyle(child).flexGrow) > 0;
|
|
437
|
-
}) ?? null;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
function ensureFallbackSummaryToolbarGroup(nativeButton) {
|
|
441
|
-
const toolbarHost = conversationToolbarHost(nativeButton);
|
|
442
|
-
if (!toolbarHost) return null;
|
|
443
|
-
let group = toolbarHost.querySelector(":scope > [data-codex-personal-summary-toolbar-group]");
|
|
444
|
-
if (!group) {
|
|
445
|
-
group = document.createElement("div");
|
|
446
|
-
group.setAttribute("data-codex-personal-summary-toolbar-group", "fallback");
|
|
447
|
-
// The host toolbar strip is pointer-events:none; without this the
|
|
448
|
-
// fallback entries would be visible but unclickable.
|
|
449
|
-
group.className = "ms-auto flex shrink-0 items-center gap-1.5 pointer-events-auto";
|
|
450
|
-
// Same structure as the native tool group (e.g. the temporary-chat
|
|
451
|
-
// group in Chat mode): an in-flow ms-auto strip inside the flexible
|
|
452
|
-
// header area. It sits at the right edge while the side panel is
|
|
453
|
-
// closed and, when the side panel widens its host container, moves
|
|
454
|
-
// together with the native layout exactly like Chat mode does.
|
|
455
|
-
toolbarHost.append(group);
|
|
456
|
-
}
|
|
457
|
-
return group;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
function toolbarEntryFitsHeader(entryRoot) {
|
|
461
|
-
const header = entryRoot.closest("header");
|
|
462
|
-
if (!header) return true;
|
|
463
|
-
const headerRect = header.getBoundingClientRect();
|
|
464
|
-
const entryRect = entryRoot.getBoundingClientRect();
|
|
465
|
-
if (entryRect.width <= 0 || entryRect.height <= 0) return false;
|
|
466
|
-
return entryRect.right <= headerRect.right + 1 && entryRect.left >= headerRect.left - 1;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
function isNativeSummaryButton(element) {
|
|
470
|
-
if (!(element instanceof Element)) return false;
|
|
471
|
-
const button = element.closest("button");
|
|
472
|
-
return hasNativeToolbarLabel(button, "summary")
|
|
473
|
-
|| hasNativeToolbarLabel(button, "pinnedSummary");
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
function mainContentViewport() {
|
|
477
|
-
const composer = currentComposer();
|
|
478
|
-
return composer?.closest("main[data-app-shell-main-surface]")
|
|
479
|
-
?? document.querySelector("main[data-app-shell-main-surface]")
|
|
480
|
-
?? document.querySelector("[data-app-shell-main-content-layout]")
|
|
481
|
-
?? document.querySelector("main");
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
function visibleRightWorkspacePanels() {
|
|
485
|
-
return Array.from(document.querySelectorAll('[data-app-shell-tab-panel-controller="right"]'))
|
|
486
|
-
.filter((panel) => {
|
|
487
|
-
if (!(panel instanceof HTMLElement) || panel.hidden) return false;
|
|
488
|
-
const style = getComputedStyle(panel);
|
|
489
|
-
const rect = panel.getBoundingClientRect();
|
|
490
|
-
return style.display !== "none" && style.visibility !== "hidden" && rect.width > 0;
|
|
491
|
-
});
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
function mainContentTargetGeometry() {
|
|
495
|
-
const viewport = mainContentViewport();
|
|
496
|
-
const rect = viewport?.getBoundingClientRect() ?? {
|
|
497
|
-
left: Math.max(0, sidebarRight()),
|
|
498
|
-
right: innerWidth,
|
|
499
|
-
top: 0,
|
|
500
|
-
bottom: innerHeight,
|
|
501
|
-
width: Math.max(0, innerWidth - sidebarRight()),
|
|
502
|
-
};
|
|
503
|
-
let right = rect.right;
|
|
504
|
-
for (const panel of visibleRightWorkspacePanels()) {
|
|
505
|
-
const panelRect = panel.getBoundingClientRect();
|
|
506
|
-
if (panelRect.left > rect.left && panelRect.left < right) right = panelRect.left;
|
|
507
|
-
}
|
|
508
|
-
return {
|
|
509
|
-
viewport,
|
|
510
|
-
left: rect.left,
|
|
511
|
-
right,
|
|
512
|
-
top: rect.top,
|
|
513
|
-
bottom: rect.bottom,
|
|
514
|
-
width: Math.max(0, right - rect.left),
|
|
515
|
-
};
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
function summaryDisplayMode() {
|
|
519
|
-
const layout = config.summaryLayout;
|
|
520
|
-
const clearance = (mainContentTargetGeometry().width - layout.contentBaseWidth) / 2;
|
|
521
|
-
if (clearance < layout.overlayClearance) return "overlay";
|
|
522
|
-
if (clearance < layout.gutterClearance) return "shift";
|
|
523
|
-
return "gutter";
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
function summaryState(featureId) {
|
|
527
|
-
let state = summaryStates.get(featureId);
|
|
528
|
-
if (!state) {
|
|
529
|
-
state = { isPinned: false, isPopoverOpen: false };
|
|
530
|
-
summaryStates.set(featureId, state);
|
|
531
|
-
}
|
|
532
|
-
return state;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
function visibleSummarySurface(featureId, displayMode = summaryDisplayMode()) {
|
|
536
|
-
const state = summaryState(featureId);
|
|
537
|
-
if (displayMode !== "overlay" && state.isPinned) return "inline";
|
|
538
|
-
if (displayMode === "overlay" && state.isPopoverOpen) return "popover";
|
|
539
|
-
return null;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
function summaryButtonLabel(feature, displayMode = summaryDisplayMode()) {
|
|
543
|
-
const definition = feature.pinnedSummary ?? {};
|
|
544
|
-
return displayMode === "overlay"
|
|
545
|
-
? definition.overlayLabel ?? (locale === "zh-CN" ? "切换笔记" : "Toggle notes")
|
|
546
|
-
: definition.pinnedLabel
|
|
547
|
-
?? feature.entry.ariaLabel
|
|
548
|
-
?? (locale === "zh-CN" ? "切换固定笔记" : "Toggle pinned notes");
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
function reducedMotion() {
|
|
552
|
-
return window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches ?? false;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
// ========================================================================
|
|
556
|
-
// 通用图标工厂 — feature 入口与插件适配共享的 markup 图标
|
|
557
|
-
// ========================================================================
|
|
558
|
-
|
|
559
|
-
function createMarkupIcon(iconDefinition, className, size = 16) {
|
|
560
|
-
if (!iconDefinition?.markup) return null;
|
|
561
|
-
const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
562
|
-
icon.setAttribute("width", String(size));
|
|
563
|
-
icon.setAttribute("height", String(size));
|
|
564
|
-
icon.setAttribute("viewBox", iconDefinition.viewBox ?? "0 0 24 24");
|
|
565
|
-
icon.setAttribute("fill", "none");
|
|
566
|
-
icon.setAttribute("stroke", "currentColor");
|
|
567
|
-
icon.setAttribute("stroke-width", "1.5");
|
|
568
|
-
icon.setAttribute("stroke-linecap", "round");
|
|
569
|
-
icon.setAttribute("stroke-linejoin", "round");
|
|
570
|
-
icon.setAttribute("aria-hidden", "true");
|
|
571
|
-
if (className) icon.setAttribute("class", className);
|
|
572
|
-
icon.innerHTML = iconDefinition.markup;
|
|
573
|
-
return icon;
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
function createFeatureIcon(feature, className, size = 16) {
|
|
577
|
-
return createMarkupIcon(feature.icon, className, size);
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
// ========================================================================
|
|
583
|
-
// Surface 注册与主题桥 — surface 记录、侧栏状态、主题 token 与 Composer 文本
|
|
584
|
-
// ========================================================================
|
|
585
|
-
|
|
586
|
-
function surfaceKey(featureId, kind, detailId = "") {
|
|
587
|
-
return `${featureId}:${kind}:${detailId}`;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
function surfaceFrameName(featureId) {
|
|
591
|
-
const nonce = crypto.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
592
|
-
return `minecodex-surface-${config.sessionId}-${featureId}-${nonce}`;
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
function requestSurfaceLoad(record) {
|
|
596
|
-
if (!record || record.loading || typeof globalThis[config.bindingName] !== "function") return false;
|
|
597
|
-
const requestId = crypto.randomUUID?.()
|
|
598
|
-
?? `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
599
|
-
record.loading = true;
|
|
600
|
-
record.ready = false;
|
|
601
|
-
record.loadError = null;
|
|
602
|
-
pendingHostActions.set(requestId, { recordKey: record.key, kind: "surface-load" });
|
|
603
|
-
globalThis[config.bindingName](JSON.stringify({
|
|
604
|
-
token: config.bindingToken,
|
|
605
|
-
featureId: record.featureId,
|
|
606
|
-
requestId,
|
|
607
|
-
action: "load-surface",
|
|
608
|
-
payload: {
|
|
609
|
-
frameName: record.frame.name,
|
|
610
|
-
surfaceUrl: record.surfaceUrl,
|
|
611
|
-
},
|
|
612
|
-
}));
|
|
613
|
-
return true;
|
|
614
|
-
}
|
|
615
|
-
|
|
616
174
|
function updateHostBinding(bindingName, bindingToken) {
|
|
617
175
|
if (config.bindingName === bindingName && config.bindingToken === bindingToken) return;
|
|
618
|
-
// CDP 重连会轮换 token
|
|
176
|
+
// CDP 重连会轮换 token;页面脚本保留原状态,只更新后续请求使用的凭据。
|
|
619
177
|
config.bindingName = bindingName;
|
|
620
178
|
config.bindingToken = bindingToken;
|
|
621
179
|
for (const feature of pageScriptFeatures) {
|
|
622
180
|
feature.pageScript.config.bindingName = bindingName;
|
|
623
181
|
feature.pageScript.config.bindingToken = bindingToken;
|
|
624
182
|
}
|
|
625
|
-
for (const [requestId, pending] of pendingHostActions) {
|
|
626
|
-
if (pending.kind === "surface-load") pendingHostActions.delete(requestId);
|
|
627
|
-
}
|
|
628
|
-
for (const record of surfaceRecords.values()) {
|
|
629
|
-
if (record.ready) continue;
|
|
630
|
-
record.loading = false;
|
|
631
|
-
requestSurfaceLoad(record);
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
function postSurfaceActive(record, active) {
|
|
636
|
-
if (!record || record.kind !== "page") return;
|
|
637
|
-
record.frame.contentWindow?.postMessage({
|
|
638
|
-
type: "codex-personal:surface-active",
|
|
639
|
-
active: Boolean(active),
|
|
640
|
-
}, "/");
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
function nativeSidebarTrigger() {
|
|
644
|
-
const triggers = Array.from(document.querySelectorAll(
|
|
645
|
-
'button[data-app-shell-sidebar-trigger="true"]',
|
|
646
|
-
));
|
|
647
|
-
return triggers.find((button) => isVisibleToolbarControl(button)) ?? triggers[0] ?? null;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
function sidebarState() {
|
|
651
|
-
const trigger = nativeSidebarTrigger();
|
|
652
|
-
const expanded = trigger?.getAttribute("aria-expanded");
|
|
653
|
-
const triggerRect = trigger?.getBoundingClientRect();
|
|
654
|
-
return {
|
|
655
|
-
available: Boolean(trigger),
|
|
656
|
-
open: expanded === null ? sidebarRight() > 0 : expanded === "true",
|
|
657
|
-
label: trigger?.getAttribute("aria-label") ?? "Show sidebar",
|
|
658
|
-
leading: triggerRect?.left ?? 16,
|
|
659
|
-
};
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
function postSidebarState(record, state = sidebarState()) {
|
|
663
|
-
if (!record || record.kind !== "page") return;
|
|
664
|
-
record.frame.contentWindow?.postMessage({
|
|
665
|
-
type: "codex-personal:sidebar-state",
|
|
666
|
-
...state,
|
|
667
|
-
}, "/");
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
function registerSurface(key, feature, kind, surfaceUrl, element, frame, detailId = null) {
|
|
671
|
-
const record = {
|
|
672
|
-
key,
|
|
673
|
-
featureId: feature.id,
|
|
674
|
-
kind,
|
|
675
|
-
detailId,
|
|
676
|
-
origin: window.location.origin,
|
|
677
|
-
surfaceUrl,
|
|
678
|
-
element,
|
|
679
|
-
frame,
|
|
680
|
-
ready: false,
|
|
681
|
-
loading: false,
|
|
682
|
-
loadError: null,
|
|
683
|
-
preferredHeight: null,
|
|
684
|
-
};
|
|
685
|
-
surfaceRecords.set(key, record);
|
|
686
|
-
frame.addEventListener("load", () => {
|
|
687
|
-
queueTheme();
|
|
688
|
-
postSurfaceActive(record, activePageFeatureId === record.featureId && !record.element.hidden);
|
|
689
|
-
postSidebarState(record);
|
|
690
|
-
});
|
|
691
|
-
requestSurfaceLoad(record);
|
|
692
|
-
return record;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
function reloadSurfaceIfUnready(key, surfaceUrl) {
|
|
696
|
-
const record = surfaceRecords.get(key);
|
|
697
|
-
if (!record || record.ready || record.surfaceUrl !== surfaceUrl) return false;
|
|
698
|
-
return requestSurfaceLoad(record);
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
function removeSurfaceRecord(key) {
|
|
702
|
-
if (activePromptPreview?.recordKey === key) closePromptPreview({ immediate: true });
|
|
703
|
-
surfaceRecords.delete(key);
|
|
704
|
-
pendingHostActions.forEach((pending, requestId) => {
|
|
705
|
-
if (pending.recordKey !== key) return;
|
|
706
|
-
pendingHostActions.delete(requestId);
|
|
707
|
-
});
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
const resolvedTheme=featureUI.theme.read;
|
|
711
|
-
resolvedTheme();
|
|
712
|
-
|
|
713
|
-
function sendTheme() {
|
|
714
|
-
themeQueued = false;
|
|
715
|
-
const { style, tokens, mode } = resolvedTheme();
|
|
716
|
-
const radiusToken = tokens["--radius-3xl-base"] ?? "20px";
|
|
717
|
-
const radiusBase = Number.parseFloat(radiusToken) || 20;
|
|
718
|
-
const radiusUnit = radiusToken.match(/[a-z%]+$/i)?.[0] ?? "px";
|
|
719
|
-
const radiusScale = Number.parseFloat(tokens["--codex-corner-radius-scale"] ?? "1.25") || 1.25;
|
|
720
|
-
const panelRadius = `${radiusBase * radiusScale}${radiusUnit}`;
|
|
721
|
-
const panelCornerShape = tokens["--codex-corner-shape"] ?? "superellipse(1.5)";
|
|
722
|
-
const dialogRadius = tokens["--radius-3xl"];
|
|
723
|
-
const dialogShadow = `0 0 0 .5px ${tokens["--color-border"]},${tokens["--shadow-lg"]}`;
|
|
724
|
-
const surfaceTokens = {
|
|
725
|
-
...tokens,
|
|
726
|
-
// 部分 Codex 版本仅提供等价的正文前景 token。
|
|
727
|
-
"--color-token-input-foreground": tokens["--color-token-input-foreground"] ?? tokens["--color-text-foreground"],
|
|
728
|
-
"--codex-summary-section-title-font-size": "14px",
|
|
729
|
-
"--codex-summary-section-title-line-height": "21px",
|
|
730
|
-
"--codex-summary-section-title-font-weight": tokens["--font-weight-normal"] ?? "400",
|
|
731
|
-
"--codex-summary-row-font-size": "16px",
|
|
732
|
-
"--codex-summary-row-line-height": "24px",
|
|
733
|
-
"--codex-summary-label-font-size": tokens["--text-base"] ?? "14px",
|
|
734
|
-
"--codex-summary-label-line-height": tokens["--text-base--line-height"] ?? "1.5",
|
|
735
|
-
"--codex-summary-font-weight": style.fontWeight || "445",
|
|
736
|
-
"--codex-summary-icon-size": "18px",
|
|
737
|
-
"--codex-summary-icon-stroke-width": "1.5px",
|
|
738
|
-
"--codex-summary-panel-radius": panelRadius,
|
|
739
|
-
"--codex-summary-panel-corner-shape": panelCornerShape,
|
|
740
|
-
"--codex-dialog-radius": dialogRadius,
|
|
741
|
-
"--codex-dialog-corner-shape": "round",
|
|
742
|
-
"--codex-dialog-shadow": dialogShadow,
|
|
743
|
-
"--codex-summary-row-radius": tokens["--radius-sm"] ?? "7.5px",
|
|
744
|
-
"--codex-summary-control-radius": tokens["--radius-md"] ?? "10px",
|
|
745
|
-
"--codex-summary-section-title-color": tokens["--color-token-text-tertiary"] ?? "rgba(13,13,13,.484)",
|
|
746
|
-
"--codex-summary-icon-primary": tokens["--color-icon-primary"] ?? tokens["--color-token-foreground"] ?? style.color,
|
|
747
|
-
"--codex-summary-icon-secondary": tokens["--color-icon-secondary"] ?? tokens["--color-token-text-secondary"] ?? style.color,
|
|
748
|
-
"--codex-summary-icon-tertiary": tokens["--color-icon-tertiary"] ?? tokens["--color-token-text-tertiary"] ?? style.color,
|
|
749
|
-
};
|
|
750
|
-
for (const record of surfaceRecords.values()) {
|
|
751
|
-
if (!record.element.isConnected) continue;
|
|
752
|
-
if (record.kind === "pinned") {
|
|
753
|
-
record.element.style.borderRadius = panelRadius;
|
|
754
|
-
record.element.style.setProperty("corner-shape", panelCornerShape);
|
|
755
|
-
record.element.style.boxShadow = tokens["--elevation-prominent"]
|
|
756
|
-
?? "0 0 0 .5px rgba(13,13,13,.11),0 3px 7.5px rgba(0,0,0,.04),0 0 20px rgba(0,0,0,.05)";
|
|
757
|
-
}
|
|
758
|
-
if (record.kind !== "modal") record.element.style.background = record.kind === "pinned"
|
|
759
|
-
? tokens["--color-token-dropdown-background"]
|
|
760
|
-
?? tokens["--color-token-main-surface-primary"]
|
|
761
|
-
?? style.backgroundColor
|
|
762
|
-
: tokens["--color-token-main-surface-primary"] ?? style.backgroundColor;
|
|
763
|
-
record.frame.contentWindow?.postMessage(
|
|
764
|
-
{ type: "codex-personal:theme", theme: mode, mode, locale, tokens: surfaceTokens },
|
|
765
|
-
"/",
|
|
766
|
-
);
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
function queueTheme() {
|
|
771
|
-
if (themeQueued) return;
|
|
772
|
-
themeQueued = true;
|
|
773
|
-
queueMicrotask(sendTheme);
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
function sidebarRight() {
|
|
777
|
-
const sidebar = document.querySelector("[data-app-action-sidebar-scroll]");
|
|
778
|
-
return sidebar?.getBoundingClientRect().right ?? 0;
|
|
779
183
|
}
|
|
780
184
|
|
|
781
|
-
//
|
|
782
|
-
//
|
|
783
|
-
//
|
|
784
|
-
function applicationMenuTopInset() {
|
|
785
|
-
const menuBar = document.querySelector('[class*="_ApplicationMenuTopBar_"]');
|
|
786
|
-
if (!menuBar?.isConnected) return 0;
|
|
787
|
-
const bottom = menuBar.getBoundingClientRect().bottom;
|
|
788
|
-
return Number.isFinite(bottom) && bottom > 0 ? Math.round(bottom) : 0;
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
function updatePageSurfacePositions() {
|
|
792
|
-
const state = sidebarState();
|
|
793
|
-
const left = `${state.open ? Math.max(0, sidebarRight()) : 0}px`;
|
|
794
|
-
const topInset = applicationMenuTopInset();
|
|
795
|
-
const embeddedCard = topInset > 0;
|
|
796
|
-
for (const [featureId, surface] of pageSurfaces) {
|
|
797
|
-
surface.style.left = left;
|
|
798
|
-
surface.style.top = `${topInset}px`;
|
|
799
|
-
surface.style.borderTopLeftRadius = embeddedCard ? "12px" : "";
|
|
800
|
-
surface.style.overflow = embeddedCard ? "hidden" : "";
|
|
801
|
-
postSidebarState(surfaceRecords.get(surfaceKey(featureId, "page")), state);
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
function syncAfterNativeSidebarToggle() {
|
|
806
|
-
queueMicrotask(updatePageSurfacePositions);
|
|
807
|
-
}
|
|
185
|
+
// ========================================================================
|
|
186
|
+
// Composer 文本插入 — 供 Notes 快捷提示词等页面脚本复用
|
|
187
|
+
// ========================================================================
|
|
808
188
|
|
|
809
189
|
function threadIdentity() {
|
|
810
190
|
const selected = document.querySelector('[data-app-action-sidebar-thread-selected="true"]');
|
|
@@ -814,21 +194,6 @@ export function createInjectionSource(features, {
|
|
|
814
194
|
return `${project ?? ""}:${id ?? ""}:${location.pathname}:${location.search}`;
|
|
815
195
|
}
|
|
816
196
|
|
|
817
|
-
function checkThreadChange() {
|
|
818
|
-
const next = threadIdentity();
|
|
819
|
-
if (currentThread === null) {
|
|
820
|
-
currentThread = next;
|
|
821
|
-
return;
|
|
822
|
-
}
|
|
823
|
-
if (next === currentThread) return;
|
|
824
|
-
currentThread = next;
|
|
825
|
-
stopToolbarReadiness();
|
|
826
|
-
savedComposerRange = null;
|
|
827
|
-
savedComposerThread = null;
|
|
828
|
-
hidePinnedSurfaces();
|
|
829
|
-
queueEnsure();
|
|
830
|
-
}
|
|
831
|
-
|
|
832
197
|
function currentComposer() {
|
|
833
198
|
return codex.composer(null, { purpose: "insertion" })?.editable ?? null;
|
|
834
199
|
}
|
|
@@ -875,1179 +240,15 @@ export function createInjectionSource(features, {
|
|
|
875
240
|
return { mode: "inserted", fallback };
|
|
876
241
|
}
|
|
877
242
|
|
|
878
|
-
function suppressNativeSelections() {
|
|
879
|
-
const sidebar = document.querySelector("[data-app-action-sidebar-scroll]");
|
|
880
|
-
if (!sidebar) return;
|
|
881
|
-
const alreadySuppressed = new Set(suppressedSelections.map(({ element }) => element));
|
|
882
|
-
const selected = new Set([
|
|
883
|
-
...sidebar.querySelectorAll('[aria-current="page"]'),
|
|
884
|
-
...sidebar.querySelectorAll('[data-app-action-sidebar-thread-selected="true"]'),
|
|
885
|
-
]);
|
|
886
|
-
for (const element of selected) {
|
|
887
|
-
if (element.hasAttribute(entryMarker) || alreadySuppressed.has(element)) continue;
|
|
888
|
-
const selection = {
|
|
889
|
-
element,
|
|
890
|
-
ariaCurrent: element.getAttribute("aria-current"),
|
|
891
|
-
threadSelected: element.getAttribute("data-app-action-sidebar-thread-selected"),
|
|
892
|
-
hadPrimaryGhost: element.classList.contains("bg-primary-ghost-hover"),
|
|
893
|
-
hadHoverPrimaryGhost: element.classList.contains("hover:bg-primary-ghost-hover"),
|
|
894
|
-
hadActiveClass: element.classList.contains("bg-token-list-hover-background"),
|
|
895
|
-
hadHoverClass: element.classList.contains("hover:bg-token-list-hover-background"),
|
|
896
|
-
};
|
|
897
|
-
suppressedSelections.push(selection);
|
|
898
|
-
element.removeAttribute("aria-current");
|
|
899
|
-
element.removeAttribute("data-app-action-sidebar-thread-selected");
|
|
900
|
-
// 原生选中态(灰底)现在由 bg-primary-ghost-hover 承载,需一并移除;
|
|
901
|
-
// 旧 token 类仍保留处理,以便兼容旧主题。
|
|
902
|
-
element.classList.remove("bg-primary-ghost-hover");
|
|
903
|
-
element.classList.remove("bg-token-list-hover-background");
|
|
904
|
-
if (!selection.hadHoverPrimaryGhost) element.classList.add("hover:bg-primary-ghost-hover");
|
|
905
|
-
element.classList.add("hover:bg-token-list-hover-background");
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
function restoreNativeSelections() {
|
|
910
|
-
for (const selection of suppressedSelections) {
|
|
911
|
-
const { element } = selection;
|
|
912
|
-
if (!element.isConnected) continue;
|
|
913
|
-
if (selection.ariaCurrent !== null) element.setAttribute("aria-current", selection.ariaCurrent);
|
|
914
|
-
if (selection.threadSelected !== null) {
|
|
915
|
-
element.setAttribute("data-app-action-sidebar-thread-selected", selection.threadSelected);
|
|
916
|
-
}
|
|
917
|
-
element.classList.toggle("bg-primary-ghost-hover", Boolean(selection.hadPrimaryGhost));
|
|
918
|
-
element.classList.toggle("hover:bg-primary-ghost-hover", Boolean(selection.hadHoverPrimaryGhost));
|
|
919
|
-
element.classList.toggle("bg-token-list-hover-background", selection.hadActiveClass);
|
|
920
|
-
element.classList.toggle("hover:bg-token-list-hover-background", selection.hadHoverClass);
|
|
921
|
-
}
|
|
922
|
-
suppressedSelections = [];
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
function updateSidebarSelectedState() {
|
|
926
|
-
for (const feature of sidebarFeatures) {
|
|
927
|
-
const entry = findEntry(feature.id);
|
|
928
|
-
if (!entry) continue;
|
|
929
|
-
const selected = feature.id === activePageFeatureId;
|
|
930
|
-
entry.classList.toggle("bg-primary-ghost-hover", selected);
|
|
931
|
-
entry.classList.toggle("bg-token-list-hover-background", selected);
|
|
932
|
-
entry.classList.toggle("hover:bg-token-list-hover-background", !selected);
|
|
933
|
-
if (selected) entry.setAttribute("aria-current", "page");
|
|
934
|
-
else entry.removeAttribute("aria-current");
|
|
935
|
-
}
|
|
936
|
-
if (activePageFeatureId) suppressNativeSelections();
|
|
937
|
-
else restoreNativeSelections();
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
function updateToolbarState() {
|
|
941
|
-
const displayMode = summaryDisplayMode();
|
|
942
|
-
for (const feature of toolbarFeatures) {
|
|
943
|
-
const entry = findEntry(feature.id);
|
|
944
|
-
if (!entry) continue;
|
|
945
|
-
const open = visibleSummarySurface(feature.id, displayMode) !== null;
|
|
946
|
-
const label = summaryButtonLabel(feature, displayMode);
|
|
947
|
-
const pressed = String(open);
|
|
948
|
-
const state = open ? "open" : "closed";
|
|
949
|
-
if (entry.getAttribute("aria-label") !== label) entry.setAttribute("aria-label", label);
|
|
950
|
-
if (entry.getAttribute("data-minecodex-tooltip") !== label) entry.setAttribute("data-minecodex-tooltip", label);
|
|
951
|
-
if (entry.getAttribute("aria-pressed") !== pressed) entry.setAttribute("aria-pressed", pressed);
|
|
952
|
-
if (entry.getAttribute("aria-expanded") !== pressed) entry.setAttribute("aria-expanded", pressed);
|
|
953
|
-
if (entry.getAttribute("data-state") !== state) entry.setAttribute("data-state", state);
|
|
954
|
-
entry.setAttribute("data-codex-personal-summary-mode", displayMode);
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
|
|
958
243
|
// ========================================================================
|
|
959
|
-
//
|
|
244
|
+
// 刷新调度 — DOM 变化后合并到下一帧刷新 Model Selector
|
|
960
245
|
// ========================================================================
|
|
961
246
|
|
|
962
|
-
function createFrame(featureId, title) {
|
|
963
|
-
const frame = document.createElement("iframe");
|
|
964
|
-
frame.name = surfaceFrameName(featureId);
|
|
965
|
-
frame.src = "about:blank";
|
|
966
|
-
frame.title = title;
|
|
967
|
-
frame.allow = "clipboard-write";
|
|
968
|
-
frame.style.cssText = "width:100%;height:100%;border:0;display:block;background:transparent";
|
|
969
|
-
// 图片页覆盖原生标题栏,避免控件点击被窗口拖拽区域吞掉。
|
|
970
|
-
if (featureId === "images") frame.style.setProperty("-webkit-app-region", "no-drag");
|
|
971
|
-
return frame;
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
function createPageSurface(feature) {
|
|
975
|
-
const surface = document.createElement("section");
|
|
976
|
-
surface.setAttribute(pageSurfaceMarker, feature.id);
|
|
977
|
-
featureUI.page(surface, { transparent:true });
|
|
978
|
-
surface.style.cssText = [
|
|
979
|
-
"top:0",
|
|
980
|
-
"right:0",
|
|
981
|
-
"bottom:0",
|
|
982
|
-
"background:transparent",
|
|
983
|
-
].join(";");
|
|
984
|
-
const frame = createFrame(feature.id, feature.label);
|
|
985
|
-
surface.append(frame);
|
|
986
|
-
document.body.append(surface);
|
|
987
|
-
pageSurfaces.set(feature.id, surface);
|
|
988
|
-
registerSurface(surfaceKey(feature.id, "page"), feature, "page", feature.surfaceUrl, surface, frame);
|
|
989
|
-
return surface;
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
function showPageSurface(featureId) {
|
|
993
|
-
const feature = featureById.get(featureId);
|
|
994
|
-
if (!feature) return;
|
|
995
|
-
hidePinnedSurfaces();
|
|
996
|
-
for (const [otherFeatureId, surface] of pageSurfaces) {
|
|
997
|
-
if (otherFeatureId === featureId) continue;
|
|
998
|
-
surface.hidden = true;
|
|
999
|
-
const record = surfaceRecords.get(surfaceKey(otherFeatureId, "page"));
|
|
1000
|
-
postSurfaceActive(record, false);
|
|
1001
|
-
}
|
|
1002
|
-
const existing = pageSurfaces.get(featureId);
|
|
1003
|
-
const reusable = existing?.isConnected ? existing : null;
|
|
1004
|
-
const surface = reusable ?? createPageSurface(feature);
|
|
1005
|
-
if (reusable) reloadSurfaceIfUnready(surfaceKey(feature.id, "page"), feature.surfaceUrl);
|
|
1006
|
-
surface.hidden = false;
|
|
1007
|
-
activePageFeatureId = featureId;
|
|
1008
|
-
const record = surfaceRecords.get(surfaceKey(feature.id, "page"));
|
|
1009
|
-
postSurfaceActive(record, true);
|
|
1010
|
-
updatePageSurfacePositions();
|
|
1011
|
-
updateSidebarSelectedState();
|
|
1012
|
-
queueTheme();
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
function hidePageSurfaces() {
|
|
1016
|
-
for (const [featureId, surface] of pageSurfaces) {
|
|
1017
|
-
surface.hidden = true;
|
|
1018
|
-
const record = surfaceRecords.get(surfaceKey(featureId, "page"));
|
|
1019
|
-
postSurfaceActive(record, false);
|
|
1020
|
-
}
|
|
1021
|
-
activePageFeatureId = null;
|
|
1022
|
-
updateSidebarSelectedState();
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
function closeNativeSummary() {
|
|
1026
|
-
const toggle = nativeSummaryButton();
|
|
1027
|
-
if (!toggle) return;
|
|
1028
|
-
if (toggle.getAttribute("aria-expanded") === "true" || toggle.getAttribute("aria-pressed") === "true") {
|
|
1029
|
-
toggle.click();
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
function restoreThreadContentShift() {
|
|
1034
|
-
for (const { element, shift, translate } of shiftedConversationOwners) {
|
|
1035
|
-
if (!element.isConnected) continue;
|
|
1036
|
-
if (shift.value) {
|
|
1037
|
-
element.style.setProperty(
|
|
1038
|
-
"--thread-wide-block-inline-shift",
|
|
1039
|
-
shift.value,
|
|
1040
|
-
shift.priority,
|
|
1041
|
-
);
|
|
1042
|
-
} else {
|
|
1043
|
-
element.style.removeProperty("--thread-wide-block-inline-shift");
|
|
1044
|
-
}
|
|
1045
|
-
if (translate.value) element.style.setProperty("translate", translate.value, translate.priority);
|
|
1046
|
-
else element.style.removeProperty("translate");
|
|
1047
|
-
element.removeAttribute("data-codex-personal-summary-shift");
|
|
1048
|
-
}
|
|
1049
|
-
shiftedConversationOwners = [];
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
function applyThreadContentShift(value) {
|
|
1053
|
-
if (value === 0) {
|
|
1054
|
-
restoreThreadContentShift();
|
|
1055
|
-
return;
|
|
1056
|
-
}
|
|
1057
|
-
const viewport = mainContentViewport();
|
|
1058
|
-
const owners = viewport
|
|
1059
|
-
? Array.from(viewport.querySelectorAll('[style*="--thread-wide-block-inline-shift"]'))
|
|
1060
|
-
: [];
|
|
1061
|
-
if (!owners.length) {
|
|
1062
|
-
restoreThreadContentShift();
|
|
1063
|
-
return;
|
|
1064
|
-
}
|
|
1065
|
-
if (
|
|
1066
|
-
shiftedConversationOwners.length !== owners.length
|
|
1067
|
-
|| owners.some((owner, index) => shiftedConversationOwners[index]?.element !== owner)
|
|
1068
|
-
) {
|
|
1069
|
-
restoreThreadContentShift();
|
|
1070
|
-
shiftedConversationOwners = owners.map((owner) => ({
|
|
1071
|
-
element: owner,
|
|
1072
|
-
shift: {
|
|
1073
|
-
value: owner.style.getPropertyValue("--thread-wide-block-inline-shift"),
|
|
1074
|
-
priority: owner.style.getPropertyPriority("--thread-wide-block-inline-shift"),
|
|
1075
|
-
},
|
|
1076
|
-
translate: {
|
|
1077
|
-
value: owner.style.getPropertyValue("translate"),
|
|
1078
|
-
priority: owner.style.getPropertyPriority("translate"),
|
|
1079
|
-
},
|
|
1080
|
-
}));
|
|
1081
|
-
}
|
|
1082
|
-
for (const owner of owners) {
|
|
1083
|
-
owner.style.setProperty(
|
|
1084
|
-
"--thread-wide-block-inline-shift",
|
|
1085
|
-
`${Number(value)}px`,
|
|
1086
|
-
"important",
|
|
1087
|
-
);
|
|
1088
|
-
owner.style.setProperty("translate", `${Number(value)}px 0`, "important");
|
|
1089
|
-
owner.setAttribute("data-codex-personal-summary-shift", String(value));
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
function setSummarySurfaceVisible(surface, visible, mode) {
|
|
1094
|
-
const wasVisible = surface.dataset.codexPersonalVisible === "true";
|
|
1095
|
-
const previousMode = surface.dataset.codexPersonalMode;
|
|
1096
|
-
const duration = reducedMotion() ? 0 : 300;
|
|
1097
|
-
surface.dataset.codexPersonalVisible = String(visible);
|
|
1098
|
-
surface.dataset.codexPersonalMode = mode;
|
|
1099
|
-
surface.style.transition = duration
|
|
1100
|
-
? "opacity 180ms ease, transform 300ms cubic-bezier(.2,.8,.2,1)"
|
|
1101
|
-
: "none";
|
|
1102
|
-
if (!visible) {
|
|
1103
|
-
closePromptPreview({ immediate: true });
|
|
1104
|
-
surface.style.pointerEvents = "none";
|
|
1105
|
-
surface.style.opacity = "0";
|
|
1106
|
-
surface.style.transform = mode === "overlay"
|
|
1107
|
-
? "translateY(-4px) scale(.98)"
|
|
1108
|
-
: "translateX(100%) scale(.8)";
|
|
1109
|
-
const transitionId = `${performance.now()}-${Math.random()}`;
|
|
1110
|
-
surface.dataset.codexPersonalTransition = transitionId;
|
|
1111
|
-
if (duration === 0) surface.hidden = true;
|
|
1112
|
-
else window.setTimeout(() => {
|
|
1113
|
-
if (
|
|
1114
|
-
surface.dataset.codexPersonalTransition === transitionId
|
|
1115
|
-
&& surface.dataset.codexPersonalVisible !== "true"
|
|
1116
|
-
) surface.hidden = true;
|
|
1117
|
-
}, duration);
|
|
1118
|
-
return;
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
surface.dataset.codexPersonalTransition = "visible";
|
|
1122
|
-
surface.hidden = false;
|
|
1123
|
-
surface.style.pointerEvents = "auto";
|
|
1124
|
-
if (!wasVisible || previousMode !== mode) {
|
|
1125
|
-
surface.style.opacity = "0";
|
|
1126
|
-
surface.style.transform = mode === "overlay"
|
|
1127
|
-
? "translateY(-4px) scale(.98)"
|
|
1128
|
-
: "translateX(100%) scale(.8)";
|
|
1129
|
-
const reveal = () => {
|
|
1130
|
-
if (surface.dataset.codexPersonalVisible !== "true") return;
|
|
1131
|
-
surface.style.opacity = "1";
|
|
1132
|
-
surface.style.transform = "translate(0, 0) scale(1)";
|
|
1133
|
-
};
|
|
1134
|
-
requestAnimationFrame(reveal);
|
|
1135
|
-
window.setTimeout(reveal, 50);
|
|
1136
|
-
if (duration) window.setTimeout(() => {
|
|
1137
|
-
if (surface.dataset.codexPersonalVisible !== "true") return;
|
|
1138
|
-
surface.style.transition = "none";
|
|
1139
|
-
surface.style.opacity = "1";
|
|
1140
|
-
surface.style.transform = "translate(0, 0) scale(1)";
|
|
1141
|
-
}, duration + 50);
|
|
1142
|
-
} else {
|
|
1143
|
-
surface.style.opacity = "1";
|
|
1144
|
-
surface.style.transform = "translate(0, 0) scale(1)";
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
function updatePinnedPosition(featureId) {
|
|
1149
|
-
const surface = pinnedSurfaces.get(featureId);
|
|
1150
|
-
const entry = findEntry(featureId);
|
|
1151
|
-
const feature = featureById.get(featureId);
|
|
1152
|
-
if (!surface || !entry || !feature) return;
|
|
1153
|
-
const layout = config.summaryLayout;
|
|
1154
|
-
const displayMode = summaryDisplayMode();
|
|
1155
|
-
const visibleSurface = visibleSummarySurface(featureId, displayMode);
|
|
1156
|
-
if (!visibleSurface) {
|
|
1157
|
-
setSummarySurfaceVisible(surface, false, displayMode);
|
|
1158
|
-
if (activePinnedFeatureId === featureId) applyThreadContentShift(0);
|
|
1159
|
-
return;
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
const target = mainContentTargetGeometry();
|
|
1163
|
-
const entryRect = entry.getBoundingClientRect();
|
|
1164
|
-
const scrollRect = mainContentViewport()?.querySelector(".thread-scroll-container")
|
|
1165
|
-
?.getBoundingClientRect();
|
|
1166
|
-
// The native conversation header is a fixed overlay that starts above the
|
|
1167
|
-
// thread scroll container (scroll top is often 0 while the header still
|
|
1168
|
-
// occupies the top toolbar strip). Anchor below the header so the pinned
|
|
1169
|
-
// surface never covers the toolbar entry used to close it again.
|
|
1170
|
-
const header = entry.closest("header");
|
|
1171
|
-
const headerBottom = header?.getBoundingClientRect().bottom ?? 0;
|
|
1172
|
-
const contentTop = Math.max(scrollRect?.top ?? 0, headerBottom);
|
|
1173
|
-
const top = displayMode === "overlay"
|
|
1174
|
-
? entryRect.bottom + layout.popoverOffset
|
|
1175
|
-
: (contentTop || 46) + layout.verticalInset;
|
|
1176
|
-
const right = displayMode === "overlay"
|
|
1177
|
-
? Math.max(layout.popoverOffset, innerWidth - entryRect.right)
|
|
1178
|
-
: Math.max(layout.panelInset, innerWidth - target.right + layout.panelInset);
|
|
1179
|
-
const bottomInset = displayMode === "overlay"
|
|
1180
|
-
? layout.popoverBottomInset
|
|
1181
|
-
: layout.verticalInset;
|
|
1182
|
-
const available = Math.max(120, innerHeight - top - bottomInset);
|
|
1183
|
-
const record = surfaceRecords.get(surfaceKey(featureId, "pinned"));
|
|
1184
|
-
const height = Math.min(record?.preferredHeight ?? 620, available);
|
|
1185
|
-
Object.assign(surface.style, {
|
|
1186
|
-
top: `${top}px`,
|
|
1187
|
-
right: `${right}px`,
|
|
1188
|
-
width: `${layout.panelWidth}px`,
|
|
1189
|
-
height: `${height}px`,
|
|
1190
|
-
maxHeight: `${available}px`,
|
|
1191
|
-
});
|
|
1192
|
-
surface.setAttribute("data-codex-personal-summary-presentation", displayMode);
|
|
1193
|
-
setSummarySurfaceVisible(surface, true, displayMode);
|
|
1194
|
-
|
|
1195
|
-
const shift = displayMode !== "overlay"
|
|
1196
|
-
? -(layout.panelWidth + layout.panelInset) / 2
|
|
1197
|
-
: 0;
|
|
1198
|
-
applyThreadContentShift(shift);
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
function createPinnedSurface(feature) {
|
|
1202
|
-
const definition = feature.pinnedSummary;
|
|
1203
|
-
const surface = document.createElement("section");
|
|
1204
|
-
surface.setAttribute(pinnedSurfaceMarker, feature.id);
|
|
1205
|
-
surface.setAttribute("role", "dialog");
|
|
1206
|
-
surface.setAttribute("aria-label", definition.label ?? feature.label);
|
|
1207
|
-
surface.hidden = true;
|
|
1208
|
-
featureUI.page(surface, { transparent:true });
|
|
1209
|
-
surface.style.cssText = [
|
|
1210
|
-
"overflow:hidden",
|
|
1211
|
-
"border-radius:25px",
|
|
1212
|
-
"corner-shape:var(--codex-corner-shape,superellipse(1.5))",
|
|
1213
|
-
"background:var(--color-token-dropdown-background,var(--color-token-main-surface-primary))",
|
|
1214
|
-
"box-shadow:var(--elevation-prominent,0 0 0 .5px rgba(13,13,13,.11),0 3px 7.5px rgba(0,0,0,.04),0 0 20px rgba(0,0,0,.05))",
|
|
1215
|
-
"transform-origin:top right",
|
|
1216
|
-
"opacity:0",
|
|
1217
|
-
"pointer-events:none",
|
|
1218
|
-
"will-change:transform,opacity",
|
|
1219
|
-
].join(";");
|
|
1220
|
-
const frame = createFrame(feature.id, definition.label ?? feature.label);
|
|
1221
|
-
surface.append(frame);
|
|
1222
|
-
document.body.append(surface);
|
|
1223
|
-
pinnedSurfaces.set(feature.id, surface);
|
|
1224
|
-
registerSurface(
|
|
1225
|
-
surfaceKey(feature.id, "pinned"),
|
|
1226
|
-
feature,
|
|
1227
|
-
"pinned",
|
|
1228
|
-
definition.surfaceUrl,
|
|
1229
|
-
surface,
|
|
1230
|
-
frame,
|
|
1231
|
-
);
|
|
1232
|
-
return surface;
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
function closePromptPreview({ immediate = false } = {}) {
|
|
1236
|
-
clearTimeout(promptPreviewDismissTimer);
|
|
1237
|
-
promptPreviewDismissTimer = null;
|
|
1238
|
-
const current = activePromptPreview;
|
|
1239
|
-
if (!current) return false;
|
|
1240
|
-
activePromptPreview = null;
|
|
1241
|
-
const remove = () => current.element.remove();
|
|
1242
|
-
if (immediate || reducedMotion()) remove();
|
|
1243
|
-
else {
|
|
1244
|
-
current.element.style.pointerEvents = "none";
|
|
1245
|
-
current.element.style.opacity = "0";
|
|
1246
|
-
window.setTimeout(remove, 80);
|
|
1247
|
-
}
|
|
1248
|
-
return true;
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
function requestPromptPreviewClose(record) {
|
|
1252
|
-
if (!activePromptPreview || activePromptPreview.recordKey !== record.key) return false;
|
|
1253
|
-
clearTimeout(promptPreviewDismissTimer);
|
|
1254
|
-
promptPreviewDismissTimer = window.setTimeout(() => {
|
|
1255
|
-
if (!activePromptPreview || activePromptPreview.recordKey !== record.key) return;
|
|
1256
|
-
if (activePromptPreview.element.matches(":hover")) return;
|
|
1257
|
-
closePromptPreview();
|
|
1258
|
-
}, 120);
|
|
1259
|
-
return true;
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
function showPromptPreview(record, payload = {}) {
|
|
1263
|
-
if (record.kind !== "pinned" || record.element.hidden) return false;
|
|
1264
|
-
const body = String(payload.prompt?.body ?? "");
|
|
1265
|
-
if (!body.trim()) return false;
|
|
1266
|
-
const title = String(payload.prompt?.title ?? "").trim();
|
|
1267
|
-
const surfaceLeft = record.element.offsetLeft;
|
|
1268
|
-
const surfaceTop = record.element.offsetTop;
|
|
1269
|
-
const anchorTop = Number(payload.anchor?.top);
|
|
1270
|
-
if (!Number.isFinite(anchorTop) || record.element.offsetWidth <= 0 || record.element.offsetHeight <= 0) return false;
|
|
1271
|
-
|
|
1272
|
-
closePromptPreview({ immediate: true });
|
|
1273
|
-
const preview = document.createElement("aside");
|
|
1274
|
-
preview.setAttribute(promptPreviewMarker, record.featureId);
|
|
1275
|
-
preview.setAttribute("role", "tooltip");
|
|
1276
|
-
preview.setAttribute("aria-label", locale === "zh-CN" ? "提示词预览" : "Prompt preview");
|
|
1277
|
-
if (payload.prompt?.id) preview.setAttribute("data-codex-personal-prompt-id", String(payload.prompt.id));
|
|
1278
|
-
preview.style.cssText = [
|
|
1279
|
-
"position:fixed",
|
|
1280
|
-
"z-index:41",
|
|
1281
|
-
"box-sizing:border-box",
|
|
1282
|
-
"display:flex",
|
|
1283
|
-
"flex-direction:column",
|
|
1284
|
-
"gap:8px",
|
|
1285
|
-
"max-height:min(420px,calc(100vh - 24px))",
|
|
1286
|
-
"overflow:hidden",
|
|
1287
|
-
"padding:14px 16px 15px",
|
|
1288
|
-
"border:.5px solid var(--color-token-border,var(--color-border,rgba(13,13,13,.11)))",
|
|
1289
|
-
"border-radius:16px",
|
|
1290
|
-
"corner-shape:var(--codex-corner-shape,superellipse(1.5))",
|
|
1291
|
-
"color:var(--color-token-foreground,var(--color-text-foreground,#0d0d0d))",
|
|
1292
|
-
"background:var(--color-token-dropdown-background,var(--color-token-main-surface-primary,#fff))",
|
|
1293
|
-
"box-shadow:var(--elevation-prominent,0 0 0 .5px rgba(13,13,13,.08),0 8px 24px rgba(0,0,0,.10))",
|
|
1294
|
-
"font-family:var(--font-sans-default,ui-sans-serif,system-ui,sans-serif)",
|
|
1295
|
-
"pointer-events:auto",
|
|
1296
|
-
"transition:opacity 80ms ease",
|
|
1297
|
-
].join(";");
|
|
1298
|
-
|
|
1299
|
-
if (title) {
|
|
1300
|
-
const heading = document.createElement("strong");
|
|
1301
|
-
heading.textContent = title.slice(0, 500);
|
|
1302
|
-
heading.style.cssText = [
|
|
1303
|
-
"display:-webkit-box",
|
|
1304
|
-
"overflow:hidden",
|
|
1305
|
-
"-webkit-box-orient:vertical",
|
|
1306
|
-
"-webkit-line-clamp:2",
|
|
1307
|
-
"font-size:15px",
|
|
1308
|
-
"font-weight:550",
|
|
1309
|
-
"line-height:21px",
|
|
1310
|
-
"overflow-wrap:anywhere",
|
|
1311
|
-
].join(";");
|
|
1312
|
-
preview.append(heading);
|
|
1313
|
-
}
|
|
1314
|
-
const content = document.createElement("div");
|
|
1315
|
-
content.textContent = body;
|
|
1316
|
-
content.style.cssText = [
|
|
1317
|
-
"min-height:0",
|
|
1318
|
-
"overflow:auto",
|
|
1319
|
-
"padding-right:2px",
|
|
1320
|
-
"white-space:pre-wrap",
|
|
1321
|
-
"overflow-wrap:anywhere",
|
|
1322
|
-
"font-size:14px",
|
|
1323
|
-
"font-weight:400",
|
|
1324
|
-
"line-height:20px",
|
|
1325
|
-
].join(";");
|
|
1326
|
-
preview.append(content);
|
|
1327
|
-
|
|
1328
|
-
const inset = 8;
|
|
1329
|
-
const gap = 10;
|
|
1330
|
-
const availableLeft = Math.max(160, surfaceLeft - gap - inset);
|
|
1331
|
-
const width = Math.min(360, availableLeft);
|
|
1332
|
-
preview.style.width = `${width}px`;
|
|
1333
|
-
document.body.append(preview);
|
|
1334
|
-
const previewRect = preview.getBoundingClientRect();
|
|
1335
|
-
const left = Math.max(inset, surfaceLeft - gap - width);
|
|
1336
|
-
const desiredTop = surfaceTop + anchorTop - 8;
|
|
1337
|
-
const top = Math.min(
|
|
1338
|
-
Math.max(12, desiredTop),
|
|
1339
|
-
Math.max(12, innerHeight - previewRect.height - 12),
|
|
1340
|
-
);
|
|
1341
|
-
preview.style.left = `${left}px`;
|
|
1342
|
-
preview.style.top = `${top}px`;
|
|
1343
|
-
activePromptPreview = { element: preview, recordKey: record.key };
|
|
1344
|
-
|
|
1345
|
-
preview.addEventListener("pointerenter", () => {
|
|
1346
|
-
clearTimeout(promptPreviewDismissTimer);
|
|
1347
|
-
promptPreviewDismissTimer = null;
|
|
1348
|
-
}, { signal: lifetime.signal });
|
|
1349
|
-
preview.addEventListener("pointerleave", () => closePromptPreview(), { signal: lifetime.signal });
|
|
1350
|
-
return true;
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
function closeModalSurface() {
|
|
1354
|
-
if (!activeModalRecordKey) return false;
|
|
1355
|
-
const record = surfaceRecords.get(activeModalRecordKey);
|
|
1356
|
-
record?.overlay?.remove();
|
|
1357
|
-
record?.element?.remove();
|
|
1358
|
-
if (record) removeSurfaceRecord(record.key);
|
|
1359
|
-
activeModalRecordKey = null;
|
|
1360
|
-
return true;
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
function openEditorModal(sourceRecord, payload = {}) {
|
|
1364
|
-
const feature = featureById.get(sourceRecord.featureId);
|
|
1365
|
-
const kind = payload.kind === "todo"
|
|
1366
|
-
? "todo"
|
|
1367
|
-
: new Set(["prompt", "note"]).has(payload.kind)
|
|
1368
|
-
? "prompt"
|
|
1369
|
-
: null;
|
|
1370
|
-
if (!feature || !kind) throw new Error("Editor kind must be prompt or todo");
|
|
1371
|
-
|
|
1372
|
-
closePromptPreview({ immediate: true });
|
|
1373
|
-
closeModalSurface();
|
|
1374
|
-
const editorUrl = new URL(sourceRecord.surfaceUrl);
|
|
1375
|
-
editorUrl.searchParams.set("surface", "editor");
|
|
1376
|
-
editorUrl.searchParams.set("kind", kind);
|
|
1377
|
-
if (typeof payload.id === "string" && payload.id) editorUrl.searchParams.set("id", payload.id);
|
|
1378
|
-
else editorUrl.searchParams.delete("id");
|
|
1379
|
-
|
|
1380
|
-
const overlay = document.createElement("div");
|
|
1381
|
-
overlay.setAttribute("data-codex-personal-modal-overlay", feature.id);
|
|
1382
|
-
featureUI.overlay(overlay);
|
|
1383
|
-
overlay.setAttribute("aria-hidden", "true");
|
|
1384
|
-
overlay.style.cssText = [
|
|
1385
|
-
"position:fixed",
|
|
1386
|
-
"inset:0",
|
|
1387
|
-
"z-index:50",
|
|
1388
|
-
].join(";");
|
|
1389
|
-
|
|
1390
|
-
const dialog = document.createElement("section");
|
|
1391
|
-
dialog.setAttribute(modalSurfaceMarker, feature.id);
|
|
1392
|
-
featureUI.surface(dialog, "dialog");
|
|
1393
|
-
dialog.setAttribute("role", "dialog");
|
|
1394
|
-
dialog.setAttribute("aria-modal", "true");
|
|
1395
|
-
dialog.setAttribute("aria-label", `${payload.id ? "Edit" : "Add"} ${kind}`);
|
|
1396
|
-
dialog.setAttribute("tabindex", "-1");
|
|
1397
|
-
dialog.style.cssText = [
|
|
1398
|
-
"position:fixed",
|
|
1399
|
-
"left:50%",
|
|
1400
|
-
"top:50%",
|
|
1401
|
-
"z-index:51",
|
|
1402
|
-
"width:min(420px,92vw)",
|
|
1403
|
-
`height:${kind === "prompt" ? "250px" : "198px"}`,
|
|
1404
|
-
"max-height:88vh",
|
|
1405
|
-
"transform:translate(-50%,-50%)",
|
|
1406
|
-
"overflow:hidden",
|
|
1407
|
-
"padding:0",
|
|
1408
|
-
"outline:none",
|
|
1409
|
-
].join(";");
|
|
1410
|
-
|
|
1411
|
-
const frame = createFrame(feature.id, `${payload.id ? "Edit" : "Add"} ${kind}`);
|
|
1412
|
-
dialog.append(frame);
|
|
1413
|
-
document.body.append(overlay, dialog);
|
|
1414
|
-
const key = surfaceKey(feature.id, "modal");
|
|
1415
|
-
const record = registerSurface(key, feature, "modal", editorUrl.href, dialog, frame);
|
|
1416
|
-
record.overlay = overlay;
|
|
1417
|
-
activeModalRecordKey = key;
|
|
1418
|
-
overlay.addEventListener("click", closeModalSurface, { signal: lifetime.signal });
|
|
1419
|
-
queueTheme();
|
|
1420
|
-
return { opened: true };
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
|
-
function showPinnedSurface(featureId) {
|
|
1424
|
-
const feature = featureById.get(featureId);
|
|
1425
|
-
if (!feature?.pinnedSummary) return;
|
|
1426
|
-
hidePageSurfaces();
|
|
1427
|
-
closeNativeSummary();
|
|
1428
|
-
for (const [id, state] of summaryStates) {
|
|
1429
|
-
if (id === featureId) continue;
|
|
1430
|
-
state.isPinned = false;
|
|
1431
|
-
state.isPopoverOpen = false;
|
|
1432
|
-
const other = pinnedSurfaces.get(id);
|
|
1433
|
-
if (other) setSummarySurfaceVisible(other, false, summaryDisplayMode());
|
|
1434
|
-
}
|
|
1435
|
-
const existing = pinnedSurfaces.get(featureId);
|
|
1436
|
-
const surface = existing ?? createPinnedSurface(feature);
|
|
1437
|
-
if (existing) {
|
|
1438
|
-
reloadSurfaceIfUnready(
|
|
1439
|
-
surfaceKey(feature.id, "pinned"),
|
|
1440
|
-
feature.pinnedSummary.surfaceUrl,
|
|
1441
|
-
);
|
|
1442
|
-
}
|
|
1443
|
-
const displayMode = summaryDisplayMode();
|
|
1444
|
-
const state = summaryState(featureId);
|
|
1445
|
-
if (displayMode === "overlay") state.isPopoverOpen = true;
|
|
1446
|
-
else state.isPinned = true;
|
|
1447
|
-
activePinnedFeatureId = featureId;
|
|
1448
|
-
updateToolbarState();
|
|
1449
|
-
updatePinnedPosition(featureId);
|
|
1450
|
-
queueTheme();
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
function hidePinnedSurfaces() {
|
|
1454
|
-
const displayMode = summaryDisplayMode();
|
|
1455
|
-
for (const [featureId, state] of summaryStates) {
|
|
1456
|
-
state.isPinned = false;
|
|
1457
|
-
state.isPopoverOpen = false;
|
|
1458
|
-
const surface = pinnedSurfaces.get(featureId);
|
|
1459
|
-
if (surface) setSummarySurfaceVisible(surface, false, displayMode);
|
|
1460
|
-
}
|
|
1461
|
-
activePinnedFeatureId = null;
|
|
1462
|
-
restoreThreadContentShift();
|
|
1463
|
-
updateToolbarState();
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
function togglePinnedSurface(featureId) {
|
|
1467
|
-
const feature = featureById.get(featureId);
|
|
1468
|
-
if (!feature?.pinnedSummary) return;
|
|
1469
|
-
const displayMode = summaryDisplayMode();
|
|
1470
|
-
const state = summaryState(featureId);
|
|
1471
|
-
const isOpen = visibleSummarySurface(featureId, displayMode) !== null;
|
|
1472
|
-
if (!isOpen) {
|
|
1473
|
-
showPinnedSurface(featureId);
|
|
1474
|
-
return;
|
|
1475
|
-
}
|
|
1476
|
-
if (displayMode === "overlay") state.isPopoverOpen = false;
|
|
1477
|
-
else state.isPinned = false;
|
|
1478
|
-
if (!state.isPinned && !state.isPopoverOpen) activePinnedFeatureId = null;
|
|
1479
|
-
updatePinnedPosition(featureId);
|
|
1480
|
-
if (!activePinnedFeatureId) restoreThreadContentShift();
|
|
1481
|
-
updateToolbarState();
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
|
-
function updateResponsiveSummaryPresentation() {
|
|
1485
|
-
const nextMode = summaryDisplayMode();
|
|
1486
|
-
if (currentSummaryDisplayMode === "overlay" && nextMode !== "overlay") {
|
|
1487
|
-
for (const state of summaryStates.values()) state.isPopoverOpen = false;
|
|
1488
|
-
}
|
|
1489
|
-
currentSummaryDisplayMode = nextMode;
|
|
1490
|
-
for (const feature of toolbarFeatures) updatePinnedPosition(feature.id);
|
|
1491
|
-
if (!toolbarFeatures.some((feature) => (
|
|
1492
|
-
visibleSummarySurface(feature.id, nextMode) === "inline"
|
|
1493
|
-
))) applyThreadContentShift(0);
|
|
1494
|
-
updateToolbarState();
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
function ensureMainContentObserver() {
|
|
1498
|
-
const viewport = mainContentViewport();
|
|
1499
|
-
if (!viewport || mainContentObserver?.target === viewport) return;
|
|
1500
|
-
mainContentObserver?.observer.disconnect();
|
|
1501
|
-
const observer = new ResizeObserver(() => updateResponsiveSummaryPresentation());
|
|
1502
|
-
observer.observe(viewport);
|
|
1503
|
-
mainContentObserver = { target: viewport, observer };
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
// ========================================================================
|
|
1507
|
-
// 原生 Detail Tab capability — React fiber/右侧面板探测、原生详情页签打开与线程跳转
|
|
1508
|
-
// ========================================================================
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
function reactFiberFor(element) {
|
|
1512
|
-
if (!(element instanceof Element)) return null;
|
|
1513
|
-
const key = Object.getOwnPropertyNames(element).find((name) => (
|
|
1514
|
-
name.startsWith("__reactFiber$")
|
|
1515
|
-
|| name.startsWith("__reactInternalInstance$")
|
|
1516
|
-
));
|
|
1517
|
-
return key ? element[key] : null;
|
|
1518
|
-
}
|
|
1519
|
-
|
|
1520
|
-
function currentRightPanelScope() {
|
|
1521
|
-
let anchor = document.querySelector('[data-app-shell-tabs="true"]');
|
|
1522
|
-
let anchorFiber = reactFiberFor(anchor);
|
|
1523
|
-
if (!anchorFiber) {
|
|
1524
|
-
for (const element of document.querySelectorAll("body *")) {
|
|
1525
|
-
const fiber = reactFiberFor(element);
|
|
1526
|
-
if (!fiber) continue;
|
|
1527
|
-
anchor = element;
|
|
1528
|
-
anchorFiber = fiber;
|
|
1529
|
-
break;
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1532
|
-
if (!anchorFiber) return null;
|
|
1533
|
-
|
|
1534
|
-
let rootFiber = anchorFiber;
|
|
1535
|
-
while (rootFiber.return) rootFiber = rootFiber.return;
|
|
1536
|
-
|
|
1537
|
-
const isRightPanelScope = (value) => (
|
|
1538
|
-
value
|
|
1539
|
-
&& typeof value === "object"
|
|
1540
|
-
&& typeof value.get === "function"
|
|
1541
|
-
&& typeof value.set === "function"
|
|
1542
|
-
&& rightPanelRouteKinds.has(value.value?.routeKind)
|
|
1543
|
-
&& typeof value.query?.getOrFetch === "function"
|
|
1544
|
-
);
|
|
1545
|
-
const inspected = new Set();
|
|
1546
|
-
const inspect = (value) => {
|
|
1547
|
-
if (
|
|
1548
|
-
value == null
|
|
1549
|
-
|| (typeof value !== "object" && typeof value !== "function")
|
|
1550
|
-
|| inspected.has(value)
|
|
1551
|
-
) return null;
|
|
1552
|
-
inspected.add(value);
|
|
1553
|
-
if (isRightPanelScope(value)) return value;
|
|
1554
|
-
if (isRightPanelScope(value.current)) return value.current;
|
|
1555
|
-
return null;
|
|
1556
|
-
};
|
|
1557
|
-
|
|
1558
|
-
const stack = [rootFiber];
|
|
1559
|
-
let visitedFibers = 0;
|
|
1560
|
-
while (stack.length > 0 && visitedFibers < 50_000) {
|
|
1561
|
-
const fiber = stack.pop();
|
|
1562
|
-
visitedFibers += 1;
|
|
1563
|
-
let scope = inspect(fiber.memoizedProps);
|
|
1564
|
-
if (!scope && fiber.memoizedProps && typeof fiber.memoizedProps === "object") {
|
|
1565
|
-
for (const value of Object.values(fiber.memoizedProps)) {
|
|
1566
|
-
scope = inspect(value);
|
|
1567
|
-
if (scope) break;
|
|
1568
|
-
}
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1571
|
-
let hook = fiber.memoizedState;
|
|
1572
|
-
let hookIndex = 0;
|
|
1573
|
-
while (!scope && hook && typeof hook === "object" && hookIndex < 140) {
|
|
1574
|
-
scope = inspect(hook.memoizedState);
|
|
1575
|
-
if (!scope && hook.memoizedState && typeof hook.memoizedState === "object") {
|
|
1576
|
-
scope = inspect(hook.memoizedState.current);
|
|
1577
|
-
}
|
|
1578
|
-
hook = hook.next;
|
|
1579
|
-
hookIndex += 1;
|
|
1580
|
-
}
|
|
1581
|
-
if (scope) return scope;
|
|
1582
|
-
if (fiber.sibling) stack.push(fiber.sibling);
|
|
1583
|
-
if (fiber.child) stack.push(fiber.child);
|
|
1584
|
-
}
|
|
1585
|
-
return null;
|
|
1586
|
-
}
|
|
1587
|
-
|
|
1588
|
-
async function waitForCurrentRightPanelScope() {
|
|
1589
|
-
const deadline = performance.now() + 2_000;
|
|
1590
|
-
do {
|
|
1591
|
-
const scope = currentRightPanelScope();
|
|
1592
|
-
if (scope) return scope;
|
|
1593
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1594
|
-
} while (performance.now() < deadline);
|
|
1595
|
-
return null;
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
async function waitForNativeTabModuleUrl() {
|
|
1599
|
-
return findCodexModule('app-initial',{signal:lifetime.signal,waitForEntry:true});
|
|
1600
|
-
}
|
|
1601
|
-
|
|
1602
|
-
async function loadNativeTabCapability() {
|
|
1603
|
-
if (nativeTabCapability) return nativeTabCapability;
|
|
1604
|
-
if (nativeTabCapabilityPromise) return nativeTabCapabilityPromise;
|
|
1605
|
-
nativeTabCapabilityPromise = (async () => {
|
|
1606
|
-
try {
|
|
1607
|
-
const moduleUrl = await waitForNativeTabModuleUrl();
|
|
1608
|
-
if (!moduleUrl) throw new Error("Codex app-initial module was not found");
|
|
1609
|
-
const appModule = await import(moduleUrl);
|
|
1610
|
-
const controller = Object.values(appModule).find((value) => (
|
|
1611
|
-
value
|
|
1612
|
-
&& typeof value === "object"
|
|
1613
|
-
&& typeof value.openTab === "function"
|
|
1614
|
-
&& typeof value.closeTab === "function"
|
|
1615
|
-
&& typeof value.activateTab === "function"
|
|
1616
|
-
&& value.panelId === "right"
|
|
1617
|
-
));
|
|
1618
|
-
if (!controller) throw new Error("Codex right-panel controller is unavailable");
|
|
1619
|
-
if (!controller?.tabById$) throw new Error("Codex native tab capability is incomplete");
|
|
1620
|
-
nativeTabCapability = {
|
|
1621
|
-
controller,
|
|
1622
|
-
moduleAsset: moduleUrl.split("/").pop(),
|
|
1623
|
-
};
|
|
1624
|
-
nativeTabCapabilityError = null;
|
|
1625
|
-
return nativeTabCapability;
|
|
1626
|
-
} catch (error) {
|
|
1627
|
-
nativeTabCapabilityError = error;
|
|
1628
|
-
return null;
|
|
1629
|
-
}
|
|
1630
|
-
})();
|
|
1631
|
-
const capability = await nativeTabCapabilityPromise;
|
|
1632
|
-
if (!capability) nativeTabCapabilityPromise = null;
|
|
1633
|
-
return capability;
|
|
1634
|
-
}
|
|
1635
|
-
|
|
1636
|
-
function nativeReactElement(type, props) {
|
|
1637
|
-
return {
|
|
1638
|
-
$$typeof: Symbol.for("react.transitional.element"),
|
|
1639
|
-
type,
|
|
1640
|
-
key: null,
|
|
1641
|
-
ref: null,
|
|
1642
|
-
props: props ?? {},
|
|
1643
|
-
};
|
|
1644
|
-
}
|
|
1645
|
-
|
|
1646
|
-
function nativeDetailIcon(feature, detail) {
|
|
1647
|
-
const icon = detail.icon ?? feature.icon;
|
|
1648
|
-
if (!icon?.markup) return undefined;
|
|
1649
|
-
return nativeReactElement("svg", {
|
|
1650
|
-
width: 16,
|
|
1651
|
-
height: 16,
|
|
1652
|
-
viewBox: icon.viewBox ?? "0 0 24 24",
|
|
1653
|
-
fill: "none",
|
|
1654
|
-
stroke: "currentColor",
|
|
1655
|
-
strokeWidth: 1.5,
|
|
1656
|
-
strokeLinecap: "round",
|
|
1657
|
-
strokeLinejoin: "round",
|
|
1658
|
-
"aria-hidden": true,
|
|
1659
|
-
className: "icon-xs shrink-0",
|
|
1660
|
-
dangerouslySetInnerHTML: { __html: icon.markup },
|
|
1661
|
-
});
|
|
1662
|
-
}
|
|
1663
|
-
|
|
1664
|
-
function nativeDetailComponent(feature, detail) {
|
|
1665
|
-
const detailKey = `${feature.id}:${detail.id}`;
|
|
1666
|
-
let Component = nativeDetailComponents.get(detailKey);
|
|
1667
|
-
if (Component) return Component;
|
|
1668
|
-
const frameName = surfaceFrameName(feature.id);
|
|
1669
|
-
Component = function CodexPersonalDetailTab() {
|
|
1670
|
-
return nativeReactElement("div", {
|
|
1671
|
-
"data-codex-personal-native-detail": detailKey,
|
|
1672
|
-
style: {
|
|
1673
|
-
height: "100%",
|
|
1674
|
-
minHeight: 0,
|
|
1675
|
-
overflow: "hidden",
|
|
1676
|
-
background: "var(--color-token-main-surface-primary)",
|
|
1677
|
-
},
|
|
1678
|
-
children: nativeReactElement("iframe", {
|
|
1679
|
-
name: frameName,
|
|
1680
|
-
src: "about:blank",
|
|
1681
|
-
title: detail.label,
|
|
1682
|
-
allow: "clipboard-write",
|
|
1683
|
-
style: {
|
|
1684
|
-
display: "block",
|
|
1685
|
-
width: "100%",
|
|
1686
|
-
height: "100%",
|
|
1687
|
-
border: 0,
|
|
1688
|
-
background: "transparent",
|
|
1689
|
-
},
|
|
1690
|
-
}),
|
|
1691
|
-
});
|
|
1692
|
-
};
|
|
1693
|
-
nativeDetailComponents.set(detailKey, Component);
|
|
1694
|
-
return Component;
|
|
1695
|
-
}
|
|
1696
|
-
|
|
1697
|
-
function reloadNativeDetailIfUnready(feature, detail) {
|
|
1698
|
-
const keys = nativeDetailSurfaceKeys.get(`${feature.id}:${detail.id}`);
|
|
1699
|
-
if (!keys) return false;
|
|
1700
|
-
let reloaded = false;
|
|
1701
|
-
for (const key of keys) {
|
|
1702
|
-
if (reloadSurfaceIfUnready(key, detail.surfaceUrl)) reloaded = true;
|
|
1703
|
-
}
|
|
1704
|
-
return reloaded;
|
|
1705
|
-
}
|
|
1706
|
-
|
|
1707
|
-
async function waitForNativeDetailTabElement(detailKey) {
|
|
1708
|
-
const deadline = performance.now() + 2_000;
|
|
1709
|
-
do {
|
|
1710
|
-
const element = document.querySelector(
|
|
1711
|
-
`[data-codex-personal-native-detail="${detailKey}"]`,
|
|
1712
|
-
);
|
|
1713
|
-
const frame = element?.querySelector("iframe");
|
|
1714
|
-
if (element && frame) return { element, frame };
|
|
1715
|
-
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
1716
|
-
} while (performance.now() < deadline);
|
|
1717
|
-
return null;
|
|
1718
|
-
}
|
|
1719
|
-
|
|
1720
|
-
async function openNativeDetailTab(feature, detail) {
|
|
1721
|
-
const capability = await loadNativeTabCapability();
|
|
1722
|
-
if (!capability) return null;
|
|
1723
|
-
const scope = await waitForCurrentRightPanelScope();
|
|
1724
|
-
if (!scope) {
|
|
1725
|
-
nativeTabCapabilityError = new Error("The current Codex right-panel scope was not found");
|
|
1726
|
-
return null;
|
|
1727
|
-
}
|
|
1728
|
-
|
|
1729
|
-
const { controller } = capability;
|
|
1730
|
-
const tabId = `${feature.id}-${detail.id}`;
|
|
1731
|
-
const detailKey = `${feature.id}:${detail.id}`;
|
|
1732
|
-
let session = Array.from(nativeOpenTabSessions).find((candidate) => (
|
|
1733
|
-
candidate.scope === scope && candidate.tabId === tabId
|
|
1734
|
-
));
|
|
1735
|
-
let focusedExisting = Boolean(session);
|
|
1736
|
-
if (!focusedExisting && controller.tabById$) {
|
|
1737
|
-
try {
|
|
1738
|
-
focusedExisting = scope.get(controller.tabById$, tabId) != null;
|
|
1739
|
-
} catch {}
|
|
1740
|
-
}
|
|
1741
|
-
const isNewSession = !session;
|
|
1742
|
-
if (!session) {
|
|
1743
|
-
session = { scope, controller, tabId };
|
|
1744
|
-
nativeOpenTabSessions.add(session);
|
|
1745
|
-
}
|
|
1746
|
-
|
|
1747
|
-
try {
|
|
1748
|
-
// 原生 openTab 接收标签描述对象,并在挂载前检查可用性。
|
|
1749
|
-
controller.openTab(scope, {
|
|
1750
|
-
Component: nativeDetailComponent(feature, detail),
|
|
1751
|
-
kind: tabId,
|
|
1752
|
-
dropDestinations: ["right"],
|
|
1753
|
-
isAvailable: () => true,
|
|
1754
|
-
}, {
|
|
1755
|
-
id: tabId,
|
|
1756
|
-
kind: tabId,
|
|
1757
|
-
title: detail.label,
|
|
1758
|
-
tooltip: detail.label,
|
|
1759
|
-
icon: nativeDetailIcon(feature, detail),
|
|
1760
|
-
isClosable: true,
|
|
1761
|
-
props: {},
|
|
1762
|
-
onClose: () => {
|
|
1763
|
-
nativeOpenTabSessions.delete(session);
|
|
1764
|
-
const closedKeys = nativeDetailSurfaceKeys.get(detailKey);
|
|
1765
|
-
if (closedKeys) {
|
|
1766
|
-
for (const key of closedKeys) removeSurfaceRecord(key);
|
|
1767
|
-
nativeDetailSurfaceKeys.delete(detailKey);
|
|
1768
|
-
}
|
|
1769
|
-
},
|
|
1770
|
-
});
|
|
1771
|
-
} catch (error) {
|
|
1772
|
-
if (isNewSession) nativeOpenTabSessions.delete(session);
|
|
1773
|
-
nativeTabCapabilityError = error;
|
|
1774
|
-
return null;
|
|
1775
|
-
}
|
|
1776
|
-
|
|
1777
|
-
if (!nativeDetailSurfaceKeys.has(detailKey)) {
|
|
1778
|
-
const mounted = await waitForNativeDetailTabElement(detailKey);
|
|
1779
|
-
if (!mounted) {
|
|
1780
|
-
if (isNewSession) nativeOpenTabSessions.delete(session);
|
|
1781
|
-
if (!focusedExisting) {
|
|
1782
|
-
try {
|
|
1783
|
-
controller.closeTab(scope, tabId);
|
|
1784
|
-
} catch {}
|
|
1785
|
-
}
|
|
1786
|
-
nativeTabCapabilityError = new Error("Codex native right-panel Tab did not mount");
|
|
1787
|
-
return null;
|
|
1788
|
-
}
|
|
1789
|
-
const instanceId = crypto.randomUUID?.()
|
|
1790
|
-
?? `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
1791
|
-
const recordKey = surfaceKey(feature.id, "native-detail", `${detail.id}:${instanceId}`);
|
|
1792
|
-
registerSurface(recordKey, feature, "detail", detail.surfaceUrl, mounted.element, mounted.frame, detail.id);
|
|
1793
|
-
let keys = nativeDetailSurfaceKeys.get(detailKey);
|
|
1794
|
-
if (!keys) {
|
|
1795
|
-
keys = new Set();
|
|
1796
|
-
nativeDetailSurfaceKeys.set(detailKey, keys);
|
|
1797
|
-
}
|
|
1798
|
-
keys.add(recordKey);
|
|
1799
|
-
queueTheme();
|
|
1800
|
-
}
|
|
1801
|
-
|
|
1802
|
-
nativeTabCapabilityError = null;
|
|
1803
|
-
if (focusedExisting) reloadNativeDetailIfUnready(feature, detail);
|
|
1804
|
-
return { tabId, focusedExisting, presentation: "native" };
|
|
1805
|
-
}
|
|
1806
|
-
|
|
1807
|
-
function detailTabCapability() {
|
|
1808
|
-
return {
|
|
1809
|
-
presentation: nativeTabCapability ? "native" : "unavailable",
|
|
1810
|
-
moduleAsset: nativeTabCapability?.moduleAsset ?? null,
|
|
1811
|
-
error: nativeTabCapabilityError?.message ?? null,
|
|
1812
|
-
openNativeTabs: nativeOpenTabSessions.size,
|
|
1813
|
-
};
|
|
1814
|
-
}
|
|
1815
|
-
|
|
1816
|
-
function closeNativeDetailTabs() {
|
|
1817
|
-
for (const keys of Array.from(nativeDetailSurfaceKeys.values())) {
|
|
1818
|
-
for (const key of keys) removeSurfaceRecord(key);
|
|
1819
|
-
}
|
|
1820
|
-
for (const session of Array.from(nativeOpenTabSessions)) {
|
|
1821
|
-
try {
|
|
1822
|
-
session.controller.closeTab(session.scope, session.tabId);
|
|
1823
|
-
} catch {}
|
|
1824
|
-
}
|
|
1825
|
-
nativeOpenTabSessions.clear();
|
|
1826
|
-
nativeDetailComponents.clear();
|
|
1827
|
-
nativeDetailSurfaceKeys.clear();
|
|
1828
|
-
}
|
|
1829
|
-
|
|
1830
|
-
async function openDetailTab(featureId, detailId) {
|
|
1831
|
-
const feature = featureById.get(featureId);
|
|
1832
|
-
const detail = feature?.detailTabs?.find((candidate) => candidate.id === detailId);
|
|
1833
|
-
if (!feature || !detail) throw new Error("The requested detail Tab is not registered");
|
|
1834
|
-
const result = await openNativeDetailTab(feature, detail);
|
|
1835
|
-
if (!result) {
|
|
1836
|
-
const error = nativeTabCapabilityError
|
|
1837
|
-
?? new Error("Codex native right-panel tabs are unavailable");
|
|
1838
|
-
error.code = "NATIVE_TAB_UNAVAILABLE";
|
|
1839
|
-
throw error;
|
|
1840
|
-
}
|
|
1841
|
-
return result;
|
|
1842
|
-
}
|
|
1843
|
-
|
|
1844
|
-
function openThread(threadId) {
|
|
1845
|
-
const nativeThread = Array.from(
|
|
1846
|
-
document.querySelectorAll("[data-app-action-sidebar-thread-id]"),
|
|
1847
|
-
).find((element) => {
|
|
1848
|
-
const nativeId = element.getAttribute("data-app-action-sidebar-thread-id");
|
|
1849
|
-
return nativeId === threadId || nativeId?.endsWith(`:${threadId}`);
|
|
1850
|
-
});
|
|
1851
|
-
|
|
1852
|
-
hidePageSurfaces();
|
|
1853
|
-
hidePinnedSurfaces();
|
|
1854
|
-
if (nativeThread) {
|
|
1855
|
-
nativeThread.click();
|
|
1856
|
-
nativeThread.focus({ preventScroll: false });
|
|
1857
|
-
return;
|
|
1858
|
-
}
|
|
1859
|
-
|
|
1860
|
-
const link = document.createElement("a");
|
|
1861
|
-
link.href = `codex://threads/${threadId}`;
|
|
1862
|
-
link.target = "_blank";
|
|
1863
|
-
link.rel = "noreferrer";
|
|
1864
|
-
link.hidden = true;
|
|
1865
|
-
document.body.append(link);
|
|
1866
|
-
link.click();
|
|
1867
|
-
link.remove();
|
|
1868
|
-
}
|
|
1869
|
-
|
|
1870
|
-
// ========================================================================
|
|
1871
|
-
// Sidebar/Toolbar 入口与 ensure 调度 — 入口克隆注入、ensure 批处理调度
|
|
1872
|
-
// ========================================================================
|
|
1873
|
-
|
|
1874
|
-
function createSidebarEntry(feature, reference) {
|
|
1875
|
-
const entry = reference.cloneNode(true);
|
|
1876
|
-
entry.setAttribute(entryMarker, feature.id);
|
|
1877
|
-
entry.removeAttribute("href");
|
|
1878
|
-
entry.removeAttribute("aria-current");
|
|
1879
|
-
entry.querySelectorAll("[data-state]").forEach((node) => node.removeAttribute("data-state"));
|
|
1880
|
-
const originalIcon = entry.querySelector("svg");
|
|
1881
|
-
const featureIcon = createFeatureIcon(feature, originalIcon?.getAttribute("class"));
|
|
1882
|
-
if (originalIcon && featureIcon) originalIcon.replaceWith(featureIcon);
|
|
1883
|
-
|
|
1884
|
-
const referenceLabels = nativeLabels.plugins;
|
|
1885
|
-
const textLeaf = Array.from(entry.querySelectorAll("*")).find((node) => (
|
|
1886
|
-
node.children.length === 0
|
|
1887
|
-
&& referenceLabels.includes(node.textContent?.trim().toLowerCase())
|
|
1888
|
-
));
|
|
1889
|
-
if (textLeaf) textLeaf.textContent = feature.label;
|
|
1890
|
-
else entry.setAttribute("aria-label", feature.label);
|
|
1891
|
-
|
|
1892
|
-
entry.addEventListener("click", (event) => {
|
|
1893
|
-
event.preventDefault();
|
|
1894
|
-
event.stopPropagation();
|
|
1895
|
-
showPageSurface(feature.id);
|
|
1896
|
-
});
|
|
1897
|
-
return entry;
|
|
1898
|
-
}
|
|
1899
|
-
|
|
1900
|
-
function createToolbarEntry(feature, nativeButton) {
|
|
1901
|
-
const nativeRoot = toolbarControlRoot(nativeButton);
|
|
1902
|
-
const root = nativeRoot.cloneNode(true);
|
|
1903
|
-
root.setAttribute("data-codex-personal-toolbar-entry", feature.id);
|
|
1904
|
-
root.classList.remove("ms-auto");
|
|
1905
|
-
root.querySelectorAll("[id]").forEach((node) => node.removeAttribute("id"));
|
|
1906
|
-
root.querySelectorAll("[aria-describedby]").forEach((node) => node.removeAttribute("aria-describedby"));
|
|
1907
|
-
const button = root.matches("button") ? root : root.querySelector("button");
|
|
1908
|
-
if (!button) return null;
|
|
1909
|
-
const label = summaryButtonLabel(feature);
|
|
1910
|
-
button.setAttribute(entryMarker, feature.id);
|
|
1911
|
-
button.removeAttribute("title");
|
|
1912
|
-
button.removeAttribute("aria-describedby");
|
|
1913
|
-
button.setAttribute("aria-label", label);
|
|
1914
|
-
button.setAttribute("data-minecodex-tooltip", label);
|
|
1915
|
-
button.setAttribute("aria-pressed", "false");
|
|
1916
|
-
button.setAttribute("aria-expanded", "false");
|
|
1917
|
-
button.setAttribute("data-state", "closed");
|
|
1918
|
-
button.removeAttribute("disabled");
|
|
1919
|
-
button.classList.remove(
|
|
1920
|
-
"text-token-foreground",
|
|
1921
|
-
"bg-token-foreground/5",
|
|
1922
|
-
"bg-token-foreground/10",
|
|
1923
|
-
"enabled:hover:bg-token-foreground/10",
|
|
1924
|
-
"data-[state=open]:bg-token-foreground/10",
|
|
1925
|
-
"bg-token-list-hover-background",
|
|
1926
|
-
);
|
|
1927
|
-
button.classList.add(
|
|
1928
|
-
"text-token-text-tertiary",
|
|
1929
|
-
"enabled:hover:bg-token-list-hover-background",
|
|
1930
|
-
"data-[state=open]:bg-token-list-hover-background",
|
|
1931
|
-
);
|
|
1932
|
-
const originalIcon = button.querySelector("svg");
|
|
1933
|
-
const featureIcon = createFeatureIcon(feature, originalIcon?.getAttribute("class"), 16);
|
|
1934
|
-
if (originalIcon && featureIcon) originalIcon.replaceWith(featureIcon);
|
|
1935
|
-
button.addEventListener("click", (event) => {
|
|
1936
|
-
event.preventDefault();
|
|
1937
|
-
event.stopPropagation();
|
|
1938
|
-
togglePinnedSurface(feature.id);
|
|
1939
|
-
});
|
|
1940
|
-
return button;
|
|
1941
|
-
}
|
|
1942
|
-
|
|
1943
|
-
function ensureSidebarEntries() {
|
|
1944
|
-
if (!sidebarFeatures.length) return;
|
|
1945
|
-
const reference = findReferenceEntry();
|
|
1946
|
-
if (!reference) return;
|
|
1947
|
-
for (const feature of sidebarFeatures) {
|
|
1948
|
-
if (!findEntry(feature.id)) reference.after(createSidebarEntry(feature, reference));
|
|
1949
|
-
}
|
|
1950
|
-
const cursors = new Map();
|
|
1951
|
-
for (const feature of sidebarFeatures) {
|
|
1952
|
-
const anchorKey = feature.placement?.after ?? "sites";
|
|
1953
|
-
const anchor = cursors.get(anchorKey) ?? findNativeEntry(anchorKey) ?? reference;
|
|
1954
|
-
const entry = findEntry(feature.id);
|
|
1955
|
-
if (anchor && entry && anchor.nextElementSibling !== entry) anchor.after(entry);
|
|
1956
|
-
if (entry) cursors.set(anchorKey, entry);
|
|
1957
|
-
}
|
|
1958
|
-
}
|
|
1959
|
-
|
|
1960
|
-
function ensureToolbarEntries() {
|
|
1961
|
-
if (!toolbarFeatures.length) {
|
|
1962
|
-
stopToolbarReadiness();
|
|
1963
|
-
return;
|
|
1964
|
-
}
|
|
1965
|
-
const temporaryChatButton = nativeTemporaryChatButton();
|
|
1966
|
-
const summaryButton = nativeSummaryButton();
|
|
1967
|
-
const bottomPanelButton = nativeBottomPanelButton();
|
|
1968
|
-
const sidePanelButton = nativeSidePanelButton();
|
|
1969
|
-
const anchorButton = temporaryChatButton ?? summaryButton ?? bottomPanelButton ?? sidePanelButton;
|
|
1970
|
-
if (!anchorButton) {
|
|
1971
|
-
const candidate = structuralToolbarAnchor();
|
|
1972
|
-
if (candidate) ensureToolbarReadiness(candidate);
|
|
1973
|
-
return;
|
|
1974
|
-
}
|
|
1975
|
-
const anchorRoot = toolbarControlRoot(anchorButton);
|
|
1976
|
-
// The bottom/side-panel toggles sit in narrow fixed containers that
|
|
1977
|
-
// travel with the native panels. Entries anchored to them must live in
|
|
1978
|
-
// the fallback toolbar group, otherwise the Notes entry would be
|
|
1979
|
-
// dragged into the panel or push the native toggle out of the header.
|
|
1980
|
-
// The Notes entry is only grouped with the native summary button when
|
|
1981
|
-
// that button is the anchor.
|
|
1982
|
-
const useFallbackAnchor = anchorButton === sidePanelButton || anchorButton === bottomPanelButton;
|
|
1983
|
-
const summaryGroup = useFallbackAnchor ? null : nativeSummaryToolbarGroup(anchorButton);
|
|
1984
|
-
let targetGroup = summaryGroup ?? ensureFallbackSummaryToolbarGroup(anchorButton);
|
|
1985
|
-
if (!targetGroup) {
|
|
1986
|
-
ensureToolbarReadiness(anchorButton);
|
|
1987
|
-
return;
|
|
1988
|
-
}
|
|
1989
|
-
let cursor = useFallbackAnchor ? null : anchorRoot;
|
|
1990
|
-
for (const feature of toolbarFeatures) {
|
|
1991
|
-
const entries = Array.from(document.querySelectorAll(`[${entryMarker}="${feature.id}"]`));
|
|
1992
|
-
for (const duplicate of entries.slice(1)) {
|
|
1993
|
-
(duplicate.closest("[data-codex-personal-toolbar-entry]") ?? duplicate).remove();
|
|
1994
|
-
}
|
|
1995
|
-
const entry = entries[0]?.isConnected
|
|
1996
|
-
? entries[0]
|
|
1997
|
-
: createToolbarEntry(feature, anchorButton);
|
|
1998
|
-
const entryRoot = entry?.closest("[data-codex-personal-toolbar-entry]") ?? entry;
|
|
1999
|
-
if (!entryRoot) continue;
|
|
2000
|
-
if (cursor) {
|
|
2001
|
-
if (cursor.nextElementSibling !== entryRoot) cursor.after(entryRoot);
|
|
2002
|
-
} else if (targetGroup.lastElementChild !== entryRoot) {
|
|
2003
|
-
targetGroup.append(entryRoot);
|
|
2004
|
-
}
|
|
2005
|
-
// Narrow native anchor containers (e.g. the Work-mode side-panel
|
|
2006
|
-
// button) can push the entry outside the header so it becomes
|
|
2007
|
-
// unreachable. Move it into the fallback toolbar group instead.
|
|
2008
|
-
if (summaryGroup && !toolbarEntryFitsHeader(entryRoot)) {
|
|
2009
|
-
const fallback = ensureFallbackSummaryToolbarGroup(anchorButton);
|
|
2010
|
-
if (fallback && fallback !== summaryGroup) {
|
|
2011
|
-
fallback.append(entryRoot);
|
|
2012
|
-
targetGroup = fallback;
|
|
2013
|
-
cursor = null;
|
|
2014
|
-
}
|
|
2015
|
-
}
|
|
2016
|
-
cursor = entryRoot;
|
|
2017
|
-
}
|
|
2018
|
-
if (summaryGroup) {
|
|
2019
|
-
document.querySelectorAll("[data-codex-personal-summary-toolbar-group]").forEach((group) => {
|
|
2020
|
-
const used = [...group.querySelectorAll(`[${entryMarker}]`)].some((entry) => entry.isConnected);
|
|
2021
|
-
if (!used) group.remove();
|
|
2022
|
-
});
|
|
2023
|
-
}
|
|
2024
|
-
if (toolbarFeatures.every((feature) => findEntry(feature.id)?.isConnected)) {
|
|
2025
|
-
stopToolbarReadiness();
|
|
2026
|
-
} else {
|
|
2027
|
-
ensureToolbarReadiness(anchorButton);
|
|
2028
|
-
}
|
|
2029
|
-
}
|
|
2030
|
-
|
|
2031
247
|
function ensureEntries() {
|
|
2032
248
|
ensureQueued = false;
|
|
2033
249
|
ensureFrame = null;
|
|
2034
250
|
ensureTimer = null;
|
|
2035
|
-
ensureSidebarEntries();
|
|
2036
|
-
ensureToolbarEntries();
|
|
2037
251
|
modelSlider.ensure();
|
|
2038
|
-
checkThreadChange();
|
|
2039
|
-
updateSidebarSelectedState();
|
|
2040
|
-
updatePageSurfacePositions();
|
|
2041
|
-
ensureMainContentObserver();
|
|
2042
|
-
updateResponsiveSummaryPresentation();
|
|
2043
|
-
const nativeToggle = nativeSummaryButton();
|
|
2044
|
-
if (
|
|
2045
|
-
activePinnedFeatureId
|
|
2046
|
-
&& (nativeToggle?.getAttribute("aria-expanded") === "true"
|
|
2047
|
-
|| nativeToggle?.getAttribute("aria-pressed") === "true")
|
|
2048
|
-
) {
|
|
2049
|
-
hidePinnedSurfaces();
|
|
2050
|
-
}
|
|
2051
252
|
}
|
|
2052
253
|
|
|
2053
254
|
function flushEnsure() {
|
|
@@ -2067,115 +268,9 @@ export function createInjectionSource(features, {
|
|
|
2067
268
|
}
|
|
2068
269
|
|
|
2069
270
|
// ========================================================================
|
|
2070
|
-
//
|
|
271
|
+
// host action 通道 — page-script 经 CDP binding 请求 RuntimeHost 并接收结果
|
|
2071
272
|
// ========================================================================
|
|
2072
273
|
|
|
2073
|
-
function findMessageSurface(event) {
|
|
2074
|
-
for (const record of surfaceRecords.values()) {
|
|
2075
|
-
// 身份以 event.source(WindowProxy 不可伪造)为准;Windows 上
|
|
2076
|
-
// setDocumentContent 的 iframe 消息可能报告 null origin,
|
|
2077
|
-
// 因此用已注册 frame 的 WindowProxy 精确匹配来源。
|
|
2078
|
-
if (event.source === record.frame.contentWindow) return record;
|
|
2079
|
-
}
|
|
2080
|
-
return null;
|
|
2081
|
-
}
|
|
2082
|
-
|
|
2083
|
-
function respondToSurface(record, requestId, { ok, result = null, error = null }) {
|
|
2084
|
-
record.frame.contentWindow?.postMessage({
|
|
2085
|
-
type: "codex-personal:host-result",
|
|
2086
|
-
feature: record.featureId,
|
|
2087
|
-
requestId,
|
|
2088
|
-
ok,
|
|
2089
|
-
result,
|
|
2090
|
-
error,
|
|
2091
|
-
}, "/");
|
|
2092
|
-
}
|
|
2093
|
-
|
|
2094
|
-
function hostActionFailure(error) {
|
|
2095
|
-
return {
|
|
2096
|
-
code: typeof error?.code === "string" ? error.code : "HOST_ACTION_FAILED",
|
|
2097
|
-
message: error?.message ?? "Host action failed",
|
|
2098
|
-
};
|
|
2099
|
-
}
|
|
2100
|
-
|
|
2101
|
-
async function handleSurfaceHostAction(record, message) {
|
|
2102
|
-
const feature = featureById.get(record.featureId);
|
|
2103
|
-
const { requestId, action, payload = {} } = message;
|
|
2104
|
-
if (typeof requestId !== "string" || !feature?.hostActions.has(action)) {
|
|
2105
|
-
respondToSurface(record, requestId, {
|
|
2106
|
-
ok: false,
|
|
2107
|
-
error: { code: "ACTION_NOT_ALLOWED", message: "This Host action is not registered" },
|
|
2108
|
-
});
|
|
2109
|
-
return;
|
|
2110
|
-
}
|
|
2111
|
-
try {
|
|
2112
|
-
if (action === "insert-text") {
|
|
2113
|
-
respondToSurface(record, requestId, { ok: true, result: insertComposerText(payload.text) });
|
|
2114
|
-
return;
|
|
2115
|
-
}
|
|
2116
|
-
if (action === "open-detail-tab") {
|
|
2117
|
-
respondToSurface(record, requestId, {
|
|
2118
|
-
ok: true,
|
|
2119
|
-
result: await openDetailTab(feature.id, payload.surface),
|
|
2120
|
-
});
|
|
2121
|
-
return;
|
|
2122
|
-
}
|
|
2123
|
-
if (action === "open-editor-modal") {
|
|
2124
|
-
respondToSurface(record, requestId, {
|
|
2125
|
-
ok: true,
|
|
2126
|
-
result: openEditorModal(record, payload),
|
|
2127
|
-
});
|
|
2128
|
-
return;
|
|
2129
|
-
}
|
|
2130
|
-
if (action === "open-sidebar") {
|
|
2131
|
-
const trigger = nativeSidebarTrigger();
|
|
2132
|
-
if (!trigger) throw new Error("The native sidebar control was not found");
|
|
2133
|
-
const state = sidebarState();
|
|
2134
|
-
if (!state.open) trigger.click();
|
|
2135
|
-
queueMicrotask(() => postSidebarState(record, { ...state, open: true }));
|
|
2136
|
-
respondToSurface(record, requestId, { ok: true, result: { opened: true } });
|
|
2137
|
-
return;
|
|
2138
|
-
}
|
|
2139
|
-
if (action === "resolve-file-paths") {
|
|
2140
|
-
const getPathForFile = window.electronBridge?.getPathForFile;
|
|
2141
|
-
if (typeof getPathForFile !== "function") {
|
|
2142
|
-
throw new Error("Codex did not expose its File path bridge");
|
|
2143
|
-
}
|
|
2144
|
-
const files = Array.isArray(payload.files) ? payload.files : [];
|
|
2145
|
-
if (!files.length || files.some((file) => !(file instanceof File))) {
|
|
2146
|
-
throw new Error("Real dropped File objects are required");
|
|
2147
|
-
}
|
|
2148
|
-
// Windows 盘符路径(C:\...)不以 / 开头,需同时接受 POSIX 与 Windows 形态。
|
|
2149
|
-
const paths = files.map((file) => getPathForFile(file)).filter((value) => (
|
|
2150
|
-
typeof value === "string" && (/^[/\\]/.test(value) || /^[a-zA-Z]:[/\\]/.test(value))
|
|
2151
|
-
));
|
|
2152
|
-
if (!paths.length) throw new Error("Codex could not resolve the dropped file paths");
|
|
2153
|
-
respondToSurface(record, requestId, { ok: true, result: { paths } });
|
|
2154
|
-
return;
|
|
2155
|
-
}
|
|
2156
|
-
if (["attach-file", "create-image-edit-thread", "list-cloud-images", "import-cloud-image"].includes(action)) {
|
|
2157
|
-
if (typeof globalThis[config.bindingName] !== "function") {
|
|
2158
|
-
throw new Error("The native file bridge is unavailable");
|
|
2159
|
-
}
|
|
2160
|
-
pendingHostActions.set(requestId, { recordKey: record.key });
|
|
2161
|
-
globalThis[config.bindingName](JSON.stringify({
|
|
2162
|
-
token: config.bindingToken,
|
|
2163
|
-
featureId: feature.id,
|
|
2164
|
-
requestId,
|
|
2165
|
-
action,
|
|
2166
|
-
payload,
|
|
2167
|
-
}));
|
|
2168
|
-
return;
|
|
2169
|
-
}
|
|
2170
|
-
throw new Error("Unsupported Host action");
|
|
2171
|
-
} catch (error) {
|
|
2172
|
-
respondToSurface(record, requestId, {
|
|
2173
|
-
ok: false,
|
|
2174
|
-
error: hostActionFailure(error),
|
|
2175
|
-
});
|
|
2176
|
-
}
|
|
2177
|
-
}
|
|
2178
|
-
|
|
2179
274
|
function requestPageHostAction(featureId, action, payload) {
|
|
2180
275
|
if (!featureById.get(featureId)?.hostActions.has(action)) return Promise.reject(new Error("Host action is not registered"));
|
|
2181
276
|
if (typeof globalThis[config.bindingName] !== "function") return Promise.reject(new Error("Host bridge is unavailable"));
|
|
@@ -2185,7 +280,7 @@ export function createInjectionSource(features, {
|
|
|
2185
280
|
pendingHostActions.delete(requestId);
|
|
2186
281
|
reject(new Error("Host request timed out"));
|
|
2187
282
|
}, featureId === "minecodex" ? 30_000 : 10_000);
|
|
2188
|
-
pendingHostActions.set(requestId, {
|
|
283
|
+
pendingHostActions.set(requestId, { resolve, reject, timer });
|
|
2189
284
|
try {
|
|
2190
285
|
globalThis[config.bindingName](JSON.stringify({ token: config.bindingToken, featureId, requestId, action, payload }));
|
|
2191
286
|
} catch (error) {
|
|
@@ -2200,160 +295,33 @@ export function createInjectionSource(features, {
|
|
|
2200
295
|
const pending = pendingHostActions.get(requestId);
|
|
2201
296
|
if (!pending) return false;
|
|
2202
297
|
pendingHostActions.delete(requestId);
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
else pending.reject(new Error(response.error?.message ?? "Host request failed"));
|
|
2207
|
-
return true;
|
|
2208
|
-
}
|
|
2209
|
-
const record = surfaceRecords.get(pending.recordKey);
|
|
2210
|
-
if (!record) return false;
|
|
2211
|
-
if (pending.kind === "surface-load") {
|
|
2212
|
-
record.loading = false;
|
|
2213
|
-
record.loadError = response.ok ? null : response.error;
|
|
2214
|
-
return response.ok;
|
|
2215
|
-
}
|
|
2216
|
-
respondToSurface(record, requestId, response);
|
|
298
|
+
clearTimeout(pending.timer);
|
|
299
|
+
if (response.ok) pending.resolve(response.result);
|
|
300
|
+
else pending.reject(new Error(response.error?.message ?? "Host request failed"));
|
|
2217
301
|
return true;
|
|
2218
302
|
}
|
|
2219
303
|
|
|
2220
304
|
document.addEventListener("selectionchange", rememberComposerSelection, { signal: lifetime.signal });
|
|
2221
|
-
|
|
2222
|
-
"click",
|
|
2223
|
-
(event) => {
|
|
2224
|
-
if (event.target.closest?.('[data-app-shell-sidebar-trigger="true"]')) {
|
|
2225
|
-
syncAfterNativeSidebarToggle();
|
|
2226
|
-
}
|
|
2227
|
-
if (isNativeSummaryButton(event.target) && activePinnedFeatureId) hidePinnedSurfaces();
|
|
2228
|
-
if (activePinnedFeatureId && summaryDisplayMode() === "overlay") {
|
|
2229
|
-
const state = summaryState(activePinnedFeatureId);
|
|
2230
|
-
const surface = pinnedSurfaces.get(activePinnedFeatureId);
|
|
2231
|
-
const ownEntry = event.target.closest?.(`[${entryMarker}="${activePinnedFeatureId}"]`);
|
|
2232
|
-
const rightWorkspaceInteraction = event.target.closest?.(
|
|
2233
|
-
'[data-app-shell-tab-strip-controller="right"],'
|
|
2234
|
-
+ '[data-app-shell-tab-panel-controller="right"]',
|
|
2235
|
-
);
|
|
2236
|
-
const promptPreviewInteraction = event.target.closest?.(`[${promptPreviewMarker}]`);
|
|
2237
|
-
if (
|
|
2238
|
-
state.isPopoverOpen
|
|
2239
|
-
&& !surface?.contains(event.target)
|
|
2240
|
-
&& !ownEntry
|
|
2241
|
-
&& !rightWorkspaceInteraction
|
|
2242
|
-
&& !promptPreviewInteraction
|
|
2243
|
-
) {
|
|
2244
|
-
const featureId = activePinnedFeatureId;
|
|
2245
|
-
state.isPopoverOpen = false;
|
|
2246
|
-
if (!state.isPinned) activePinnedFeatureId = null;
|
|
2247
|
-
updatePinnedPosition(featureId);
|
|
2248
|
-
updateToolbarState();
|
|
2249
|
-
}
|
|
2250
|
-
}
|
|
2251
|
-
if (!activePageFeatureId) return;
|
|
2252
|
-
if (event.target.closest?.(`[${entryMarker}]`)) return;
|
|
2253
|
-
if (event.target.closest?.("[data-app-action-sidebar-scroll]")) hidePageSurfaces();
|
|
2254
|
-
},
|
|
2255
|
-
{ capture: true, signal: lifetime.signal },
|
|
2256
|
-
);
|
|
2257
|
-
|
|
2258
|
-
document.addEventListener("keydown", (event) => {
|
|
2259
|
-
if (event.key !== "Escape") return;
|
|
2260
|
-
if (closePromptPreview({ immediate: true })) {
|
|
2261
|
-
event.preventDefault();
|
|
2262
|
-
event.stopPropagation();
|
|
2263
|
-
return;
|
|
2264
|
-
}
|
|
2265
|
-
if (closeModalSurface()) {
|
|
2266
|
-
event.preventDefault();
|
|
2267
|
-
event.stopPropagation();
|
|
2268
|
-
return;
|
|
2269
|
-
}
|
|
2270
|
-
if (!activePinnedFeatureId || summaryDisplayMode() !== "overlay") return;
|
|
2271
|
-
const state = summaryState(activePinnedFeatureId);
|
|
2272
|
-
if (!state.isPopoverOpen) return;
|
|
2273
|
-
const featureId = activePinnedFeatureId;
|
|
2274
|
-
state.isPopoverOpen = false;
|
|
2275
|
-
if (!state.isPinned) activePinnedFeatureId = null;
|
|
2276
|
-
updatePinnedPosition(featureId);
|
|
2277
|
-
updateToolbarState();
|
|
2278
|
-
}, { capture: true, signal: lifetime.signal });
|
|
2279
|
-
|
|
2280
|
-
window.addEventListener("message", (event) => {
|
|
2281
|
-
const record = findMessageSurface(event);
|
|
2282
|
-
if (!record || !event.data || typeof event.data !== "object") return;
|
|
2283
|
-
if (event.data.feature && event.data.feature !== record.featureId) return;
|
|
2284
|
-
if (event.data.type === "codex-personal:ready") {
|
|
2285
|
-
record.ready = true;
|
|
2286
|
-
queueTheme();
|
|
2287
|
-
postSidebarState(record);
|
|
2288
|
-
if (record.kind === "modal") {
|
|
2289
|
-
record.frame.focus();
|
|
2290
|
-
record.frame.contentWindow?.postMessage({
|
|
2291
|
-
type: "codex-personal:focus",
|
|
2292
|
-
feature: record.featureId,
|
|
2293
|
-
}, "/");
|
|
2294
|
-
}
|
|
2295
|
-
}
|
|
2296
|
-
if (event.data.type === "codex-personal:surface-metrics" && record.kind === "pinned") {
|
|
2297
|
-
const preferredHeight = Number(event.data.height);
|
|
2298
|
-
if (Number.isFinite(preferredHeight) && preferredHeight > 0) {
|
|
2299
|
-
record.preferredHeight = Math.min(2_000, Math.max(80, Math.ceil(preferredHeight)));
|
|
2300
|
-
updatePinnedPosition(record.featureId);
|
|
2301
|
-
}
|
|
2302
|
-
}
|
|
2303
|
-
if (event.data.type === "codex-personal:prompt-preview" && record.kind === "pinned") {
|
|
2304
|
-
if (event.data.phase === "show") showPromptPreview(record, event.data);
|
|
2305
|
-
if (event.data.phase === "hide") requestPromptPreviewClose(record);
|
|
2306
|
-
}
|
|
2307
|
-
if (event.data.type === "codex-personal:close") {
|
|
2308
|
-
if (record.kind === "pinned") hidePinnedSurfaces();
|
|
2309
|
-
if (record.kind === "page") hidePageSurfaces();
|
|
2310
|
-
if (record.kind === "modal") closeModalSurface();
|
|
2311
|
-
}
|
|
2312
|
-
if (event.data.type === "codex-personal:open-thread" && event.data.threadId) {
|
|
2313
|
-
openThread(event.data.threadId);
|
|
2314
|
-
}
|
|
2315
|
-
if (event.data.type === "codex-personal:host-action") {
|
|
2316
|
-
handleSurfaceHostAction(record, event.data);
|
|
2317
|
-
}
|
|
2318
|
-
}, { signal: lifetime.signal });
|
|
2319
|
-
|
|
2320
|
-
window.addEventListener("resize", () => {
|
|
2321
|
-
closePromptPreview({ immediate: true });
|
|
2322
|
-
updatePageSurfacePositions();
|
|
2323
|
-
updateResponsiveSummaryPresentation();
|
|
2324
|
-
}, { signal: lifetime.signal });
|
|
2325
|
-
const ensureObserver = new MutationObserver((mutations) => {
|
|
2326
|
-
queueEnsure();
|
|
2327
|
-
const sidebarTriggerChanged = mutations.some((mutation) => {
|
|
2328
|
-
if (
|
|
2329
|
-
mutation.type === "attributes"
|
|
2330
|
-
&& mutation.target.matches?.('[data-app-shell-sidebar-trigger="true"]')
|
|
2331
|
-
) return true;
|
|
2332
|
-
return mutation.type === "childList"
|
|
2333
|
-
&& [...mutation.addedNodes, ...mutation.removedNodes].some((node) => (
|
|
2334
|
-
node instanceof Element
|
|
2335
|
-
&& (
|
|
2336
|
-
node.matches?.('[data-app-shell-sidebar-trigger="true"]')
|
|
2337
|
-
|| node.querySelector?.('[data-app-shell-sidebar-trigger="true"]')
|
|
2338
|
-
)
|
|
2339
|
-
));
|
|
2340
|
-
});
|
|
2341
|
-
if (sidebarTriggerChanged) updatePageSurfacePositions();
|
|
2342
|
-
});
|
|
305
|
+
const ensureObserver = new MutationObserver(queueEnsure);
|
|
2343
306
|
ensureObserver.observe(document.documentElement, {
|
|
2344
307
|
childList: true,
|
|
2345
308
|
subtree: true,
|
|
2346
309
|
attributes: true,
|
|
2347
|
-
attributeFilter: [
|
|
2348
|
-
"aria-expanded",
|
|
2349
|
-
"aria-pressed",
|
|
2350
|
-
"data-state",
|
|
2351
|
-
"data-app-action-sidebar-thread-selected",
|
|
2352
|
-
"data-app-action-sidebar-thread-id",
|
|
2353
|
-
],
|
|
310
|
+
attributeFilter: ["aria-expanded", "aria-pressed", "data-state"],
|
|
2354
311
|
});
|
|
2355
312
|
domObservers.push(ensureObserver);
|
|
2356
|
-
|
|
313
|
+
// theme.read 会把原生主题 token 同步到根节点的 --mcx-* 变量,页面脚本依赖它跟随主题。
|
|
314
|
+
let themeQueued = false;
|
|
315
|
+
const syncTheme = () => {
|
|
316
|
+
if (themeQueued) return;
|
|
317
|
+
themeQueued = true;
|
|
318
|
+
queueMicrotask(() => {
|
|
319
|
+
themeQueued = false;
|
|
320
|
+
featureUI.theme.read();
|
|
321
|
+
});
|
|
322
|
+
};
|
|
323
|
+
featureUI.theme.read();
|
|
324
|
+
const themeObserver = new MutationObserver(syncTheme);
|
|
2357
325
|
themeObserver.observe(document.documentElement, {
|
|
2358
326
|
attributes: true,
|
|
2359
327
|
attributeFilter: ["class", "style", "data-theme"],
|
|
@@ -2361,7 +329,7 @@ export function createInjectionSource(features, {
|
|
|
2361
329
|
domObservers.push(themeObserver);
|
|
2362
330
|
|
|
2363
331
|
// ========================================================================
|
|
2364
|
-
// 运行时 API 与清理
|
|
332
|
+
// 运行时 API 与清理
|
|
2365
333
|
// ========================================================================
|
|
2366
334
|
|
|
2367
335
|
const runtimeApi = {
|
|
@@ -2371,22 +339,11 @@ export function createInjectionSource(features, {
|
|
|
2371
339
|
nativeCapabilities: () => codex.diagnostics(),
|
|
2372
340
|
selfCheck: () => ({...selfCheck.snapshot(),capabilities:codex.diagnostics()}),
|
|
2373
341
|
ensure: queueEnsure,
|
|
2374
|
-
show: showPageSurface,
|
|
2375
|
-
hide: () => {
|
|
2376
|
-
hidePageSurfaces();
|
|
2377
|
-
hidePinnedSurfaces();
|
|
2378
|
-
closeModalSurface();
|
|
2379
|
-
},
|
|
2380
|
-
showPinned: showPinnedSurface,
|
|
2381
|
-
hidePinned: hidePinnedSurfaces,
|
|
2382
|
-
openDetailTab,
|
|
2383
|
-
detailTabCapability,
|
|
2384
342
|
insertText: insertComposerText,
|
|
2385
343
|
resolveHostAction,
|
|
2386
344
|
dispose: () => {
|
|
2387
345
|
lifetime.abort();
|
|
2388
346
|
for (const [id, pending] of pendingHostActions) {
|
|
2389
|
-
if (pending.kind !== "page-script") continue;
|
|
2390
347
|
clearTimeout(pending.timer);
|
|
2391
348
|
pending.reject(new Error("Runtime disposed"));
|
|
2392
349
|
pendingHostActions.delete(id);
|
|
@@ -2399,26 +356,12 @@ export function createInjectionSource(features, {
|
|
|
2399
356
|
if (ensureFrame != null) cancelAnimationFrame(ensureFrame);
|
|
2400
357
|
if (ensureTimer != null) clearTimeout(ensureTimer);
|
|
2401
358
|
for (const observer of domObservers) observer.disconnect();
|
|
2402
|
-
stopToolbarReadiness();
|
|
2403
|
-
mainContentObserver?.observer.disconnect();
|
|
2404
|
-
restoreThreadContentShift();
|
|
2405
|
-
for (const entry of document.querySelectorAll(`[${entryMarker}]`)) entry.remove();
|
|
2406
|
-
for (const root of document.querySelectorAll("[data-codex-personal-toolbar-entry]")) root.remove();
|
|
2407
|
-
for (const group of document.querySelectorAll("[data-codex-personal-summary-toolbar-group]")) group.remove();
|
|
2408
|
-
for (const surface of pageSurfaces.values()) surface.remove();
|
|
2409
|
-
for (const surface of pinnedSurfaces.values()) surface.remove();
|
|
2410
|
-
closePromptPreview({ immediate: true });
|
|
2411
|
-
closeModalSurface();
|
|
2412
|
-
closeNativeDetailTabs();
|
|
2413
359
|
modelSlider.dispose();
|
|
2414
|
-
surfaceRecords.clear();
|
|
2415
360
|
featureUI.theme.dispose();
|
|
2416
361
|
if (window.__codexPersonalRuntime === runtimeApi) delete window.__codexPersonalRuntime;
|
|
2417
362
|
},
|
|
2418
363
|
};
|
|
2419
364
|
window.__codexPersonalRuntime = runtimeApi;
|
|
2420
|
-
currentThread = threadIdentity();
|
|
2421
|
-
currentSummaryDisplayMode = summaryDisplayMode();
|
|
2422
365
|
queueEnsure();
|
|
2423
366
|
}
|
|
2424
367
|
|
|
@@ -2427,12 +370,7 @@ export function createInjectionSource(features, {
|
|
|
2427
370
|
id: feature.id,
|
|
2428
371
|
label: feature.label,
|
|
2429
372
|
labels: feature.labels ?? null,
|
|
2430
|
-
|
|
2431
|
-
icon: feature.icon,
|
|
2432
|
-
placement: feature.placement,
|
|
2433
|
-
entry: feature.entry ?? { kind: "sidebar" },
|
|
2434
|
-
pinnedSummary: feature.pinnedSummary ?? null,
|
|
2435
|
-
detailTabs: feature.detailTabs ?? [],
|
|
373
|
+
entry: feature.entry,
|
|
2436
374
|
hostActions: feature.hostActions ?? [],
|
|
2437
375
|
modelSelector: feature.modelSelector ?? null,
|
|
2438
376
|
pageScript: feature.pageScript ? {
|
|
@@ -2451,8 +389,6 @@ export function createInjectionSource(features, {
|
|
|
2451
389
|
sessionId: runtimeSessionId,
|
|
2452
390
|
bindingName,
|
|
2453
391
|
bindingToken,
|
|
2454
|
-
summaryLayout: RESPONSIVE_SUMMARY_LAYOUT,
|
|
2455
|
-
rightPanelRouteKinds: RIGHT_PANEL_ROUTE_KINDS,
|
|
2456
392
|
themeTokens: CODEX_THEME_TOKENS,
|
|
2457
393
|
themeTokenAliases: CODEX_THEME_TOKEN_ALIASES,
|
|
2458
394
|
themeBindings: MINECODEX_THEME_BINDINGS,
|