minecodex 2.0.5 → 2.0.7
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/DESIGN.md +19 -0
- package/README.md +5 -1
- package/apps/dashboard/DESIGN.md +53 -31
- package/apps/dashboard/app.js +55 -39
- package/apps/dashboard/assets/OFL.txt +93 -0
- package/apps/dashboard/assets/info.svg +15 -0
- package/apps/dashboard/assets/moon.svg +13 -0
- package/apps/dashboard/assets/refresh-cw.svg +16 -0
- package/apps/dashboard/assets/silkscreen.ttf +0 -0
- package/apps/dashboard/assets/sun.svg +21 -0
- package/apps/dashboard/assets/terrain.js +89 -0
- package/apps/dashboard/assets/triangle-alert.svg +15 -0
- package/apps/dashboard/favicon.svg +12 -17
- package/apps/dashboard/index.html +24 -18
- package/apps/dashboard/styles.css +147 -181
- package/assets/brand/README.md +19 -0
- package/assets/brand/minecodex-icon-dark.svg +9 -0
- package/assets/brand/minecodex-icon-light.svg +9 -0
- package/assets/brand/minecodex-icon.svg +8 -0
- package/assets/brand/references/lucide-LICENSE.txt +43 -0
- package/assets/readme/hero.svg +93 -0
- package/features/hide-upsell-banner/README.md +7 -5
- package/features/hide-upsell-banner/codex-feature.json +2 -2
- package/features/hide-upsell-banner/src/hide-upsell-banner.mjs +30 -12
- package/features/images/DESIGN.md +18 -3
- package/features/images/README.md +4 -3
- package/features/images/codex-feature.json +2 -2
- package/features/images/src/adapters/codex/native-image-modules.mjs +220 -0
- package/features/images/src/adapters/codex/native-image-source.mjs +113 -198
- package/features/images/src/assets/prompts-menu.svg +15 -0
- package/features/images/src/frontend/cloud-image-actions.mjs +8 -6
- package/features/images/src/frontend/local-image-source.mjs +2 -1
- package/features/images/src/library-prompt-page.mjs +10 -4
- package/features/images/src/library-prompt-source.mjs +4 -5
- package/features/model-slider/codex-feature.json +2 -2
- package/features/notes/DESIGN.md +1 -1
- package/features/notes/codex-feature.json +2 -2
- package/features/notes/src/frontend/entry.mjs +1 -1
- package/features/session-status/codex-feature.json +2 -2
- package/features/sidebar-focus/DESIGN.md +4 -4
- package/features/sidebar-focus/codex-feature.json +3 -2
- package/features/sidebar-focus/src/adapters/codex/renderer.mjs +1 -1
- package/features/sidebar-focus/src/sidebar-focus.mjs +9 -7
- package/features/sites/DESIGN.md +25 -3
- package/features/sites/README.md +6 -2
- package/features/sites/codex-feature.json +3 -2
- package/features/sites/src/adapters/codex/renderer.mjs +25 -4
- package/features/sites/src/backend/project-sites.mjs +18 -6
- package/features/sites/src/backend/sites-service.mjs +22 -10
- package/features/sites/src/domain/discovery-candidates.mjs +53 -0
- package/features/sites/src/frontend/entry.mjs +76 -38
- package/features/sites/src/frontend/styles.css +3 -3
- package/features/unread-inbox/DESIGN.md +24 -25
- package/features/unread-inbox/codex-feature.json +3 -2
- package/features/unread-inbox/src/adapters/codex/modules.mjs +52 -0
- package/features/unread-inbox/src/adapters/codex/renderer.mjs +90 -0
- package/features/unread-inbox/src/domain/notifications.mjs +21 -0
- package/features/unread-inbox/src/injection-source.mjs +5 -2
- package/features/unread-inbox/src/unread-inbox.mjs +212 -297
- package/features/usage-meter/codex-feature.json +2 -2
- package/package.json +11 -3
- package/packages/cli/native/MineCodex.app/Contents/Info.plist +4 -2
- package/packages/cli/native/MineCodex.app/Contents/MacOS/MineCodexServiceManager +0 -0
- package/packages/cli/native/MineCodex.app/Contents/Resources/MineCodex.icns +0 -0
- package/packages/cli/native/MineCodex.app/Contents/_CodeSignature/CodeResources +11 -0
- package/packages/cli/native/README.md +31 -0
- package/packages/cli/src/browser-control-panel.mjs +48 -0
- package/packages/cli/src/commands.mjs +3 -1
- package/packages/cli/src/control-server.mjs +6 -3
- package/packages/cli/src/platform.mjs +5 -0
- package/packages/runtime-host/README.md +10 -5
- package/packages/runtime-host/src/codex-injection.mjs +12 -3
- package/packages/runtime-host/src/codex-runtime.mjs +10 -0
- package/packages/ui/DESIGN.md +5 -1
- package/packages/ui/assets/arrow-up-right.svg +13 -0
- package/packages/ui/assets/lucide-LICENSE.txt +43 -0
- package/packages/ui/assets/minecodex-menu-16.svg +7 -0
- package/packages/ui/assets/minecodex-menu-20.svg +7 -0
- package/packages/ui/src/components/account-menu.mjs +88 -0
- package/packages/ui/src/components/page.mjs +2 -2
- package/packages/ui/src/foundation/layers.mjs +1 -0
- package/packages/ui/src/install.mjs +3 -0
- package/packages/ui/src/native/account-menu.mjs +21 -0
- package/packages/ui/src/native/page.mjs +17 -2
- package/packages/ui/src/native/tokens.mjs +1 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lucide Icons and Contributors
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
The following Lucide icons are derived from the Feather project:
|
|
20
|
+
|
|
21
|
+
airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out
|
|
22
|
+
|
|
23
|
+
The MIT License (MIT) (for the icons listed above)
|
|
24
|
+
|
|
25
|
+
Copyright (c) 2013-present Cole Bemis
|
|
26
|
+
|
|
27
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
28
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
29
|
+
in the Software without restriction, including without limitation the rights
|
|
30
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
31
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
32
|
+
furnished to do so, subject to the following conditions:
|
|
33
|
+
|
|
34
|
+
The above copyright notice and this permission notice shall be included in all
|
|
35
|
+
copies or substantial portions of the Software.
|
|
36
|
+
|
|
37
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
38
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
39
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
40
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
41
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
42
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
43
|
+
SOFTWARE.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg"
|
|
2
|
+
width="1200" height="400" viewBox="0 0 1200 400"
|
|
3
|
+
role="img" aria-labelledby="hero-title hero-desc">
|
|
4
|
+
<title id="hero-title">MineCodex</title>
|
|
5
|
+
<desc id="hero-desc">MineCodex 给 Codex Desktop 增加轻量、本地优先的插件:模型滑条、笔记与图片库。</desc>
|
|
6
|
+
|
|
7
|
+
<defs>
|
|
8
|
+
<style>
|
|
9
|
+
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
|
|
10
|
+
.sans { font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", "Segoe UI", "PingFang SC", "Hiragino Sans GB", sans-serif; }
|
|
11
|
+
</style>
|
|
12
|
+
<filter id="card-shadow" x="-8%" y="-8%" width="116%" height="120%">
|
|
13
|
+
<feDropShadow dx="0" dy="6" stdDeviation="12" flood-color="#000000" flood-opacity="0.04"/>
|
|
14
|
+
<feDropShadow dx="0" dy="1" stdDeviation="2" flood-color="#000000" flood-opacity="0.03"/>
|
|
15
|
+
</filter>
|
|
16
|
+
<filter id="terminal-shadow" x="-4%" y="-8%" width="108%" height="124%">
|
|
17
|
+
<feDropShadow dx="0" dy="2" stdDeviation="4" flood-color="#000000" flood-opacity="0.03"/>
|
|
18
|
+
</filter>
|
|
19
|
+
</defs>
|
|
20
|
+
|
|
21
|
+
<!-- Page background: Codex light theme surface -->
|
|
22
|
+
<rect width="1200" height="400" rx="24" fill="#f7f7f7"/>
|
|
23
|
+
|
|
24
|
+
<!-- Left: Title block -->
|
|
25
|
+
<g transform="translate(64 84)">
|
|
26
|
+
<text class="mono" x="0" y="14" font-size="13" font-weight="600" letter-spacing="2.5" fill="#0169cc">CODEX DESKTOP PLUGINS · macOS</text>
|
|
27
|
+
<!-- MineCodex brand start -->
|
|
28
|
+
<g transform="translate(0 23) scale(2.333333)" color="#0d0d0d" fill="none">
|
|
29
|
+
<path id="codex-outline" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M9.3316 2.03768C11.1644 1.54688 13.0268 2.04668 14.356 3.20174C16.0859 2.86512 17.9511 3.36418 19.2935 4.70663C20.6356 6.04882 21.1326 7.91288 20.7964 9.64217C21.9527 10.9716 22.4534 12.8349 21.9625 14.6685C21.4711 16.5025 20.1051 17.8657 18.439 18.439C17.8658 20.1047 16.503 21.47 14.6695 21.9615C12.8356 22.4528 10.9727 21.9519 9.64312 20.7955C7.91371 21.1319 6.04993 20.635 4.70757 19.2926C3.36558 17.9505 2.86659 16.0866 3.20269 14.357C2.04693 13.0273 1.54757 11.1638 2.03863 9.33065C2.52997 7.49759 3.89436 6.13282 5.56011 5.55917C6.13382 3.8935 7.49837 2.52891 9.3316 2.03768ZM13.2623 4.63534C12.3606 3.80149 11.067 3.4361 9.79742 3.77596C8.52745 4.11626 7.58924 5.0796 7.22515 6.25253C7.08145 6.71575 6.71753 7.07928 6.25445 7.22323C5.08133 7.58702 4.11844 8.52622 3.77789 9.79647C3.43759 11.0667 3.80148 12.3606 4.63531 13.2623C4.96467 13.6184 5.09776 14.1154 4.99078 14.5885C4.71939 15.7864 5.05109 17.0891 5.98101 18.0191C6.91085 18.949 8.21345 19.2801 9.41168 19.0084L9.58941 18.9791C9.94695 18.9432 10.3065 19.0397 10.5982 19.2496L10.7378 19.3639L10.9117 19.5142C11.8015 20.2407 13.0127 20.5413 14.2037 20.2223C15.4733 19.8818 16.4111 18.9191 16.775 17.7457C16.9189 17.2824 17.2832 16.9186 17.7466 16.775L17.9634 16.6998C19.0376 16.2923 19.9051 15.3936 20.2242 14.2027C20.5641 12.9328 20.1989 11.6385 19.3648 10.7369C19.0353 10.3806 18.9021 9.88399 19.0093 9.41073L19.0533 9.18514C19.2372 8.0514 18.8917 6.85176 18.0201 5.98006C17.0901 5.05003 15.7865 4.71826 14.5884 4.98983C14.1153 5.09712 13.6186 4.96467 13.2623 4.63534Z"/>
|
|
30
|
+
<g id="pickaxe" transform="translate(6.24 6.24) scale(.48)" fill="currentColor">
|
|
31
|
+
<path d="M15.686 4.314 16.3 3.7a1 1 0 0 1 1.4 0l2.6 2.6a1 1 0 0 1 0 1.4l-.614.614a12.501 12.501 0 0 1 1.356 10.225 1 1 0 0 1-1.751-.119 22 22 0 0 0-3.393-6.319l-.198.199a1 1 0 0 1-1.4 0l-.219.169L6.619 20.38a1 1 0 1 1-3-3l7.912-7.912.169.232a1 1 0 0 1 0-1.4l.198-.197A22 22 0 0 0 5.58 4.71a1 1 0 0 1-.119-1.752 12.5 12.5 0 0 1 10.225 1.356Z"/>
|
|
32
|
+
</g>
|
|
33
|
+
</g>
|
|
34
|
+
<!-- MineCodex brand end -->
|
|
35
|
+
<text class="sans" x="72" y="68" font-size="56" font-weight="700" letter-spacing="-0.5" fill="#0d0d0d">MineCodex</text>
|
|
36
|
+
<text class="sans" x="0" y="112" font-size="21" font-weight="400" fill="rgba(13,13,13,0.68)">给 Codex Desktop 增加轻量、本地优先的插件</text>
|
|
37
|
+
|
|
38
|
+
<!-- Install command pill -->
|
|
39
|
+
<g transform="translate(0 148)" filter="url(#terminal-shadow)">
|
|
40
|
+
<rect x="0" y="0" width="432" height="52" rx="14" fill="#ffffff" stroke="rgba(13,13,13,0.1)"/>
|
|
41
|
+
<circle cx="24" cy="26" r="4.5" fill="rgba(13,13,13,0.28)"/>
|
|
42
|
+
<text class="mono" x="42" y="32" font-size="17" font-weight="500" fill="#0d0d0d">npm install -g minecodex</text>
|
|
43
|
+
</g>
|
|
44
|
+
|
|
45
|
+
<text class="mono" x="0" y="234" font-size="14" fill="rgba(13,13,13,0.45)">Node 22.5+ · npm 包 · MIT License</text>
|
|
46
|
+
</g>
|
|
47
|
+
|
|
48
|
+
<!-- Right: Three capability cards -->
|
|
49
|
+
<g transform="translate(572 74)" filter="url(#card-shadow)">
|
|
50
|
+
<!-- Card 1: Model Slider -->
|
|
51
|
+
<g transform="translate(0 0)">
|
|
52
|
+
<rect x="0" y="0" width="172" height="252" rx="20" fill="#ffffff" stroke="rgba(13,13,13,0.09)"/>
|
|
53
|
+
<!-- Icon well -->
|
|
54
|
+
<rect x="50" y="28" width="72" height="72" rx="20" fill="#0d0d0d"/>
|
|
55
|
+
<g transform="translate(69.2 47.2) scale(1.4)" fill="none" stroke="#ffffff" stroke-width="1.43" stroke-linecap="round" stroke-linejoin="round">
|
|
56
|
+
<path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/>
|
|
57
|
+
<path d="M20 2v4"/>
|
|
58
|
+
<path d="M22 4h-4"/>
|
|
59
|
+
<circle cx="4" cy="20" r="2"/>
|
|
60
|
+
</g>
|
|
61
|
+
<text class="sans" x="86" y="146" font-size="19" font-weight="600" text-anchor="middle" fill="#0d0d0d">Model Slider</text>
|
|
62
|
+
<text class="sans" x="86" y="176" font-size="14" text-anchor="middle" fill="rgba(13,13,13,0.68)">更清晰顺滑的模型选择</text>
|
|
63
|
+
<text class="mono" x="86" y="212" font-size="12" letter-spacing="1.5" text-anchor="middle" fill="rgba(13,13,13,0.45)">MODEL PICKER</text>
|
|
64
|
+
</g>
|
|
65
|
+
|
|
66
|
+
<!-- Card 2: Notes -->
|
|
67
|
+
<g transform="translate(196 0)">
|
|
68
|
+
<rect x="0" y="0" width="172" height="252" rx="20" fill="#ffffff" stroke="rgba(13,13,13,0.09)"/>
|
|
69
|
+
<rect x="50" y="28" width="72" height="72" rx="20" fill="#0d0d0d"/>
|
|
70
|
+
<g transform="translate(68 46) scale(1.5)" fill="none" stroke="#ffffff" stroke-width="1.333" stroke-linecap="round" stroke-linejoin="round">
|
|
71
|
+
<path d="M21 9a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 15 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2z"/>
|
|
72
|
+
<path d="M15 3v5a1 1 0 0 0 1 1h5"/>
|
|
73
|
+
</g>
|
|
74
|
+
<text class="sans" x="86" y="146" font-size="19" font-weight="600" text-anchor="middle" fill="#0d0d0d">Notes</text>
|
|
75
|
+
<text class="sans" x="86" y="176" font-size="14" text-anchor="middle" fill="rgba(13,13,13,0.68)">跨会话管理待办与提示词</text>
|
|
76
|
+
<text class="mono" x="86" y="212" font-size="12" letter-spacing="1.5" text-anchor="middle" fill="rgba(13,13,13,0.45)">TOOLBAR</text>
|
|
77
|
+
</g>
|
|
78
|
+
|
|
79
|
+
<!-- Card 3: Images -->
|
|
80
|
+
<g transform="translate(392 0)">
|
|
81
|
+
<rect x="0" y="0" width="172" height="252" rx="20" fill="#ffffff" stroke="rgba(13,13,13,0.09)"/>
|
|
82
|
+
<rect x="50" y="28" width="72" height="72" rx="20" fill="#0d0d0d"/>
|
|
83
|
+
<g transform="translate(68 46) scale(1.5)" fill="none" stroke="#ffffff" stroke-width="1.333" stroke-linecap="round" stroke-linejoin="round">
|
|
84
|
+
<rect x="3" y="3" width="18" height="18" rx="2"/>
|
|
85
|
+
<circle cx="9" cy="9" r="2"/>
|
|
86
|
+
<path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/>
|
|
87
|
+
</g>
|
|
88
|
+
<text class="sans" x="86" y="146" font-size="19" font-weight="600" text-anchor="middle" fill="#0d0d0d">Images</text>
|
|
89
|
+
<text class="sans" x="86" y="176" font-size="14" text-anchor="middle" fill="rgba(13,13,13,0.68)">集中浏览生成图片与来源</text>
|
|
90
|
+
<text class="mono" x="86" y="212" font-size="12" letter-spacing="1.5" text-anchor="middle" fill="rgba(13,13,13,0.45)">LIBRARY</text>
|
|
91
|
+
</g>
|
|
92
|
+
</g>
|
|
93
|
+
</svg>
|
|
@@ -9,10 +9,10 @@ Toolbar 入口,由 `CodexRuntimeHost` 在渲染进程内注入一段脚本。
|
|
|
9
9
|
功能范围:
|
|
10
10
|
|
|
11
11
|
- 仅隐藏横幅的 GUI 显示(`aside` 卡片,`display: none`);
|
|
12
|
-
-
|
|
12
|
+
- 以 `aside` 和中英文 Codex 额度耗尽标题识别;通用标题 `You’re out of usage`(也支持直引号)还须带原生用量横幅关闭按钮,避免误隐藏普通通知;不依赖卡片 class 或购买按钮文案;
|
|
13
13
|
- 不拦截、不篡改额度查询接口(`account/rateLimits/read` 与 app-server
|
|
14
14
|
JSON-RPC 流量保持原样);
|
|
15
|
-
-
|
|
15
|
+
- 横幅样式被重新渲染覆盖后,由 `MutationObserver` 重新隐藏;只在样式变化时写入,避免观察器循环。节点改作普通通知或离开文档时释放隐藏状态;
|
|
16
16
|
- 注入运行时随 RuntimeHost 卸载自动清理(恢复元素样式、断开 observer)。
|
|
17
17
|
|
|
18
18
|
## 验证
|
|
@@ -23,6 +23,8 @@ npm test
|
|
|
23
23
|
|
|
24
24
|
## 适配说明
|
|
25
25
|
|
|
26
|
-
这是非官方 CDP / DOM
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
这是非官方 CDP / DOM 适配。横幅识别与隐藏逻辑由本功能的 page-script 维护,Runtime Host 负责注入和卸载。
|
|
27
|
+
|
|
28
|
+
Codex 26.930.21537(2026-10-02 原生源码核对):默认横幅和 recovery 分支均使用 `aside` / `h3`,标题来自后端 `banner.title`。通用标题目前仅在同时具有 `Dismiss usage banner`、`不再显示用量横幅`、`關閉用量橫幅` 或 `關閉用量橫額` 关闭语义时隐藏;没有该控件或其他未知标题时保守保留。
|
|
29
|
+
|
|
30
|
+
行为测试覆盖标题边界、动态内容、重复隐藏、节点复用及卸载后的排队扫描。隔离浏览器 DOM 已验证隐藏和样式覆盖后的恢复;真实账户额度耗尽横幅尚未做端到端验收。
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
"zh-CN": "隐藏用量横幅"
|
|
8
8
|
},
|
|
9
9
|
"description": {
|
|
10
|
-
"en": "Hide
|
|
11
|
-
"zh-CN": "
|
|
10
|
+
"en": "Hide usage upsell banners above the composer.",
|
|
11
|
+
"zh-CN": "隐藏输入框上方的额度推广横幅。"
|
|
12
12
|
},
|
|
13
13
|
"entry": {
|
|
14
14
|
"kind": "page-script",
|
|
@@ -8,29 +8,49 @@
|
|
|
8
8
|
// touches the rate-limit query path: `account/rateLimits/read` and the
|
|
9
9
|
// app-server JSON-RPC traffic stay intact.
|
|
10
10
|
const HIDDEN_MARKER = "data-codex-upsell-banner-hidden";
|
|
11
|
+
const hiddenBanners = new Map();
|
|
12
|
+
let disposed = false;
|
|
11
13
|
|
|
12
14
|
function isUpsellBanner(element) {
|
|
13
15
|
if (!(element instanceof window.HTMLElement)) return false;
|
|
14
|
-
const text = (element.
|
|
16
|
+
const text = (element.textContent ?? "").trim();
|
|
15
17
|
if (!text) return false;
|
|
16
18
|
const title = (
|
|
17
|
-
element.querySelector("h1, h2, h3, [role='heading']")?.
|
|
19
|
+
element.querySelector("h1, h2, h3, [role='heading']")?.textContent
|
|
18
20
|
?? text.split("\n", 1)[0]
|
|
19
21
|
).trim();
|
|
20
22
|
return /out of Codex(?: and Work)? usage/i.test(title)
|
|
21
23
|
|| /out of Codex messages/i.test(title)
|
|
22
24
|
|| /Codex (?:message )?usage limit reached/i.test(title)
|
|
23
25
|
|| /Codex.*(?:用量|使用量|使用额度|额度|消息).*(?:用完|用尽|耗尽|已达|达到.*上限|上限)/i.test(title)
|
|
24
|
-
|| /(?:已达到|达到|已用完|已用尽|耗尽).{0,20}Codex.{0,20}(?:用量|使用量|额度|消息|上限)/i.test(title)
|
|
26
|
+
|| /(?:已达到|达到|已用完|已用尽|耗尽).{0,20}Codex.{0,20}(?:用量|使用量|额度|消息|上限)/i.test(title)
|
|
27
|
+
// 通用标题须同时具有原生用量横幅的关闭语义,不能仅按耗尽文案隐藏通知。
|
|
28
|
+
|| (/^you['’]re out of usage[.!]?$/i.test(title)
|
|
29
|
+
&& [...element.querySelectorAll("button[aria-label]")].some(button =>
|
|
30
|
+
/^(?:Dismiss usage banner|不再显示用量横幅|關閉用量橫幅|關閉用量橫額)$/i.test(button.getAttribute("aria-label") ?? "")));
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
function hideBanner(element) {
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
if (!hiddenBanners.has(element)) hiddenBanners.set(element, {
|
|
35
|
+
display:element.style.getPropertyValue("display"), priority:element.style.getPropertyPriority("display"),
|
|
36
|
+
});
|
|
37
|
+
if (!element.hasAttribute(HIDDEN_MARKER)) element.setAttribute(HIDDEN_MARKER, "");
|
|
38
|
+
// 标记不代表样式仍有效;只在需要时写入,避免 observer 自触发循环。
|
|
39
|
+
if (element.style.display !== "none") element.style.display = "none";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function restoreBanner(element) {
|
|
43
|
+
const original = hiddenBanners.get(element);
|
|
44
|
+
element.removeAttribute(HIDDEN_MARKER);
|
|
45
|
+
if (element.style.display === "none") {
|
|
46
|
+
if (original.display) element.style.setProperty("display", original.display, original.priority);
|
|
47
|
+
else element.style.removeProperty("display");
|
|
48
|
+
}
|
|
49
|
+
hiddenBanners.delete(element);
|
|
31
50
|
}
|
|
32
51
|
|
|
33
52
|
function hideVisibleBanners(root = document) {
|
|
53
|
+
for (const element of hiddenBanners.keys()) if (!element.isConnected || !isUpsellBanner(element)) restoreBanner(element);
|
|
34
54
|
if (root.matches?.("aside") && isUpsellBanner(root)) hideBanner(root);
|
|
35
55
|
for (const element of root.querySelectorAll("aside")) {
|
|
36
56
|
if (isUpsellBanner(element)) hideBanner(element);
|
|
@@ -47,7 +67,7 @@ function rescanBanners() {
|
|
|
47
67
|
rescanQueued = true;
|
|
48
68
|
queueMicrotask(() => {
|
|
49
69
|
rescanQueued = false;
|
|
50
|
-
hideVisibleBanners(document);
|
|
70
|
+
if (!disposed) hideVisibleBanners(document);
|
|
51
71
|
});
|
|
52
72
|
}
|
|
53
73
|
|
|
@@ -59,14 +79,12 @@ observer.observe(document.documentElement, {
|
|
|
59
79
|
childList: true,
|
|
60
80
|
subtree: true,
|
|
61
81
|
attributes: true,
|
|
62
|
-
attributeFilter: ["style", "class", "hidden"],
|
|
82
|
+
attributeFilter: ["style", "class", "hidden", "aria-label"],
|
|
63
83
|
characterData: true,
|
|
64
84
|
});
|
|
65
85
|
|
|
66
86
|
return () => {
|
|
87
|
+
disposed = true;
|
|
67
88
|
observer.disconnect();
|
|
68
|
-
for (const element of
|
|
69
|
-
element.removeAttribute(HIDDEN_MARKER);
|
|
70
|
-
element.style.display = "";
|
|
71
|
-
}
|
|
89
|
+
for (const element of hiddenBanners.keys()) restoreBanner(element);
|
|
72
90
|
};
|
|
@@ -12,6 +12,8 @@ hover sidebars and the native full-page shell remain owned by Codex.
|
|
|
12
12
|
|
|
13
13
|
## Image Gen
|
|
14
14
|
|
|
15
|
+
全屏图片查看器使用原生 Canvas 的 `presentation:"overlay"`,由外层 CanvasLayout 的 `bg-surface` 提供背景。Codex 26.930.21537(2026-10-03 源码与 live DOM):`dialog` / `panel` 分支会在内部画布叠加 `bg-surface-secondary`(当前浅色 #f7f7f7),不能用于这里的全屏画布。保留 standalone、原生 viewer header、底部编辑工具栏和 Composer 接入。
|
|
16
|
+
|
|
15
17
|
Codex 26.930.21537(2026-10-02 隔离实测):通过原生 `variant="browse"` 的 Image sources 契约接入。保留原生标签节点及路由,在插件拥有的 React root 中复用同一个原生 Tabs 组件,追加 Image Gen;退出时恢复原节点。原生上下文由 Adapter 从当前已提交的组件祖先读取,业务层不遍历 Fiber。
|
|
16
18
|
|
|
17
19
|
本地来源复用 ImageLibrary、ThreadMetadataIndex 及文件监听,索引存放在当前 runtime profile;按生成时间倒序,每页 36 张,滚动增量加载,搜索复用原生输入框。稳定身份为 threadId + imageGenerationItemId,不猜测云端 ID 与本地文件的关系。
|
|
@@ -20,9 +22,13 @@ Codex 26.930.21537(2026-10-02 隔离实测):通过原生 `variant="browse"
|
|
|
20
22
|
|
|
21
23
|
点击图片进入原生 Library 主内容 viewport 内的图片画布,保留 navbar、Space 二级侧栏和窗口栏;不打开 side panel,也不使用覆盖整窗的 Dialog。画布、缩放、下载、打开、Markup、Comment、Remove BG、Erase、Resize 和底部 “Ask about this file” 输入框复用原生组件。底部直接使用画布的原生 `renderComposer` / overlay 承载,沿用底部留白、居中、避让和工具状态显隐;不得另建 absolute 底部输入框。进入 Comment 时隐藏大输入框,退出时恢复;原生拖拽工具栏保留位置记忆及 Home 居中重置。初始缩放与每张图的 Zoom to fit 使用原生 `zoom` 契约,`manualZoomPercent` 初始为 `null`,不自算比例,也不继承上一张图的手动缩放。关闭回到原列表并恢复交互,标注中的关闭交由原生编辑器处理。
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
2026-10-03 跟进原生文件预览:底部复用当前 File Composer 的本地任务分支,宽度 `min(48rem, calc(100vw - 2rem))`、`auto-single-line`、`surfaceVariant:"inline"`、隐藏 utility bar 和运行位置控件,输入布局、模型控件、权限、语音及发送按钮均由原生组件决定,不再固定旧 Chat 的单行组件。画布在主内容 viewport 内,因此只在此画布范围内去掉原生 overlay header 容器的窗口交通灯左右避让,保留原生 header 自身 8px inset。背景、缩放、Open、下载、关闭、菜单及编辑状态使用原生 Canvas;按当前产品要求保留黑色编辑行动条。底部预留区通过 ResizeObserver 读取实际 File Composer 高度,加原生 `spacing × 4` 底部 inset;不沿用原生旧输入框的 118px 最小预留。行动条默认位置与拖拽下边界共用此画布尺寸,输入框展开、收起或隐藏时同步更新;默认紧凑输入框 44px 时预留为 60px,行动条与输入框间距为 16px。
|
|
26
|
+
|
|
27
|
+
本地来源以真实路径、来源 thread 和生成项 ID 接入;底部输入框首次发送时通过原生文件服务准备本地临时附件,不上传云端、不伪造 Library 身份。关闭或切换账号后晚到的附件会释放;提交接受后沿用原生本地会话导航。黑色行动条的生成编辑仍在明确提交编辑时通过原生上传准备临时附件,同一画布复用编辑附件,查看不上传到 Library。附件使用原生返回的 `id`;不得把本地 ID 伪装为 cloud file ID。生成编辑调用原生提交能力,接受后沿用原生会话导航。View prompts 通过原生菜单扩展进入已有的 File info 风格面板。以上组件契约已按 Codex 26.930.21537 的产物与隔离窗口检查;实际云端生成结果须单独验证。
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
发送准备只在用户点击发送后进行,不在开始输入时预准备;本地字节转换直接填充 typed array,避免大图片经字符串迭代器生成中间数组。该优化只减少本地准备成本,不代表上传或原生提交网络等待已经缩短。
|
|
30
|
+
|
|
31
|
+
图库菜单复用原生 mediaOverlay More Button、Dropdown、Menu.Item 与原生图标,按 Chat about this、View prompts、Add to Favorites/Remove from Favorites、Download、Add to folder、Delete 排列。View prompts 的图标约束为菜单使用的 16px,不继承导航栏尺寸。项目现有 Lucide book-open 的合并路径图形随 Images 注入,资源位于 `src/assets/prompts-menu.svg`;图库 More、大图菜单和云端菜单共用此资源,不从 Prompts 导航节点临时取图,也不依赖 Notes 功能启用。
|
|
26
32
|
|
|
27
33
|
只有用户点击收藏或文件夹操作时,通过原生 Library upload(required persistence)上传所选 PNG;取得真实 Library ID 后保存账号、用户、本地图片 ID、SHA-256 内容指纹对应关系。收藏调用原生 setLibraryFavorite 并以服务器状态切换,文件夹使用原生 MoveLibraryItemsDialog;不在本地保存收藏或文件夹关系。云端节点读取采用原生 `/files/library/files/{library_file_id}/node` 契约,不能把上传返回的 Library ID 当普通内容 file ID。
|
|
28
34
|
|
|
@@ -122,7 +128,7 @@ traceable request with its distinct provenance. A valid generated image without
|
|
|
122
128
|
|
|
123
129
|
Image Gen 按已索引的精确图片 ID 提取对应 thread 的生成记录。已完成 `image_gen.generation` 的 revisedPrompt 优先于可静态解析的工具请求参数。没有真实生成提示词则明确显示不可用。云端图片保留 generation → 已标明来源的 Original request 回退;已移除没有实测命中的云端 gen_id → 本地文件猜测补充链路。
|
|
124
130
|
|
|
125
|
-
2026-10-02 菜单核对:网格 More
|
|
131
|
+
2026-10-02 菜单核对:网格 More 使用原生 Library 的 mediaOverlay 分支,操作通过 leftIcon 传入真实原生图标。菜单按 Chat about this、View prompts、Add to Favorites/Remove from Favorites、Download、Add to folder、Delete 排列。收藏和文件夹使用真实云端 Library 身份;Delete 仅持久隐藏 Image Gen 条目。Chat about this 组合原生模式切换、新 Chat 草稿导航与图片面板链路,不发送消息;图片随消息提交的实际链路未进行发送验收。生成图片通用菜单中的重复查看、新标签、复制和 Finder 不混入图库菜单。Image Gen 不提供 Rename 与 Share。
|
|
126
132
|
|
|
127
133
|
|
|
128
134
|
## Image Gen 多选与隐藏
|
|
@@ -134,3 +140,12 @@ Codex 26.930.21537(2026-10-02):卡片通过原生 Card 的 selection、sel
|
|
|
134
140
|
动作条复用原生 Button、Dropdown、Menu、图标与 Library 布局分支,固定在底部,dark 材质,包含数量、Start chat、Download、Delete、More→Move 和 Clear selection。Start chat 调用原生 Chat 附件上传和草稿导航,不发送消息;Download 使用原生保存流程逐项处理;Move 在明确点击后登记所选图片并使用原生文件夹选择器,选择本身不上传。权限/忙状态属于当前操作,不伪造云端图片身份。
|
|
135
141
|
|
|
136
142
|
Delete 在单张 More 与批量动作条共用一条隐藏流程,复用原生 Dialog、LibraryItemConfirmationContent 和实色警戒确认按钮。确认标题明确限定 Image Gen,正文明确“仅隐藏,本地原图和云端副本保留”。取消不改变任何条目;确认成功后立即移除当前条目、更新选中数、列表总数和分页偏移,并显示原生 toast。隐藏 ID 存于当前 Runtime profile 的 `hidden-image-ids.json`,本地源文件、原始生成提示词及已登记云端身份不修改;刷新和重启后继续隐藏。
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
## 维护接口(2026-10-02)
|
|
146
|
+
|
|
147
|
+
`adapters/codex/native-image-modules.mjs` 拥有构建产物读取、import 引用与组件发现。导航、图库、画布分别连接,画布与 Composer 在用户打开图片时加载;画布契约失效通过操作反馈报告,不移除仍可用的图库。Composer 角色按文件预览实际调用关系解析,不固定压缩变量名。
|
|
148
|
+
|
|
149
|
+
`adapters/codex/native-image-source.mjs` 拥有 React 呈现、上下文、原生动作与 UI 生命周期。异步结果进入对话框、收藏状态或原生修改动作前核对挂载实例及账号;成功上传的云端身份仍须登记,不能因页面关闭丢失已完成上传。普通 workflow 只接收/返回 `libraryFileId` 与 `created`,原生文件节点和 MoveDialog entries 由 Adapter 构建。
|
|
150
|
+
|
|
151
|
+
`library-prompt-source.mjs` 只序列化显式依赖;`library-prompt-page.mjs` 直接安装本地来源并组合清理,不通过普通语句的文本替换接线。清理为幂等操作,手动卸载与 signal abort 不重复释放。
|
|
@@ -17,8 +17,9 @@ Images 在 Codex 原生 **Space → Images** 中增加 **Image Gen**,集中浏
|
|
|
17
17
|
统一宿主通过 `codex-feature.json` 与 `src/library-prompt-source.mjs` 组装页面脚本。
|
|
18
18
|
|
|
19
19
|
- `src/backend/local-image-source.mjs`:本地索引、提示词与云端身份映射/上传预留。
|
|
20
|
-
- `src/frontend/cloud-image-actions.mjs
|
|
21
|
-
- `src/adapters/codex
|
|
20
|
+
- `src/frontend/cloud-image-actions.mjs`:首次云端登记、去重与收藏工作流;只返回云端身份,不解释原生文件节点。
|
|
21
|
+
- `src/adapters/codex/native-image-modules.mjs`:原生构建产物发现,按导航/图库/画布能力分别连接。
|
|
22
|
+
- `src/adapters/codex/native-image-source.mjs`:原生呈现、上下文及异步动作归属。其余 Adapter 维护 Library API、图片来源与提示词协议。
|
|
22
23
|
- `src/frontend/local-image-source.mjs`:来源选择、搜索、分页和生命周期。
|
|
23
24
|
- Runtime Host:鉴权、功能声明校验与转发。
|
|
24
25
|
|
|
@@ -30,7 +31,7 @@ node --test features/images/test/*.test.mjs
|
|
|
30
31
|
|
|
31
32
|
独立 `web/` 与旧服务入口保留为已有历史工具,不由当前 manifest 启动;ImageLibrary 与日志索引已由当前本地来源复用。历史图库交接见 [research/images](../../docs/research/images/2026-08-07-gallery-handoff.md)。
|
|
32
33
|
|
|
33
|
-
2026-10-02 菜单核对:网格 More 明确使用原生 Library 的 mediaOverlay 分支,操作通过 leftIcon 传入真实原生图标。图库菜单按 Chat about this、Add to Favorites、Download、Add to folder、Delete
|
|
34
|
+
2026-10-02 菜单核对:网格 More 明确使用原生 Library 的 mediaOverlay 分支,操作通过 leftIcon 传入真实原生图标。图库菜单按 Chat about this、View prompts、Add to Favorites/Remove from Favorites、Download、Add to folder、Delete 排列。Chat about this 组合原生模式切换、新 Chat 草稿导航与图片面板链路,不发送消息;导航与面板已隔离验证,图片随消息提交的实际链路未进行发送验收。生成图片通用菜单中的重复查看、新标签、复制和 Finder 不再混入图库菜单。Add to Favorites/Remove from Favorites 和 Add to folder 已接入原生云端 Library。首次操作上传所选图片,之后复用账号与内容指纹对应的云端身份;收藏与文件夹归属保存在服务器。取消文件夹选择会显示原生 toast,已上传的图片仍留在 Library。上传结果不确定时不盲目重试;Image Gen 不提供分享与重命名;Delete 的隐藏范围见下文。
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
Image Gen 支持原生圆形多选框、Shift 区间选择和底部动作条。Delete 会先二次确认,仅从 Image Gen 隐藏;本地原图和云端副本保留。隐藏状态由 MineCodex 当前 Runtime profile 持久维护,确认后立即生效,刷新/重启不再显示。缩略卡片沿用 Library 的 1:2 至 20:13 比例范围;极宽和极窄图片在卡片内完整缩放、居中留白,不裁切内容。
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
"zh-CN": "图片"
|
|
8
8
|
},
|
|
9
9
|
"description": {
|
|
10
|
-
"en": "Browse
|
|
11
|
-
"zh-CN": "
|
|
10
|
+
"en": "Browse generated images and copy their prompts.",
|
|
11
|
+
"zh-CN": "浏览本地生成图片,查看与复制提示词。"
|
|
12
12
|
},
|
|
13
13
|
"icon": {
|
|
14
14
|
"viewBox": "0 0 24 24",
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// 按文件预览的调用关系读取角色,不依赖构建时分配的短变量名。
|
|
2
|
+
export function discoverFilePreviewComposerBindings(source) {
|
|
3
|
+
const starts = [...source.matchAll(/function [\w$]+\(/g)].map(match => match.index);
|
|
4
|
+
const bodies = starts.map((start,index) => source.slice(start,starts[index+1] ?? source.length));
|
|
5
|
+
const requireBody = marker => {
|
|
6
|
+
const found=bodies.filter(body=>marker.test(body));
|
|
7
|
+
if(found.length!==1)throw new Error("Native file preview Composer contract changed");
|
|
8
|
+
return found[0];
|
|
9
|
+
};
|
|
10
|
+
const scopeBody=requireBody(/draftId:[\w$]+,imageDataUrl:/);
|
|
11
|
+
const homeBody=requireBody(/homeComposerController:[\w$]+,homeComposerMode:/);
|
|
12
|
+
const composerBody=requireBody(/lockedLayout:`single-line`/);
|
|
13
|
+
const localBody=requireBody(/prepareLocalConversationAttachments:/);
|
|
14
|
+
const alias=(body,local,call=false)=>{
|
|
15
|
+
const escaped=local?.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");
|
|
16
|
+
const value=escaped&&body.match(new RegExp(`(?:[,;]|let )${escaped}=([\\w$]+)${call?"\\(":"(?=[,;])"}`))?.[1];
|
|
17
|
+
if(!value)throw new Error("Native file preview Composer role changed");
|
|
18
|
+
return value;
|
|
19
|
+
};
|
|
20
|
+
const controller=scopeBody.match(/composerController:([\w$]+)/)?.[1];
|
|
21
|
+
const scopes=[...scopeBody.matchAll(/jsx\)\(([\w$]+),\{scope:([\w$]+),value:([\w$]+)/g)];
|
|
22
|
+
if(scopes.length!==2)throw new Error("Native file preview Composer scopes changed");
|
|
23
|
+
const scopeProvider=alias(scopeBody,scopes[0][1]);
|
|
24
|
+
if(scopeProvider!==alias(scopeBody,scopes[1][1]))throw new Error("Native file preview Composer providers changed");
|
|
25
|
+
const roles={
|
|
26
|
+
useComposerController:alias(scopeBody,controller,true),
|
|
27
|
+
useHomeComposer:homeBody.match(/let [\w$]+=([\w$]+)\([\w$]+\),[\w$]+=[\w$]+\?\.composerProps/)?.[1],
|
|
28
|
+
Composer:composerBody.match(/jsx\)\(([\w$]+),\{\.\.\.[\w$]+,className:`mx-auto w-full max-w-2xl px-toolbar`/)?.[1],
|
|
29
|
+
ComposerSurface:composerBody.match(/jsx\)\(([\w$]+),\{placement:/)?.[1],
|
|
30
|
+
ScopeProvider:scopeProvider,
|
|
31
|
+
composerScope:alias(scopeBody,scopes[0][2]),
|
|
32
|
+
createComposerValue:alias(scopeBody,scopes[0][3],true),
|
|
33
|
+
composerRouteScope:alias(scopeBody,scopes[1][2]),
|
|
34
|
+
FileComposer:localBody.match(/jsx\)\(([\w$]+),\{\.\.\.[\w$]+,aboveComposerContent:/)?.[1],
|
|
35
|
+
fileComposerAvailability:localBody.match(/composerModeAvailability:([\w$]+)/)?.[1],
|
|
36
|
+
localAttachmentService:localBody.match(/await ([\w$]+)\([\w$]+,[\w$]+\)\.createFileAttachment/)?.[1],
|
|
37
|
+
deleteLocalAttachment:localBody.match(/aborted&&\(([\w$]+)\(/)?.[1],
|
|
38
|
+
useFileNavigate:alias(localBody,localBody.match(/([\w$]+)\(([\w$]+)\([\w$]+\),\{state:/)?.[1],true),
|
|
39
|
+
localThreadRoute:localBody.match(/[\w$]+\(([\w$]+)\([\w$]+\),\{state:/)?.[1],
|
|
40
|
+
};
|
|
41
|
+
if(Object.values(roles).some(value=>!value))throw new Error("Native file preview Composer role changed");
|
|
42
|
+
return roles;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 构建产物发现只返回当前能力;原生组件呈现不参与引用解析。
|
|
46
|
+
export function createNativeImageModules({read = async url => (await fetch(url)).text(), load = url => import(url)} = {}) {
|
|
47
|
+
let starting, connecting, canvasConnecting;
|
|
48
|
+
function imported(source, local) {
|
|
49
|
+
for (const match of source.matchAll(/import\{([^}]+)\}from["']([^"']+)["']/g)) {
|
|
50
|
+
for (const binding of match[1].split(",")) {
|
|
51
|
+
const parts = binding.trim().split(/\s+as\s+/);
|
|
52
|
+
if (parts.at(-1) === local) return { name: parts[0], file: match[2] };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
throw new Error(`Native Images import contract changed: ${local}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function bootstrap() {
|
|
59
|
+
if (!starting) starting = (async () => {
|
|
60
|
+
const urls = [...document.querySelectorAll('link[rel="modulepreload"],script[src]')].map(node => node.href || node.src);
|
|
61
|
+
const initialUrl = urls.find(url => /\/app-initial-[^/]+\.js/.test(url));
|
|
62
|
+
const mainUrl = urls.find(url => /\/app-main-[^/]+\.js/.test(url));
|
|
63
|
+
if (!initialUrl || !mainUrl) throw new Error("Native Images modules are unavailable");
|
|
64
|
+
const [initialSource, mainSource] = await Promise.all([read(initialUrl), read(mainUrl)]);
|
|
65
|
+
const asset = (name, references = initialSource) => {
|
|
66
|
+
const file = references.match(new RegExp(`(?:["'./])(${name}-[\\w-]+\\.js)`))?.[1];
|
|
67
|
+
if (!file) throw new Error(`Native Images capability is unavailable: ${name}`);
|
|
68
|
+
return new URL(file, initialUrl).href;
|
|
69
|
+
};
|
|
70
|
+
const module = async (name,references) => {
|
|
71
|
+
const url = asset(name,references);
|
|
72
|
+
const [source, exports] = await Promise.all([read(url), load(url)]);
|
|
73
|
+
// 调用产物公开的惰性初始化器,不扫描或执行任意导出函数。
|
|
74
|
+
for (const value of Object.values(exports)) {
|
|
75
|
+
if (typeof value === "function" && /^function ([\w$]+)\(\)\{return\(\1=/.test(String(value))) value();
|
|
76
|
+
}
|
|
77
|
+
return {url, source, exports};
|
|
78
|
+
};
|
|
79
|
+
const component = (exports, predicate, name) => {
|
|
80
|
+
const matches = Object.values(exports).filter(value => typeof value === "function" && predicate(String(value)));
|
|
81
|
+
if (matches.length !== 1) throw new Error(`Native Images contract changed: ${name}`);
|
|
82
|
+
return matches[0];
|
|
83
|
+
};
|
|
84
|
+
const binding = async (source, url, local) => {
|
|
85
|
+
if (!local) throw new Error("Native Images component contract changed");
|
|
86
|
+
const importedBinding = imported(source, local);
|
|
87
|
+
return (await load(new URL(importedBinding.file, url).href))[importedBinding.name];
|
|
88
|
+
};
|
|
89
|
+
const masonry = await module("library-masonry");
|
|
90
|
+
const rootOwner = mainSource.match(/\(0,([\w$]+)\.createRoot\)/)?.[1];
|
|
91
|
+
const rootInit = rootOwner && mainSource.match(new RegExp(`${rootOwner}=([\\w$]+)\\(\\)`))?.[1];
|
|
92
|
+
const reactOwner = masonry.source.match(/([\w$]+)\.useRef\b/)?.[1];
|
|
93
|
+
const reactInit = reactOwner && masonry.source.match(new RegExp(`${reactOwner}=([\\w$]+)\\(\\)`))?.[1];
|
|
94
|
+
const [reactFactory, rootFactory] = await Promise.all([
|
|
95
|
+
binding(masonry.source, masonry.url, reactInit), binding(mainSource, mainUrl, rootInit),
|
|
96
|
+
]);
|
|
97
|
+
const React = reactFactory(), ReactDOM = rootFactory();
|
|
98
|
+
const initial = await load(initialUrl);
|
|
99
|
+
return {React,ReactDOM,module,component,binding,initialUrl,initialSource,initial};
|
|
100
|
+
})().catch(error=>{starting=null;throw error;});
|
|
101
|
+
return starting;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function gallery() {
|
|
105
|
+
if (!connecting) connecting = (async () => {
|
|
106
|
+
const {React,ReactDOM,module,component,binding,initialUrl,initialSource,initial}=await bootstrap();
|
|
107
|
+
const [masonry,layout,sourceImage,menu,selection]=await Promise.all([
|
|
108
|
+
module("library-masonry"),module("library-item-layout"),module("generated-image-source"),
|
|
109
|
+
module("generated-image-context-menu"),module("library-selection-toolbar"),
|
|
110
|
+
]);
|
|
111
|
+
const [moveDialog,dialogs]=await Promise.all([module("library-move-dialog",selection.source),module("library-item-dialogs",selection.source)]);
|
|
112
|
+
const routerLocal = selection.source.match(/get\(([\w$]+)\)\.prepareNavigation\(/)?.[1];
|
|
113
|
+
const routerAtom = await binding(selection.source, selection.url, routerLocal);
|
|
114
|
+
const switchLocal = selection.source.match(/([\w$]+)\(e,i\);let _=t.length===1/)?.[1];
|
|
115
|
+
const switchMode = await binding(selection.source, selection.url, switchLocal);
|
|
116
|
+
const draftLocal = selection.source.match(/let u=([\w$]+)\(e,\[\]\);e.set/)?.[1];
|
|
117
|
+
const createChatDraft = await binding(selection.source, selection.url, draftLocal);
|
|
118
|
+
const dropdownLocal = selection.source.match(/\(0,[\w$]+\.jsxs?\)\(([\w$]+),\{contentWidth:`menu`/)?.[1];
|
|
119
|
+
const itemsLocal = selection.source.match(/\(0,[\w$]+\.jsxs?\)\(([\w$]+)\.Item,\{/)?.[1];
|
|
120
|
+
const buttonLocal = selection.source.match(/\(0,[\w$]+\.jsx\)\(([\w$]+),\{ref:[^,]+,color:`mediaOverlay`/)?.[1];
|
|
121
|
+
const iconLocal = selection.source.match(/color:`mediaOverlay`[^;]+?children:\(0,[\w$]+\.jsx\)\(([\w$]+),\{\}\)/)?.[1];
|
|
122
|
+
const [Dropdown, Menu, Button, MoreIcon] = await Promise.all([dropdownLocal, itemsLocal, buttonLocal, iconLocal].map(local => binding(selection.source, selection.url, local)));
|
|
123
|
+
const checkboxFunction = component(selection.exports,source=>/onCheckedChange:/.test(source)&&/viewMode:/.test(source),"selection checkbox");
|
|
124
|
+
const checkboxLocal = String(checkboxFunction).match(/\(0,[\w$]+\.jsx\)\(([\w$]+),\{className:`cursor-interaction`,variant:/)?.[1];
|
|
125
|
+
const Checkbox = await binding(selection.source,selection.url,checkboxLocal);
|
|
126
|
+
const rangeSelection = component(selection.exports,source=>/^function [\w$]+\(\{orderedIds:/.test(source)&&/extendRange:/.test(source),"range selection");
|
|
127
|
+
const selectionLocal = selection.source.match(/\.set\(([\w$]+),new Set\)/)?.[1];
|
|
128
|
+
const selectionExport = selection.source.match(/export\{([^}]+)\}/)?.[1].split(",").map(value=>value.trim().split(/\s+as\s+/)).find(parts=>parts[0]===selectionLocal)?.at(-1);
|
|
129
|
+
const nativeSelectedIds = selection.exports[selectionExport];
|
|
130
|
+
if (!nativeSelectedIds) throw new Error("Native selection state contract changed");
|
|
131
|
+
const clearPrefix = selection.source.slice(selection.source.indexOf("id:`libraryNext.selection.clear`"));
|
|
132
|
+
const clearLocal = clearPrefix.match(/\(0,[\w$]+\.jsx\)\(([\w$]+),\{\}\)/)?.[1];
|
|
133
|
+
const ClearIcon = await binding(selection.source,selection.url,clearLocal);
|
|
134
|
+
const dialogLocal = dialogs.source.match(/\(0,[\w$]+\.jsx\)\(([\w$]+),\{open:[^,]+,showDialogClose:!1/)?.[1];
|
|
135
|
+
const contentLocal = dialogs.source.match(/\(0,[\w$]+\.jsx\)\(([\w$]+),\{as:`form`,onSubmit:/)?.[1];
|
|
136
|
+
const [Dialog,DialogContent] = await Promise.all([dialogLocal,contentLocal].map(local=>binding(dialogs.source,dialogs.url,local)));
|
|
137
|
+
const menuCoreFile = menu.source.match(/from["']([^"']*generated-image-context-menu-[^"']+)["']/)?.[1];
|
|
138
|
+
const menuCoreUrl = menuCoreFile ? new URL(menuCoreFile,menu.url).href : menu.url;
|
|
139
|
+
const menuCoreSource = menuCoreFile ? await read(menuCoreUrl) : menu.source;
|
|
140
|
+
const imageActions = component(menu.exports,source=>/onOpenedInNewTab:/.test(source),"image actions");
|
|
141
|
+
const imageActionSource = String(imageActions);
|
|
142
|
+
const imageFileLocal = imageActionSource.match(/let\{file:[\w$]+\}=([\w$]+)\(/)?.[1];
|
|
143
|
+
const attachLocal = imageActionSource.match(/await ([\w$]+)\(e,i,\[n\],/)?.[1];
|
|
144
|
+
const downloadLocal = imageActionSource.match(/await ([\w$]+)\(\{downloadSrc:/)?.[1];
|
|
145
|
+
const [imageFile,attachChatImages,downloadImage] = await Promise.all([imageFileLocal,attachLocal,downloadLocal].map(local=>binding(menuCoreSource,menuCoreUrl,local)));
|
|
146
|
+
const toolbarClass = selection.source.match(/className:`(pointer-events-auto flex h-toolbar[^`]+)`/)?.[1];
|
|
147
|
+
const toolbarPositionClass = selection.source.match(/className:`(pointer-events-none fixed inset-x-4 bottom-6[^`]+)`/)?.[1];
|
|
148
|
+
if (!toolbarClass || !toolbarPositionClass) throw new Error("Native selection toolbar layout changed");
|
|
149
|
+
const libraryIcon = async label => {
|
|
150
|
+
const marker = selection.source.indexOf(`defaultMessage:\`${label}\``);
|
|
151
|
+
const prefix = selection.source.slice(Math.max(0, marker - 500), marker);
|
|
152
|
+
const match = [...prefix.matchAll(/leftIcon:\(0,[\w$]+\.jsx\)\(([\w$]+),\{icon:\{(assets:\{)?16:([\w$]+),20:([\w$]+)/g)].at(-1);
|
|
153
|
+
if (!match) throw new Error(`Native Library icon contract changed: ${label}`);
|
|
154
|
+
const [Icon, small, medium] = await Promise.all([match[1], match[3], match[4]].map(local => binding(selection.source, selection.url, local)));
|
|
155
|
+
const sizes = {16:small, 20:medium};
|
|
156
|
+
return React.createElement(Icon, {icon:match[2] ? {assets:sizes} : sizes});
|
|
157
|
+
};
|
|
158
|
+
const upload = component(initial, source => /async function/.test(source) && /libraryPersistenceMode:/.test(source) && /uploadPurpose:/.test(source), "Library upload");
|
|
159
|
+
const setFavorite = component(initial, source => /target_ref:/.test(source) && /showToast:/.test(source), "Library favorite");
|
|
160
|
+
const auth = String(setFavorite).match(/accountId:e.get\(([\w$]+)\),userId:e.get\(([\w$]+)\)/);
|
|
161
|
+
const [accountAtom,userAtom] = await Promise.all([auth?.[1],auth?.[2]].map(local => binding(initialSource,initialUrl,local)));
|
|
162
|
+
if (!accountAtom || !userAtom) throw new Error("Native Library account contract changed");
|
|
163
|
+
const toastLocal = selection.source.match(/get\(([\w$]+)\)\.danger\(/)?.[1];
|
|
164
|
+
const toastAtom = await binding(selection.source, selection.url, toastLocal);
|
|
165
|
+
const favoritePrefix = selection.source.slice(Math.max(0,selection.source.indexOf("id:`library.favorites.add`")-2000),selection.source.indexOf("id:`library.favorites.add`")+500);
|
|
166
|
+
const favoriteAsset = favoritePrefix.match(/\{16:([\w$]+),20:([\w$]+)\}:\{16:([\w$]+),20:([\w$]+)\}/);
|
|
167
|
+
if (!favoriteAsset) throw new Error("Native Library favorite icon contract changed");
|
|
168
|
+
const favoriteIconLocal = favoritePrefix.match(/icon:\{assets:/)?.index;
|
|
169
|
+
const favoriteIconOwner = favoritePrefix.slice(0,favoriteIconLocal).match(/\(0,[\w$]+\.jsx\)\(([\w$]+),\{$/)?.[1];
|
|
170
|
+
const [FavoriteIcon, savedSmall, savedMedium, emptySmall, emptyMedium] = await Promise.all([favoriteIconOwner,...favoriteAsset.slice(1)].map(local => binding(selection.source,selection.url,local)));
|
|
171
|
+
const favoriteIcon = saved => React.createElement(FavoriteIcon,{icon:{assets:saved?{16:savedSmall,20:savedMedium}:{16:emptySmall,20:emptyMedium}}});
|
|
172
|
+
const [chatIcon, downloadIcon, folderIcon, deleteIcon] = await Promise.all([
|
|
173
|
+
libraryIcon("Chat about this"), libraryIcon("Download"), libraryIcon("Add to folder"), libraryIcon("{kind, select, directory {Delete folder} other {Delete}}"),
|
|
174
|
+
]);
|
|
175
|
+
if (!React.createElement || !ReactDOM.createRoot || !Menu.Item) throw new Error("Native Images rendering contract changed");
|
|
176
|
+
return {
|
|
177
|
+
React, ReactDOM, Dropdown, Menu, Button, MoreIcon, chatIcon, downloadIcon, folderIcon, favoriteIcon, routerAtom, switchMode, createChatDraft, upload, setFavorite, toastAtom, accountAtom, userAtom,
|
|
178
|
+
MoveDialog:moveDialog.exports.MoveLibraryItemsDialog,
|
|
179
|
+
Checkbox,rangeSelection,nativeSelectedIds,ClearIcon,Dialog,DialogContent,ConfirmationContent:dialogs.exports.LibraryItemConfirmationContent,
|
|
180
|
+
imageFile,attachChatImages,downloadImage,toolbarClass,toolbarPositionClass,deleteIcon,
|
|
181
|
+
Masonry: component(masonry.exports, source => /\{items:[^}]+animate:/.test(source), "masonry"),
|
|
182
|
+
Skeleton: component(masonry.exports, source => /square:/.test(source) && /Loading library/.test(source), "skeleton"),
|
|
183
|
+
Card: component(layout.exports, source => /actionIndicator:/.test(source) && /documentHeader:/.test(source), "card"),
|
|
184
|
+
useImage: component(sourceImage.exports, source => /shouldLoadFileDataUrl:/.test(source), "image resolver"),
|
|
185
|
+
imageMenu: component(menu.exports, source => /onOpenedInNewTab:/.test(source), "image actions"),
|
|
186
|
+
openImage: component(initial, source => /availableImageCount:/.test(source) && /openInEditor:/.test(source) && /entrypoint:/.test(source), "image preview"),
|
|
187
|
+
};
|
|
188
|
+
})().catch(error=>{connecting=null;throw error;});
|
|
189
|
+
return connecting;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function canvas() {
|
|
193
|
+
if (!canvasConnecting) canvasConnecting = (async () => {
|
|
194
|
+
const {module,binding,component,initial}=await bootstrap();
|
|
195
|
+
const [canvas,canvasLayout,filePreview]=await Promise.all([
|
|
196
|
+
module("image-side-panel"),module("preview-layout"),module("library-cloud-file-preview"),
|
|
197
|
+
]);
|
|
198
|
+
const portalOwner=canvas.source.match(/\(0,([\w$]+)\.createPortal\)/)?.[1];
|
|
199
|
+
const portalInit=portalOwner&&canvas.source.match(new RegExp(`${portalOwner}=([\\w$]+)\\(\\)`))?.[1];
|
|
200
|
+
const createPortal=(await binding(canvas.source,canvas.url,portalInit))().createPortal;
|
|
201
|
+
// 文件画布使用原生 Home Composer 的控制器、附件 scope 和输入组件。
|
|
202
|
+
const composerLocals=Object.values(discoverFilePreviewComposerBindings(filePreview.source));
|
|
203
|
+
const [useComposerController,useHomeComposer,Composer,ComposerSurface,ScopeProvider,composerScope,createComposerValue,composerRouteScope,FileComposer,fileComposerAvailability,localAttachmentService,deleteLocalAttachment,useFileNavigate,localThreadRoute] = await Promise.all(composerLocals.map(local=>binding(filePreview.source,filePreview.url,local)));
|
|
204
|
+
if (!canvas.exports.t || !canvasLayout.exports.r || !Composer || !ComposerSurface || !ScopeProvider
|
|
205
|
+
|| !composerScope || !composerRouteScope?.parent
|
|
206
|
+
|| !FileComposer || !fileComposerAvailability
|
|
207
|
+
|| [createPortal,useComposerController,useHomeComposer,createComposerValue,localAttachmentService,deleteLocalAttachment,useFileNavigate,localThreadRoute].some(value=>typeof value!=="function")) {
|
|
208
|
+
throw new Error("Native Images canvas contract changed");
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
createPortal,Canvas:canvas.exports.t,CanvasLayout:canvasLayout.exports.r,
|
|
212
|
+
useComposerController,useHomeComposer,Composer,ComposerSurface,ScopeProvider,composerScope,createComposerValue,composerRouteScope,
|
|
213
|
+
FileComposer,fileComposerAvailability,localAttachmentService,deleteLocalAttachment,useFileNavigate,localThreadRoute,
|
|
214
|
+
editImage:component(initial,source=>/async function/.test(source)&&/requireDispatchAcceptance:/.test(source)&&/sourceAttachment:/.test(source)&&/imageVariant/.test(source),"image edit submission"),
|
|
215
|
+
};
|
|
216
|
+
})().catch(error=>{canvasConnecting=null;throw error;});
|
|
217
|
+
return canvasConnecting;
|
|
218
|
+
}
|
|
219
|
+
return {navigation:async()=>{const {React,ReactDOM}=await bootstrap();return {React,ReactDOM};},gallery,canvas};
|
|
220
|
+
}
|