minecodex 2.0.35 → 2.0.36
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/features/images/src/adapters/codex/image-sources.mjs +20 -13
- package/features/images/src/adapters/codex/native-image-source.mjs +6 -3
- package/features/images/src/frontend/local-image-source.mjs +5 -3
- package/features/images/src/library-prompt-page.mjs +1 -1
- package/features/sites/src/adapters/codex/renderer.mjs +63 -9
- package/features/sites/src/frontend/entry.mjs +41 -15
- package/features/usage-meter/src/usage-meter.mjs +23 -2
- package/package.json +1 -1
- package/packages/codex-adapter/src/renderer/environment.mjs +36 -1
|
@@ -1,28 +1,35 @@
|
|
|
1
|
-
export function createImageSourcesAdapter(codex) {
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
export function createImageSourcesAdapter(codex, signal, invalidate) {
|
|
2
|
+
const account = codex.account(signal, invalidate);
|
|
3
|
+
const visible = node => node?.isConnected && node.getBoundingClientRect().width > 0 && node.getBoundingClientRect().height > 0;
|
|
4
|
+
const isImageNavigation = (props, plus) => props?.variant === "browse"
|
|
5
|
+
&& (plus ? ["images", "all", "folders"] : ["generated", "uploaded"]).every(key => props.tabs?.some(item => item.key === key));
|
|
5
6
|
|
|
6
7
|
function applicability() {
|
|
7
8
|
const props=codex.props(codex.rail(),value=>Boolean((value.router ?? value.value?.router)?.state?.location));
|
|
8
9
|
const location=(props?.router ?? props?.value?.router)?.state?.location;
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
const current = account.read();
|
|
11
|
+
if (!location || current.status !== "available") return "unknown";
|
|
12
|
+
return (current.planType === "plus" ? location.pathname === "/library"
|
|
13
|
+
: location.pathname === "/space/files" && new URLSearchParams(location.search).get("tab") === "images")
|
|
11
14
|
? "applicable" : "not-applicable";
|
|
12
15
|
}
|
|
13
16
|
return {applicability,read() {
|
|
17
|
+
if (applicability() !== "applicable") return null;
|
|
18
|
+
const current = account.read(), plus = current.planType === "plus";
|
|
14
19
|
const tabs = [...document.querySelectorAll('[role="tablist"]')].find(node =>
|
|
15
|
-
|
|
16
|
-
&&
|
|
20
|
+
(visible(node) || node.hidden && visible(node.parentElement?.querySelector('[data-minecodex-image-navigation]')))
|
|
21
|
+
&& !node.closest("[data-minecodex-image-navigation]")
|
|
22
|
+
&& codex.props(node, props => isImageNavigation(props, plus)));
|
|
17
23
|
if (!tabs) return null;
|
|
18
24
|
// 26.1007 把来源工具栏包进独立 inset;图库仍是同级的 flex 内容区。
|
|
19
|
-
let navigation = tabs.parentElement?.parentElement;
|
|
25
|
+
let navigation = plus ? tabs.parentElement : tabs.parentElement?.parentElement;
|
|
20
26
|
while (navigation && !navigation.nextElementSibling) navigation = navigation.parentElement;
|
|
21
27
|
const content = navigation?.nextElementSibling;
|
|
22
28
|
const shell = navigation?.parentElement;
|
|
23
|
-
if (!content || !shell)
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
29
|
+
if (!content || !shell || !(visible(content) || content.hidden
|
|
30
|
+
&& content.nextElementSibling?.id === "minecodex-image-gen-content" && visible(content.nextElementSibling))) return null;
|
|
31
|
+
const props = codex.props(tabs, props => isImageNavigation(props, plus));
|
|
32
|
+
const search = shell.querySelector('input[type="search"],input[placeholder="Search"],input[placeholder="Search library"],input[placeholder="搜索资料库"],input[placeholder="搜索库"],input[placeholder="搜尋資料庫"]');
|
|
33
|
+
return {tabs, navigation, content, shell, search, selectedKey:props.selectedKey, accountKey:current.key, mode:plus ? "library" : "space"};
|
|
27
34
|
}};
|
|
28
35
|
}
|
|
@@ -4,6 +4,7 @@ import {createNativeImageModules} from "./native-image-modules.mjs";
|
|
|
4
4
|
export function createNativeImageSource(codex, { requestHostAction, libraryApi, createCloudImageActions, promptGlyphSource, signal, modules = createNativeImageModules() } = {}) {
|
|
5
5
|
const connect = modules.gallery;
|
|
6
6
|
const opened = new Map();
|
|
7
|
+
const account = codex.account(signal);
|
|
7
8
|
|
|
8
9
|
function uploadFile({base64,name}) {
|
|
9
10
|
const decoded=atob(base64),bytes=new Uint8Array(decoded.length);
|
|
@@ -42,11 +43,11 @@ export function createNativeImageSource(codex, { requestHostAction, libraryApi,
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
function navigationContract(anchor) {
|
|
46
|
+
const plus = account.read().planType === "plus";
|
|
45
47
|
for (let fiber = codex.currentFiber(anchor); fiber; fiber = fiber.return) {
|
|
46
48
|
const props = fiber.memoizedProps;
|
|
47
49
|
if (typeof fiber.type === "function" && props?.variant === "browse"
|
|
48
|
-
&& props.tabs?.some(item => item.key ===
|
|
49
|
-
&& props.tabs?.some(item => item.key === "uploaded")) return {type:fiber.type, props};
|
|
50
|
+
&& (plus ? ["images", "all", "folders"] : ["generated", "uploaded"]).every(key => props.tabs?.some(item => item.key === key))) return {type:fiber.type, props};
|
|
50
51
|
}
|
|
51
52
|
throw new Error("Native image navigation contract changed");
|
|
52
53
|
}
|
|
@@ -61,7 +62,9 @@ export function createNativeImageSource(codex, { requestHostAction, libraryApi,
|
|
|
61
62
|
const props = {
|
|
62
63
|
...contract.props,
|
|
63
64
|
selectedKey,
|
|
64
|
-
tabs:
|
|
65
|
+
tabs:account.read().planType === "plus"
|
|
66
|
+
? contract.props.tabs.flatMap(tab => tab.key === "images" ? [tab, {key:"imagegen", name:"Image Gen"}] : [tab])
|
|
67
|
+
: [...contract.props.tabs, {key:"imagegen", name:"Image Gen"}],
|
|
65
68
|
onSelect:key => {
|
|
66
69
|
onSelect(key);
|
|
67
70
|
if (key !== "imagegen") navigationContract(anchor).props.onSelect(key);
|
|
@@ -78,7 +78,7 @@ export function installLocalImageSource(config, signal, sources, native) {
|
|
|
78
78
|
async function scan() {
|
|
79
79
|
if (signal.aborted) return;
|
|
80
80
|
const entry = sources.read();
|
|
81
|
-
if (mounted && mounted.entry.tabs === entry?.tabs) {
|
|
81
|
+
if (mounted && mounted.entry.tabs === entry?.tabs && mounted.entry.accountKey === entry?.accountKey && mounted.entry.mode === entry?.mode) {
|
|
82
82
|
if (!mounted.active) mounted.nav?.render(entry.selectedKey);
|
|
83
83
|
return;
|
|
84
84
|
}
|
|
@@ -176,11 +176,12 @@ export function installLocalImageSource(config, signal, sources, native) {
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
let scheduled = false;
|
|
179
|
-
|
|
179
|
+
function schedule() {
|
|
180
180
|
if (scheduled) return;
|
|
181
181
|
scheduled = true;
|
|
182
182
|
queueMicrotask(() => {scheduled = false; void scan();});
|
|
183
|
-
}
|
|
183
|
+
}
|
|
184
|
+
const observer = new MutationObserver(schedule);
|
|
184
185
|
observer.observe(document.body, {childList:true, subtree:true, attributes:true, attributeFilter:["aria-selected"]});
|
|
185
186
|
void scan();
|
|
186
187
|
let cleaned = false;
|
|
@@ -188,6 +189,7 @@ export function installLocalImageSource(config, signal, sources, native) {
|
|
|
188
189
|
signal.addEventListener("abort", cleanup, {once:true});
|
|
189
190
|
return {
|
|
190
191
|
cleanup,
|
|
192
|
+
refresh:schedule,
|
|
191
193
|
selfCheck() {
|
|
192
194
|
const entry=sources.read(),scope=sources.applicability();
|
|
193
195
|
if (!entry) return {state:scope === "applicable" ? "unavailable" : scope,reason:scope === "not-applicable" ? "outside-image-sources" : "image-sources-anchor-unavailable"};
|
|
@@ -259,7 +259,7 @@ function localPromptRequested(){openPendingLocalPrompt();void scan();}
|
|
|
259
259
|
document.addEventListener("minecodex:image-prompt",localPromptRequested);
|
|
260
260
|
const localeObserver = new MutationObserver(() => mounted?.refreshLocale());
|
|
261
261
|
localeObserver.observe(document.documentElement, { attributes:true, attributeFilter:["lang"] });
|
|
262
|
-
localImageSource = installLocalImageSource(config, signal, createImageSourcesAdapter(config.codex), createNativeImageSource(config.codex, {
|
|
262
|
+
localImageSource = installLocalImageSource(config, signal, createImageSourcesAdapter(config.codex, signal, () => localImageSource?.refresh()), createNativeImageSource(config.codex, {
|
|
263
263
|
requestHostAction:config.requestHostAction, libraryApi, createCloudImageActions, promptGlyphSource, signal,
|
|
264
264
|
}));
|
|
265
265
|
config.registerSelfCheck?.(() => {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// 专有路由、页面锚点和原生网页打开协议不跨出此适配器。
|
|
2
|
-
export function createSitesAdapter(signal,codex) {
|
|
2
|
+
export function createSitesAdapter(signal,codex,invalidate) {
|
|
3
3
|
let nativeButton,nativeInner,nativePlus,nativeSiteIcon,nativeMore,nativeExternal,nativeGlobe,nativeActionsClass;
|
|
4
4
|
const owned="data-mcx-sites";
|
|
5
|
+
const account=codex.account(signal,invalidate);
|
|
5
6
|
const text=(en,zh)=>(document.documentElement.lang || navigator.language).startsWith("zh") ? zh : en;
|
|
6
7
|
const visible=e=>!!e?.isConnected && e.getBoundingClientRect().width>0 && e.getBoundingClientRect().height>0;
|
|
7
8
|
async function until(check) {
|
|
@@ -19,20 +20,73 @@ function router() {
|
|
|
19
20
|
return p?.router || p?.value?.router;
|
|
20
21
|
}
|
|
21
22
|
function pageInfo() {
|
|
22
|
-
if (
|
|
23
|
-
const
|
|
23
|
+
if (applicability() !== "applicable") return null;
|
|
24
|
+
const plus=account.read().planType === "plus";
|
|
25
|
+
const input = [...document.querySelectorAll("input")].find(e => visible(e) && /Search sites|搜索站点|搜尋網站/.test(e.placeholder));
|
|
24
26
|
if (!input) return null;
|
|
25
27
|
let page = input.parentElement;
|
|
26
28
|
while (page && !page.classList.contains("min-h-full")) page = page.parentElement;
|
|
27
29
|
if (!page || page.children.length < 2) return null;
|
|
28
|
-
const content = page.children[1], list = page.querySelector('[data-testid="sites-table"], [data-testid="sites-grid"]');
|
|
30
|
+
const content = plus ? page.lastElementChild : page.children[1], list = page.querySelector('[data-testid="sites-table"], [data-testid="sites-grid"]');
|
|
31
|
+
if (!visible(content)) return null;
|
|
29
32
|
const native = nativeProps(list || content.querySelector("button") || content.querySelector("p") || content.lastElementChild?.lastElementChild, p => "viewMode" in p && "shared" in p);
|
|
30
33
|
const grid = native ? native.viewMode === "grid" : !!page.querySelector('button[aria-label="Grid view"][aria-pressed="true"],button[aria-label="网格视图"][aria-pressed="true"]');
|
|
31
|
-
const
|
|
32
|
-
const
|
|
34
|
+
const ownershipTabs=[...page.querySelectorAll('[role="tablist"]')].find(node=>!node.closest(`[${owned}]`)&&nativeProps(node,p=>p.tabs?.some(tab=>tab.key==="owned")&&p.tabs?.some(tab=>tab.key==="shared")));
|
|
35
|
+
const ownership=ownershipTabs&&nativeProps(ownershipTabs,p=>p.tabs?.some(tab=>tab.key==="owned")&&p.tabs?.some(tab=>tab.key==="shared"));
|
|
36
|
+
const shared = ownership ? ownership.selectedKey === "shared" : native ? !!native.shared : [...page.querySelectorAll("button[aria-pressed=true]")].some(b => /Shared with you|与你共享|與你共用/.test(b.textContent));
|
|
37
|
+
const create = [...(plus ? page : page.firstElementChild).querySelectorAll("button")].find(b => visible(b) && (plus ? /^(Create Site|创建站点|建立網站)$/ : /^(New|新建|新增)$/).test(b.textContent.trim()) && !b.hasAttribute(owned));
|
|
33
38
|
codex.report("sites-page",true);
|
|
34
|
-
return { page, input, content, list, grid, shared, create, empty: list ? null : [...content.children].findLast(e => !e.hasAttribute(owned)) };
|
|
39
|
+
return { page, input, content, list, grid, shared, create, plus, ownershipTabs:plus?(ownershipTabs??null):null, accountKey:account.read().key, empty: list ? null : [...content.children].findLast(e => !e.hasAttribute(owned)) };
|
|
35
40
|
}
|
|
41
|
+
function mountOwnership(page,onSelect) {
|
|
42
|
+
const tabs=page.ownershipTabs,original=tabs?[...tabs.querySelectorAll('[role="tab"]')]:[];
|
|
43
|
+
let wrapper;
|
|
44
|
+
const hidden=tabs?.hidden,node=tabs?tabs.cloneNode(false):document.createElement("div");
|
|
45
|
+
if (!tabs) {
|
|
46
|
+
// 26.1007:共享能力确认后可能移除原标签组;保留已测 browse 分支。
|
|
47
|
+
node.className="flex min-w-0 items-center gap-1 hide-scrollbar overflow-x-auto overflow-y-hidden";
|
|
48
|
+
node.setAttribute("role","tablist");node.setAttribute("aria-label",text("Site views","站点视图"));
|
|
49
|
+
const native=document.createElement("button");native.type="button";native.setAttribute("role","tab");native.setAttribute("aria-selected","true");
|
|
50
|
+
native.textContent=text("Owned by you","你拥有的");
|
|
51
|
+
native.className="cursor-interaction items-center text-sm select-none focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-0 flex h-9 min-w-0 rounded-full border border-transparent px-4 py-0 font-medium ring-inset bg-primary-ghost-hover text-default";
|
|
52
|
+
native.onclick=()=>{const r=router(),location=r.state.location,search=new URLSearchParams(location.search);search.delete("view");search.delete("search");r.navigate({pathname:location.pathname,search:search.size?`?${search}`:""},{state:location.state});};
|
|
53
|
+
original.push(native);
|
|
54
|
+
wrapper=page.content.cloneNode(false);
|
|
55
|
+
wrapper.classList.remove("min-h-0","flex-1","flex-col","pb-4","bg-surface");wrapper.classList.add("min-h-15","items-center");
|
|
56
|
+
wrapper.setAttribute(owned,"ownership-wrapper");wrapper.append(node);page.content.before(wrapper);
|
|
57
|
+
}
|
|
58
|
+
const buttons=original.map(button=>button.cloneNode(true));
|
|
59
|
+
let selected="native";
|
|
60
|
+
node.removeAttribute("id");node.setAttribute(owned,"ownership");
|
|
61
|
+
const added=original[0].cloneNode(false);added.textContent=text("Added by you","由你添加");buttons.push(added);
|
|
62
|
+
buttons.forEach((button,index)=>{
|
|
63
|
+
button.removeAttribute("id");button.removeAttribute("aria-controls");
|
|
64
|
+
button.onclick=()=>{selected=index===original.length?"added":"native";if(selected==="native")original[index].click();sync();onSelect(selected);};
|
|
65
|
+
node.append(button);
|
|
66
|
+
});
|
|
67
|
+
function sync() {
|
|
68
|
+
const active=original.find(button=>button.getAttribute("aria-selected")==="true"),inactive=original.find(button=>button.getAttribute("aria-selected")!=="true");
|
|
69
|
+
buttons.forEach((button,index)=>{
|
|
70
|
+
const chosen=selected==="added"?index===original.length:original[index]?.getAttribute("aria-selected")==="true";
|
|
71
|
+
button.className=(chosen?active:inactive)?.className || original[0].className.replace("bg-primary-ghost-hover text-default","text-secondary hover:bg-background-primary-ghost-hover/50 hover:text-default");
|
|
72
|
+
button.setAttribute("aria-selected",String(chosen));button.tabIndex=chosen?0:-1;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
node.onkeydown=event=>{
|
|
76
|
+
if (!["ArrowLeft","ArrowRight","Home","End"].includes(event.key))return;
|
|
77
|
+
event.preventDefault();const index=buttons.indexOf(document.activeElement),last=buttons.length-1;
|
|
78
|
+
const next=event.key==="Home"?0:event.key==="End"?last:(index+(event.key==="ArrowRight"?1:last))%buttons.length;
|
|
79
|
+
buttons[next].click();buttons[next].focus();
|
|
80
|
+
};
|
|
81
|
+
if (tabs) {tabs.after(node);tabs.hidden=true;}sync();
|
|
82
|
+
return {node,tabs,sync,selection:()=>selected,selectAdded:()=>added.click(),dispose(){wrapper?.remove();node.remove();if(tabs)tabs.hidden=hidden;}};
|
|
83
|
+
}
|
|
84
|
+
function applicability() {
|
|
85
|
+
const current=account.read(),path=router()?.state?.location?.pathname;
|
|
86
|
+
if (!path || current.status !== "available") return "unknown";
|
|
87
|
+
return path === (current.planType === "plus" ? "/sites" : "/space/sites") ? "applicable" : "not-applicable";
|
|
88
|
+
}
|
|
89
|
+
function isSitesPath() { return ["/sites","/space/sites"].includes(router()?.state?.location?.pathname); }
|
|
36
90
|
function browserMenu() {
|
|
37
91
|
for (const menu of document.querySelectorAll('[role="menu"]')) {
|
|
38
92
|
if (!visible(menu)) continue;
|
|
@@ -85,7 +139,7 @@ async function open(origin,url,cwd) {
|
|
|
85
139
|
function templates(page) {
|
|
86
140
|
const create = page.create;
|
|
87
141
|
if (create) {
|
|
88
|
-
const template = page.page.querySelector('button[data-size="lg"][data-color="secondary"]');
|
|
142
|
+
const template = page.page.querySelector('button[data-size="lg"][data-color="secondary"]') || create;
|
|
89
143
|
if (template) { nativeButton = template.className; nativeInner = template.firstElementChild?.className || ""; }
|
|
90
144
|
nativePlus = create.querySelector("svg")?.cloneNode(true);
|
|
91
145
|
// New 的图标在桌面端隐藏,克隆后只保留图标自身样式。
|
|
@@ -100,5 +154,5 @@ function templates(page) {
|
|
|
100
154
|
return {nativeButton,nativeInner,nativePlus,nativeSiteIcon,nativeMore,nativeExternal,nativeGlobe,nativeActionsClass};
|
|
101
155
|
}
|
|
102
156
|
|
|
103
|
-
return {templates,pageInfo,browserMenu,fullViewControl,open,path:()=>router()?.state?.location?.pathname};
|
|
157
|
+
return {templates,pageInfo,mountOwnership,browserMenu,fullViewControl,open,applicability,isSitesPath,accountKey:()=>account.read().key,path:()=>router()?.state?.location?.pathname};
|
|
104
158
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const nativeSites=createSitesAdapter(signal,config.codex);
|
|
1
|
+
const nativeSites=createSitesAdapter(signal,config.codex,schedule);
|
|
2
2
|
// 复用 Prompts 已验证的原生图标与 Lucide 图标,保持交互一致。
|
|
3
3
|
const ICONS = config.resources.icons;
|
|
4
4
|
const MENU_CLASSES = {"menu": "no-drag z-50 m-px flex select-none flex-col overflow-y-auto bg-surface-elevated-secondary/90 text-default ring-border ring-[0.5px] shadow-xl-spread backdrop-blur-sm rounded-2xl p-[var(--app-menu-gutter,var(--mcx-spacing))] min-w-[var(--app-menu-min-width,220px)]", "item": "no-drag outline-hidden flex min-h-[var(--app-menu-item-height,0px)] shrink-0 items-center justify-center p-[var(--app-menu-item-padding,var(--padding-row-y)_var(--padding-row-x))] text-(length:--app-menu-item-font-size,var(--mcx-text-sm)) leading-(--app-menu-item-line-height,var(--mcx-text-sm--line-height)) rounded-xl text-default group hover:bg-primary-ghost-hover focus:bg-primary-ghost-hover cursor-interaction flex flex-col", "row": "flex w-full min-w-0 items-center gap-[var(--spacing-menu-item-content,calc(var(--mcx-spacing)*1.5))]", "leading": "_leadingIcon_ml6jk_2"};
|
|
@@ -8,7 +8,11 @@ const owned = "data-mcx-sites";
|
|
|
8
8
|
let disposed = false, state = { sites: [], projects: [], threads: [] }, addButton, rowsRoot, fallback, dialog, menu;
|
|
9
9
|
let rowSignature = "", nativeButton, nativeInner, nativePlus, nativeSiteIcon, nativeMore, timer, revision = 0, opening = false;
|
|
10
10
|
let nativeExternal, nativeGlobe, nativeActionsClass;
|
|
11
|
-
let currentPage, refreshAt = 0, pendingLayout = null;
|
|
11
|
+
let currentPage, currentAccount, refreshAt = 0, pendingLayout = null;
|
|
12
|
+
let ownership;
|
|
13
|
+
const nativeHidden=new Map();
|
|
14
|
+
function restoreNativeContent() { for (const [node,hidden] of nativeHidden) node.hidden=hidden; nativeHidden.clear(); }
|
|
15
|
+
function restoreOwnership() { restoreNativeContent();ownership?.dispose();ownership=null; }
|
|
12
16
|
const style = document.createElement("style");
|
|
13
17
|
style.setAttribute(owned, "style");
|
|
14
18
|
style.textContent = config.resources.styles;
|
|
@@ -106,7 +110,7 @@ function loadPreview(container, url, cachedOnly = false) {
|
|
|
106
110
|
});
|
|
107
111
|
}
|
|
108
112
|
function row(site, grid) {
|
|
109
|
-
const r = element("div", grid ? "group/site-card relative grid h-[210px] min-w-0 grid-rows-[148px_62px] overflow-hidden rounded-2xl border border-subtle bg-surface-card shadow-sm" : "relative col-span-full grid min-h-[82px] grid-cols-subgrid items-center overflow-hidden rounded-xl px-4 py-4 select-none hover:bg-primary-ghost-hover");
|
|
113
|
+
const r = element("div", grid ? "group/site-card relative grid h-[210px] min-w-0 grid-rows-[148px_62px] overflow-hidden rounded-2xl border border-subtle bg-surface-card shadow-sm" : "group/site-row relative col-span-full grid min-h-[82px] grid-cols-subgrid items-center overflow-hidden rounded-xl px-4 py-4 select-none hover:bg-primary-ghost-hover");
|
|
110
114
|
r.setAttribute(owned, "entry"); r.dataset.siteId = site.id;
|
|
111
115
|
const open = element("button", "absolute inset-0 cursor-interaction rounded-xl bg-transparent focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none focus-visible:ring-inset");
|
|
112
116
|
open.type = "button"; open.setAttribute("aria-label", text("Open ", "打开 ") + site.title); open.onclick = () => openSite(site); r.append(open);
|
|
@@ -210,32 +214,54 @@ function showBrowserAdd({ site, project, preferProject }) {
|
|
|
210
214
|
}
|
|
211
215
|
function reconcile() {
|
|
212
216
|
if (disposed) return;
|
|
217
|
+
if (currentAccount && nativeSites.accountKey() !== currentAccount) { closeMenu();dialog?.close(); }
|
|
213
218
|
updateBrowserMenu();
|
|
214
219
|
const page = nativeSites.pageInfo();
|
|
215
|
-
if (!page) { addButton?.remove(); addButton = null; rowsRoot?.remove(); rowsRoot = null; fallback?.remove(); fallback = null; rowSignature = ""; currentPage = null; closeMenu(); return; }
|
|
220
|
+
if (!page) { restoreOwnership();addButton?.remove(); addButton = null; rowsRoot?.remove(); rowsRoot = null; fallback?.remove(); fallback = null; rowSignature = ""; currentPage = null; closeMenu(); document.querySelectorAll("[data-mcx-sites-empty-suppressed]").forEach(e => e.removeAttribute("data-mcx-sites-empty-suppressed")); return; }
|
|
216
221
|
cacheNative(page);
|
|
217
|
-
if (currentPage !== page.page
|
|
222
|
+
if (currentPage !== page.page || currentAccount !== page.accountKey) {
|
|
223
|
+
restoreOwnership();
|
|
224
|
+
if (currentAccount && currentAccount !== page.accountKey) { closeMenu(); dialog?.close(); }
|
|
225
|
+
addButton?.remove(); addButton=null; rowsRoot?.remove(); rowsRoot=null; fallback?.remove(); fallback=null;
|
|
226
|
+
currentPage = page.page; currentAccount=page.accountKey; rowSignature = "";
|
|
227
|
+
if (Date.now() - refreshAt > 1500) refresh().catch(e => notice(e.message));
|
|
228
|
+
}
|
|
229
|
+
if (page.plus && (!ownership || ownership.tabs !== page.ownershipTabs || !ownership.node.isConnected)) {
|
|
230
|
+
const added=ownership?.selection()==="added";
|
|
231
|
+
restoreOwnership();ownership=nativeSites.mountOwnership(page,()=>{restoreNativeContent();rowSignature="";schedule();});
|
|
232
|
+
if (added) ownership.selectAdded();
|
|
233
|
+
}
|
|
234
|
+
ownership?.sync();
|
|
235
|
+
const added=ownership?.selection()==="added";
|
|
236
|
+
if (added) {
|
|
237
|
+
for (const child of page.content.children) if (!child.hasAttribute(owned)) {
|
|
238
|
+
if (!nativeHidden.has(child)) nativeHidden.set(child,child.hidden);
|
|
239
|
+
child.hidden=true;
|
|
240
|
+
}
|
|
241
|
+
} else restoreNativeContent();
|
|
218
242
|
if (!addButton?.isConnected && page.create) {
|
|
219
243
|
addButton = button(text("Add site", "添加站点"), () => showDiscovery()); addButton.setAttribute(owned, "add");
|
|
220
244
|
if (nativePlus) addButton.firstElementChild.prepend(nativePlus.cloneNode(true));
|
|
221
245
|
page.create.before(addButton);
|
|
222
246
|
}
|
|
223
247
|
const query = page.input.value.trim().toLowerCase();
|
|
224
|
-
const sites = state.sites.filter(s => [s.title, s.url || "", state.projects.find(p => p.id === s.origin.projectId)?.name || ""].some(v => v.toLowerCase().includes(query)));
|
|
225
|
-
const
|
|
226
|
-
|
|
248
|
+
const sites = ownership && !added || !ownership && page.shared ? [] : state.sites.filter(s => [s.title, s.url || "", state.projects.find(p => p.id === s.origin.projectId)?.name || ""].some(v => v.toLowerCase().includes(query)));
|
|
249
|
+
const list=added?null:page.list;
|
|
250
|
+
const signature = JSON.stringify([revision, query, page.shared, page.grid,added]);
|
|
251
|
+
if (signature === rowSignature && rowsRoot?.isConnected && (list ? rowsRoot.parentElement === list : fallback?.isConnected)) return;
|
|
227
252
|
rowsRoot?.remove(); fallback?.remove(); fallback = null;
|
|
228
253
|
document.querySelectorAll("[data-mcx-sites-empty-suppressed]").forEach(e => e.removeAttribute("data-mcx-sites-empty-suppressed"));
|
|
229
254
|
rowsRoot = element("div"); rowsRoot.setAttribute(owned, "rows"); rowSignature = signature;
|
|
230
|
-
if (!sites.length) { rowsRoot = null; return; }
|
|
231
|
-
if (page.empty) page.empty.setAttribute("data-mcx-sites-empty-suppressed", "");
|
|
232
|
-
if (
|
|
255
|
+
if (!sites.length && !added) { rowsRoot = null; return; }
|
|
256
|
+
if (page.empty && !added) page.empty.setAttribute("data-mcx-sites-empty-suppressed", "");
|
|
257
|
+
if (list) list.append(rowsRoot);
|
|
233
258
|
else {
|
|
234
259
|
fallback = element("div", "@container pb-3"); fallback.setAttribute(owned, "fallback");
|
|
235
260
|
const layout = element("div"); layout.setAttribute(owned, page.grid ? "grid" : "table"); fallback.append(layout); layout.append(rowsRoot);
|
|
236
261
|
page.content.append(fallback);
|
|
237
262
|
}
|
|
238
263
|
rowsRoot.append(...sites.map(s => row(s, page.grid)));
|
|
264
|
+
if (!sites.length) rowsRoot.append(element("p","text-sm text-secondary",text("No added sites found.","没有找到你添加的站点。")));
|
|
239
265
|
}
|
|
240
266
|
function schedule() { clearTimeout(timer); timer = setTimeout(reconcile, 60); }
|
|
241
267
|
function newDialog(title, back) {
|
|
@@ -375,7 +401,7 @@ async function until(check, milliseconds = 7000) {
|
|
|
375
401
|
|
|
376
402
|
async function applyWebLayout(token) {
|
|
377
403
|
try {
|
|
378
|
-
await until(() => pendingLayout !== token || (nativeSites.
|
|
404
|
+
await until(() => pendingLayout !== token || (!nativeSites.isSitesPath() && (nativeSites.fullViewControl(true) || nativeSites.fullViewControl(false))));
|
|
379
405
|
if (pendingLayout !== token) return;
|
|
380
406
|
const hide = [...document.querySelectorAll("button")].find(e => visible(e) && /^(Hide sidebar|隐藏侧栏|隐藏侧边栏|隱藏側邊欄|收起侧栏)$/.test(e.getAttribute("aria-label") || "")); hide?.click();
|
|
381
407
|
// 使用原生完整视图入口,保留原生网页标签和底部聊天输入框。
|
|
@@ -414,9 +440,8 @@ observer.observe(document.body, { childList: true, subtree: true, attributes: tr
|
|
|
414
440
|
document.addEventListener("input", schedule); document.addEventListener("click", onClick, true); document.addEventListener("keydown", onKey, true); document.addEventListener("pointerdown", onPointer, true);
|
|
415
441
|
refresh().catch(e => notice(e.message)); schedule();
|
|
416
442
|
config.registerSelfCheck?.(() => {
|
|
417
|
-
const
|
|
418
|
-
if (
|
|
419
|
-
if (path !== "/space/sites") return {state:"not-applicable",reason:"outside-sites-page"};
|
|
443
|
+
const scope=nativeSites.applicability();
|
|
444
|
+
if (scope !== "applicable") return {state:scope,reason:scope === "unknown" ? "account-or-navigation-unavailable" : "outside-sites-page"};
|
|
420
445
|
const page=nativeSites.pageInfo(),available=Boolean(page?.create && addButton?.isConnected);
|
|
421
446
|
return {state:available ? "available" : "unavailable",reason:available ? null : "sites-anchor-unavailable",
|
|
422
447
|
checks:[{name:"sites-page",available:Boolean(page)},{name:"sites-create-anchor",available}],scopes:[{name:"sites-page",element:page?.page},{name:"sites-dialog",element:dialog},{name:"sites-menu",element:menu}]};
|
|
@@ -425,5 +450,6 @@ return () => {
|
|
|
425
450
|
disposed = true; pendingLayout = null; clearTimeout(timer); observer.disconnect();
|
|
426
451
|
document.removeEventListener("input", schedule); document.removeEventListener("click", onClick, true); document.removeEventListener("keydown", onKey, true); document.removeEventListener("pointerdown", onPointer, true);
|
|
427
452
|
closeMenu(); dialog?.close(); document.querySelectorAll(`[${owned}]`).forEach(e => e.remove());
|
|
453
|
+
restoreOwnership();
|
|
428
454
|
document.querySelectorAll("[data-mcx-sites-empty-suppressed]").forEach(e => e.removeAttribute("data-mcx-sites-empty-suppressed"));
|
|
429
455
|
};
|
|
@@ -36,10 +36,31 @@ function svgNode(name, attributes) {
|
|
|
36
36
|
return node;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
function quotaArc(start, end, clockwise) {
|
|
40
|
+
const radius = 43 / 3;
|
|
41
|
+
const point = angle => {
|
|
42
|
+
const radians = (angle - 90) * Math.PI / 180;
|
|
43
|
+
return [16 + radius * Math.cos(radians), 16 + radius * Math.sin(radians)];
|
|
44
|
+
};
|
|
45
|
+
const from = point(start), to = point(end);
|
|
46
|
+
return `M ${from[0]} ${from[1]} A ${radius} ${radius} 0 ${Math.abs(end - start) > 180 ? 1 : 0} ${clockwise ? 1 : 0} ${to[0]} ${to[1]}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
39
49
|
function draw(windows) {
|
|
40
50
|
const svg = svgNode("svg", { viewBox: "0 0 32 32", class: "mcx-usage-ring", "aria-hidden": "true" });
|
|
41
|
-
|
|
42
|
-
|
|
51
|
+
if (windows.fiveHour != null) {
|
|
52
|
+
// 左下、右下各留 24° 圆头中心间距;两段都从左下切口扣减。
|
|
53
|
+
for (const [key, start, end, clockwise] of [["fiveHour", 237, 483, true], ["weekly", 213, 147, false]]) {
|
|
54
|
+
const group = svgNode("g", { "data-window": key, fill: "none", stroke: "currentColor", "stroke-width": 10 / 3, "stroke-linecap": "round" });
|
|
55
|
+
group.append(svgNode("path", { d: quotaArc(start, end, clockwise), opacity: 0.18 }));
|
|
56
|
+
if (windows[key] > 0) {
|
|
57
|
+
const remainingStart = start + (end - start) * (1 - windows[key] / 100);
|
|
58
|
+
group.append(svgNode("path", { d: quotaArc(remainingStart, end, clockwise) }));
|
|
59
|
+
}
|
|
60
|
+
svg.append(group);
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
const key = "weekly", radius = 43 / 3;
|
|
43
64
|
const group = svgNode("g", { "data-window": key, fill: "none", stroke: "currentColor", "stroke-width": 10 / 3 });
|
|
44
65
|
group.append(svgNode("circle", { cx: 16, cy: 16, r: radius, opacity: 0.18 }));
|
|
45
66
|
if (windows[key] > 0) group.append(svgNode("circle", {
|
package/package.json
CHANGED
|
@@ -101,7 +101,42 @@ export function createCodexEnvironment() {
|
|
|
101
101
|
const scope = node && props(node, value => Boolean((value.router ?? value.value?.router)?.state?.location));
|
|
102
102
|
return (scope?.router ?? scope?.value?.router)?.state?.location?.pathname ?? null;
|
|
103
103
|
}
|
|
104
|
-
|
|
104
|
+
function account(signal, invalidate) {
|
|
105
|
+
let client, unsubscribe;
|
|
106
|
+
const cleanup = () => { unsubscribe?.(); unsubscribe = null; client = null; };
|
|
107
|
+
signal?.addEventListener("abort", cleanup, {once:true});
|
|
108
|
+
return {read() {
|
|
109
|
+
let auth, nextClient;
|
|
110
|
+
props(rail(), value => {
|
|
111
|
+
for (const candidate of [value.auth, value.value]) {
|
|
112
|
+
if (candidate && "authMethod" in Object(candidate) && "accountId" in Object(candidate)) auth ||= candidate;
|
|
113
|
+
}
|
|
114
|
+
for (const candidate of [value.queryClient, value.client, value.value]) {
|
|
115
|
+
if (typeof candidate?.getQueryData === "function" && typeof candidate?.getQueryCache === "function") nextClient ||= candidate;
|
|
116
|
+
}
|
|
117
|
+
return Boolean(auth && nextClient);
|
|
118
|
+
});
|
|
119
|
+
if (nextClient !== client) {
|
|
120
|
+
cleanup(); client = nextClient;
|
|
121
|
+
if (invalidate && !signal?.aborted) unsubscribe = client?.getQueryCache().subscribe(event => {
|
|
122
|
+
if (event.query?.queryKey?.[0] === "accounts") invalidate();
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
const unknown = {status:"unknown", planType:null, source:null, key:null};
|
|
126
|
+
if (signal?.aborted || !auth || auth.isLoading || !auth.userId || !auth.accountId
|
|
127
|
+
|| !["chatgpt", "chatgptAuthTokens"].includes(auth.authMethod)) return unknown;
|
|
128
|
+
const data = client?.getQueryData(["accounts", "check"]);
|
|
129
|
+
const current = data?.accounts?.find(value => value.id === auth.accountId);
|
|
130
|
+
// 已有账号清单却找不到当前身份时,不借用上一个账号的登录套餐。
|
|
131
|
+
if (data && !current) return unknown;
|
|
132
|
+
const cachedPlan = typeof current?.plan_type === "string" ? current.plan_type : null;
|
|
133
|
+
const loginPlan = typeof auth.planAtLogin === "string" ? auth.planAtLogin : null;
|
|
134
|
+
const planType = cachedPlan ?? loginPlan;
|
|
135
|
+
if (!planType || planType === "unknown") return unknown;
|
|
136
|
+
return {status:"available", planType, source:cachedPlan ? "accounts/check" : "planAtLogin", key:JSON.stringify([auth.userId, auth.accountId])};
|
|
137
|
+
}};
|
|
138
|
+
}
|
|
139
|
+
return { composer, composerSurface, rail, railSlot, route, navigation, account, mcpAppSource, currentThreadId:element => composer(element)?.threadId ?? null,
|
|
105
140
|
// 仅供 UI/插件 native Adapter 使用,交互层不读取 React props。
|
|
106
141
|
currentFiber, props, report, diagnostics:() => [...capabilities.values()].map(item => ({...item})) };
|
|
107
142
|
}
|