fluxplugin 1.0.3 → 1.0.4
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 +15 -12
- package/dist/browser/chunks/chunk-AV4KI7LI.js +1792 -0
- package/dist/browser/chunks/{chunk-TL63576R.js → chunk-OAYWYU5O.js} +48 -11
- package/dist/browser/index.js +3 -1
- package/dist/browser/manager-ui.js +386 -185
- package/dist/browser/react.js +46 -2
- package/dist/cli.js +3622 -257
- package/dist/node/chunks/{chunk-EVX2O65A.js → chunk-SDCGUBEW.js} +49 -11
- package/dist/node/chunks/{chunk-C7VWZZOX.js → chunk-ZLA5XUXN.js} +175 -51
- package/dist/node/cli-api.js +742 -76
- package/dist/node/index.js +3 -1
- package/dist/node/main.js +8 -2
- package/dist/node/preload.js +1 -1
- package/dist/types/cli/build.d.ts +5 -0
- package/dist/types/cli/cli.d.ts +2 -0
- package/dist/types/cli/dist.d.ts +18 -0
- package/dist/types/cli/host-templates.d.ts +2 -0
- package/dist/types/cli/store.d.ts +13 -0
- package/dist/types/cli/templates.d.ts +2 -0
- package/dist/types/core/manifest.d.ts +15 -1
- package/dist/types/core/protocol.d.ts +18 -5
- package/dist/types/core/registry.d.ts +11 -0
- package/dist/types/core/routes.d.ts +3 -1
- package/dist/types/main/adapters.d.ts +3 -0
- package/dist/types/main/assets.d.ts +8 -0
- package/dist/types/main/github-registry.d.ts +2 -1
- package/dist/types/main/index.d.ts +1 -0
- package/dist/types/main/registry.d.ts +55 -18
- package/dist/types/main/stores.d.ts +7 -1
- package/dist/types/manager-ui/index.d.ts +35 -3
- package/dist/types/react/base-css.d.ts +6 -2
- package/dist/types/react/http-bridge.d.ts +5 -0
- package/dist/types/react/i18n.d.ts +33 -0
- package/dist/types/react/index.d.ts +5 -0
- package/dist/types/react/markdown.d.ts +2 -1
- package/dist/types/react/media.d.ts +33 -0
- package/dist/types/react/settings.d.ts +6 -0
- package/dist/types/react/sidebar.d.ts +42 -0
- package/dist/types/react/theme.d.ts +42 -4
- package/dist/types/react/ui.d.ts +42 -0
- package/docs/CUSTOMIZING.md +56 -0
- package/docs/STORE.md +19 -0
- package/docs/screenshots/plugin-page.png +0 -0
- package/docs/screenshots/settings-appearance.png +0 -0
- package/docs/screenshots/site-guide-dark.png +0 -0
- package/docs/screenshots/site-home.png +0 -0
- package/docs/screenshots/site-mobile.png +0 -0
- package/docs/screenshots/site-search.png +0 -0
- package/package.json +1 -1
- package/dist/browser/chunks/chunk-VXQJIDXW.js +0 -1232
|
@@ -0,0 +1,1792 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MGMT,
|
|
3
|
+
PermissionManager,
|
|
4
|
+
PluginManager,
|
|
5
|
+
errorMessage,
|
|
6
|
+
normalizePath,
|
|
7
|
+
s,
|
|
8
|
+
scopeCss
|
|
9
|
+
} from "./chunk-OAYWYU5O.js";
|
|
10
|
+
|
|
11
|
+
// src/react/runtime.ts
|
|
12
|
+
var RemoteError = class extends Error {
|
|
13
|
+
constructor(payload) {
|
|
14
|
+
super(payload.message);
|
|
15
|
+
this.payload = payload;
|
|
16
|
+
this.name = "RemoteError";
|
|
17
|
+
}
|
|
18
|
+
payload;
|
|
19
|
+
get code() {
|
|
20
|
+
return this.payload.code;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
var PluginsClient = class {
|
|
24
|
+
constructor(bridge) {
|
|
25
|
+
this.bridge = bridge;
|
|
26
|
+
}
|
|
27
|
+
bridge;
|
|
28
|
+
call(channel, input) {
|
|
29
|
+
return this.bridge.invoke(channel, input);
|
|
30
|
+
}
|
|
31
|
+
list = () => this.call(MGMT.list);
|
|
32
|
+
enable = (id) => this.call(MGMT.enable, { id });
|
|
33
|
+
disable = (id) => this.call(MGMT.disable, { id });
|
|
34
|
+
reload = (id) => this.call(MGMT.reload, { id });
|
|
35
|
+
grant = (id, permission) => this.call(MGMT.grant, { id, permission });
|
|
36
|
+
revoke = (id, permission) => this.call(MGMT.revoke, { id, permission });
|
|
37
|
+
uninstall = (id) => this.call(MGMT.uninstall, { id });
|
|
38
|
+
search = (query) => this.call(MGMT.search, { query });
|
|
39
|
+
updates = () => this.call(MGMT.updates);
|
|
40
|
+
install = (entry) => this.call(MGMT.install, { entry });
|
|
41
|
+
update = (entry) => this.call(MGMT.update, { entry });
|
|
42
|
+
rollback = (id, version) => this.call(MGMT.rollback, { id, version });
|
|
43
|
+
configGet = (id) => this.call(MGMT.configGet, { id });
|
|
44
|
+
configSet = (id, patch) => this.call(MGMT.configSet, { id, patch });
|
|
45
|
+
configReset = (id) => this.call(MGMT.configReset, { id });
|
|
46
|
+
configSchema = (id) => this.call(MGMT.configSchema, { id });
|
|
47
|
+
logs = (id) => this.call(MGMT.logs, { id });
|
|
48
|
+
details = (id) => this.call(MGMT.details, { id });
|
|
49
|
+
diagnostics = (id) => this.call(MGMT.diagnostics, { id });
|
|
50
|
+
};
|
|
51
|
+
var asDefinition = (mod) => {
|
|
52
|
+
const d = mod && typeof mod === "object" && "default" in mod ? mod.default : mod;
|
|
53
|
+
if (!d || typeof d !== "object") throw new Error("renderer entry must default-export definePlugin({...})");
|
|
54
|
+
return d;
|
|
55
|
+
};
|
|
56
|
+
var RendererFramework = class {
|
|
57
|
+
constructor(opts) {
|
|
58
|
+
this.opts = opts;
|
|
59
|
+
this.bridge = opts.bridge;
|
|
60
|
+
this.client = new PluginsClient(opts.bridge);
|
|
61
|
+
this.manager = new PluginManager({
|
|
62
|
+
runtime: "renderer",
|
|
63
|
+
permissions: this.permissions,
|
|
64
|
+
ipcBridge: {
|
|
65
|
+
invoke: (name, input, caller) => opts.bridge.invoke(name, input, caller),
|
|
66
|
+
send: (channel, payload, caller) => opts.bridge.send(channel, payload, caller),
|
|
67
|
+
on: (channel, fn) => ({ dispose: opts.bridge.on(channel, fn) })
|
|
68
|
+
},
|
|
69
|
+
// Renderer plugins that crash must never take the app down; the main side persists auto-disable.
|
|
70
|
+
...opts.manager
|
|
71
|
+
});
|
|
72
|
+
this.manager.subscribe(() => this.touch());
|
|
73
|
+
}
|
|
74
|
+
opts;
|
|
75
|
+
bridge;
|
|
76
|
+
client;
|
|
77
|
+
manager;
|
|
78
|
+
permissions = new PermissionManager({ autoGrant: false });
|
|
79
|
+
revision = -1;
|
|
80
|
+
bootstrap;
|
|
81
|
+
syncing = Promise.resolve();
|
|
82
|
+
renderErrors = /* @__PURE__ */ new Map();
|
|
83
|
+
unsubscribers = [];
|
|
84
|
+
storeListeners = /* @__PURE__ */ new Set();
|
|
85
|
+
_storeVersion = 0;
|
|
86
|
+
/** `useSyncExternalStore` plumbing for the bootstrap-derived view (plugin list etc.). */
|
|
87
|
+
get storeVersion() {
|
|
88
|
+
return this._storeVersion + this.manager.version;
|
|
89
|
+
}
|
|
90
|
+
subscribe = (l) => {
|
|
91
|
+
this.storeListeners.add(l);
|
|
92
|
+
const off = this.manager.subscribe(l);
|
|
93
|
+
return () => {
|
|
94
|
+
this.storeListeners.delete(l);
|
|
95
|
+
off();
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
touch() {
|
|
99
|
+
this._storeVersion++;
|
|
100
|
+
for (const l of [...this.storeListeners]) l();
|
|
101
|
+
}
|
|
102
|
+
get currentBootstrap() {
|
|
103
|
+
return this.bootstrap;
|
|
104
|
+
}
|
|
105
|
+
/** Fetches the bootstrap, activates enabled plugins and starts listening for changes. */
|
|
106
|
+
async start() {
|
|
107
|
+
await this.sync();
|
|
108
|
+
this.unsubscribers.push(
|
|
109
|
+
this.bridge.on(MGMT.changed, () => {
|
|
110
|
+
void this.sync().catch((e) => console.error("[fluxplugin] sync failed", e));
|
|
111
|
+
}),
|
|
112
|
+
this.bridge.on("flux.notification", (n) => void this.manager.notifications.push(n, n?.owner ?? "main")),
|
|
113
|
+
this.bridge.on("flux.event.plugin.hotreload", ({ id } = {}) => void this.hotReload(id))
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
stop() {
|
|
117
|
+
for (const u of this.unsubscribers.splice(0)) u();
|
|
118
|
+
}
|
|
119
|
+
hotReload(id) {
|
|
120
|
+
this.cacheBust = Date.now();
|
|
121
|
+
return this.sync(true).then(async () => {
|
|
122
|
+
if (id && this.manager.has(id)) await this.manager.reload(id).catch(() => void 0);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
cacheBust = 0;
|
|
126
|
+
/** Mirrors main-process state into the renderer manager. Calls are serialized. */
|
|
127
|
+
sync(force = false) {
|
|
128
|
+
const run = this.syncing.then(async () => {
|
|
129
|
+
const boot = await this.bridge.bootstrap();
|
|
130
|
+
if (!force && boot.revision === this.revision && this.bootstrap) return;
|
|
131
|
+
this.revision = boot.revision;
|
|
132
|
+
this.bootstrap = boot;
|
|
133
|
+
await this.reconcile(boot);
|
|
134
|
+
this.installWindowPlugins(boot.preload);
|
|
135
|
+
this.touch();
|
|
136
|
+
});
|
|
137
|
+
this.syncing = run.catch(() => void 0);
|
|
138
|
+
return run;
|
|
139
|
+
}
|
|
140
|
+
async reconcile(boot) {
|
|
141
|
+
const seen = /* @__PURE__ */ new Set();
|
|
142
|
+
for (const d of boot.plugins) {
|
|
143
|
+
seen.add(d.id);
|
|
144
|
+
this.permissions.restore({ [d.id]: d.granted });
|
|
145
|
+
if (!this.manager.has(d.id)) {
|
|
146
|
+
if (d.state === "error" && !d.rendererUrl) continue;
|
|
147
|
+
this.manager.register({ manifest: d.manifest, origin: "renderer", load: () => this.load(d) });
|
|
148
|
+
this.permissions.restore({ [d.id]: d.granted });
|
|
149
|
+
} else {
|
|
150
|
+
this.permissions.restore({ [d.id]: d.granted });
|
|
151
|
+
}
|
|
152
|
+
const info = this.manager.info(d.id);
|
|
153
|
+
if (!d.enabled && info.enabled) await this.manager.disable(d.id).catch(() => void 0);
|
|
154
|
+
else if (d.enabled && !info.enabled) await this.manager.enable(d.id, { activate: false }).catch(() => void 0);
|
|
155
|
+
}
|
|
156
|
+
for (const info of this.manager.list()) if (!seen.has(info.id)) await this.manager.unregister(info.id);
|
|
157
|
+
await this.manager.startup();
|
|
158
|
+
}
|
|
159
|
+
async load(d) {
|
|
160
|
+
let def = {};
|
|
161
|
+
if (d.rendererUrl) {
|
|
162
|
+
const url = this.cacheBust ? `${d.rendererUrl}${d.rendererUrl.includes("?") ? "&" : "?"}flux=${this.cacheBust}` : d.rendererUrl;
|
|
163
|
+
const mod = await (this.opts.importPlugin ? this.opts.importPlugin(url, d) : import(
|
|
164
|
+
/* @vite-ignore */
|
|
165
|
+
url
|
|
166
|
+
));
|
|
167
|
+
def = asDefinition(mod);
|
|
168
|
+
}
|
|
169
|
+
const hrefs = d.styleUrls;
|
|
170
|
+
if (!hrefs.length) return def;
|
|
171
|
+
const activate = def.onActivate ?? def.activate;
|
|
172
|
+
return {
|
|
173
|
+
...def,
|
|
174
|
+
onActivate: async (ctx) => {
|
|
175
|
+
hrefs.forEach((href, i) => ctx.lifecycle.subscriptions.add(this.manager.styles.register({ id: `manifest-${i}`, href, priority: 50 }, ctx.pluginId)));
|
|
176
|
+
await activate?.(ctx);
|
|
177
|
+
},
|
|
178
|
+
activate: void 0
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/** Called by plugin error boundaries. Repeated render failures disable the plugin. */
|
|
182
|
+
reportRenderError(pluginId, error) {
|
|
183
|
+
this.manager.logs.logger(pluginId).error(`render error: ${errorMessage(error)}`, error instanceof Error ? error.stack : void 0);
|
|
184
|
+
const now = Date.now();
|
|
185
|
+
const list = [...(this.renderErrors.get(pluginId) ?? []).filter((t) => now - t < 6e4), now];
|
|
186
|
+
this.renderErrors.set(pluginId, list);
|
|
187
|
+
if (list.length >= (this.opts.maxRenderErrors ?? 3) && this.manager.has(pluginId) && this.manager.info(pluginId).enabled) {
|
|
188
|
+
this.manager.logs.logger(pluginId).warn("disabled automatically after repeated render errors");
|
|
189
|
+
this.renderErrors.delete(pluginId);
|
|
190
|
+
void this.client.disable(pluginId).catch(() => this.manager.disable(pluginId));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/** Builds `window.plugins.<ns>.<member>` from the preload manifest. Rebuilt on every sync. */
|
|
194
|
+
installWindowPlugins(spec) {
|
|
195
|
+
if (typeof window === "undefined") return;
|
|
196
|
+
window.plugins = buildPluginApis(this.bridge, spec);
|
|
197
|
+
}
|
|
198
|
+
/** Config handle usable from React (mirrors ctx.config through the management API). */
|
|
199
|
+
configOf(id) {
|
|
200
|
+
return {
|
|
201
|
+
get: () => this.client.configGet(id),
|
|
202
|
+
set: (p) => this.client.configSet(id, p),
|
|
203
|
+
reset: () => this.client.configReset(id)
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
function buildPluginApis(bridge, spec) {
|
|
208
|
+
const out = {};
|
|
209
|
+
for (const [ns, members] of Object.entries(spec)) {
|
|
210
|
+
const api = /* @__PURE__ */ Object.create(null);
|
|
211
|
+
for (const [name, m] of Object.entries(members)) {
|
|
212
|
+
if (m.kind === "invoke") api[name] = (input) => bridge.invoke(m.channel, input, ns);
|
|
213
|
+
else if (m.kind === "send") api[name] = (payload) => bridge.send(m.channel, payload, ns);
|
|
214
|
+
else api[name] = (listener) => bridge.on(m.channel, listener);
|
|
215
|
+
}
|
|
216
|
+
out[ns] = Object.freeze(api);
|
|
217
|
+
}
|
|
218
|
+
return Object.freeze(out);
|
|
219
|
+
}
|
|
220
|
+
function installSharedModules(modules) {
|
|
221
|
+
const g = globalThis;
|
|
222
|
+
g.__FLUXPLUGIN_SHARED__ = { ...g.__FLUXPLUGIN_SHARED__ ?? {}, ...modules };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/react/components.tsx
|
|
226
|
+
import {
|
|
227
|
+
Component,
|
|
228
|
+
createContext,
|
|
229
|
+
useCallback as useCallback2,
|
|
230
|
+
useContext,
|
|
231
|
+
useEffect as useEffect2,
|
|
232
|
+
useMemo as useMemo2,
|
|
233
|
+
useRef,
|
|
234
|
+
useState as useState2,
|
|
235
|
+
useSyncExternalStore as useSyncExternalStore2
|
|
236
|
+
} from "react";
|
|
237
|
+
|
|
238
|
+
// src/react/router.tsx
|
|
239
|
+
import {
|
|
240
|
+
Suspense,
|
|
241
|
+
lazy,
|
|
242
|
+
useCallback,
|
|
243
|
+
useEffect,
|
|
244
|
+
useMemo,
|
|
245
|
+
useState,
|
|
246
|
+
useSyncExternalStore
|
|
247
|
+
} from "react";
|
|
248
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
249
|
+
function currentPath() {
|
|
250
|
+
if (typeof window === "undefined") return "/";
|
|
251
|
+
const h = window.location.hash.replace(/^#/, "");
|
|
252
|
+
return normalizePath(h || "/");
|
|
253
|
+
}
|
|
254
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
255
|
+
if (typeof window !== "undefined") window.addEventListener("hashchange", () => listeners.forEach((l) => l()));
|
|
256
|
+
var subscribeLocation = (l) => (listeners.add(l), () => void listeners.delete(l));
|
|
257
|
+
function navigate(to, opts = {}) {
|
|
258
|
+
const target = "#" + normalizePath(to) + (to.includes("?") ? "?" + to.split("?")[1] : "");
|
|
259
|
+
if (opts.replace) window.location.replace(target);
|
|
260
|
+
else window.location.hash = target;
|
|
261
|
+
listeners.forEach((l) => l());
|
|
262
|
+
}
|
|
263
|
+
function useLocation() {
|
|
264
|
+
return useSyncExternalStore(subscribeLocation, currentPath, () => "/");
|
|
265
|
+
}
|
|
266
|
+
function useNavigate() {
|
|
267
|
+
return navigate;
|
|
268
|
+
}
|
|
269
|
+
function Link({ to, children, ...rest }) {
|
|
270
|
+
const here = useLocation();
|
|
271
|
+
const active = here === normalizePath(to) || to !== "/" && here.startsWith(normalizePath(to) + "/");
|
|
272
|
+
return /* @__PURE__ */ jsx("a", { ...rest, href: "#" + normalizePath(to), "data-active": active || void 0, "aria-current": active ? "page" : void 0, children });
|
|
273
|
+
}
|
|
274
|
+
function usePluginRoute() {
|
|
275
|
+
const fw = usePluginFramework();
|
|
276
|
+
const path = useLocation();
|
|
277
|
+
const routes = fw.manager.routes;
|
|
278
|
+
const version = useSyncExternalStore(routes.subscribe, () => routes.version, () => routes.version);
|
|
279
|
+
return useMemo(() => {
|
|
280
|
+
const match = routes.match(path);
|
|
281
|
+
return { match, params: match?.params ?? {}, breadcrumbs: match ? routes.breadcrumbs(match) : [], path };
|
|
282
|
+
}, [routes, path, version]);
|
|
283
|
+
}
|
|
284
|
+
var useParams = () => usePluginRoute().params;
|
|
285
|
+
function Breadcrumbs({ separator = "/" }) {
|
|
286
|
+
const { breadcrumbs } = usePluginRoute();
|
|
287
|
+
if (!breadcrumbs.length) return null;
|
|
288
|
+
return /* @__PURE__ */ jsx("nav", { "aria-label": "Breadcrumb", className: "flux-breadcrumbs", children: breadcrumbs.map((b, i) => /* @__PURE__ */ jsxs("span", { children: [
|
|
289
|
+
i > 0 && /* @__PURE__ */ jsxs("span", { className: "flux-breadcrumb-sep", children: [
|
|
290
|
+
" ",
|
|
291
|
+
separator,
|
|
292
|
+
" "
|
|
293
|
+
] }),
|
|
294
|
+
i === breadcrumbs.length - 1 ? /* @__PURE__ */ jsx("span", { "aria-current": "page", children: b.label }) : /* @__PURE__ */ jsx(Link, { to: b.path, children: b.label })
|
|
295
|
+
] }, b.path)) });
|
|
296
|
+
}
|
|
297
|
+
var lazyCache = /* @__PURE__ */ new WeakMap();
|
|
298
|
+
function resolveComponent(def) {
|
|
299
|
+
if (def.component) return def.component;
|
|
300
|
+
const loader = def.lazy;
|
|
301
|
+
let c = lazyCache.get(loader);
|
|
302
|
+
if (!c) {
|
|
303
|
+
c = lazy(async () => {
|
|
304
|
+
const mod = await loader();
|
|
305
|
+
const comp = mod && typeof mod === "object" && "default" in mod ? mod.default : mod;
|
|
306
|
+
return { default: comp };
|
|
307
|
+
});
|
|
308
|
+
lazyCache.set(loader, c);
|
|
309
|
+
}
|
|
310
|
+
return c;
|
|
311
|
+
}
|
|
312
|
+
function PluginRoutes({ notFound, forbidden, loading, defaultLayout: DefaultLayout }) {
|
|
313
|
+
const fw = usePluginFramework();
|
|
314
|
+
const path = useLocation();
|
|
315
|
+
const routes = fw.manager.routes;
|
|
316
|
+
useSyncExternalStore(routes.subscribe, () => routes.version, () => routes.version);
|
|
317
|
+
const [resolution, setResolution] = useState(null);
|
|
318
|
+
const version = routes.version;
|
|
319
|
+
useEffect(() => {
|
|
320
|
+
let live = true;
|
|
321
|
+
(async () => {
|
|
322
|
+
const match2 = await routes.matchAsync(path);
|
|
323
|
+
if (!match2) return live && setResolution({ path });
|
|
324
|
+
const guard2 = await routes.checkGuards(match2, path);
|
|
325
|
+
if (live) setResolution({ path, match: match2, guard: guard2 });
|
|
326
|
+
})().catch(() => live && setResolution({ path }));
|
|
327
|
+
return () => {
|
|
328
|
+
live = false;
|
|
329
|
+
};
|
|
330
|
+
}, [routes, path, version]);
|
|
331
|
+
useEffect(() => {
|
|
332
|
+
if (resolution?.path === path && resolution.guard && !resolution.guard.allow && resolution.guard.redirect) navigate(resolution.guard.redirect, { replace: true });
|
|
333
|
+
}, [resolution, path]);
|
|
334
|
+
if (!resolution || resolution.path !== path) return /* @__PURE__ */ jsx(Fragment, { children: loading ?? null });
|
|
335
|
+
const { match, guard } = resolution;
|
|
336
|
+
if (!match) return /* @__PURE__ */ jsx(Fragment, { children: notFound ?? /* @__PURE__ */ jsxs("div", { className: "flux-not-found", children: [
|
|
337
|
+
"Page not found: ",
|
|
338
|
+
path
|
|
339
|
+
] }) });
|
|
340
|
+
if (guard && !guard.allow) {
|
|
341
|
+
if (guard.redirect) return /* @__PURE__ */ jsx(Fragment, { children: loading ?? null });
|
|
342
|
+
return /* @__PURE__ */ jsx(Fragment, { children: forbidden ? forbidden(guard.reason) : /* @__PURE__ */ jsxs("div", { className: "flux-forbidden", children: [
|
|
343
|
+
"Access denied: ",
|
|
344
|
+
guard.reason
|
|
345
|
+
] }) });
|
|
346
|
+
}
|
|
347
|
+
const owner = match.route.owner ?? "host";
|
|
348
|
+
const Page = resolveComponent(match.route.definition);
|
|
349
|
+
let node = /* @__PURE__ */ jsx(Page, { params: match.params });
|
|
350
|
+
for (const r of [...match.chain].reverse()) {
|
|
351
|
+
const l = r.definition.layout;
|
|
352
|
+
if (!l) continue;
|
|
353
|
+
const Layout = typeof l === "string" ? routes.getLayout(l) : l;
|
|
354
|
+
if (Layout) node = /* @__PURE__ */ jsx(Layout, { children: node });
|
|
355
|
+
}
|
|
356
|
+
if (!match.chain.some((r) => r.definition.layout) && DefaultLayout) node = /* @__PURE__ */ jsx(DefaultLayout, { children: node });
|
|
357
|
+
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(PluginBoundary, { owner, children: /* @__PURE__ */ jsx(Suspense, { fallback: loading ?? /* @__PURE__ */ jsx("div", { className: "flux-loading", children: "Loading\u2026" }), children: node }) }, match.route.fullPath) });
|
|
358
|
+
}
|
|
359
|
+
function useMenu(location2) {
|
|
360
|
+
const fw = usePluginFramework();
|
|
361
|
+
const menus = fw.manager.menus;
|
|
362
|
+
useSyncExternalStore(menus.subscribe, () => menus.version, () => menus.version);
|
|
363
|
+
const run = useCallback(
|
|
364
|
+
(item) => {
|
|
365
|
+
if (item.command) return fw.manager.commands.execute(item.command, ...item.args ?? []);
|
|
366
|
+
if (item.path) navigate(item.path);
|
|
367
|
+
},
|
|
368
|
+
[fw]
|
|
369
|
+
);
|
|
370
|
+
return { items: menus.items(location2), run };
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// src/react/components.tsx
|
|
374
|
+
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
375
|
+
var FrameworkContext = createContext(null);
|
|
376
|
+
function PluginProvider({ framework, children }) {
|
|
377
|
+
return /* @__PURE__ */ jsx2(FrameworkContext.Provider, { value: framework, children });
|
|
378
|
+
}
|
|
379
|
+
function usePluginFramework() {
|
|
380
|
+
const fw = useContext(FrameworkContext);
|
|
381
|
+
if (!fw) throw new Error("usePlugin* hooks must be used inside <PluginProvider>");
|
|
382
|
+
return fw;
|
|
383
|
+
}
|
|
384
|
+
var PluginErrorBoundary = class extends Component {
|
|
385
|
+
state = { error: null };
|
|
386
|
+
static getDerivedStateFromError(error) {
|
|
387
|
+
return { error };
|
|
388
|
+
}
|
|
389
|
+
componentDidCatch(error, _info) {
|
|
390
|
+
this.props.framework.reportRenderError(this.props.owner, error);
|
|
391
|
+
}
|
|
392
|
+
reset = () => this.setState({ error: null });
|
|
393
|
+
render() {
|
|
394
|
+
const { error } = this.state;
|
|
395
|
+
if (!error) return this.props.children;
|
|
396
|
+
if (this.props.fallback) return this.props.fallback(error, this.reset);
|
|
397
|
+
return /* @__PURE__ */ jsxs2("div", { role: "alert", className: "fluxplugin-error", "data-flux-error": this.props.owner, children: [
|
|
398
|
+
/* @__PURE__ */ jsx2("strong", { children: this.props.owner }),
|
|
399
|
+
" failed to render.",
|
|
400
|
+
" ",
|
|
401
|
+
/* @__PURE__ */ jsx2("button", { type: "button", onClick: this.reset, children: "Retry" })
|
|
402
|
+
] });
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
function PluginBoundary({ owner, children }) {
|
|
406
|
+
const fw = usePluginFramework();
|
|
407
|
+
return /* @__PURE__ */ jsx2("div", { "data-fluxplugin": owner, style: { display: "contents" }, children: /* @__PURE__ */ jsx2(PluginErrorBoundary, { owner, framework: fw, children }) });
|
|
408
|
+
}
|
|
409
|
+
function useRegistryVersion(subscribe, version) {
|
|
410
|
+
return useSyncExternalStore2(subscribe, version, version);
|
|
411
|
+
}
|
|
412
|
+
function renderContribution(c, props) {
|
|
413
|
+
const C = c.component;
|
|
414
|
+
return /* @__PURE__ */ jsx2(PluginBoundary, { owner: c.owner ?? "unknown", children: /* @__PURE__ */ jsx2(C, { ...c.props ?? {}, ...props }) }, c.id);
|
|
415
|
+
}
|
|
416
|
+
function PluginSlot({ name, children, props = {} }) {
|
|
417
|
+
const fw = usePluginFramework();
|
|
418
|
+
useRegistryVersion(fw.manager.slots.subscribe, () => fw.manager.slots.version);
|
|
419
|
+
const resolved = fw.manager.slots.resolve(name, props);
|
|
420
|
+
return /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
421
|
+
resolved.before.map((c) => renderContribution(c, props)),
|
|
422
|
+
resolved.replacement ? renderContribution(resolved.replacement, { ...props, children }) : children,
|
|
423
|
+
resolved.after.map((c) => renderContribution(c, props))
|
|
424
|
+
] });
|
|
425
|
+
}
|
|
426
|
+
function useExtensionItems(point, key) {
|
|
427
|
+
const fw = usePluginFramework();
|
|
428
|
+
const version = useRegistryVersion(fw.manager.extensionPoints.subscribe, () => fw.manager.extensionPoints.version);
|
|
429
|
+
return useMemo2(() => fw.manager.extensionPoints.items(point, key), [fw, point, key, version]);
|
|
430
|
+
}
|
|
431
|
+
function usePlugin(id) {
|
|
432
|
+
const fw = usePluginFramework();
|
|
433
|
+
useRegistryVersion(fw.subscribe, () => fw.storeVersion);
|
|
434
|
+
return fw.manager.has(id) ? fw.manager.info(id) : void 0;
|
|
435
|
+
}
|
|
436
|
+
function usePlugins() {
|
|
437
|
+
const fw = usePluginFramework();
|
|
438
|
+
useRegistryVersion(fw.subscribe, () => fw.storeVersion);
|
|
439
|
+
return fw.manager.list();
|
|
440
|
+
}
|
|
441
|
+
function usePluginState(pluginId, key, initial) {
|
|
442
|
+
const fw = usePluginFramework();
|
|
443
|
+
const store = fw.manager.stateOf(pluginId);
|
|
444
|
+
const sub = useCallback2((l) => store.subscribe(l, key).dispose, [store, key]);
|
|
445
|
+
const value = useSyncExternalStore2(
|
|
446
|
+
sub,
|
|
447
|
+
() => store.get(key, initial),
|
|
448
|
+
() => initial
|
|
449
|
+
);
|
|
450
|
+
const set = useCallback2((v) => store.set(key, typeof v === "function" ? v(store.get(key, initial)) : v), [store, key, initial]);
|
|
451
|
+
return [value, set];
|
|
452
|
+
}
|
|
453
|
+
function usePluginEvent(name, onEvent) {
|
|
454
|
+
const fw = usePluginFramework();
|
|
455
|
+
const [last, setLast] = useState2(void 0);
|
|
456
|
+
const cb = useRef(onEvent);
|
|
457
|
+
cb.current = onEvent;
|
|
458
|
+
useEffect2(() => {
|
|
459
|
+
const handle = (p) => {
|
|
460
|
+
setLast(p);
|
|
461
|
+
cb.current?.(p);
|
|
462
|
+
};
|
|
463
|
+
const subs = [fw.manager.events.on(name, (p) => handle(p)), fw.bridge.on(name, handle), fw.bridge.on(`flux.event.${name}`, handle)];
|
|
464
|
+
return () => {
|
|
465
|
+
subs.forEach((s2) => typeof s2 === "function" ? s2() : s2.dispose());
|
|
466
|
+
};
|
|
467
|
+
}, [fw, name]);
|
|
468
|
+
return last;
|
|
469
|
+
}
|
|
470
|
+
function usePluginCommand(id) {
|
|
471
|
+
const fw = usePluginFramework();
|
|
472
|
+
useRegistryVersion(fw.manager.commands.subscribe, () => fw.manager.commands.version);
|
|
473
|
+
const cmd = fw.manager.commands.get(id);
|
|
474
|
+
const run = useCallback2((...args) => fw.manager.commands.execute(id, ...args), [fw, id]);
|
|
475
|
+
return { run, available: !!cmd && (!cmd.when || cmd.when()), title: cmd?.title };
|
|
476
|
+
}
|
|
477
|
+
function usePluginService(name) {
|
|
478
|
+
const fw = usePluginFramework();
|
|
479
|
+
useRegistryVersion(fw.manager.services.subscribe, () => fw.manager.services.version);
|
|
480
|
+
useEffect2(() => {
|
|
481
|
+
if (!fw.manager.services.has(name)) void fw.manager.services.resolveAsync(name).catch(() => void 0);
|
|
482
|
+
}, [fw, name]);
|
|
483
|
+
return fw.manager.services.resolve(name);
|
|
484
|
+
}
|
|
485
|
+
function usePluginIPC(endpoint, opts = {}) {
|
|
486
|
+
const fw = usePluginFramework();
|
|
487
|
+
const name = typeof endpoint === "string" ? endpoint : endpoint.name;
|
|
488
|
+
const [state, setState] = useState2({ data: void 0, error: void 0, loading: false });
|
|
489
|
+
const alive = useRef(true);
|
|
490
|
+
useEffect2(() => () => void (alive.current = false), []);
|
|
491
|
+
const call = useCallback2(
|
|
492
|
+
async (input) => {
|
|
493
|
+
setState((s2) => ({ ...s2, loading: true, error: void 0 }));
|
|
494
|
+
try {
|
|
495
|
+
const data = await fw.bridge.invoke(name, input, opts.caller);
|
|
496
|
+
if (alive.current) setState({ data, error: void 0, loading: false });
|
|
497
|
+
return data;
|
|
498
|
+
} catch (e) {
|
|
499
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
500
|
+
if (alive.current) setState({ data: void 0, error, loading: false });
|
|
501
|
+
throw error;
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
[fw, name, opts.caller]
|
|
505
|
+
);
|
|
506
|
+
return { ...state, call };
|
|
507
|
+
}
|
|
508
|
+
function usePluginConfig(pluginId) {
|
|
509
|
+
const fw = usePluginFramework();
|
|
510
|
+
const handle = useMemo2(() => fw.configOf(pluginId), [fw, pluginId]);
|
|
511
|
+
const [config, setConfig] = useState2();
|
|
512
|
+
const [error, setError] = useState2();
|
|
513
|
+
const [loading, setLoading] = useState2(true);
|
|
514
|
+
useEffect2(() => {
|
|
515
|
+
let live = true;
|
|
516
|
+
setLoading(true);
|
|
517
|
+
handle.get().then((c) => live && (setConfig(c), setError(void 0))).catch((e) => live && setError(e)).finally(() => live && setLoading(false));
|
|
518
|
+
const sub = fw.manager.events.on("plugin.config.changed", (p) => {
|
|
519
|
+
if (p?.id === pluginId) setConfig(p.next);
|
|
520
|
+
});
|
|
521
|
+
return () => {
|
|
522
|
+
live = false;
|
|
523
|
+
sub.dispose();
|
|
524
|
+
};
|
|
525
|
+
}, [fw, handle, pluginId]);
|
|
526
|
+
return {
|
|
527
|
+
config,
|
|
528
|
+
loading,
|
|
529
|
+
error,
|
|
530
|
+
set: async (patch) => {
|
|
531
|
+
const next = await handle.set(patch);
|
|
532
|
+
setConfig(next);
|
|
533
|
+
return next;
|
|
534
|
+
},
|
|
535
|
+
reset: async () => {
|
|
536
|
+
const next = await handle.reset();
|
|
537
|
+
setConfig(next);
|
|
538
|
+
return next;
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
function StyleHost() {
|
|
543
|
+
const fw = usePluginFramework();
|
|
544
|
+
useRegistryVersion(fw.manager.styles.subscribe, () => fw.manager.styles.version);
|
|
545
|
+
const entries = fw.manager.styles.ordered();
|
|
546
|
+
const sig = entries.map((e) => `${e.owner}/${e.id}/${e.priority}/${e.css?.length ?? 0}/${e.href ?? ""}`).join("|");
|
|
547
|
+
useEffect2(() => {
|
|
548
|
+
const created = [];
|
|
549
|
+
for (const e of entries) {
|
|
550
|
+
let el;
|
|
551
|
+
if (e.href) {
|
|
552
|
+
const link = document.createElement("link");
|
|
553
|
+
link.rel = "stylesheet";
|
|
554
|
+
link.href = e.href;
|
|
555
|
+
el = link;
|
|
556
|
+
} else {
|
|
557
|
+
const style = document.createElement("style");
|
|
558
|
+
style.textContent = e.scoped ? scopeCss(e.css ?? "", e.owner) : e.css ?? "";
|
|
559
|
+
el = style;
|
|
560
|
+
}
|
|
561
|
+
el.setAttribute("data-flux-style", `${e.owner}:${e.id}`);
|
|
562
|
+
document.head.appendChild(el);
|
|
563
|
+
created.push(el);
|
|
564
|
+
}
|
|
565
|
+
return () => created.forEach((el) => el.remove());
|
|
566
|
+
}, [sig]);
|
|
567
|
+
return null;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// src/react/palette.tsx
|
|
571
|
+
import { useEffect as useEffect4, useMemo as useMemo4, useRef as useRef2, useState as useState4, useSyncExternalStore as useSyncExternalStore3 } from "react";
|
|
572
|
+
|
|
573
|
+
// src/react/i18n.tsx
|
|
574
|
+
import { createContext as createContext2, useCallback as useCallback3, useContext as useContext2, useEffect as useEffect3, useMemo as useMemo3, useState as useState3 } from "react";
|
|
575
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
576
|
+
var interpolate = (text, params) => params ? text.replace(/\{(\w+)\}/g, (m, k) => k in params ? String(params[k]) : m) : text;
|
|
577
|
+
var fallbackCtx = { locale: "en", setLocale: () => {
|
|
578
|
+
}, languages: [{ code: "en", label: "English" }], t: (_k, fb, p) => interpolate(fb ?? _k, p) };
|
|
579
|
+
var Ctx = createContext2(fallbackCtx);
|
|
580
|
+
function declareI18nPoints(framework) {
|
|
581
|
+
const ep = framework.manager.extensionPoints;
|
|
582
|
+
if (ep.definition("i18n")) return;
|
|
583
|
+
ep.declare({
|
|
584
|
+
name: "i18n",
|
|
585
|
+
contributions: {
|
|
586
|
+
languages: s.object({ code: s.string(), label: s.string(), nativeLabel: s.string().optional(), rtl: s.boolean().optional() }),
|
|
587
|
+
messages: s.object({ locale: s.string(), messages: s.record(s.string()) })
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
var read = (k) => {
|
|
592
|
+
try {
|
|
593
|
+
return localStorage.getItem(k);
|
|
594
|
+
} catch {
|
|
595
|
+
return null;
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
function I18nProvider({ children, defaultLocale = "en", storageKey = "fluxplugin.locale" }) {
|
|
599
|
+
const extra = useExtensionItems("i18n", "languages");
|
|
600
|
+
const bundles = useExtensionItems("i18n", "messages");
|
|
601
|
+
const [locale, setLocaleState] = useState3(() => read(storageKey) ?? (typeof navigator !== "undefined" ? navigator.language : defaultLocale));
|
|
602
|
+
const languages = useMemo3(() => {
|
|
603
|
+
const map = /* @__PURE__ */ new Map([[defaultLocale, { code: defaultLocale, label: "English", nativeLabel: "English" }]]);
|
|
604
|
+
for (const l of extra) map.set(l.code, l);
|
|
605
|
+
return [...map.values()];
|
|
606
|
+
}, [extra, defaultLocale]);
|
|
607
|
+
const active = useMemo3(() => {
|
|
608
|
+
const codes = languages.map((l) => l.code);
|
|
609
|
+
return codes.includes(locale) ? locale : codes.find((c) => locale.toLowerCase().startsWith(c.toLowerCase() + "-")) ?? defaultLocale;
|
|
610
|
+
}, [locale, languages, defaultLocale]);
|
|
611
|
+
const table = useMemo3(() => {
|
|
612
|
+
const m = /* @__PURE__ */ new Map();
|
|
613
|
+
for (const b of bundles) m.set(b.locale, { ...m.get(b.locale) ?? {}, ...b.messages });
|
|
614
|
+
return m;
|
|
615
|
+
}, [bundles]);
|
|
616
|
+
const t = useCallback3((key, fb, params) => interpolate(table.get(active)?.[key] ?? table.get(active.split("-")[0])?.[key] ?? fb ?? key, params), [table, active]);
|
|
617
|
+
const setLocale = useCallback3((code) => {
|
|
618
|
+
setLocaleState(code);
|
|
619
|
+
try {
|
|
620
|
+
localStorage.setItem(storageKey, code);
|
|
621
|
+
} catch {
|
|
622
|
+
}
|
|
623
|
+
}, [storageKey]);
|
|
624
|
+
useEffect3(() => {
|
|
625
|
+
document.documentElement.lang = active;
|
|
626
|
+
document.documentElement.dir = languages.find((l) => l.code === active)?.rtl ? "rtl" : "ltr";
|
|
627
|
+
}, [active, languages]);
|
|
628
|
+
const value = useMemo3(() => ({ locale: active, setLocale, languages, t }), [active, setLocale, languages, t]);
|
|
629
|
+
return /* @__PURE__ */ jsx3(Ctx.Provider, { value, children });
|
|
630
|
+
}
|
|
631
|
+
var useI18n = () => useContext2(Ctx);
|
|
632
|
+
var useT = () => useContext2(Ctx).t;
|
|
633
|
+
|
|
634
|
+
// src/react/palette.tsx
|
|
635
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
636
|
+
function useKeybindings(opts = {}) {
|
|
637
|
+
const fw = usePluginFramework();
|
|
638
|
+
useEffect4(() => {
|
|
639
|
+
if (opts.enabled === false) return;
|
|
640
|
+
const onKey = (e) => {
|
|
641
|
+
if (e.defaultPrevented) return;
|
|
642
|
+
const target = e.target;
|
|
643
|
+
if (target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) && !(e.ctrlKey || e.metaKey)) return;
|
|
644
|
+
const parts = [];
|
|
645
|
+
if (e.ctrlKey || e.metaKey) parts.push("mod");
|
|
646
|
+
if (e.shiftKey) parts.push("shift");
|
|
647
|
+
if (e.altKey) parts.push("alt");
|
|
648
|
+
const key = e.key.toLowerCase();
|
|
649
|
+
if (["control", "shift", "alt", "meta"].includes(key)) return;
|
|
650
|
+
parts.push(key);
|
|
651
|
+
const cmd = fw.manager.commands.byKeybinding(parts.join("+"));
|
|
652
|
+
if (cmd) {
|
|
653
|
+
e.preventDefault();
|
|
654
|
+
void fw.manager.commands.execute(cmd.id).catch((err) => fw.manager.logs.logger("host").error(`command ${cmd.id} failed: ${String(err)}`));
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
window.addEventListener("keydown", onKey);
|
|
658
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
659
|
+
}, [fw, opts.enabled]);
|
|
660
|
+
}
|
|
661
|
+
function CommandPalette({ open, onClose }) {
|
|
662
|
+
const fw = usePluginFramework();
|
|
663
|
+
const t = useT();
|
|
664
|
+
const cmds = fw.manager.commands;
|
|
665
|
+
useSyncExternalStore3(cmds.subscribe, () => cmds.version, () => cmds.version);
|
|
666
|
+
const [internal, setInternal] = useState4(false);
|
|
667
|
+
const isOpen = open ?? internal;
|
|
668
|
+
const close = () => onClose ? onClose() : setInternal(false);
|
|
669
|
+
const [query, setQuery] = useState4("");
|
|
670
|
+
const [index, setIndex] = useState4(0);
|
|
671
|
+
const input = useRef2(null);
|
|
672
|
+
useEffect4(() => {
|
|
673
|
+
const onKey = (e) => {
|
|
674
|
+
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") {
|
|
675
|
+
e.preventDefault();
|
|
676
|
+
setInternal((v) => !v);
|
|
677
|
+
} else if (e.key === "Escape") close();
|
|
678
|
+
};
|
|
679
|
+
window.addEventListener("keydown", onKey);
|
|
680
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
681
|
+
}, []);
|
|
682
|
+
useEffect4(() => {
|
|
683
|
+
if (isOpen) {
|
|
684
|
+
setQuery("");
|
|
685
|
+
setIndex(0);
|
|
686
|
+
setTimeout(() => input.current?.focus(), 0);
|
|
687
|
+
}
|
|
688
|
+
}, [isOpen]);
|
|
689
|
+
const results = useMemo4(() => cmds.palette(query), [cmds, query, cmds.version]);
|
|
690
|
+
if (!isOpen) return null;
|
|
691
|
+
const run = (i) => {
|
|
692
|
+
const c = results[i];
|
|
693
|
+
if (!c) return;
|
|
694
|
+
close();
|
|
695
|
+
void cmds.execute(c.id).catch((e) => fw.manager.logs.logger("host").error(String(e)));
|
|
696
|
+
};
|
|
697
|
+
return /* @__PURE__ */ jsx4("div", { className: "flux-palette-backdrop", onMouseDown: close, role: "presentation", children: /* @__PURE__ */ jsxs3("div", { className: "flux-palette", role: "dialog", "aria-label": "Command palette", onMouseDown: (e) => e.stopPropagation(), children: [
|
|
698
|
+
/* @__PURE__ */ jsx4(
|
|
699
|
+
"input",
|
|
700
|
+
{
|
|
701
|
+
ref: input,
|
|
702
|
+
className: "flux-palette-input",
|
|
703
|
+
placeholder: t("palette.placeholder", "Type a command\u2026"),
|
|
704
|
+
value: query,
|
|
705
|
+
onChange: (e) => (setQuery(e.target.value), setIndex(0)),
|
|
706
|
+
onKeyDown: (e) => {
|
|
707
|
+
if (e.key === "ArrowDown") e.preventDefault(), setIndex((i) => Math.min(i + 1, results.length - 1));
|
|
708
|
+
else if (e.key === "ArrowUp") e.preventDefault(), setIndex((i) => Math.max(i - 1, 0));
|
|
709
|
+
else if (e.key === "Enter") run(index);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
),
|
|
713
|
+
/* @__PURE__ */ jsxs3("ul", { className: "flux-palette-list", role: "listbox", children: [
|
|
714
|
+
results.length === 0 && /* @__PURE__ */ jsx4("li", { className: "flux-palette-empty", children: t("palette.empty", "No matching commands") }),
|
|
715
|
+
results.map((c, i) => /* @__PURE__ */ jsxs3("li", { role: "option", "aria-selected": i === index, className: "flux-palette-item" + (i === index ? " is-active" : ""), onMouseEnter: () => setIndex(i), onClick: () => run(i), children: [
|
|
716
|
+
/* @__PURE__ */ jsxs3("span", { children: [
|
|
717
|
+
c.category && /* @__PURE__ */ jsxs3("span", { className: "flux-palette-cat", children: [
|
|
718
|
+
c.category,
|
|
719
|
+
": "
|
|
720
|
+
] }),
|
|
721
|
+
c.title
|
|
722
|
+
] }),
|
|
723
|
+
c.keybinding && /* @__PURE__ */ jsx4("kbd", { children: c.keybinding.replace("Mod", /Mac/.test(navigator.platform) ? "\u2318" : "Ctrl") })
|
|
724
|
+
] }, c.id))
|
|
725
|
+
] })
|
|
726
|
+
] }) });
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// src/react/base-css.ts
|
|
730
|
+
var baseCss = `
|
|
731
|
+
:root{--flux-font:-apple-system,BlinkMacSystemFont,"SF Pro Text","Segoe UI Variable","Segoe UI",system-ui,Roboto,"Helvetica Neue",sans-serif;--flux-font-size:14px;--flux-line:1.5;--flux-radius:10px;--flux-radius-lg:14px;--flux-pad:1;
|
|
732
|
+
--flux-bg:#fff;--flux-surface:#f5f5f7;--flux-surface-2:#e9e9ee;--flux-fg:#1d1d1f;--flux-muted:#6e6e73;--flux-border:rgba(60,60,67,.16);--flux-accent:#007aff;--flux-accent-fg:#fff;--flux-ok:#34c759;--flux-warn:#ff9f0a;--flux-danger:#ff3b30;--flux-shadow:0 10px 34px rgba(0,0,0,.14);--flux-material:rgba(246,246,248,.78)}
|
|
733
|
+
@media (prefers-color-scheme:dark){:root:not([data-theme]){--flux-bg:#1c1c1e;--flux-surface:#2c2c2e;--flux-surface-2:#3a3a3c;--flux-fg:#f5f5f7;--flux-muted:#98989d;--flux-border:rgba(235,235,245,.16);--flux-accent:#0a84ff;--flux-shadow:0 10px 34px rgba(0,0,0,.55);--flux-material:rgba(44,44,46,.72)}}
|
|
734
|
+
:root[data-theme="dark"]{--flux-bg:#1c1c1e;--flux-surface:#2c2c2e;--flux-surface-2:#3a3a3c;--flux-fg:#f5f5f7;--flux-muted:#98989d;--flux-border:rgba(235,235,245,.16);--flux-accent:#0a84ff;--flux-shadow:0 10px 34px rgba(0,0,0,.55);--flux-material:rgba(44,44,46,.72)}
|
|
735
|
+
:root[data-theme="light"]{--flux-bg:#fff;--flux-surface:#f5f5f7;--flux-surface-2:#e9e9ee;--flux-fg:#1d1d1f;--flux-muted:#6e6e73;--flux-border:rgba(60,60,67,.16);--flux-accent:#007aff;--flux-material:rgba(246,246,248,.78)}
|
|
736
|
+
:root[data-radius="sharp"]{--flux-radius:4px;--flux-radius-lg:6px}:root[data-radius="round"]{--flux-radius:14px;--flux-radius-lg:22px}:root[data-density="compact"]{--flux-pad:.7}
|
|
737
|
+
:root[data-motion="reduced"] *{transition:none!important;animation:none!important}
|
|
738
|
+
html{font-family:var(--flux-font);font-size:var(--flux-font-size);line-height:var(--flux-line);-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
|
|
739
|
+
body{font-family:var(--flux-font);font-size:1em;line-height:var(--flux-line);color:var(--flux-fg);background:var(--flux-bg)}
|
|
740
|
+
button,input,select,textarea{font-family:inherit;font-size:inherit}
|
|
741
|
+
.flux-icon{flex:none;vertical-align:middle}
|
|
742
|
+
.flux-large-title{font-size:2em;font-weight:700;letter-spacing:-.025em;margin:0 0 .6em;line-height:1.15}.flux-title{font-size:1.5em;font-weight:700;letter-spacing:-.02em;margin:0 0 .8em}
|
|
743
|
+
.flux-empty,.flux-not-found,.flux-forbidden,.flux-loading{padding:24px;color:var(--flux-muted)}
|
|
744
|
+
.flux-plugin-error{padding:10px 14px;border-radius:var(--flux-radius);background:color-mix(in srgb,var(--flux-danger) 12%,transparent);color:var(--flux-danger);font-size:.92em}
|
|
745
|
+
.flux-breadcrumbs{font-size:.86em;color:var(--flux-muted);margin-bottom:6px}.flux-breadcrumbs a{color:inherit;text-decoration:none}.flux-breadcrumbs a:hover{color:var(--flux-accent)}
|
|
746
|
+
/* ---- materials (vibrancy) */
|
|
747
|
+
.flux-titlebar,.flux-side,.flux-dropdown,.flux-toast,.flux-palette{background:var(--flux-material);-webkit-backdrop-filter:saturate(180%) blur(24px);backdrop-filter:saturate(180%) blur(24px)}
|
|
748
|
+
:root[data-translucency="off"] .flux-titlebar,:root[data-translucency="off"] .flux-side,:root[data-translucency="off"] .flux-dropdown,:root[data-translucency="off"] .flux-toast,:root[data-translucency="off"] .flux-palette{background:var(--flux-surface);-webkit-backdrop-filter:none;backdrop-filter:none}
|
|
749
|
+
/* ---- buttons / switch / segmented */
|
|
750
|
+
.flux-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;border:1px solid var(--flux-border);background:var(--flux-bg);color:var(--flux-fg);border-radius:calc(var(--flux-radius) - 2px);padding:calc(5px*var(--flux-pad)) 14px;font-weight:500;cursor:pointer;transition:background .12s,transform .06s}
|
|
751
|
+
.flux-btn:hover{background:var(--flux-surface)}.flux-btn:active{transform:scale(.97)}.flux-btn:disabled{opacity:.5;cursor:default}
|
|
752
|
+
.flux-btn-primary{background:var(--flux-accent);border-color:transparent;color:var(--flux-accent-fg)}.flux-btn-primary:hover{background:var(--flux-accent);filter:brightness(1.08)}
|
|
753
|
+
.flux-btn-destructive{color:var(--flux-danger)}.flux-btn-plain{border-color:transparent;background:transparent;color:var(--flux-accent)}.flux-btn-plain:hover{background:var(--flux-surface)}
|
|
754
|
+
.flux-btn-sm{padding:3px 11px;font-size:.92em;border-radius:99px}.flux-btn-lg{padding:9px 22px;font-size:1.05em;border-radius:99px}
|
|
755
|
+
.flux-switch{position:relative;width:42px;height:26px;border-radius:99px;border:0;background:var(--flux-surface-2);cursor:pointer;transition:background .18s;flex:none;padding:0}
|
|
756
|
+
.flux-switch span{position:absolute;top:2px;left:2px;width:22px;height:22px;border-radius:50%;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.35);transition:transform .18s}
|
|
757
|
+
.flux-switch[data-on]{background:var(--flux-ok)}.flux-switch[data-on] span{transform:translateX(16px)}.flux-switch:focus-visible{outline:2px solid var(--flux-accent);outline-offset:2px}
|
|
758
|
+
.flux-seg{display:inline-flex;background:var(--flux-surface-2);border-radius:9px;padding:2px;gap:2px}
|
|
759
|
+
.flux-seg button{border:0;background:transparent;color:var(--flux-fg);border-radius:7px;padding:calc(4px*var(--flux-pad)) 14px;font-weight:500;cursor:pointer}
|
|
760
|
+
.flux-seg button[data-on]{background:var(--flux-bg);box-shadow:0 1px 3px rgba(0,0,0,.18)}
|
|
761
|
+
.flux-input{width:100%;box-sizing:border-box;border:1px solid var(--flux-border);background:var(--flux-bg);color:var(--flux-fg);border-radius:var(--flux-radius);padding:calc(7px*var(--flux-pad)) 12px;outline:none}.flux-input:focus{border-color:var(--flux-accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--flux-accent) 25%,transparent)}
|
|
762
|
+
.flux-select{background:var(--flux-bg);color:inherit;border:1px solid var(--flux-border);border-radius:7px;padding:3px 6px}
|
|
763
|
+
/* ---- grouped lists */
|
|
764
|
+
.flux-group-box{margin:0 0 calc(22px*var(--flux-pad))}.flux-group-title{font-size:.78em;text-transform:uppercase;letter-spacing:.06em;color:var(--flux-muted);margin:0 14px 6px;font-weight:600}
|
|
765
|
+
.flux-group-body{background:var(--flux-surface);border-radius:var(--flux-radius-lg);overflow:hidden}.flux-group-footer{font-size:.82em;color:var(--flux-muted);margin:6px 14px 0}
|
|
766
|
+
.flux-row{display:flex;align-items:center;gap:12px;width:100%;box-sizing:border-box;padding:calc(10px*var(--flux-pad)) 14px;border:0;background:transparent;color:inherit;text-align:left;position:relative;min-height:calc(44px*var(--flux-pad))}
|
|
767
|
+
.flux-row+.flux-row::before{content:"";position:absolute;top:0;left:14px;right:0;height:1px;background:var(--flux-border)}
|
|
768
|
+
.flux-row.is-link{cursor:pointer}.flux-row.is-link:hover{background:color-mix(in srgb,var(--flux-fg) 5%,transparent)}.flux-row[data-selected]{background:color-mix(in srgb,var(--flux-accent) 14%,transparent)}
|
|
769
|
+
.flux-row-icon{width:28px;height:28px;border-radius:7px;display:inline-flex;align-items:center;justify-content:center;background:var(--flux-accent);color:#fff;flex:none;overflow:hidden}
|
|
770
|
+
.flux-row-text{display:flex;flex-direction:column;min-width:0;flex:1}.flux-row-title{font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.flux-row-sub{font-size:.88em;color:var(--flux-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.flux-row-right{display:flex;align-items:center;gap:8px;color:var(--flux-muted)}
|
|
771
|
+
.flux-img-fallback{display:inline-flex;align-items:center;justify-content:center;background:var(--flux-surface-2);color:var(--flux-muted);border-radius:inherit;min-width:32px;min-height:32px}
|
|
772
|
+
/* ---- title bar */
|
|
773
|
+
.flux-titlebar{--flux-titlebar-h:40px;height:var(--flux-titlebar-h);box-sizing:border-box;display:grid;grid-template-columns:1fr minmax(160px,400px) 1fr;align-items:center;gap:8px;padding:0 10px;border-bottom:1px solid var(--flux-border);color:var(--flux-fg);-webkit-app-region:drag;user-select:none;position:relative;z-index:50}
|
|
774
|
+
.flux-titlebar[data-electron][data-os="mac"]{padding-left:82px}
|
|
775
|
+
.flux-titlebar[data-electron][data-os="windows"],.flux-titlebar[data-electron][data-os="linux"]{padding-right:calc(100vw - env(titlebar-area-x,0px) - env(titlebar-area-width,100vw) + 8px)}
|
|
776
|
+
.flux-titlebar button,.flux-titlebar input,.flux-titlebar select,.flux-titlebar .flux-dropdown{-webkit-app-region:no-drag}
|
|
777
|
+
.flux-tb-left,.flux-tb-right{display:flex;align-items:center;gap:2px;min-width:0}.flux-tb-right{justify-content:flex-end}
|
|
778
|
+
.flux-brand{display:flex;align-items:center;gap:8px;font-weight:650;margin-right:8px;white-space:nowrap}
|
|
779
|
+
.flux-tb-btn{display:inline-flex;align-items:center;gap:6px;border:0;background:transparent;color:inherit;border-radius:6px;padding:4px 9px;font:inherit;cursor:pointer;position:relative}
|
|
780
|
+
.flux-tb-btn:hover,.flux-tb-btn.is-open{background:color-mix(in srgb,var(--flux-fg) 9%,transparent)}
|
|
781
|
+
.flux-menubar{display:flex}.flux-menu{position:relative}
|
|
782
|
+
.flux-dropdown{position:absolute;top:calc(100% + 4px);left:0;min-width:230px;max-width:380px;margin:0;padding:5px;list-style:none;color:var(--flux-fg);border:1px solid var(--flux-border);border-radius:10px;box-shadow:var(--flux-shadow);z-index:60}
|
|
783
|
+
.flux-dd-item{display:flex;justify-content:space-between;align-items:center;gap:18px;width:100%;border:0;background:transparent;color:inherit;font:inherit;text-align:left;padding:5px 10px;border-radius:5px;cursor:pointer}
|
|
784
|
+
.flux-dd-item:hover,.flux-dd-item.is-active{background:var(--flux-accent);color:var(--flux-accent-fg)}.flux-dd-item kbd,.flux-dd-item small{opacity:.6;font:11px ui-monospace,monospace}.flux-sep{height:1px;background:var(--flux-border);margin:5px 6px}
|
|
785
|
+
.flux-search{position:relative;display:flex;align-items:center;gap:8px;background:color-mix(in srgb,var(--flux-fg) 7%,transparent);border-radius:8px;padding:0 10px;height:26px;color:var(--flux-muted)}
|
|
786
|
+
.flux-search input{flex:1;min-width:0;border:0;background:transparent;color:var(--flux-fg);outline:none}.flux-search:focus-within{box-shadow:0 0 0 3px color-mix(in srgb,var(--flux-accent) 30%,transparent)}
|
|
787
|
+
.flux-search-results{left:0;right:0;max-width:none;top:calc(100% + 6px)}.flux-group{font-size:.74em;text-transform:uppercase;letter-spacing:.06em;color:var(--flux-muted);padding:7px 10px 2px;font-weight:600}
|
|
788
|
+
.flux-bell{position:relative}.flux-badge{position:absolute;top:0;right:0;min-width:15px;height:15px;border-radius:99px;background:var(--flux-danger);color:#fff;font-size:10px;line-height:15px;text-align:center}
|
|
789
|
+
.flux-notif-panel{left:auto;right:0;width:350px;max-height:440px;overflow:auto;padding:0}.flux-notif-head{display:flex;justify-content:space-between;padding:12px 14px;border-bottom:1px solid var(--flux-border)}
|
|
790
|
+
.flux-notif{display:flex;justify-content:space-between;gap:10px;padding:11px 14px;border-bottom:1px solid var(--flux-border)}.flux-notif p{margin:2px 0 4px;font-size:.92em}.flux-notif small{color:var(--flux-muted)}.flux-notif-actions{display:flex;flex-direction:column;align-items:flex-end;gap:4px}
|
|
791
|
+
.flux-notif.flux-success{box-shadow:inset 3px 0 var(--flux-ok)}.flux-notif.flux-warning{box-shadow:inset 3px 0 var(--flux-warn)}.flux-notif.flux-error{box-shadow:inset 3px 0 var(--flux-danger)}.flux-notif.flux-info{box-shadow:inset 3px 0 var(--flux-accent)}
|
|
792
|
+
.flux-link{background:none;border:0;color:var(--flux-accent);cursor:pointer;font:inherit;font-size:.88em;padding:0}
|
|
793
|
+
.flux-toasts{position:fixed;top:48px;right:14px;display:flex;flex-direction:column;gap:8px;z-index:40;pointer-events:none}
|
|
794
|
+
.flux-toast{pointer-events:auto;color:var(--flux-fg);border:1px solid var(--flux-border);border-radius:14px;padding:11px 15px;box-shadow:var(--flux-shadow);max-width:350px;font-size:.93em}
|
|
795
|
+
.flux-toast.flux-success{border-left:4px solid var(--flux-ok)}.flux-toast.flux-warning{border-left:4px solid var(--flux-warn)}.flux-toast.flux-error{border-left:4px solid var(--flux-danger)}
|
|
796
|
+
/* ---- command palette (Spotlight) */
|
|
797
|
+
.flux-palette-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.28);display:flex;justify-content:center;align-items:flex-start;padding-top:14vh;z-index:9999}
|
|
798
|
+
.flux-palette{width:min(600px,92vw);color:var(--flux-fg);border:1px solid var(--flux-border);border-radius:16px;box-shadow:var(--flux-shadow);overflow:hidden}
|
|
799
|
+
.flux-palette-input{width:100%;box-sizing:border-box;padding:16px 18px;border:0;border-bottom:1px solid var(--flux-border);background:transparent;color:inherit;font-size:1.25em;outline:none}
|
|
800
|
+
.flux-palette-list{list-style:none;margin:0;padding:6px;max-height:360px;overflow:auto}.flux-palette-item{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:8px 12px;border-radius:8px;cursor:pointer}
|
|
801
|
+
.flux-palette-item.is-active{background:var(--flux-accent);color:var(--flux-accent-fg)}.flux-palette-cat{opacity:.65}.flux-palette-empty{padding:16px;color:var(--flux-muted)}.flux-palette kbd{font:11px ui-monospace,monospace;opacity:.7}
|
|
802
|
+
/* ---- sidebar */
|
|
803
|
+
.flux-side{display:flex;flex-direction:column;justify-content:space-between;height:100%;box-sizing:border-box;padding:12px 10px;border-right:1px solid var(--flux-border);overflow-y:auto;overflow-x:hidden;transition:padding .18s}
|
|
804
|
+
.flux-side-nav,.flux-side-bottom{display:flex;flex-direction:column;gap:2px}.flux-side-sep{height:1px;background:var(--flux-border);margin:8px 8px}
|
|
805
|
+
.flux-nav-group{position:relative}
|
|
806
|
+
.flux-nav{display:flex;align-items:center;gap:10px;width:100%;box-sizing:border-box;padding:calc(6px*var(--flux-pad)) 10px;border:0;background:transparent;color:inherit;text-decoration:none;border-radius:8px;cursor:pointer;font-weight:500;text-align:left;white-space:nowrap}
|
|
807
|
+
.flux-nav:hover{background:color-mix(in srgb,var(--flux-fg) 7%,transparent)}.flux-nav[data-active]{background:var(--flux-accent);color:var(--flux-accent-fg)}.flux-nav[data-parent-active]{font-weight:650}
|
|
808
|
+
.flux-nav-icon{display:inline-flex;width:22px;justify-content:center;flex:none}.flux-nav-label{flex:1;overflow:hidden;text-overflow:ellipsis}
|
|
809
|
+
.flux-nav-badge{font-size:.78em;background:var(--flux-danger);color:#fff;border-radius:99px;padding:0 6px}.flux-nav-chev{display:inline-flex;transition:transform .15s;opacity:.6}.flux-nav-chev[data-open]{transform:rotate(90deg)}
|
|
810
|
+
.flux-subnav{margin-left:14px;padding-left:8px;border-left:1px solid var(--flux-border);display:flex;flex-direction:column;gap:2px;margin-top:2px}.flux-subnav .flux-nav{font-weight:450;padding-top:calc(4px*var(--flux-pad));padding-bottom:calc(4px*var(--flux-pad))}
|
|
811
|
+
.flux-side[data-collapsed] .flux-nav{justify-content:center;padding:9px 0}
|
|
812
|
+
.flux-flyout{display:none;position:absolute;left:calc(100% + 6px);top:0;min-width:190px;padding:6px;background:var(--flux-bg);border:1px solid var(--flux-border);border-radius:10px;box-shadow:var(--flux-shadow);z-index:80}.flux-nav-group:hover>.flux-flyout{display:block}
|
|
813
|
+
.flux-flyout-title{font-size:.75em;text-transform:uppercase;letter-spacing:.06em;color:var(--flux-muted);padding:4px 10px}.flux-flyout-item{display:block;padding:6px 10px;border-radius:6px;color:inherit;text-decoration:none}.flux-flyout-item:hover,.flux-flyout-item[data-active]{background:var(--flux-accent);color:var(--flux-accent-fg)}
|
|
814
|
+
.flux-side-footer{font-size:.78em;color:var(--flux-muted);padding:6px 10px}
|
|
815
|
+
/* ---- settings */
|
|
816
|
+
.flux-settings{display:grid;grid-template-columns:240px minmax(0,1fr);gap:28px;align-items:start}.flux-settings-nav{display:flex;flex-direction:column;gap:2px;position:sticky;top:0}
|
|
817
|
+
.flux-settings-item{display:flex;align-items:center;gap:10px;border:0;background:transparent;color:inherit;border-radius:9px;padding:calc(7px*var(--flux-pad)) 10px;cursor:pointer;text-align:left;font-weight:500}.flux-settings-item:hover{background:color-mix(in srgb,var(--flux-fg) 7%,transparent)}.flux-settings-item[data-active]{background:var(--flux-accent);color:var(--flux-accent-fg)}
|
|
818
|
+
.flux-settings-body{min-width:0;max-width:720px}.flux-swatches{display:flex;gap:10px;flex-wrap:wrap}.flux-swatch{width:26px;height:26px;border-radius:50%;border:2px solid transparent;cursor:pointer;box-shadow:inset 0 0 0 2px var(--flux-bg)}.flux-swatch[data-on]{border-color:var(--flux-fg)}
|
|
819
|
+
.flux-theme-card{border:2px solid var(--flux-border);border-radius:12px;overflow:hidden;cursor:pointer;background:none;padding:0;text-align:left;width:128px}.flux-theme-card[data-on]{border-color:var(--flux-accent)}.flux-theme-card .flux-theme-prev{height:62px;display:flex}.flux-theme-card span.cap{display:block;padding:6px 9px;font-size:.84em;font-weight:500}
|
|
820
|
+
.flux-range{width:180px;accent-color:var(--flux-accent)}
|
|
821
|
+
/* ---- plugin pages */
|
|
822
|
+
.flux-pm{max-width:860px;margin:0 auto}.flux-pm-top{display:flex;justify-content:space-between;align-items:center;gap:12px;margin-bottom:14px;flex-wrap:wrap}
|
|
823
|
+
.flux-pm-back{display:inline-flex;align-items:center;gap:2px;border:0;background:none;color:var(--flux-accent);cursor:pointer;font-size:1em;padding:0;margin-bottom:12px}
|
|
824
|
+
.flux-pm-icon{width:56px;height:56px;border-radius:24%;object-fit:cover;flex:none;box-shadow:0 1px 4px rgba(0,0,0,.2);overflow:hidden;display:inline-flex}.flux-pm-icon.lg{width:104px;height:104px;border-radius:23%;box-shadow:0 4px 18px rgba(0,0,0,.22)}
|
|
825
|
+
.flux-pm-hero{display:flex;gap:22px;align-items:center;margin:4px 0 22px;flex-wrap:wrap}.flux-pm-hero h1{margin:0;font-size:2em;letter-spacing:-.02em;line-height:1.15}.flux-pm-hero .by{color:var(--flux-muted);margin:3px 0 12px}.flux-pm-actions{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
|
|
826
|
+
.flux-pm-facts{display:flex;border-top:1px solid var(--flux-border);border-bottom:1px solid var(--flux-border);margin:0 0 22px;overflow:auto}.flux-pm-fact{flex:1;min-width:104px;padding:12px 8px;text-align:center;border-right:1px solid var(--flux-border)}.flux-pm-fact:last-child{border:0}.flux-pm-fact small{display:block;font-size:.7em;letter-spacing:.06em;text-transform:uppercase;color:var(--flux-muted);font-weight:600}.flux-pm-fact b{font-size:1.15em;font-weight:650}
|
|
827
|
+
.flux-pm-shots{display:flex;gap:14px;overflow-x:auto;padding:4px 2px 14px;scroll-snap-type:x mandatory}.flux-pm-shots img,.flux-pm-banner{height:260px;border-radius:var(--flux-radius-lg);border:1px solid var(--flux-border);scroll-snap-align:start;cursor:zoom-in;flex:none}.flux-pm-banner{width:100%;height:auto;max-height:240px;object-fit:cover;cursor:default;margin-bottom:18px}
|
|
828
|
+
.flux-pm h3.sec{font-size:1.3em;font-weight:700;letter-spacing:-.015em;margin:26px 0 10px}
|
|
829
|
+
.flux-pill{display:inline-block;font-size:.76em;padding:2px 9px;border-radius:99px;background:var(--flux-surface-2);color:var(--flux-muted);font-weight:600}.flux-pill.ok{background:color-mix(in srgb,var(--flux-ok) 18%,transparent);color:var(--flux-ok)}.flux-pill.err{background:color-mix(in srgb,var(--flux-danger) 16%,transparent);color:var(--flux-danger)}.flux-pill.warn{background:color-mix(in srgb,var(--flux-warn) 20%,transparent);color:var(--flux-warn)}
|
|
830
|
+
.flux-zoom{position:fixed;inset:0;background:rgba(0,0,0,.82);display:flex;align-items:center;justify-content:center;z-index:9999;cursor:zoom-out}.flux-zoom img{max-width:92vw;max-height:90vh;border-radius:14px}
|
|
831
|
+
.flux-video{width:100%;max-width:640px;aspect-ratio:16/9;border:0;border-radius:var(--flux-radius-lg);background:#000;display:block}.flux-video-link{display:inline-flex;gap:6px;align-items:center;color:var(--flux-accent);text-decoration:none}
|
|
832
|
+
.flux-pre{max-height:200px;overflow:auto;font:12px ui-monospace,monospace;background:var(--flux-surface);padding:12px;border-radius:var(--flux-radius);margin:0;white-space:pre-wrap}
|
|
833
|
+
.flux-fields label{display:flex;gap:12px;align-items:center;justify-content:space-between;padding:10px 14px}
|
|
834
|
+
/* ---- Markdown: app-store style typography */
|
|
835
|
+
.flux-md{font-size:1.04em;line-height:1.68;color:var(--flux-fg);letter-spacing:-.004em;max-width:70ch;overflow-wrap:anywhere}
|
|
836
|
+
.flux-md>:first-child{margin-top:0}.flux-md p{margin:.85em 0}
|
|
837
|
+
.flux-md h2{font-size:1.6em;font-weight:700;letter-spacing:-.022em;line-height:1.2;margin:1.7em 0 .5em}.flux-md h3{font-size:1.28em;font-weight:650;letter-spacing:-.015em;margin:1.5em 0 .4em}.flux-md h4,.flux-md h5{font-size:.82em;font-weight:650;letter-spacing:.07em;text-transform:uppercase;color:var(--flux-muted);margin:1.6em 0 .4em}
|
|
838
|
+
.flux-md a{color:var(--flux-accent);text-decoration:none}.flux-md a:hover{text-decoration:underline;text-underline-offset:3px}
|
|
839
|
+
.flux-md ul,.flux-md ol{padding-left:1.3em;margin:.8em 0}.flux-md li{margin:.4em 0;padding-left:.2em}.flux-md li::marker{color:var(--flux-muted)}.flux-md li.flux-task{list-style:none;margin-left:-1.2em;display:flex;gap:8px;align-items:baseline}
|
|
840
|
+
.flux-md code{font-family:ui-monospace,"SF Mono",Menlo,Consolas,monospace;font-size:.86em;background:var(--flux-surface-2);padding:.14em .42em;border-radius:6px}
|
|
841
|
+
.flux-md pre{background:var(--flux-surface);border:1px solid var(--flux-border);border-radius:14px;padding:14px 18px;overflow:auto;line-height:1.55;margin:1.1em 0}.flux-md pre code{background:none;padding:0;font-size:.85em}
|
|
842
|
+
.flux-md blockquote{margin:1.1em 0;padding:.2em 1em;border-left:3px solid var(--flux-border);color:var(--flux-muted)}
|
|
843
|
+
.flux-md hr{border:0;height:1px;background:var(--flux-border);margin:2em 0}
|
|
844
|
+
.flux-md table{width:100%;border-collapse:separate;border-spacing:0;margin:1.2em 0;font-size:.94em;border:1px solid var(--flux-border);border-radius:14px;overflow:hidden}.flux-md th{text-align:left;font-size:.78em;letter-spacing:.06em;text-transform:uppercase;color:var(--flux-muted);background:var(--flux-surface);padding:9px 14px}.flux-md td{padding:10px 14px;border-top:1px solid var(--flux-border)}
|
|
845
|
+
.flux-md-img{max-width:100%;height:auto;border-radius:14px;border:1px solid var(--flux-border);margin:.9em 0;display:block}
|
|
846
|
+
.flux-callout{display:flex;flex-direction:column;gap:2px;margin:1.2em 0;padding:13px 16px;border-radius:14px;background:color-mix(in srgb,var(--c,var(--flux-accent)) 11%,transparent);border-left:4px solid var(--c,var(--flux-accent))}
|
|
847
|
+
.flux-callout strong{color:var(--c,var(--flux-accent));font-size:.82em;letter-spacing:.05em;text-transform:uppercase}.flux-callout[data-kind="warning"],.flux-callout[data-kind="caution"]{--c:var(--flux-warn)}.flux-callout[data-kind="tip"]{--c:var(--flux-ok)}.flux-callout[data-kind="important"]{--c:#af52de}
|
|
848
|
+
`;
|
|
849
|
+
|
|
850
|
+
// src/react/http-bridge.ts
|
|
851
|
+
var HttpBridgeError = class extends Error {
|
|
852
|
+
constructor(message, code) {
|
|
853
|
+
super(message);
|
|
854
|
+
this.code = code;
|
|
855
|
+
this.name = "HttpBridgeError";
|
|
856
|
+
}
|
|
857
|
+
code;
|
|
858
|
+
};
|
|
859
|
+
function createHttpBridge(baseUrl = "") {
|
|
860
|
+
const listeners2 = /* @__PURE__ */ new Map();
|
|
861
|
+
let source;
|
|
862
|
+
const ensureSource = () => {
|
|
863
|
+
if (source || typeof EventSource === "undefined") return;
|
|
864
|
+
source = new EventSource(`${baseUrl}/flux/events`);
|
|
865
|
+
source.onmessage = (ev) => {
|
|
866
|
+
try {
|
|
867
|
+
const msg = JSON.parse(ev.data);
|
|
868
|
+
for (const l of [...listeners2.get(msg.channel) ?? []]) l(msg.payload);
|
|
869
|
+
} catch {
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
};
|
|
873
|
+
const post = (path, body) => fetch(`${baseUrl}${path}`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
|
|
874
|
+
return {
|
|
875
|
+
async invoke(channel, input, caller) {
|
|
876
|
+
const res = await (await post("/flux/invoke", { channel, input, caller })).json();
|
|
877
|
+
if (!res.ok) throw new HttpBridgeError(res.error.message, res.error.code);
|
|
878
|
+
return res.value;
|
|
879
|
+
},
|
|
880
|
+
send(channel, payload, caller) {
|
|
881
|
+
void post("/flux/send", { channel, payload, caller });
|
|
882
|
+
},
|
|
883
|
+
on(channel, listener) {
|
|
884
|
+
ensureSource();
|
|
885
|
+
let set = listeners2.get(channel);
|
|
886
|
+
if (!set) listeners2.set(channel, set = /* @__PURE__ */ new Set());
|
|
887
|
+
set.add(listener);
|
|
888
|
+
return () => void set.delete(listener);
|
|
889
|
+
},
|
|
890
|
+
async bootstrap() {
|
|
891
|
+
return await (await fetch(`${baseUrl}/flux/bootstrap`)).json();
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
function createStaticBridge(bootstrapUrl = "./bootstrap.json") {
|
|
896
|
+
return {
|
|
897
|
+
async invoke() {
|
|
898
|
+
throw new Error("This is a static website: main-process features (IPC, plugin management) are not available.");
|
|
899
|
+
},
|
|
900
|
+
send() {
|
|
901
|
+
},
|
|
902
|
+
on() {
|
|
903
|
+
return () => {
|
|
904
|
+
};
|
|
905
|
+
},
|
|
906
|
+
async bootstrap() {
|
|
907
|
+
return await (await fetch(bootstrapUrl)).json();
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// src/react/os.ts
|
|
913
|
+
import { useMemo as useMemo5 } from "react";
|
|
914
|
+
function detectOS(ua = typeof navigator === "undefined" ? "" : navigator.userAgent) {
|
|
915
|
+
if (/Mac|iPhone|iPad/i.test(ua)) return "mac";
|
|
916
|
+
if (/Windows/i.test(ua)) return "windows";
|
|
917
|
+
if (/Linux|X11|CrOS/i.test(ua)) return "linux";
|
|
918
|
+
return "other";
|
|
919
|
+
}
|
|
920
|
+
var useOS = () => useMemo5(() => detectOS(), []);
|
|
921
|
+
function formatShortcut(binding, os = detectOS()) {
|
|
922
|
+
const mac = os === "mac";
|
|
923
|
+
const map = mac ? { mod: "\u2318", ctrl: "\u2303", shift: "\u21E7", alt: "\u2325" } : { mod: "Ctrl", ctrl: "Ctrl", shift: "Shift", alt: "Alt" };
|
|
924
|
+
const parts = binding.split("+").map((p) => map[p.toLowerCase()] ?? (p.length === 1 ? p.toUpperCase() : p));
|
|
925
|
+
return mac ? parts.join("") : parts.join("+");
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// src/react/icons.tsx
|
|
929
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
930
|
+
var PATHS = {
|
|
931
|
+
search: "M11 4a7 7 0 105.2 11.7l3.5 3.5 1.4-1.4-3.5-3.5A7 7 0 0011 4zm0 2a5 5 0 110 10 5 5 0 010-10z",
|
|
932
|
+
bell: "M12 3a6 6 0 00-6 6v3.6L4.3 15.3A1 1 0 005 17h14a1 1 0 00.7-1.7L18 12.6V9a6 6 0 00-6-6zm-2 16a2 2 0 004 0h-4z",
|
|
933
|
+
sun: "M12 7a5 5 0 100 10 5 5 0 000-10zM11 1h2v3h-2V1zm0 19h2v3h-2v-3zM1 11h3v2H1v-2zm19 0h3v2h-3v-2zM4.2 5.6l1.4-1.4 2.1 2.1-1.4 1.4-2.1-2.1zm12.1 12.1l1.4-1.4 2.1 2.1-1.4 1.4-2.1-2.1zM5.6 19.8l-1.4-1.4 2.1-2.1 1.4 1.4-2.1 2.1zM18.4 7.7l-1.4-1.4 2.1-2.1 1.4 1.4-2.1 2.1z",
|
|
934
|
+
moon: "M20 14.5A8.5 8.5 0 019.5 4a8.5 8.5 0 1010.5 10.5z",
|
|
935
|
+
plug: "M9 2v5H7v3a5 5 0 004 4.9V19H8v2h8v-2h-3v-4.1A5 5 0 0017 10V7h-2V2h-2v5h-2V2H9z",
|
|
936
|
+
settings: "M12 8a4 4 0 100 8 4 4 0 000-8zm8.9 5l1.7 1.3-2 3.4-2-.8a7.6 7.6 0 01-1.7 1l-.3 2.1h-4l-.3-2.1a7.6 7.6 0 01-1.7-1l-2 .8-2-3.4L5.3 13a7 7 0 010-2L3.6 9.7l2-3.4 2 .8a7.6 7.6 0 011.7-1L9.6 4h4l.3 2.1a7.6 7.6 0 011.7 1l2-.8 2 3.4L17.9 11a7 7 0 010 2z",
|
|
937
|
+
check: "M9 16.2l-3.5-3.5L4 14.2l5 5 11-11-1.5-1.5L9 16.2z",
|
|
938
|
+
close: "M6.4 5l-1.4 1.4L10.6 12 5 17.6 6.4 19l5.6-5.6 5.6 5.6 1.4-1.4-5.6-5.6L19 6.4 17.6 5 12 10.6 6.4 5z",
|
|
939
|
+
chevron: "M7 10l5 5 5-5H7z",
|
|
940
|
+
play: "M8 5v14l11-7L8 5z",
|
|
941
|
+
image: "M4 5h16a1 1 0 011 1v12a1 1 0 01-1 1H4a1 1 0 01-1-1V6a1 1 0 011-1zm3 3a2 2 0 100 4 2 2 0 000-4zm-3 9h16v-3l-4-4-5 5-3-3-4 4v1z",
|
|
942
|
+
home: "M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z",
|
|
943
|
+
folder: "M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z",
|
|
944
|
+
grid: "M3 3h8v8H3V3zm10 0h8v8h-8V3zM3 13h8v8H3v-8zm10 0h8v8h-8v-8z",
|
|
945
|
+
panel: "M3 5v14h18V5H3zm6 12H5V7h4v10zm10 0h-8V7h8v10z",
|
|
946
|
+
globe: "M12 2a10 10 0 100 20 10 10 0 000-20zm6.9 6h-3a15.7 15.7 0 00-1.4-3.6A8 8 0 0118.9 8zM12 4c.8 1.2 1.5 2.5 1.9 4h-3.8c.4-1.5 1.1-2.8 1.9-4zM4.3 14a8.2 8.2 0 010-4h3.4a16.5 16.5 0 000 4H4.3zm.8 2h3a15.7 15.7 0 001.4 3.6A8 8 0 015.1 16zm3-8h-3a8 8 0 014.4-3.6A15.7 15.7 0 008.1 8zM12 20c-.8-1.2-1.5-2.5-1.9-4h3.8c-.4 1.5-1.1 2.8-1.9 4zm2.3-6H9.7a14.7 14.7 0 010-4h4.6a14.7 14.7 0 010 4zm.3 5.6a15.7 15.7 0 001.4-3.6h3a8 8 0 01-4.4 3.6zm1.7-5.6a16.5 16.5 0 000-4h3.4a8.2 8.2 0 010 4h-3.4z",
|
|
947
|
+
palette: "M12 3a9 9 0 000 18c1.1 0 2-.9 2-2 0-.5-.2-1-.5-1.3-.3-.3-.5-.8-.5-1.3 0-1.1.9-2 2-2H18c1.7 0 3-1.3 3-3 0-4.4-4-8-9-8zM6.5 12a1.5 1.5 0 110-3 1.5 1.5 0 010 3zm3-4a1.5 1.5 0 110-3 1.5 1.5 0 010 3zm5 0a1.5 1.5 0 110-3 1.5 1.5 0 010 3zm3 4a1.5 1.5 0 110-3 1.5 1.5 0 010 3z",
|
|
948
|
+
language: "M12.9 15l-2.5-2.5c1.7-1.9 2.9-4 3.6-6.5H17V4h-7V2H8v2H1v2h11.2c-.7 1.9-1.7 3.7-3.2 5.3-1-1.1-1.8-2.3-2.4-3.3h-2c.7 1.6 1.7 3.2 3 4.7L3.3 17.3l1.4 1.4L9 14.4l2.5 2.5.4-1.9zM18.5 10h-2L12 22h2l1.1-3h4.8l1.1 3h2l-4.5-12zm-2.6 7l1.6-4.3 1.6 4.3h-3.2z",
|
|
949
|
+
info: "M12 2a10 10 0 100 20 10 10 0 000-20zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z",
|
|
950
|
+
sliders: "M3 17v2h6v-2H3zM3 5v2h10V5H3zm10 16v-2h8v-2h-8v-2h-2v6h2zM7 9v2H3v2h4v2h2V9H7zm14 4v-2H11v2h10zm-6-4h2V7h4V5h-4V3h-2v6z",
|
|
951
|
+
user: "M12 12a4 4 0 100-8 4 4 0 000 8zm0 2c-2.7 0-8 1.3-8 4v2h16v-2c0-2.7-5.3-4-8-4z",
|
|
952
|
+
star: "M12 17.3l6.2 3.7-1.6-7L22 9.2l-7.2-.6L12 2 9.2 8.6 2 9.2 7.5 14l-1.6 7z",
|
|
953
|
+
download: "M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z",
|
|
954
|
+
trash: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z",
|
|
955
|
+
power: "M13 3h-2v10h2V3zm4.8 2.2l-1.4 1.4A6 6 0 116.6 6.6L5.2 5.2A8 8 0 1017.8 5.2z",
|
|
956
|
+
chevronRight: "M10 17l5-5-5-5v10z",
|
|
957
|
+
back: "M15.4 7.4L14 6l-6 6 6 6 1.4-1.4L10.8 12z",
|
|
958
|
+
type: "M5 4v3h5.5v12h3V7H19V4H5z",
|
|
959
|
+
external: "M19 19H5V5h7V3H5a2 2 0 00-2 2v14a2 2 0 002 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.6l-9.8 9.8 1.4 1.4L19 6.4V10h2V3h-7z",
|
|
960
|
+
bolt: "M7 2v11h3v9l7-12h-4l4-8z",
|
|
961
|
+
shield: "M12 1L3 5v6c0 5.6 3.8 10.7 9 12 5.2-1.3 9-6.4 9-12V5l-9-4z",
|
|
962
|
+
refresh: "M17.7 6.3A8 8 0 104 12h2a6 6 0 119.2-5.1L13 9h7V2l-2.3 2.3z",
|
|
963
|
+
command: "M7 3a4 4 0 00-1 7.9v2.2A4 4 0 107 21a4 4 0 004-4h2a4 4 0 104-4 4 4 0 00-1-.1V10.9A4 4 0 1017 3a4 4 0 00-4 4h-2a4 4 0 00-4-4zm4 9H9V10h2v2zm4 0h-2V10h2v2z"
|
|
964
|
+
};
|
|
965
|
+
function Icon({ name, d, size = 16, title }) {
|
|
966
|
+
const path = d ?? (name ? PATHS[name] : void 0) ?? PATHS["plug"];
|
|
967
|
+
return /* @__PURE__ */ jsxs4("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": title ? void 0 : true, role: title ? "img" : void 0, className: "flux-icon", children: [
|
|
968
|
+
title && /* @__PURE__ */ jsx5("title", { children: title }),
|
|
969
|
+
/* @__PURE__ */ jsx5("path", { d: path })
|
|
970
|
+
] });
|
|
971
|
+
}
|
|
972
|
+
var iconNames = Object.keys(PATHS);
|
|
973
|
+
|
|
974
|
+
// src/react/markdown.tsx
|
|
975
|
+
import React6 from "react";
|
|
976
|
+
|
|
977
|
+
// src/react/media.tsx
|
|
978
|
+
import { useEffect as useEffect5, useMemo as useMemo6, useState as useState5 } from "react";
|
|
979
|
+
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
980
|
+
var IMAGE_MIME = /^image\/(png|jpe?g|gif|webp|avif|bmp|x-icon|vnd\.microsoft\.icon|svg\+xml)$/i;
|
|
981
|
+
function sniff(bytes) {
|
|
982
|
+
const b = (...v) => v.every((x, i) => bytes[i] === x);
|
|
983
|
+
if (b(137, 80, 78, 71)) return "image/png";
|
|
984
|
+
if (b(255, 216, 255)) return "image/jpeg";
|
|
985
|
+
if (b(71, 73, 70, 56)) return "image/gif";
|
|
986
|
+
if (b(82, 73, 70, 70) && bytes[8] === 87 && bytes[9] === 69) return "image/webp";
|
|
987
|
+
if (b(66, 77)) return "image/bmp";
|
|
988
|
+
const head = new TextDecoder().decode(bytes.slice(0, 120)).trimStart();
|
|
989
|
+
if (head.startsWith("<svg") || head.startsWith("<?xml") && head.includes("<svg")) return "image/svg+xml";
|
|
990
|
+
return void 0;
|
|
991
|
+
}
|
|
992
|
+
function toImageSrc(input, opts = {}) {
|
|
993
|
+
if (input == null || input === "") return void 0;
|
|
994
|
+
if (typeof Blob !== "undefined" && input instanceof Blob) {
|
|
995
|
+
if (input.type && !IMAGE_MIME.test(input.type)) return void 0;
|
|
996
|
+
const url = URL.createObjectURL(input);
|
|
997
|
+
return { src: url, release: () => URL.revokeObjectURL(url) };
|
|
998
|
+
}
|
|
999
|
+
if (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) {
|
|
1000
|
+
const bytes = input instanceof ArrayBuffer ? new Uint8Array(input) : new Uint8Array(input.buffer, input.byteOffset, input.byteLength);
|
|
1001
|
+
const mime = sniff(bytes);
|
|
1002
|
+
if (!mime) return void 0;
|
|
1003
|
+
const url = URL.createObjectURL(new Blob([bytes], { type: mime }));
|
|
1004
|
+
return { src: url, release: () => URL.revokeObjectURL(url) };
|
|
1005
|
+
}
|
|
1006
|
+
const s2 = String(input).trim();
|
|
1007
|
+
if (/^data:/i.test(s2)) {
|
|
1008
|
+
const m = /^data:([^;,]+)[;,]/i.exec(s2);
|
|
1009
|
+
return m && IMAGE_MIME.test(m[1]) ? { src: s2 } : void 0;
|
|
1010
|
+
}
|
|
1011
|
+
if (/^blob:/i.test(s2) || /^https?:\/\//i.test(s2) || /^file:\/\//i.test(s2)) return { src: s2 };
|
|
1012
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(s2) && !/^[a-z]:[\\/]/i.test(s2)) return /^fluxplugin:\/\//i.test(s2) ? { src: s2 } : void 0;
|
|
1013
|
+
if (/^[a-z]:[\\/]/i.test(s2)) return { src: "file:///" + s2.replace(/\\/g, "/") };
|
|
1014
|
+
if (s2.length > 64 && /^[A-Za-z0-9+/=\s]+$/.test(s2)) {
|
|
1015
|
+
try {
|
|
1016
|
+
const bin = atob(s2.replace(/\s/g, ""));
|
|
1017
|
+
const bytes = Uint8Array.from(bin.slice(0, 160), (c) => c.charCodeAt(0));
|
|
1018
|
+
const mime = sniff(bytes);
|
|
1019
|
+
if (mime) return { src: `data:${mime};base64,${s2.replace(/\s/g, "")}` };
|
|
1020
|
+
} catch {
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
if (s2.startsWith("/") && typeof location !== "undefined" && location.protocol === "file:") return { src: "file://" + s2 };
|
|
1024
|
+
if (opts.base && !s2.startsWith("/")) {
|
|
1025
|
+
try {
|
|
1026
|
+
return { src: new URL(s2, opts.base).href };
|
|
1027
|
+
} catch {
|
|
1028
|
+
return void 0;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
return { src: s2 };
|
|
1032
|
+
}
|
|
1033
|
+
function Img({ src, base, alt = "", fallback, className, style, ...rest }) {
|
|
1034
|
+
const [failed, setFailed] = useState5(false);
|
|
1035
|
+
const resolved = useMemo6(() => toImageSrc(src, { base }), [src, base]);
|
|
1036
|
+
useEffect5(() => {
|
|
1037
|
+
setFailed(false);
|
|
1038
|
+
return () => resolved?.release?.();
|
|
1039
|
+
}, [resolved]);
|
|
1040
|
+
if (!resolved || failed) return /* @__PURE__ */ jsx6(Fragment3, { children: fallback ?? /* @__PURE__ */ jsx6("span", { className: "flux-img-fallback " + (className ?? ""), style, role: "img", "aria-label": alt, children: /* @__PURE__ */ jsx6(Icon, { name: "image", size: 18 }) }) });
|
|
1041
|
+
return /* @__PURE__ */ jsx6("img", { ...rest, className, style, src: resolved.src, alt, loading: "lazy", decoding: "async", referrerPolicy: "no-referrer", onError: () => setFailed(true) });
|
|
1042
|
+
}
|
|
1043
|
+
function VideoPlayer({ video, poster, title = "Video" }) {
|
|
1044
|
+
if (video.kind === "html5" && video.src) {
|
|
1045
|
+
return /* @__PURE__ */ jsxs5("video", { className: "flux-video", "data-testid": "plugin-video", "data-kind": "html5", controls: true, playsInline: true, preload: "metadata", poster, "aria-label": title, children: [
|
|
1046
|
+
/* @__PURE__ */ jsx6("source", { src: video.src }),
|
|
1047
|
+
/* @__PURE__ */ jsx6("a", { href: video.url, target: "_blank", rel: "noopener noreferrer", children: "Download the video" })
|
|
1048
|
+
] });
|
|
1049
|
+
}
|
|
1050
|
+
if (video.embedUrl) {
|
|
1051
|
+
return /* @__PURE__ */ jsx6(
|
|
1052
|
+
"iframe",
|
|
1053
|
+
{
|
|
1054
|
+
className: "flux-video",
|
|
1055
|
+
"data-testid": "plugin-video",
|
|
1056
|
+
"data-kind": video.kind,
|
|
1057
|
+
src: video.embedUrl,
|
|
1058
|
+
title,
|
|
1059
|
+
loading: "lazy",
|
|
1060
|
+
allow: "encrypted-media; picture-in-picture; fullscreen; autoplay=(self)",
|
|
1061
|
+
sandbox: "allow-scripts allow-same-origin allow-presentation allow-popups",
|
|
1062
|
+
referrerPolicy: "strict-origin-when-cross-origin",
|
|
1063
|
+
allowFullScreen: true
|
|
1064
|
+
}
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
return /* @__PURE__ */ jsxs5("a", { className: "flux-video-link", "data-testid": "plugin-video", "data-kind": "link", href: video.url, target: "_blank", rel: "noopener noreferrer", children: [
|
|
1068
|
+
/* @__PURE__ */ jsx6(Icon, { name: "play", size: 18 }),
|
|
1069
|
+
" Watch the video"
|
|
1070
|
+
] });
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
// src/react/markdown.tsx
|
|
1074
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1075
|
+
var safeHref = (h) => /^https:\/\/[^\s]+$/i.test(h) || h.startsWith("#") ? h : void 0;
|
|
1076
|
+
function inline(src, key = 0, base) {
|
|
1077
|
+
const out = [];
|
|
1078
|
+
const re = /(!\[[^\]]*\]\([^)\s]+(?:\s+"[^"]*")?\))|(`[^`]+`)|(\*\*[^*]+\*\*)|(\*[^*\s][^*]*\*)|(\[[^\]]+\]\([^)\s]+\))/g;
|
|
1079
|
+
let last = 0;
|
|
1080
|
+
let m;
|
|
1081
|
+
let i = 0;
|
|
1082
|
+
while (m = re.exec(src)) {
|
|
1083
|
+
if (m.index > last) out.push(src.slice(last, m.index));
|
|
1084
|
+
const tok = m[0];
|
|
1085
|
+
const k = `${key}-${i++}`;
|
|
1086
|
+
if (tok.startsWith("![")) {
|
|
1087
|
+
const mm = /^!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)$/.exec(tok);
|
|
1088
|
+
out.push(/* @__PURE__ */ jsx7(Img, { className: "flux-md-img", src: mm[2], base, alt: mm[1], title: mm[3] }, k));
|
|
1089
|
+
} else if (tok.startsWith("`")) out.push(/* @__PURE__ */ jsx7("code", { children: tok.slice(1, -1) }, k));
|
|
1090
|
+
else if (tok.startsWith("**")) out.push(/* @__PURE__ */ jsx7("strong", { children: tok.slice(2, -2) }, k));
|
|
1091
|
+
else if (tok.startsWith("*")) out.push(/* @__PURE__ */ jsx7("em", { children: tok.slice(1, -1) }, k));
|
|
1092
|
+
else {
|
|
1093
|
+
const mm = /^\[([^\]]+)\]\(([^)\s]+)\)$/.exec(tok);
|
|
1094
|
+
const href = safeHref(mm[2]);
|
|
1095
|
+
out.push(href ? /* @__PURE__ */ jsx7("a", { href, target: "_blank", rel: "noopener noreferrer", children: mm[1] }, k) : mm[1]);
|
|
1096
|
+
}
|
|
1097
|
+
last = m.index + tok.length;
|
|
1098
|
+
}
|
|
1099
|
+
if (last < src.length) out.push(src.slice(last));
|
|
1100
|
+
return out;
|
|
1101
|
+
}
|
|
1102
|
+
function Markdown({ source, baseUrl }) {
|
|
1103
|
+
const lines = source.replace(/\r\n/g, "\n").split("\n");
|
|
1104
|
+
const blocks = [];
|
|
1105
|
+
let i = 0;
|
|
1106
|
+
let k = 0;
|
|
1107
|
+
while (i < lines.length) {
|
|
1108
|
+
const line = lines[i];
|
|
1109
|
+
if (!line.trim()) {
|
|
1110
|
+
i++;
|
|
1111
|
+
continue;
|
|
1112
|
+
}
|
|
1113
|
+
if (line.startsWith("```")) {
|
|
1114
|
+
const buf2 = [];
|
|
1115
|
+
i++;
|
|
1116
|
+
while (i < lines.length && !lines[i].startsWith("```")) buf2.push(lines[i++]);
|
|
1117
|
+
i++;
|
|
1118
|
+
blocks.push(/* @__PURE__ */ jsx7("pre", { children: /* @__PURE__ */ jsx7("code", { children: buf2.join("\n") }) }, k++));
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
1121
|
+
const h = /^(#{1,4})\s+(.*)$/.exec(line);
|
|
1122
|
+
if (h) {
|
|
1123
|
+
const L = `h${h[1].length + 1}`;
|
|
1124
|
+
blocks.push(React6.createElement(L, { key: k++ }, inline(h[2], 0, baseUrl)));
|
|
1125
|
+
i++;
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
1128
|
+
if (/^(-{3,}|\*{3,})$/.test(line.trim())) {
|
|
1129
|
+
blocks.push(/* @__PURE__ */ jsx7("hr", {}, k++));
|
|
1130
|
+
i++;
|
|
1131
|
+
continue;
|
|
1132
|
+
}
|
|
1133
|
+
if (line.startsWith(">")) {
|
|
1134
|
+
const buf2 = [];
|
|
1135
|
+
while (i < lines.length && lines[i].startsWith(">")) buf2.push(lines[i++].replace(/^>\s?/, ""));
|
|
1136
|
+
const callout = /^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*(.*)$/i.exec(buf2[0] ?? "");
|
|
1137
|
+
if (callout) {
|
|
1138
|
+
const kind = callout[1].toLowerCase();
|
|
1139
|
+
blocks.push(/* @__PURE__ */ jsxs6("aside", { className: "flux-callout", "data-kind": kind, role: "note", children: [
|
|
1140
|
+
/* @__PURE__ */ jsx7("strong", { children: kind[0].toUpperCase() + kind.slice(1) }),
|
|
1141
|
+
/* @__PURE__ */ jsx7("span", { children: inline([callout[2], ...buf2.slice(1)].filter(Boolean).join(" "), 0, baseUrl) })
|
|
1142
|
+
] }, k++));
|
|
1143
|
+
} else blocks.push(/* @__PURE__ */ jsx7("blockquote", { children: inline(buf2.join(" "), 0, baseUrl) }, k++));
|
|
1144
|
+
continue;
|
|
1145
|
+
}
|
|
1146
|
+
if (line.includes("|") && /^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)*\|?\s*$/.test(lines[i + 1] ?? "")) {
|
|
1147
|
+
const cells = (l) => l.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => c.trim());
|
|
1148
|
+
const head = cells(line);
|
|
1149
|
+
i += 2;
|
|
1150
|
+
const rows = [];
|
|
1151
|
+
while (i < lines.length && lines[i].includes("|") && lines[i].trim()) rows.push(cells(lines[i++]));
|
|
1152
|
+
blocks.push(
|
|
1153
|
+
/* @__PURE__ */ jsxs6("table", { children: [
|
|
1154
|
+
/* @__PURE__ */ jsx7("thead", { children: /* @__PURE__ */ jsx7("tr", { children: head.map((c, j) => /* @__PURE__ */ jsx7("th", { children: inline(c, 0, baseUrl) }, j)) }) }),
|
|
1155
|
+
/* @__PURE__ */ jsx7("tbody", { children: rows.map((r, ri) => /* @__PURE__ */ jsx7("tr", { children: r.map((c, j) => /* @__PURE__ */ jsx7("td", { children: inline(c, 0, baseUrl) }, j)) }, ri)) })
|
|
1156
|
+
] }, k++)
|
|
1157
|
+
);
|
|
1158
|
+
continue;
|
|
1159
|
+
}
|
|
1160
|
+
const li = /^(\s*)([-*]|\d+\.)\s+/;
|
|
1161
|
+
if (li.test(line)) {
|
|
1162
|
+
const ordered = /^\s*\d+\./.test(line);
|
|
1163
|
+
const items = [];
|
|
1164
|
+
while (i < lines.length && li.test(lines[i])) {
|
|
1165
|
+
const text = lines[i++].replace(li, "");
|
|
1166
|
+
const task = /^\[([ xX])\]\s+(.*)$/.exec(text);
|
|
1167
|
+
items.push(task ? /* @__PURE__ */ jsxs6("li", { className: "flux-task", children: [
|
|
1168
|
+
/* @__PURE__ */ jsx7("input", { type: "checkbox", checked: task[1] !== " ", readOnly: true, "aria-label": "task" }),
|
|
1169
|
+
inline(task[2], 0, baseUrl)
|
|
1170
|
+
] }, items.length) : /* @__PURE__ */ jsx7("li", { children: inline(text, 0, baseUrl) }, items.length));
|
|
1171
|
+
}
|
|
1172
|
+
blocks.push(ordered ? /* @__PURE__ */ jsx7("ol", { children: items }, k++) : /* @__PURE__ */ jsx7("ul", { children: items }, k++));
|
|
1173
|
+
continue;
|
|
1174
|
+
}
|
|
1175
|
+
const buf = [];
|
|
1176
|
+
while (i < lines.length && lines[i].trim() && !/^(#{1,4}\s|```|>|\s*([-*]|\d+\.)\s)/.test(lines[i]) && !(lines[i].includes("|") && /^\s*\|?\s*:?-{2,}/.test(lines[i + 1] ?? ""))) buf.push(lines[i++]);
|
|
1177
|
+
blocks.push(/* @__PURE__ */ jsx7("p", { children: inline(buf.join(" "), 0, baseUrl) }, k++));
|
|
1178
|
+
}
|
|
1179
|
+
return /* @__PURE__ */ jsx7("div", { className: "flux-md", children: blocks });
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// src/react/theme.tsx
|
|
1183
|
+
import { createContext as createContext3, useCallback as useCallback4, useContext as useContext3, useEffect as useEffect6, useMemo as useMemo7, useState as useState6 } from "react";
|
|
1184
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1185
|
+
var isComponent = (v) => typeof v === "function" || typeof v === "object" && v !== null;
|
|
1186
|
+
function declareHostPoints(framework) {
|
|
1187
|
+
const ep = framework.manager.extensionPoints;
|
|
1188
|
+
if (!ep.definition("theme")) ep.declare({ name: "theme", contributions: { themes: s.object({ id: s.string(), label: s.string(), dark: s.boolean().optional(), tokens: s.record(s.string()) }) } });
|
|
1189
|
+
if (!ep.definition("font")) ep.declare({ name: "font", contributions: { fonts: s.object({ id: s.string(), label: s.string(), stack: s.string(), css: s.string().optional() }) } });
|
|
1190
|
+
if (!ep.definition("titlebar")) ep.declare({ name: "titlebar", contributions: { searchProviders: s.object({ id: s.string(), label: s.string(), search: s.fn() }) } });
|
|
1191
|
+
if (!ep.definition("settings")) {
|
|
1192
|
+
ep.declare({ name: "settings", contributions: { sections: s.object({ id: s.string(), title: s.string(), icon: s.string().optional(), order: s.number().optional(), component: s.custom(isComponent, "expected a React component") }) } });
|
|
1193
|
+
}
|
|
1194
|
+
declareI18nPoints(framework);
|
|
1195
|
+
}
|
|
1196
|
+
var DEFAULT_APPEARANCE = { fontId: "system", customFont: "", fontSize: 14, lineHeight: 1.5, density: "comfortable", translucency: true, radius: "default", reduceMotion: false };
|
|
1197
|
+
var BUILTIN_FONTS = [
|
|
1198
|
+
{ id: "system", label: "System (SF Pro)", stack: '-apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI Variable", "Segoe UI", system-ui, Roboto, "Helvetica Neue", sans-serif' },
|
|
1199
|
+
{ id: "rounded", label: "Rounded", stack: 'ui-rounded, "SF Pro Rounded", "Nunito", "Segoe UI", system-ui, sans-serif' },
|
|
1200
|
+
{ id: "serif", label: "Serif (New York)", stack: '"New York", "Iowan Old Style", "Palatino Linotype", Georgia, serif' },
|
|
1201
|
+
{ id: "humanist", label: "Humanist", stack: 'Seravek, "Gill Sans Nova", Ubuntu, Calibri, "DejaVu Sans", sans-serif' },
|
|
1202
|
+
{ id: "mono", label: "Monospace", stack: 'ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace' }
|
|
1203
|
+
];
|
|
1204
|
+
var SAFE_FONT = /[^\w\s,.-]/g;
|
|
1205
|
+
var clamp = (n, lo, hi) => Math.min(hi, Math.max(lo, Number.isFinite(n) ? n : lo));
|
|
1206
|
+
function sanitizeFontFamily(input) {
|
|
1207
|
+
return input.split(",").map((f) => f.replace(SAFE_FONT, "").replace(/\s+/g, " ").trim()).filter(Boolean).map((f) => `"${f}"`).join(", ");
|
|
1208
|
+
}
|
|
1209
|
+
var Ctx2 = createContext3(null);
|
|
1210
|
+
var read2 = (k) => {
|
|
1211
|
+
try {
|
|
1212
|
+
return localStorage.getItem(k);
|
|
1213
|
+
} catch {
|
|
1214
|
+
return null;
|
|
1215
|
+
}
|
|
1216
|
+
};
|
|
1217
|
+
var write = (k, v) => {
|
|
1218
|
+
try {
|
|
1219
|
+
v === void 0 ? localStorage.removeItem(k) : localStorage.setItem(k, v);
|
|
1220
|
+
} catch {
|
|
1221
|
+
}
|
|
1222
|
+
};
|
|
1223
|
+
function loadAppearance(key) {
|
|
1224
|
+
try {
|
|
1225
|
+
const raw = JSON.parse(read2(key) ?? "{}");
|
|
1226
|
+
return { ...DEFAULT_APPEARANCE, ...raw, fontSize: clamp(raw.fontSize ?? 14, 12, 20), lineHeight: clamp(raw.lineHeight ?? 1.5, 1.2, 2) };
|
|
1227
|
+
} catch {
|
|
1228
|
+
return DEFAULT_APPEARANCE;
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
function ThemeProvider({ children, storageKey = "fluxplugin.theme", onResolved }) {
|
|
1232
|
+
const themes = useExtensionItems("theme", "themes");
|
|
1233
|
+
const pluginFonts = useExtensionItems("font", "fonts");
|
|
1234
|
+
const [mode, setModeState] = useState6(() => read2(storageKey + ".mode") || "system");
|
|
1235
|
+
const [themeId, setThemeIdState] = useState6(() => read2(storageKey + ".id") ?? void 0);
|
|
1236
|
+
const [appearance, setAppearanceState] = useState6(() => loadAppearance(storageKey + ".appearance"));
|
|
1237
|
+
const [systemDark, setSystemDark] = useState6(() => typeof matchMedia !== "undefined" && matchMedia("(prefers-color-scheme: dark)").matches);
|
|
1238
|
+
useEffect6(() => {
|
|
1239
|
+
if (typeof matchMedia === "undefined") return;
|
|
1240
|
+
const mq = matchMedia("(prefers-color-scheme: dark)");
|
|
1241
|
+
const l = (e) => setSystemDark(e.matches);
|
|
1242
|
+
mq.addEventListener("change", l);
|
|
1243
|
+
return () => mq.removeEventListener("change", l);
|
|
1244
|
+
}, []);
|
|
1245
|
+
const theme = themes.find((t) => t.id === themeId);
|
|
1246
|
+
const resolved = theme ? theme.dark ? "dark" : "light" : mode === "system" ? systemDark ? "dark" : "light" : mode;
|
|
1247
|
+
const fonts = useMemo7(() => [...BUILTIN_FONTS, ...pluginFonts], [pluginFonts]);
|
|
1248
|
+
const font = fonts.find((f) => f.id === appearance.fontId) ?? BUILTIN_FONTS[0];
|
|
1249
|
+
useEffect6(() => {
|
|
1250
|
+
const root = document.documentElement;
|
|
1251
|
+
root.setAttribute("data-theme", resolved);
|
|
1252
|
+
const applied = Object.entries(theme?.tokens ?? {}).filter(([k]) => /^--[\w-]+$/.test(k));
|
|
1253
|
+
for (const [k, v] of applied) root.style.setProperty(k, v);
|
|
1254
|
+
if (appearance.accent) root.style.setProperty("--flux-accent", appearance.accent);
|
|
1255
|
+
const cs = getComputedStyle(root);
|
|
1256
|
+
onResolved?.({ dark: resolved === "dark", bg: cs.getPropertyValue("--flux-bg").trim(), fg: cs.getPropertyValue("--flux-fg").trim(), surface: cs.getPropertyValue("--flux-surface").trim() });
|
|
1257
|
+
return () => {
|
|
1258
|
+
applied.forEach(([k]) => root.style.removeProperty(k));
|
|
1259
|
+
if (appearance.accent) root.style.removeProperty("--flux-accent");
|
|
1260
|
+
};
|
|
1261
|
+
}, [resolved, theme, appearance.accent]);
|
|
1262
|
+
useEffect6(() => {
|
|
1263
|
+
const root = document.documentElement;
|
|
1264
|
+
const custom = sanitizeFontFamily(appearance.customFont);
|
|
1265
|
+
root.style.setProperty("--flux-font", custom ? `${custom}, ${font.stack}` : font.stack);
|
|
1266
|
+
root.style.setProperty("--flux-font-size", `${appearance.fontSize}px`);
|
|
1267
|
+
root.style.setProperty("--flux-line", String(appearance.lineHeight));
|
|
1268
|
+
root.setAttribute("data-density", appearance.density);
|
|
1269
|
+
root.setAttribute("data-translucency", appearance.translucency ? "on" : "off");
|
|
1270
|
+
root.setAttribute("data-radius", appearance.radius);
|
|
1271
|
+
root.setAttribute("data-motion", appearance.reduceMotion ? "reduced" : "full");
|
|
1272
|
+
let style;
|
|
1273
|
+
if (font.css) {
|
|
1274
|
+
style = document.createElement("style");
|
|
1275
|
+
style.setAttribute("data-flux-font", font.id);
|
|
1276
|
+
style.textContent = font.css;
|
|
1277
|
+
document.head.appendChild(style);
|
|
1278
|
+
}
|
|
1279
|
+
return () => style?.remove();
|
|
1280
|
+
}, [appearance, font]);
|
|
1281
|
+
const setMode = useCallback4((m) => (setModeState(m), setThemeIdState(void 0), write(storageKey + ".mode", m), write(storageKey + ".id", void 0)), [storageKey]);
|
|
1282
|
+
const setThemeId = useCallback4((id) => (setThemeIdState(id), write(storageKey + ".id", id)), [storageKey]);
|
|
1283
|
+
const setAppearance = useCallback4((patch) => setAppearanceState((cur) => {
|
|
1284
|
+
const next = { ...cur, ...patch };
|
|
1285
|
+
next.fontSize = clamp(next.fontSize, 12, 20);
|
|
1286
|
+
next.lineHeight = clamp(next.lineHeight, 1.2, 2);
|
|
1287
|
+
write(storageKey + ".appearance", JSON.stringify(next));
|
|
1288
|
+
return next;
|
|
1289
|
+
}), [storageKey]);
|
|
1290
|
+
const resetAppearance = useCallback4(() => (setAppearanceState(DEFAULT_APPEARANCE), write(storageKey + ".appearance", void 0)), [storageKey]);
|
|
1291
|
+
const value = useMemo7(
|
|
1292
|
+
() => ({ mode, setMode, themeId, setThemeId, resolved, themes, toggle: () => setMode(resolved === "dark" ? "light" : "dark"), appearance, setAppearance, resetAppearance, fonts }),
|
|
1293
|
+
[mode, setMode, themeId, setThemeId, resolved, themes, appearance, setAppearance, resetAppearance, fonts]
|
|
1294
|
+
);
|
|
1295
|
+
return /* @__PURE__ */ jsx8(Ctx2.Provider, { value, children });
|
|
1296
|
+
}
|
|
1297
|
+
function useTheme() {
|
|
1298
|
+
const c = useContext3(Ctx2);
|
|
1299
|
+
if (!c) throw new Error("useTheme must be used inside <ThemeProvider>");
|
|
1300
|
+
return c;
|
|
1301
|
+
}
|
|
1302
|
+
var useAppearance = () => {
|
|
1303
|
+
const { appearance, setAppearance, resetAppearance, fonts } = useTheme();
|
|
1304
|
+
return { appearance, setAppearance, resetAppearance, fonts };
|
|
1305
|
+
};
|
|
1306
|
+
function ThemeSwitcher() {
|
|
1307
|
+
const { resolved, toggle, themes, themeId, setThemeId, mode, setMode } = useTheme();
|
|
1308
|
+
const t = useT();
|
|
1309
|
+
return /* @__PURE__ */ jsxs7("span", { className: "flux-theme-switch", children: [
|
|
1310
|
+
/* @__PURE__ */ jsx8("button", { type: "button", className: "flux-tb-btn", onClick: toggle, title: t("theme.toggle", "Toggle appearance"), "aria-label": t("theme.toggle", "Toggle appearance"), "data-testid": "theme-toggle", children: /* @__PURE__ */ jsx8(Icon, { name: resolved === "dark" ? "moon" : "sun" }) }),
|
|
1311
|
+
themes.length > 0 && /* @__PURE__ */ jsxs7("select", { className: "flux-select", "aria-label": "Theme", value: themeId ?? `mode:${mode}`, onChange: (e) => e.target.value.startsWith("mode:") ? setMode(e.target.value.slice(5)) : setThemeId(e.target.value), children: [
|
|
1312
|
+
/* @__PURE__ */ jsx8("option", { value: "mode:system", children: t("theme.system", "System") }),
|
|
1313
|
+
/* @__PURE__ */ jsx8("option", { value: "mode:light", children: t("theme.light", "Light") }),
|
|
1314
|
+
/* @__PURE__ */ jsx8("option", { value: "mode:dark", children: t("theme.dark", "Dark") }),
|
|
1315
|
+
themes.map((th) => /* @__PURE__ */ jsx8("option", { value: th.id, children: th.label }, th.id))
|
|
1316
|
+
] })
|
|
1317
|
+
] });
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// src/react/titlebar.tsx
|
|
1321
|
+
import { useEffect as useEffect7, useMemo as useMemo8, useRef as useRef3, useState as useState7, useSyncExternalStore as useSyncExternalStore4 } from "react";
|
|
1322
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1323
|
+
function AppMenuBar() {
|
|
1324
|
+
const fw = usePluginFramework();
|
|
1325
|
+
const os = useOS();
|
|
1326
|
+
const menus = fw.manager.menus;
|
|
1327
|
+
useSyncExternalStore4(menus.subscribe, () => menus.version, () => menus.version);
|
|
1328
|
+
const [open, setOpen] = useState7(null);
|
|
1329
|
+
const ref = useRef3(null);
|
|
1330
|
+
useEffect7(() => {
|
|
1331
|
+
const off = (e) => ref.current && !ref.current.contains(e.target) && setOpen(null);
|
|
1332
|
+
const key = (e) => e.key === "Escape" && setOpen(null);
|
|
1333
|
+
window.addEventListener("mousedown", off);
|
|
1334
|
+
window.addEventListener("keydown", key);
|
|
1335
|
+
return () => (window.removeEventListener("mousedown", off), window.removeEventListener("keydown", key));
|
|
1336
|
+
}, []);
|
|
1337
|
+
const top = menus.items("application");
|
|
1338
|
+
if (!top.length) return null;
|
|
1339
|
+
const run = (item) => {
|
|
1340
|
+
setOpen(null);
|
|
1341
|
+
if (item.command) void fw.manager.commands.execute(item.command, ...item.args ?? []).catch((e) => fw.manager.logs.logger("host").error(String(e)));
|
|
1342
|
+
else if (item.path) navigate(item.path);
|
|
1343
|
+
};
|
|
1344
|
+
const shortcut = (item) => {
|
|
1345
|
+
const k = item.command ? fw.manager.commands.get(item.command)?.keybinding : void 0;
|
|
1346
|
+
return k ? formatShortcut(k, os) : "";
|
|
1347
|
+
};
|
|
1348
|
+
return /* @__PURE__ */ jsx9("div", { className: "flux-menubar", role: "menubar", ref, children: top.map((m) => /* @__PURE__ */ jsxs8("div", { className: "flux-menu", children: [
|
|
1349
|
+
/* @__PURE__ */ jsx9(
|
|
1350
|
+
"button",
|
|
1351
|
+
{
|
|
1352
|
+
type: "button",
|
|
1353
|
+
role: "menuitem",
|
|
1354
|
+
className: "flux-tb-btn" + (open === m.id ? " is-open" : ""),
|
|
1355
|
+
"aria-haspopup": !!m.children?.length,
|
|
1356
|
+
"aria-expanded": open === m.id,
|
|
1357
|
+
onClick: () => m.children?.length ? setOpen(open === m.id ? null : m.id) : run(m),
|
|
1358
|
+
onMouseEnter: () => open && m.children?.length && setOpen(m.id),
|
|
1359
|
+
children: m.label
|
|
1360
|
+
}
|
|
1361
|
+
),
|
|
1362
|
+
open === m.id && m.children && /* @__PURE__ */ jsx9("ul", { className: "flux-dropdown", role: "menu", children: m.children.map(
|
|
1363
|
+
(c) => c.separator ? /* @__PURE__ */ jsx9("li", { className: "flux-sep", role: "separator" }, c.id) : /* @__PURE__ */ jsx9("li", { role: "none", children: /* @__PURE__ */ jsxs8("button", { type: "button", role: "menuitem", className: "flux-dd-item", onClick: () => run(c), children: [
|
|
1364
|
+
/* @__PURE__ */ jsx9("span", { children: c.label }),
|
|
1365
|
+
/* @__PURE__ */ jsx9("kbd", { children: shortcut(c) })
|
|
1366
|
+
] }) }, c.id)
|
|
1367
|
+
) })
|
|
1368
|
+
] }, m.id)) });
|
|
1369
|
+
}
|
|
1370
|
+
function SearchBox({ placeholder }) {
|
|
1371
|
+
const fw = usePluginFramework();
|
|
1372
|
+
const t = useT();
|
|
1373
|
+
const providers = useExtensionItems("titlebar", "searchProviders");
|
|
1374
|
+
const [q, setQ] = useState7("");
|
|
1375
|
+
const [results, setResults] = useState7([]);
|
|
1376
|
+
const [idx, setIdx] = useState7(0);
|
|
1377
|
+
const [focus, setFocus] = useState7(false);
|
|
1378
|
+
const input = useRef3(null);
|
|
1379
|
+
useEffect7(() => {
|
|
1380
|
+
const k = (e) => (e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "f" && !e.shiftKey && (e.preventDefault(), input.current?.focus());
|
|
1381
|
+
window.addEventListener("keydown", k);
|
|
1382
|
+
return () => window.removeEventListener("keydown", k);
|
|
1383
|
+
}, []);
|
|
1384
|
+
useEffect7(() => {
|
|
1385
|
+
const query = q.trim();
|
|
1386
|
+
if (!query) return setResults((prev) => prev.length ? [] : prev);
|
|
1387
|
+
let live = true;
|
|
1388
|
+
(async () => {
|
|
1389
|
+
const lower = query.toLowerCase();
|
|
1390
|
+
const own = [
|
|
1391
|
+
...fw.manager.commands.palette(query).slice(0, 5).map((c) => ({ id: "cmd:" + c.id, title: c.title, subtitle: c.category, group: t("search.commands", "Commands"), run: () => void fw.manager.commands.execute(c.id) })),
|
|
1392
|
+
...fw.manager.menus.items("sidebar").filter((m) => m.path && m.label.toLowerCase().includes(lower)).map((m) => ({ id: "page:" + m.id, title: m.label, group: t("search.pages", "Pages"), run: () => navigate(m.path) }))
|
|
1393
|
+
];
|
|
1394
|
+
const extra = await Promise.all(providers.map(async (p) => {
|
|
1395
|
+
try {
|
|
1396
|
+
return (await p.search(query) ?? []).map((r) => ({ ...r, group: r.group ?? p.label }));
|
|
1397
|
+
} catch {
|
|
1398
|
+
return [];
|
|
1399
|
+
}
|
|
1400
|
+
}));
|
|
1401
|
+
if (live) setResults([...own, ...extra.flat()].slice(0, 12)), setIdx(0);
|
|
1402
|
+
})();
|
|
1403
|
+
return () => {
|
|
1404
|
+
live = false;
|
|
1405
|
+
};
|
|
1406
|
+
}, [q, fw, providers, t]);
|
|
1407
|
+
const go = (r) => {
|
|
1408
|
+
if (!r) return;
|
|
1409
|
+
setQ("");
|
|
1410
|
+
input.current?.blur();
|
|
1411
|
+
void r.run();
|
|
1412
|
+
};
|
|
1413
|
+
return /* @__PURE__ */ jsxs8("div", { className: "flux-search", "data-testid": "searchbox", children: [
|
|
1414
|
+
/* @__PURE__ */ jsx9(Icon, { name: "search" }),
|
|
1415
|
+
/* @__PURE__ */ jsx9(
|
|
1416
|
+
"input",
|
|
1417
|
+
{
|
|
1418
|
+
ref: input,
|
|
1419
|
+
value: q,
|
|
1420
|
+
placeholder: placeholder ?? t("search.placeholder", "Search\u2026"),
|
|
1421
|
+
"aria-label": t("search.label", "Search"),
|
|
1422
|
+
onChange: (e) => setQ(e.target.value),
|
|
1423
|
+
onFocus: () => setFocus(true),
|
|
1424
|
+
onBlur: () => setTimeout(() => setFocus(false), 120),
|
|
1425
|
+
onKeyDown: (e) => {
|
|
1426
|
+
if (e.key === "ArrowDown") e.preventDefault(), setIdx((i) => Math.min(i + 1, results.length - 1));
|
|
1427
|
+
else if (e.key === "ArrowUp") e.preventDefault(), setIdx((i) => Math.max(i - 1, 0));
|
|
1428
|
+
else if (e.key === "Enter") go(results[idx]);
|
|
1429
|
+
else if (e.key === "Escape") setQ(""), input.current?.blur();
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
),
|
|
1433
|
+
focus && q.trim() && /* @__PURE__ */ jsxs8("ul", { className: "flux-dropdown flux-search-results", role: "listbox", children: [
|
|
1434
|
+
results.length === 0 && /* @__PURE__ */ jsx9("li", { className: "flux-empty", children: t("search.noResults", "No results") }),
|
|
1435
|
+
results.map((r, i) => /* @__PURE__ */ jsxs8("li", { role: "option", "aria-selected": i === idx, children: [
|
|
1436
|
+
(i === 0 || results[i - 1].group !== r.group) && /* @__PURE__ */ jsx9("div", { className: "flux-group", children: r.group }),
|
|
1437
|
+
/* @__PURE__ */ jsxs8("button", { type: "button", className: "flux-dd-item" + (i === idx ? " is-active" : ""), onMouseDown: (e) => (e.preventDefault(), go(r)), children: [
|
|
1438
|
+
/* @__PURE__ */ jsx9("span", { children: r.title }),
|
|
1439
|
+
r.subtitle && /* @__PURE__ */ jsx9("small", { children: r.subtitle })
|
|
1440
|
+
] })
|
|
1441
|
+
] }, r.id))
|
|
1442
|
+
] })
|
|
1443
|
+
] });
|
|
1444
|
+
}
|
|
1445
|
+
function useNotifications() {
|
|
1446
|
+
const { manager } = usePluginFramework();
|
|
1447
|
+
const n = manager.notifications;
|
|
1448
|
+
useSyncExternalStore4(n.subscribe, () => n.version, () => n.version);
|
|
1449
|
+
return n;
|
|
1450
|
+
}
|
|
1451
|
+
function NotificationBell() {
|
|
1452
|
+
const fw = usePluginFramework();
|
|
1453
|
+
const t = useT();
|
|
1454
|
+
const center = useNotifications();
|
|
1455
|
+
const [open, setOpen] = useState7(false);
|
|
1456
|
+
const ref = useRef3(null);
|
|
1457
|
+
useEffect7(() => {
|
|
1458
|
+
const off = (e) => ref.current && !ref.current.contains(e.target) && setOpen(false);
|
|
1459
|
+
window.addEventListener("mousedown", off);
|
|
1460
|
+
return () => window.removeEventListener("mousedown", off);
|
|
1461
|
+
}, []);
|
|
1462
|
+
const unread = center.unread;
|
|
1463
|
+
return /* @__PURE__ */ jsxs8("div", { className: "flux-bell", ref, children: [
|
|
1464
|
+
/* @__PURE__ */ jsxs8("button", { type: "button", className: "flux-tb-btn", "aria-label": t("notifications.title", "Notifications"), "data-testid": "bell", onClick: () => (setOpen(!open), !open && center.markRead()), children: [
|
|
1465
|
+
/* @__PURE__ */ jsx9(Icon, { name: "bell" }),
|
|
1466
|
+
unread > 0 && /* @__PURE__ */ jsx9("span", { className: "flux-badge", "data-testid": "bell-count", children: unread })
|
|
1467
|
+
] }),
|
|
1468
|
+
open && /* @__PURE__ */ jsxs8("div", { className: "flux-dropdown flux-notif-panel", role: "dialog", "aria-label": t("notifications.title", "Notifications"), children: [
|
|
1469
|
+
/* @__PURE__ */ jsxs8("div", { className: "flux-notif-head", children: [
|
|
1470
|
+
/* @__PURE__ */ jsx9("strong", { children: t("notifications.title", "Notifications") }),
|
|
1471
|
+
/* @__PURE__ */ jsx9("button", { type: "button", className: "flux-link", onClick: () => center.clear(), children: t("notifications.clear", "Clear all") })
|
|
1472
|
+
] }),
|
|
1473
|
+
center.list().length === 0 && /* @__PURE__ */ jsx9("div", { className: "flux-empty", children: t("notifications.empty", "You're all caught up") }),
|
|
1474
|
+
center.list().map((n) => /* @__PURE__ */ jsxs8("div", { className: "flux-notif flux-" + n.level, children: [
|
|
1475
|
+
/* @__PURE__ */ jsxs8("div", { className: "flux-notif-main", children: [
|
|
1476
|
+
/* @__PURE__ */ jsx9("strong", { children: n.title }),
|
|
1477
|
+
n.body && /* @__PURE__ */ jsx9("p", { children: n.body }),
|
|
1478
|
+
/* @__PURE__ */ jsxs8("small", { children: [
|
|
1479
|
+
n.owner,
|
|
1480
|
+
" \xB7 ",
|
|
1481
|
+
new Date(n.time).toLocaleTimeString()
|
|
1482
|
+
] })
|
|
1483
|
+
] }),
|
|
1484
|
+
/* @__PURE__ */ jsxs8("div", { className: "flux-notif-actions", children: [
|
|
1485
|
+
n.actions.map((a) => /* @__PURE__ */ jsx9("button", { type: "button", className: "flux-link", onClick: () => void fw.manager.commands.execute(a.command, ...a.args ?? []), children: a.label }, a.label)),
|
|
1486
|
+
/* @__PURE__ */ jsx9("button", { type: "button", className: "flux-link", "aria-label": "Dismiss", onClick: () => center.dismiss(n.id), children: /* @__PURE__ */ jsx9(Icon, { name: "close", size: 12 }) })
|
|
1487
|
+
] })
|
|
1488
|
+
] }, n.id))
|
|
1489
|
+
] })
|
|
1490
|
+
] });
|
|
1491
|
+
}
|
|
1492
|
+
function ToastHost() {
|
|
1493
|
+
const { manager } = usePluginFramework();
|
|
1494
|
+
const [toasts, setToasts] = useState7([]);
|
|
1495
|
+
useEffect7(() => {
|
|
1496
|
+
const timers = [];
|
|
1497
|
+
const show = (n, remaining = n.timeoutMs) => {
|
|
1498
|
+
if (n.timeoutMs <= 0 || remaining <= 0) return;
|
|
1499
|
+
setToasts((t) => [n, ...t.filter((x) => x.id !== n.id)].slice(0, 4));
|
|
1500
|
+
timers.push(setTimeout(() => setToasts((t) => t.filter((x) => x.id !== n.id)), remaining));
|
|
1501
|
+
};
|
|
1502
|
+
for (const n of [...manager.notifications.list()].reverse()) if (!n.read) show(n, n.timeoutMs - (Date.now() - n.time));
|
|
1503
|
+
const sub = manager.notifications.onPush((n) => show(n));
|
|
1504
|
+
return () => (sub.dispose(), timers.forEach(clearTimeout));
|
|
1505
|
+
}, [manager]);
|
|
1506
|
+
return /* @__PURE__ */ jsx9("div", { className: "flux-toasts", "aria-live": "polite", "data-testid": "toasts", children: toasts.map((n) => /* @__PURE__ */ jsxs8("div", { className: "flux-toast flux-" + n.level, role: "status", children: [
|
|
1507
|
+
/* @__PURE__ */ jsx9("strong", { children: n.title }),
|
|
1508
|
+
n.body && /* @__PURE__ */ jsxs8("span", { children: [
|
|
1509
|
+
" ",
|
|
1510
|
+
n.body
|
|
1511
|
+
] })
|
|
1512
|
+
] }, n.id)) });
|
|
1513
|
+
}
|
|
1514
|
+
function TitleBar({ title, logo, searchPlaceholder, show }) {
|
|
1515
|
+
const os = useOS();
|
|
1516
|
+
const electron = typeof window !== "undefined" && !!window.__fluxplugin;
|
|
1517
|
+
const s2 = { menu: true, search: true, notifications: true, theme: true, ...show };
|
|
1518
|
+
const brand = useMemo8(() => /* @__PURE__ */ jsxs8("div", { className: "flux-brand", children: [
|
|
1519
|
+
logo,
|
|
1520
|
+
title && /* @__PURE__ */ jsx9("span", { children: title })
|
|
1521
|
+
] }), [logo, title]);
|
|
1522
|
+
return /* @__PURE__ */ jsxs8("header", { className: "flux-titlebar", "data-os": os, "data-electron": electron || void 0, "data-testid": "titlebar", children: [
|
|
1523
|
+
/* @__PURE__ */ jsxs8("div", { className: "flux-tb-left", children: [
|
|
1524
|
+
brand,
|
|
1525
|
+
s2.menu && /* @__PURE__ */ jsx9(AppMenuBar, {}),
|
|
1526
|
+
/* @__PURE__ */ jsx9(PluginSlot, { name: "titlebar.left" })
|
|
1527
|
+
] }),
|
|
1528
|
+
/* @__PURE__ */ jsx9("div", { className: "flux-tb-center", children: s2.search && /* @__PURE__ */ jsx9(SearchBox, { placeholder: searchPlaceholder }) }),
|
|
1529
|
+
/* @__PURE__ */ jsxs8("div", { className: "flux-tb-right", children: [
|
|
1530
|
+
/* @__PURE__ */ jsx9(PluginSlot, { name: "titlebar.right" }),
|
|
1531
|
+
s2.theme && /* @__PURE__ */ jsx9(ThemeSwitcher, {}),
|
|
1532
|
+
s2.notifications && /* @__PURE__ */ jsx9(NotificationBell, {})
|
|
1533
|
+
] })
|
|
1534
|
+
] });
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
// src/react/ui.tsx
|
|
1538
|
+
import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1539
|
+
function Switch({ checked, onChange, label, disabled }) {
|
|
1540
|
+
return /* @__PURE__ */ jsx10("button", { type: "button", role: "switch", "aria-checked": checked, "aria-label": label, disabled, className: "flux-switch", "data-on": checked || void 0, onClick: () => onChange(!checked), children: /* @__PURE__ */ jsx10("span", {}) });
|
|
1541
|
+
}
|
|
1542
|
+
function Segmented({ value, options, onChange, label }) {
|
|
1543
|
+
return /* @__PURE__ */ jsx10("div", { className: "flux-seg", role: "tablist", "aria-label": label, children: options.map((o) => /* @__PURE__ */ jsx10("button", { type: "button", role: "tab", "aria-selected": o.value === value, "data-on": o.value === value || void 0, onClick: () => onChange(o.value), children: o.label }, o.value)) });
|
|
1544
|
+
}
|
|
1545
|
+
function Group({ title, footer, children, className }) {
|
|
1546
|
+
return /* @__PURE__ */ jsxs9("section", { className: "flux-group-box " + (className ?? ""), children: [
|
|
1547
|
+
title && /* @__PURE__ */ jsx10("h3", { className: "flux-group-title", children: title }),
|
|
1548
|
+
/* @__PURE__ */ jsx10("div", { className: "flux-group-body", children }),
|
|
1549
|
+
footer && /* @__PURE__ */ jsx10("p", { className: "flux-group-footer", children: footer })
|
|
1550
|
+
] });
|
|
1551
|
+
}
|
|
1552
|
+
function Row({ icon, iconColor, title, subtitle, right, chevron, onClick, selected, ...rest }) {
|
|
1553
|
+
const content = /* @__PURE__ */ jsxs9(Fragment4, { children: [
|
|
1554
|
+
icon !== void 0 && /* @__PURE__ */ jsx10("span", { className: "flux-row-icon", style: iconColor ? { background: iconColor } : void 0, children: typeof icon === "string" ? /* @__PURE__ */ jsx10(Icon, { name: icon, size: 16 }) : icon }),
|
|
1555
|
+
/* @__PURE__ */ jsxs9("span", { className: "flux-row-text", children: [
|
|
1556
|
+
/* @__PURE__ */ jsx10("span", { className: "flux-row-title", children: title }),
|
|
1557
|
+
subtitle && /* @__PURE__ */ jsx10("span", { className: "flux-row-sub", children: subtitle })
|
|
1558
|
+
] }),
|
|
1559
|
+
right && /* @__PURE__ */ jsx10("span", { className: "flux-row-right", onClick: (e) => e.stopPropagation(), children: right }),
|
|
1560
|
+
chevron && /* @__PURE__ */ jsx10(Icon, { name: "chevronRight", size: 18 })
|
|
1561
|
+
] });
|
|
1562
|
+
return onClick ? /* @__PURE__ */ jsx10("button", { type: "button", className: "flux-row is-link", "data-selected": selected || void 0, onClick, ...rest, children: content }) : /* @__PURE__ */ jsx10("div", { className: "flux-row", ...rest, children: content });
|
|
1563
|
+
}
|
|
1564
|
+
function Button({ variant = "secondary", size = "md", children, ...rest }) {
|
|
1565
|
+
return /* @__PURE__ */ jsx10("button", { type: "button", ...rest, className: `flux-btn flux-btn-${variant} flux-btn-${size} ${rest.className ?? ""}`, children });
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
// src/react/sidebar.tsx
|
|
1569
|
+
import React9, { createContext as createContext4, useCallback as useCallback5, useContext as useContext4, useEffect as useEffect8, useMemo as useMemo9, useState as useState8 } from "react";
|
|
1570
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1571
|
+
var Ctx3 = createContext4({ collapsed: false, setCollapsed: () => {
|
|
1572
|
+
}, toggle: () => {
|
|
1573
|
+
}, width: 232 });
|
|
1574
|
+
var read3 = (k) => {
|
|
1575
|
+
try {
|
|
1576
|
+
return localStorage.getItem(k);
|
|
1577
|
+
} catch {
|
|
1578
|
+
return null;
|
|
1579
|
+
}
|
|
1580
|
+
};
|
|
1581
|
+
var write2 = (k, v) => {
|
|
1582
|
+
try {
|
|
1583
|
+
localStorage.setItem(k, v);
|
|
1584
|
+
} catch {
|
|
1585
|
+
}
|
|
1586
|
+
};
|
|
1587
|
+
function SidebarProvider({ children, storageKey = "fluxplugin.sidebar", expandedWidth = 232, collapsedWidth = 68 }) {
|
|
1588
|
+
const [collapsed, setCollapsedState] = useState8(() => read3(storageKey) === "collapsed");
|
|
1589
|
+
const width = collapsed ? collapsedWidth : expandedWidth;
|
|
1590
|
+
useEffect8(() => {
|
|
1591
|
+
document.documentElement.style.setProperty("--flux-sidebar-w", `${width}px`);
|
|
1592
|
+
}, [width]);
|
|
1593
|
+
const setCollapsed = useCallback5((v) => (setCollapsedState(v), write2(storageKey, v ? "collapsed" : "expanded")), [storageKey]);
|
|
1594
|
+
useEffect8(() => {
|
|
1595
|
+
const l = () => setCollapsed(!collapsed);
|
|
1596
|
+
window.addEventListener("flux:toggle-sidebar", l);
|
|
1597
|
+
return () => window.removeEventListener("flux:toggle-sidebar", l);
|
|
1598
|
+
}, [collapsed, setCollapsed]);
|
|
1599
|
+
const value = useMemo9(() => ({ collapsed, setCollapsed, toggle: () => setCollapsed(!collapsed), width }), [collapsed, setCollapsed, width]);
|
|
1600
|
+
return /* @__PURE__ */ jsx11(Ctx3.Provider, { value, children });
|
|
1601
|
+
}
|
|
1602
|
+
var useSidebar = () => useContext4(Ctx3);
|
|
1603
|
+
function renderIcon(icon, size = 18) {
|
|
1604
|
+
if (!icon) return /* @__PURE__ */ jsx11(Icon, { name: "grid", size });
|
|
1605
|
+
if (typeof icon === "string") return /* @__PURE__ */ jsx11(Icon, { name: icon, size });
|
|
1606
|
+
if (React9.isValidElement(icon)) return icon;
|
|
1607
|
+
if (typeof icon === "object" && icon && "d" in icon) return /* @__PURE__ */ jsx11(Icon, { d: String(icon.d), size });
|
|
1608
|
+
return /* @__PURE__ */ jsx11(Icon, { name: "grid", size });
|
|
1609
|
+
}
|
|
1610
|
+
var norm = (v) => v.replace(/^#/, "").split("?")[0].replace(/\/+$/, "") || "/";
|
|
1611
|
+
var activeFor = (cur, path) => !!path && (norm(cur) === norm(path) || norm(path) !== "/" && norm(cur).startsWith(norm(path) + "/"));
|
|
1612
|
+
var hasActive = (cur, e) => activeFor(cur, e.path) || !!e.children?.some((c) => hasActive(cur, c));
|
|
1613
|
+
var fromNode = (n) => ({ id: n.id, label: n.label, path: n.path, command: n.command, icon: n.icon, badge: n.badge, children: n.children?.map(fromNode) });
|
|
1614
|
+
function SidebarNav({ top = [], bottom = [], location: location2 = "sidebar", footer }) {
|
|
1615
|
+
const fw = usePluginFramework();
|
|
1616
|
+
const t = useT();
|
|
1617
|
+
const { collapsed, toggle } = useSidebar();
|
|
1618
|
+
const cur = useLocation();
|
|
1619
|
+
const menus = fw.manager.menus;
|
|
1620
|
+
const [version, setVersion] = useState8(menus.version);
|
|
1621
|
+
useEffect8(() => menus.subscribe(() => setVersion(menus.version)), [menus]);
|
|
1622
|
+
const plugin = useMemo9(() => menus.tree(location2).map(fromNode), [menus, location2, version]);
|
|
1623
|
+
const [open, setOpen] = useState8(() => {
|
|
1624
|
+
try {
|
|
1625
|
+
return JSON.parse(read3("fluxplugin.sidebar.open") ?? "{}");
|
|
1626
|
+
} catch {
|
|
1627
|
+
return {};
|
|
1628
|
+
}
|
|
1629
|
+
});
|
|
1630
|
+
const setGroup = (id, v) => setOpen((o) => {
|
|
1631
|
+
const n = { ...o, [id]: v };
|
|
1632
|
+
write2("fluxplugin.sidebar.open", JSON.stringify(n));
|
|
1633
|
+
return n;
|
|
1634
|
+
});
|
|
1635
|
+
const go = (e) => {
|
|
1636
|
+
if (e.command) void fw.manager.commands.execute(e.command).catch((err) => fw.manager.logs.logger("host").error(String(err)));
|
|
1637
|
+
else if (e.path) navigate(e.path);
|
|
1638
|
+
};
|
|
1639
|
+
const label = (e, host) => host ? t(`menu.${e.id}`, e.label) : e.label;
|
|
1640
|
+
const item = (e, host, depth = 0) => {
|
|
1641
|
+
const kids = e.children ?? [];
|
|
1642
|
+
const active = activeFor(cur, e.path);
|
|
1643
|
+
const expanded = kids.length > 0 && (open[e.id] ?? hasActive(cur, e));
|
|
1644
|
+
const text = label(e, host);
|
|
1645
|
+
return /* @__PURE__ */ jsxs10("div", { className: "flux-nav-group", "data-depth": depth, children: [
|
|
1646
|
+
/* @__PURE__ */ jsxs10(
|
|
1647
|
+
"a",
|
|
1648
|
+
{
|
|
1649
|
+
href: "#" + (e.path ?? ""),
|
|
1650
|
+
className: "flux-nav",
|
|
1651
|
+
"data-active": active || void 0,
|
|
1652
|
+
"data-parent-active": !active && hasActive(cur, e) || void 0,
|
|
1653
|
+
"aria-current": active ? "page" : void 0,
|
|
1654
|
+
title: collapsed ? text : void 0,
|
|
1655
|
+
"data-testid": "nav-" + e.id,
|
|
1656
|
+
onClick: (ev) => {
|
|
1657
|
+
ev.preventDefault();
|
|
1658
|
+
if (kids.length && !collapsed) setGroup(e.id, !expanded);
|
|
1659
|
+
if (e.path || e.command) go(e);
|
|
1660
|
+
else if (kids.length && collapsed) go(kids[0]);
|
|
1661
|
+
},
|
|
1662
|
+
children: [
|
|
1663
|
+
/* @__PURE__ */ jsx11("span", { className: "flux-nav-icon", children: renderIcon(e.icon, depth ? 14 : 18) }),
|
|
1664
|
+
!collapsed && /* @__PURE__ */ jsx11("span", { className: "flux-nav-label", children: text }),
|
|
1665
|
+
!collapsed && e.badge !== void 0 && /* @__PURE__ */ jsx11("span", { className: "flux-nav-badge", children: e.badge }),
|
|
1666
|
+
!collapsed && kids.length > 0 && /* @__PURE__ */ jsx11("span", { className: "flux-nav-chev", "data-open": expanded || void 0, children: /* @__PURE__ */ jsx11(Icon, { name: "chevronRight", size: 16 }) })
|
|
1667
|
+
]
|
|
1668
|
+
}
|
|
1669
|
+
),
|
|
1670
|
+
!collapsed && expanded && /* @__PURE__ */ jsx11("div", { className: "flux-subnav", children: kids.map((k) => item(k, host, depth + 1)) }),
|
|
1671
|
+
collapsed && kids.length > 0 && /* @__PURE__ */ jsxs10("div", { className: "flux-flyout", role: "menu", children: [
|
|
1672
|
+
/* @__PURE__ */ jsx11("div", { className: "flux-flyout-title", children: text }),
|
|
1673
|
+
kids.map((k) => /* @__PURE__ */ jsx11("a", { href: "#" + (k.path ?? ""), role: "menuitem", className: "flux-flyout-item", "data-active": activeFor(cur, k.path) || void 0, onClick: (ev) => (ev.preventDefault(), go(k)), children: k.label }, k.id))
|
|
1674
|
+
] })
|
|
1675
|
+
] }, e.id);
|
|
1676
|
+
};
|
|
1677
|
+
return /* @__PURE__ */ jsxs10("aside", { className: "flux-side side", "data-collapsed": collapsed || void 0, "aria-label": "Sidebar", children: [
|
|
1678
|
+
/* @__PURE__ */ jsxs10("nav", { className: "flux-side-nav", children: [
|
|
1679
|
+
top.map((e) => item(e, true)),
|
|
1680
|
+
plugin.length > 0 && /* @__PURE__ */ jsx11("div", { className: "flux-side-sep" }),
|
|
1681
|
+
plugin.map((e) => item(e, false))
|
|
1682
|
+
] }),
|
|
1683
|
+
/* @__PURE__ */ jsxs10("div", { className: "flux-side-bottom", children: [
|
|
1684
|
+
bottom.map((e) => item(e, true)),
|
|
1685
|
+
footer && !collapsed && /* @__PURE__ */ jsx11("div", { className: "flux-side-footer", children: footer }),
|
|
1686
|
+
/* @__PURE__ */ jsxs10("button", { type: "button", className: "flux-nav flux-nav-toggle", onClick: toggle, title: collapsed ? t("sidebar.expand", "Expand sidebar") : t("sidebar.collapse", "Collapse sidebar"), "aria-label": collapsed ? t("sidebar.expand", "Expand sidebar") : t("sidebar.collapse", "Collapse sidebar"), "aria-expanded": !collapsed, "data-testid": "sidebar-toggle", children: [
|
|
1687
|
+
/* @__PURE__ */ jsx11("span", { className: "flux-nav-icon", children: /* @__PURE__ */ jsx11(Icon, { name: "panel", size: 18 }) }),
|
|
1688
|
+
!collapsed && /* @__PURE__ */ jsx11("span", { className: "flux-nav-label", children: t("sidebar.collapse", "Collapse sidebar") })
|
|
1689
|
+
] })
|
|
1690
|
+
] })
|
|
1691
|
+
] });
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
// src/react/settings.tsx
|
|
1695
|
+
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1696
|
+
var COLORS = ["#007aff", "#34c759", "#ff9f0a", "#af52de", "#ff375f", "#5ac8fa", "#8e8e93"];
|
|
1697
|
+
function SettingsPage() {
|
|
1698
|
+
const t = useT();
|
|
1699
|
+
const raw = useExtensionItems("settings", "sections");
|
|
1700
|
+
const sections = [...raw].sort((a, b) => (a.order ?? 100) - (b.order ?? 100) || a.title.localeCompare(b.title));
|
|
1701
|
+
const { section } = useParams();
|
|
1702
|
+
const active = sections.find((s2) => s2.id === section) ?? sections[0];
|
|
1703
|
+
if (!active) return /* @__PURE__ */ jsx12("div", { className: "flux-empty", children: t("settings.empty", "No settings yet") });
|
|
1704
|
+
const Body = active.component;
|
|
1705
|
+
return /* @__PURE__ */ jsxs11("div", { className: "flux-settings", "data-testid": "settings-page", children: [
|
|
1706
|
+
/* @__PURE__ */ jsxs11("nav", { className: "flux-settings-nav", "aria-label": t("settings.title", "Settings"), children: [
|
|
1707
|
+
/* @__PURE__ */ jsx12("h1", { className: "flux-large-title", children: t("settings.title", "Settings") }),
|
|
1708
|
+
sections.map((s2, i) => /* @__PURE__ */ jsxs11("button", { type: "button", className: "flux-settings-item", "data-active": s2.id === active.id || void 0, "data-testid": "settings-" + s2.id, onClick: () => navigate("/settings/" + s2.id), children: [
|
|
1709
|
+
/* @__PURE__ */ jsx12("span", { className: "flux-row-icon", style: { background: COLORS[i % COLORS.length] }, children: /* @__PURE__ */ jsx12(Icon, { name: s2.icon ?? "settings", size: 15 }) }),
|
|
1710
|
+
/* @__PURE__ */ jsx12("span", { children: t(`settings.${s2.id}`, s2.title) })
|
|
1711
|
+
] }, s2.id))
|
|
1712
|
+
] }),
|
|
1713
|
+
/* @__PURE__ */ jsxs11("div", { className: "flux-settings-body", children: [
|
|
1714
|
+
/* @__PURE__ */ jsx12("h2", { className: "flux-title", children: t(`settings.${active.id}`, active.title) }),
|
|
1715
|
+
/* @__PURE__ */ jsx12(Body, {})
|
|
1716
|
+
] })
|
|
1717
|
+
] });
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
export {
|
|
1721
|
+
RemoteError,
|
|
1722
|
+
PluginsClient,
|
|
1723
|
+
RendererFramework,
|
|
1724
|
+
buildPluginApis,
|
|
1725
|
+
installSharedModules,
|
|
1726
|
+
navigate,
|
|
1727
|
+
useLocation,
|
|
1728
|
+
useNavigate,
|
|
1729
|
+
Link,
|
|
1730
|
+
usePluginRoute,
|
|
1731
|
+
useParams,
|
|
1732
|
+
Breadcrumbs,
|
|
1733
|
+
PluginRoutes,
|
|
1734
|
+
useMenu,
|
|
1735
|
+
PluginProvider,
|
|
1736
|
+
usePluginFramework,
|
|
1737
|
+
PluginErrorBoundary,
|
|
1738
|
+
PluginBoundary,
|
|
1739
|
+
PluginSlot,
|
|
1740
|
+
useExtensionItems,
|
|
1741
|
+
usePlugin,
|
|
1742
|
+
usePlugins,
|
|
1743
|
+
usePluginState,
|
|
1744
|
+
usePluginEvent,
|
|
1745
|
+
usePluginCommand,
|
|
1746
|
+
usePluginService,
|
|
1747
|
+
usePluginIPC,
|
|
1748
|
+
usePluginConfig,
|
|
1749
|
+
StyleHost,
|
|
1750
|
+
declareI18nPoints,
|
|
1751
|
+
I18nProvider,
|
|
1752
|
+
useI18n,
|
|
1753
|
+
useT,
|
|
1754
|
+
useKeybindings,
|
|
1755
|
+
CommandPalette,
|
|
1756
|
+
baseCss,
|
|
1757
|
+
HttpBridgeError,
|
|
1758
|
+
createHttpBridge,
|
|
1759
|
+
createStaticBridge,
|
|
1760
|
+
detectOS,
|
|
1761
|
+
useOS,
|
|
1762
|
+
formatShortcut,
|
|
1763
|
+
Icon,
|
|
1764
|
+
iconNames,
|
|
1765
|
+
toImageSrc,
|
|
1766
|
+
Img,
|
|
1767
|
+
VideoPlayer,
|
|
1768
|
+
Markdown,
|
|
1769
|
+
declareHostPoints,
|
|
1770
|
+
DEFAULT_APPEARANCE,
|
|
1771
|
+
BUILTIN_FONTS,
|
|
1772
|
+
sanitizeFontFamily,
|
|
1773
|
+
ThemeProvider,
|
|
1774
|
+
useTheme,
|
|
1775
|
+
useAppearance,
|
|
1776
|
+
ThemeSwitcher,
|
|
1777
|
+
AppMenuBar,
|
|
1778
|
+
SearchBox,
|
|
1779
|
+
NotificationBell,
|
|
1780
|
+
ToastHost,
|
|
1781
|
+
TitleBar,
|
|
1782
|
+
Switch,
|
|
1783
|
+
Segmented,
|
|
1784
|
+
Group,
|
|
1785
|
+
Row,
|
|
1786
|
+
Button,
|
|
1787
|
+
SidebarProvider,
|
|
1788
|
+
useSidebar,
|
|
1789
|
+
renderIcon,
|
|
1790
|
+
SidebarNav,
|
|
1791
|
+
SettingsPage
|
|
1792
|
+
};
|