fluxplugin 1.0.2 → 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
package/dist/node/index.js
CHANGED
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
normalizePath,
|
|
58
58
|
parseOrThrow,
|
|
59
59
|
parseVersion,
|
|
60
|
+
parseVideo,
|
|
60
61
|
pluginApi,
|
|
61
62
|
preloadMember,
|
|
62
63
|
routePatternMatches,
|
|
@@ -68,7 +69,7 @@ import {
|
|
|
68
69
|
validateManifest,
|
|
69
70
|
videoEmbedUrl,
|
|
70
71
|
withTimeout
|
|
71
|
-
} from "./chunks/chunk-
|
|
72
|
+
} from "./chunks/chunk-SDCGUBEW.js";
|
|
72
73
|
import "./chunks/chunk-WSFCRVEQ.js";
|
|
73
74
|
export {
|
|
74
75
|
BUILTIN_PERMISSIONS,
|
|
@@ -129,6 +130,7 @@ export {
|
|
|
129
130
|
normalizePath,
|
|
130
131
|
parseOrThrow,
|
|
131
132
|
parseVersion,
|
|
133
|
+
parseVideo,
|
|
132
134
|
pluginApi,
|
|
133
135
|
preloadMember,
|
|
134
136
|
routePatternMatches,
|
package/dist/node/main.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
JsonStateFile,
|
|
9
9
|
LocalRegistry,
|
|
10
10
|
MANIFEST_FILE,
|
|
11
|
+
MIME,
|
|
11
12
|
MainFramework,
|
|
12
13
|
PLUGIN_SCHEME,
|
|
13
14
|
PluginInstaller,
|
|
@@ -21,11 +22,13 @@ import {
|
|
|
21
22
|
createRegistries,
|
|
22
23
|
createTarGz,
|
|
23
24
|
discoverPlugins,
|
|
25
|
+
downloadFolder,
|
|
24
26
|
entriesFromIndex,
|
|
25
27
|
extractTarGz,
|
|
26
28
|
generateSigningKeyPair,
|
|
27
29
|
importFresh,
|
|
28
30
|
pruneNonRuntime,
|
|
31
|
+
readAsset,
|
|
29
32
|
readPluginDir,
|
|
30
33
|
readPluginManifest,
|
|
31
34
|
readTarGz,
|
|
@@ -35,8 +38,8 @@ import {
|
|
|
35
38
|
sha256File,
|
|
36
39
|
signManifest,
|
|
37
40
|
verifyPlugin
|
|
38
|
-
} from "./chunks/chunk-
|
|
39
|
-
import "./chunks/chunk-
|
|
41
|
+
} from "./chunks/chunk-ZLA5XUXN.js";
|
|
42
|
+
import "./chunks/chunk-SDCGUBEW.js";
|
|
40
43
|
import "./chunks/chunk-WSFCRVEQ.js";
|
|
41
44
|
export {
|
|
42
45
|
DEFAULT_STORE,
|
|
@@ -48,6 +51,7 @@ export {
|
|
|
48
51
|
JsonStateFile,
|
|
49
52
|
LocalRegistry,
|
|
50
53
|
MANIFEST_FILE,
|
|
54
|
+
MIME,
|
|
51
55
|
MainFramework,
|
|
52
56
|
PLUGIN_SCHEME,
|
|
53
57
|
PluginInstaller,
|
|
@@ -61,11 +65,13 @@ export {
|
|
|
61
65
|
createRegistries,
|
|
62
66
|
createTarGz,
|
|
63
67
|
discoverPlugins,
|
|
68
|
+
downloadFolder,
|
|
64
69
|
entriesFromIndex,
|
|
65
70
|
extractTarGz,
|
|
66
71
|
generateSigningKeyPair,
|
|
67
72
|
importFresh,
|
|
68
73
|
pruneNonRuntime,
|
|
74
|
+
readAsset,
|
|
69
75
|
readPluginDir,
|
|
70
76
|
readPluginManifest,
|
|
71
77
|
readTarGz,
|
package/dist/node/preload.js
CHANGED
|
@@ -5,3 +5,8 @@ export declare function buildPlugin(root: string, watch?: boolean, log?: {
|
|
|
5
5
|
(...data: any[]): void;
|
|
6
6
|
(message?: any, ...optionalParams: any[]): void;
|
|
7
7
|
}): Promise<() => Promise<void>>;
|
|
8
|
+
/**
|
|
9
|
+
* Keeps `<host>/plugins/<id>` in sync with this plugin project (runtime files only): lets you develop a plugin as its own
|
|
10
|
+
* project and watch it run in the main app (which hot-reloads it). Returns a function that stops syncing.
|
|
11
|
+
*/
|
|
12
|
+
export declare function linkPluginToHost(root: string, hostDir: string, log?: (m: string) => void): Promise<() => void>;
|
package/dist/types/cli/cli.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export declare function readManifest(root: string): Promise<Record<string, unknown>>;
|
|
2
2
|
export declare function run(argv: string[]): Promise<number>;
|
|
3
|
+
/** "github:owner/repo" | "https://host/plugins.json" (JSON link) | "https://host/store/" (folder with index.json) */
|
|
4
|
+
export declare function parseStoreSpec(spec: string | undefined): import("../main/index.js").StoreSpec[] | undefined;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Copies every plugin's runtime files (no src, no .ts, no sourcemaps) to `outDir/<folder>`. Returns the plugin count. */
|
|
2
|
+
export declare function stagePlugins(cwd: string, outDir: string, log?: (m: string) => void): Promise<number>;
|
|
3
|
+
/** Builds the app and packages it into an installer/executable with electron-builder (current OS by default). */
|
|
4
|
+
export declare function distApp(cwd: string, opts?: {
|
|
5
|
+
win?: boolean;
|
|
6
|
+
mac?: boolean;
|
|
7
|
+
linux?: boolean;
|
|
8
|
+
dir?: boolean;
|
|
9
|
+
log?: (m: string) => void;
|
|
10
|
+
}): Promise<number>;
|
|
11
|
+
/**
|
|
12
|
+
* Extracts the app as a static website: the built renderer, every plugin's runtime files and a `bootstrap.json` describing them.
|
|
13
|
+
* Serve the folder with any static host. Only the browser side runs (no main-process code): renderer-only plugins work fully.
|
|
14
|
+
*/
|
|
15
|
+
export declare function exportWebsite(cwd: string, outDir: string, log?: (m: string) => void): Promise<{
|
|
16
|
+
plugins: number;
|
|
17
|
+
out: string;
|
|
18
|
+
}>;
|
|
@@ -4,6 +4,8 @@ export interface HostVars {
|
|
|
4
4
|
/** Electron semver range (init resolves the latest published version). */
|
|
5
5
|
electron?: string;
|
|
6
6
|
tailwind?: boolean;
|
|
7
|
+
/** Stores for fluxplugin.config.json (default: the GitHub topic store). */
|
|
8
|
+
stores?: import("../main/index.js").StoreSpec[];
|
|
7
9
|
}
|
|
8
10
|
/** Files of a new host application (`fluxplugin init`). Plain strings so the CLI stays a single self-contained file. */
|
|
9
11
|
export declare function hostFiles(v: HostVars, withSample?: boolean): Record<string, string>;
|
|
@@ -5,3 +5,16 @@ export declare function storeAdd(pkgFile: string, dir: string, baseUrl?: string)
|
|
|
5
5
|
version: string;
|
|
6
6
|
sha256: string;
|
|
7
7
|
}>;
|
|
8
|
+
/**
|
|
9
|
+
* Builds the catalog JSON from a repository of plugin folders (`<dir>/plugins/<name>/plugin.json` + built `dist/`).
|
|
10
|
+
* One JSON holds every plugin's metadata (icon, screenshots, video, README…), the exact list of runtime files to download
|
|
11
|
+
* (sources and sourcemaps are never listed) and the sha256 of each file. Commit it next to the folders.
|
|
12
|
+
*/
|
|
13
|
+
export declare function storeBuild(dir: string, opts?: {
|
|
14
|
+
out?: string;
|
|
15
|
+
pluginsDir?: string;
|
|
16
|
+
log?: (m: string) => void;
|
|
17
|
+
}): Promise<{
|
|
18
|
+
count: number;
|
|
19
|
+
file: string;
|
|
20
|
+
}>;
|
|
@@ -10,3 +10,5 @@ export declare const TEMPLATES: Record<TemplateName, (v: TemplateVars) => Record
|
|
|
10
10
|
export declare function varsFromName(name: string): TemplateVars;
|
|
11
11
|
/** Adds Tailwind v4 to a plugin: utilities only (no preflight, so the host's styles are never reset). */
|
|
12
12
|
export declare function withTailwind(files: Record<string, string>, v: TemplateVars): Record<string, string>;
|
|
13
|
+
/** Extra files when a plugin is its own project (outside an app, or created with --standalone). */
|
|
14
|
+
export declare function standaloneExtras(v: TemplateVars): Record<string, string>;
|
|
@@ -93,5 +93,19 @@ export interface ManifestValidation {
|
|
|
93
93
|
export declare function validateManifest(raw: unknown, opts?: {
|
|
94
94
|
frameworkVersion?: string;
|
|
95
95
|
}): ManifestValidation;
|
|
96
|
-
|
|
96
|
+
export type VideoKind = "youtube" | "vimeo" | "dailymotion" | "loom" | "streamable" | "html5" | "link";
|
|
97
|
+
export interface VideoInfo {
|
|
98
|
+
kind: VideoKind;
|
|
99
|
+
url: string;
|
|
100
|
+
/** iframe URL for hosted players. */
|
|
101
|
+
embedUrl?: string;
|
|
102
|
+
/** Direct media URL for the HTML5 <video> player (also a plugin-relative path before it is resolved). */
|
|
103
|
+
src?: string;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Classifies a video reference: hosted players that must be embedded in an iframe (YouTube, Vimeo, Dailymotion, Loom,
|
|
107
|
+
* Streamable), direct media files for the HTML5 player (https URL or a path inside the plugin), or a plain link.
|
|
108
|
+
*/
|
|
109
|
+
export declare function parseVideo(url: string | undefined): VideoInfo | undefined;
|
|
110
|
+
/** Back-compat helper: the iframe URL of a hosted player, if any. */
|
|
97
111
|
export declare function videoEmbedUrl(url: string | undefined): string | undefined;
|
|
@@ -86,7 +86,21 @@ export interface RegistryEntryLike {
|
|
|
86
86
|
sha256?: string;
|
|
87
87
|
homepage?: string;
|
|
88
88
|
stars?: number;
|
|
89
|
-
|
|
89
|
+
/** Store metadata (everything a "store page" needs, straight from the catalog). */
|
|
90
|
+
iconUrl?: string;
|
|
91
|
+
bannerUrl?: string;
|
|
92
|
+
screenshots?: string[];
|
|
93
|
+
video?: string;
|
|
94
|
+
categories?: string[];
|
|
95
|
+
keywords?: string[];
|
|
96
|
+
links?: Record<string, string>;
|
|
97
|
+
license?: string;
|
|
98
|
+
/** Markdown (inline in the catalog). */
|
|
99
|
+
readme?: string;
|
|
100
|
+
/** Set by the app when the plugin is already installed locally. */
|
|
101
|
+
installedVersion?: string;
|
|
102
|
+
folder?: boolean;
|
|
103
|
+
files?: string[];
|
|
90
104
|
}
|
|
91
105
|
/** Everything a UI needs to render a plugin "store page": icon, banner, screenshots, video, long description. */
|
|
92
106
|
export interface PluginDetails {
|
|
@@ -104,10 +118,9 @@ export interface PluginDetails {
|
|
|
104
118
|
iconUrl?: string;
|
|
105
119
|
bannerUrl?: string;
|
|
106
120
|
screenshotUrls: string[];
|
|
107
|
-
video?:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
};
|
|
121
|
+
video?: import("./manifest.js").VideoInfo;
|
|
122
|
+
/** Base URL of the plugin's files (to resolve relative images/videos). */
|
|
123
|
+
assetBase?: string;
|
|
111
124
|
/** Markdown source of the long description (rendered by the UI without raw HTML). */
|
|
112
125
|
readme?: string;
|
|
113
126
|
}
|
|
@@ -66,10 +66,21 @@ export interface MenuItemDefinition {
|
|
|
66
66
|
badge?: string | number;
|
|
67
67
|
/** Route path, for sidebar entries that navigate without a command. */
|
|
68
68
|
path?: string;
|
|
69
|
+
/** Id of another item (same location) this one is nested under: builds sub-menus without the parent knowing its children. */
|
|
70
|
+
parent?: string;
|
|
69
71
|
}
|
|
72
|
+
export type MenuNode = Omit<MenuItemDefinition, "location" | "children"> & {
|
|
73
|
+
location?: MenuItemDefinition["location"];
|
|
74
|
+
children?: MenuNode[];
|
|
75
|
+
};
|
|
70
76
|
export declare class MenuRegistry extends Registry<MenuItemDefinition> {
|
|
71
77
|
constructor();
|
|
72
78
|
register(item: MenuItemDefinition, owner?: string): Disposable;
|
|
79
|
+
/**
|
|
80
|
+
* Hierarchical view of a location: items that name a `parent`, plus the parent's own `children`, are nested under it
|
|
81
|
+
* (sorted by order). Unknown parents fall back to top level, so a missing plugin never hides its siblings.
|
|
82
|
+
*/
|
|
83
|
+
tree(location: MenuLocation): MenuNode[];
|
|
73
84
|
items(location: MenuLocation): MenuItemDefinition[];
|
|
74
85
|
}
|
|
75
86
|
export interface ServiceEntry<T = unknown> {
|
|
@@ -54,6 +54,9 @@ export declare function resolvePluginAsset(fw: MainFramework, url: string): Prom
|
|
|
54
54
|
export declare function registerPluginProtocol(fw: MainFramework, protocol: {
|
|
55
55
|
handle(scheme: string, handler: (req: {
|
|
56
56
|
url: string;
|
|
57
|
+
headers?: {
|
|
58
|
+
get(name: string): string | null;
|
|
59
|
+
};
|
|
57
60
|
}) => Promise<Response> | Response): void;
|
|
58
61
|
}): void;
|
|
59
62
|
export interface HttpAdapterOptions {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const MIME: Record<string, string>;
|
|
2
|
+
export interface AssetResponse {
|
|
3
|
+
status: 200 | 206 | 416;
|
|
4
|
+
headers: Record<string, string>;
|
|
5
|
+
body: Buffer;
|
|
6
|
+
}
|
|
7
|
+
/** Serves a file, honouring `Range` so <video> can seek. Reads only the requested slice. */
|
|
8
|
+
export declare function readAsset(file: string, mime: string, range?: string | null): Promise<AssetResponse>;
|
|
@@ -30,7 +30,8 @@ export interface PluginDescriptor {
|
|
|
30
30
|
* GitHub as a plugin store. Two modes (they can be combined by adding two registries):
|
|
31
31
|
* - `topic` (default): developers publish a GitHub *release* in their own repo with `fluxplugin publish`; it is discovered
|
|
32
32
|
* through the repository topic. No central approval.
|
|
33
|
-
* - `repo`: a
|
|
33
|
+
* - `repo`: a catalog repository: plugin folders + one JSON file (`index.json` by default, see `fluxplugin store build`) with all the
|
|
34
|
+
* metadata. Plugins are installed straight from their folders in the repo (every file verified against the catalog hashes).
|
|
34
35
|
*/
|
|
35
36
|
export declare class GitHubRegistry implements PluginRegistry {
|
|
36
37
|
private readonly o;
|
|
@@ -14,7 +14,21 @@ export interface RegistryEntry {
|
|
|
14
14
|
sha256?: string;
|
|
15
15
|
homepage?: string;
|
|
16
16
|
stars?: number;
|
|
17
|
-
|
|
17
|
+
iconUrl?: string;
|
|
18
|
+
bannerUrl?: string;
|
|
19
|
+
screenshots?: string[];
|
|
20
|
+
video?: string;
|
|
21
|
+
categories?: string[];
|
|
22
|
+
keywords?: string[];
|
|
23
|
+
links?: Record<string, string>;
|
|
24
|
+
license?: string;
|
|
25
|
+
readme?: string;
|
|
26
|
+
/** Filled in by the app: version already installed locally. */
|
|
27
|
+
installedVersion?: string;
|
|
28
|
+
/** Folder entry: `location` is a folder URL; download `files` and verify them against `hashes`. */
|
|
29
|
+
folder?: boolean;
|
|
30
|
+
files?: string[];
|
|
31
|
+
hashes?: Record<string, string>;
|
|
18
32
|
}
|
|
19
33
|
/**
|
|
20
34
|
* Abstraction over where plugins come from. Implement it for npm, GitHub releases, a private HTTP registry,
|
|
@@ -36,22 +50,39 @@ export declare function readPluginManifest(dir: string): Promise<{
|
|
|
36
50
|
*/
|
|
37
51
|
export declare function copyPluginRuntime(src: string, dest: string): Promise<string[]>;
|
|
38
52
|
export declare function copyDir(src: string, dest: string): Promise<void>;
|
|
39
|
-
/** Shape of a store catalog
|
|
53
|
+
/** Shape of a store catalog: `{ "plugins": [...] }` or a bare array. Served as a file, built by `fluxplugin store build`, or returned by any API. */
|
|
54
|
+
export interface StoreIndexPlugin {
|
|
55
|
+
id: string;
|
|
56
|
+
name?: string;
|
|
57
|
+
version: string;
|
|
58
|
+
description?: string;
|
|
59
|
+
author?: string;
|
|
60
|
+
permissions?: string[];
|
|
61
|
+
/** A package (.tgz)… */
|
|
62
|
+
url?: string;
|
|
63
|
+
sha256?: string;
|
|
64
|
+
/** …or a plugin folder in the repository / on the host, with its file list and sha256 of every file. */
|
|
65
|
+
folder?: string;
|
|
66
|
+
files?: string[];
|
|
67
|
+
hashes?: Record<string, string>;
|
|
68
|
+
homepage?: string;
|
|
69
|
+
license?: string;
|
|
70
|
+
icon?: string;
|
|
71
|
+
banner?: string;
|
|
72
|
+
screenshots?: string[];
|
|
73
|
+
video?: string;
|
|
74
|
+
categories?: string[];
|
|
75
|
+
keywords?: string[];
|
|
76
|
+
links?: Record<string, string>;
|
|
77
|
+
/** Markdown, inline (keep it short) */
|
|
78
|
+
readme?: string;
|
|
79
|
+
}
|
|
40
80
|
export interface StoreIndex {
|
|
41
|
-
plugins:
|
|
42
|
-
id: string;
|
|
43
|
-
name?: string;
|
|
44
|
-
version: string;
|
|
45
|
-
description?: string;
|
|
46
|
-
author?: string;
|
|
47
|
-
permissions?: string[];
|
|
48
|
-
url: string;
|
|
49
|
-
sha256?: string;
|
|
50
|
-
homepage?: string;
|
|
51
|
-
icon?: string;
|
|
52
|
-
}[];
|
|
81
|
+
plugins: StoreIndexPlugin[];
|
|
53
82
|
}
|
|
54
|
-
export declare function entriesFromIndex(index: StoreIndex, baseUrl: string, source: string, query?: string): RegistryEntry[];
|
|
83
|
+
export declare function entriesFromIndex(index: StoreIndex | StoreIndexPlugin[], baseUrl: string, source: string, query?: string): RegistryEntry[];
|
|
84
|
+
/** Downloads a plugin folder file by file, verifying every file against the catalog's sha256 (required). */
|
|
85
|
+
export declare function downloadFolder(entry: RegistryEntry, dest: string, get: (url: string) => Promise<Response>): Promise<void>;
|
|
55
86
|
/** Removes everything that is not runtime code from an extracted plugin: sources, TypeScript, sourcemaps, node_modules. */
|
|
56
87
|
export declare function pruneNonRuntime(dir: string): Promise<string[]>;
|
|
57
88
|
/** A directory whose sub-directories are plugins (e.g. a shared network folder or a repo checkout). */
|
|
@@ -63,14 +94,20 @@ export declare class LocalRegistry implements PluginRegistry {
|
|
|
63
94
|
fetch(entry: RegistryEntry, dest: string): Promise<PluginManifest>;
|
|
64
95
|
}
|
|
65
96
|
/**
|
|
66
|
-
* HTTP
|
|
67
|
-
*
|
|
97
|
+
* HTTP/JSON store. `url` is either a folder holding `index.json` ("https://plugins.acme.com/") or a direct link to a JSON
|
|
98
|
+
* document / API endpoint that returns the catalog ("https://api.acme.com/plugins" or ".../plugins.json").
|
|
99
|
+
* Entries are packages (`url` → .tgz, checked against `sha256`) or plugin folders (`folder` + `files` + `hashes`).
|
|
68
100
|
*/
|
|
69
101
|
export declare class HttpRegistry implements PluginRegistry {
|
|
70
102
|
private readonly baseUrl;
|
|
71
103
|
private readonly fetchImpl;
|
|
104
|
+
private readonly headers;
|
|
105
|
+
private readonly direct;
|
|
72
106
|
readonly name: string;
|
|
73
|
-
constructor(baseUrl: string, fetchImpl?: typeof fetch);
|
|
107
|
+
constructor(baseUrl: string, fetchImpl?: typeof fetch, headers?: Record<string, string>, direct?: boolean);
|
|
108
|
+
/** Direct link (".json" file, or `direct: true` for an API endpoint) or a folder that contains index.json. */
|
|
109
|
+
private get catalogUrl();
|
|
110
|
+
private get;
|
|
74
111
|
search(query?: string): Promise<RegistryEntry[]>;
|
|
75
112
|
fetch(entry: RegistryEntry, dest: string): Promise<PluginManifest>;
|
|
76
113
|
}
|
|
@@ -3,7 +3,8 @@ import { type PluginRegistry } from "./registry.js";
|
|
|
3
3
|
* A plugin store, as written in `fluxplugin.config.json` → `"stores"` (or passed to `MainFramework`).
|
|
4
4
|
* - `github` + `topic`: every repo with that topic that published a release with `fluxplugin publish` (default store)
|
|
5
5
|
* - `github` + `repo`: a curated catalog repository ("owner/name") containing `index.json`
|
|
6
|
-
* - `http`:
|
|
6
|
+
* - `http`: a static host serving `index.json` + packages/folders (GitHub Pages, S3, your server)
|
|
7
|
+
* - `json`: one URL that returns the catalog as JSON (a file, or your own API); same format as index.json
|
|
7
8
|
* - `local`: a folder (network share, checkout)
|
|
8
9
|
*/
|
|
9
10
|
export type StoreSpec = {
|
|
@@ -16,6 +17,11 @@ export type StoreSpec = {
|
|
|
16
17
|
} | {
|
|
17
18
|
type: "http";
|
|
18
19
|
url: string;
|
|
20
|
+
headers?: Record<string, string>;
|
|
21
|
+
} | {
|
|
22
|
+
type: "json";
|
|
23
|
+
url: string;
|
|
24
|
+
headers?: Record<string, string>;
|
|
19
25
|
} | {
|
|
20
26
|
type: "local";
|
|
21
27
|
path: string;
|
|
@@ -1,7 +1,39 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
|
|
2
|
+
import type { VideoInfo } from "../core/index.js";
|
|
3
|
+
import { type RendererFramework } from "../react/index.js";
|
|
4
|
+
/** Everything the "app page" of a plugin needs, whether it is installed or only listed in a store. */
|
|
5
|
+
export interface PluginView {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
version: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
author?: string;
|
|
11
|
+
license?: string;
|
|
12
|
+
iconUrl?: string;
|
|
13
|
+
bannerUrl?: string;
|
|
14
|
+
screenshots: string[];
|
|
15
|
+
video?: VideoInfo;
|
|
16
|
+
categories: string[];
|
|
17
|
+
keywords: string[];
|
|
18
|
+
links: Record<string, string>;
|
|
19
|
+
readme?: string;
|
|
20
|
+
permissions: string[];
|
|
21
|
+
dependencies: Record<string, string>;
|
|
22
|
+
/** Base URL used to resolve relative images in the README. */
|
|
23
|
+
assetBase?: string;
|
|
24
|
+
homepage?: string;
|
|
25
|
+
}
|
|
26
|
+
/** Auto-generated settings form from the plugin's config schema. */
|
|
3
27
|
export declare function ConfigForm({ id }: {
|
|
4
28
|
id: string;
|
|
5
29
|
}): React.ReactElement | null;
|
|
6
|
-
/**
|
|
7
|
-
export declare function
|
|
30
|
+
/** Detail screen of an installed plugin (route `/plugins/:id`). */
|
|
31
|
+
export declare function PluginDetailsPage(): React.ReactElement;
|
|
32
|
+
/** Detail screen of a plugin listed in a store (route `/plugins/store/:id`). */
|
|
33
|
+
export declare function StorePluginPage(): React.ReactElement;
|
|
34
|
+
/** The plugin list: Installed · Discover (stores) · Updates. Rows open the detail screens. */
|
|
35
|
+
export declare function PluginsPage(): React.ReactElement;
|
|
36
|
+
/** Registers `/plugins`, `/plugins/:id` and `/plugins/store/:id` with their breadcrumbs. */
|
|
37
|
+
export declare function registerPluginManagerRoutes(framework: RendererFramework): void;
|
|
38
|
+
/** Back-compat: the list page. */
|
|
39
|
+
export declare const PluginManagerUI: typeof PluginsPage;
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Design system of the framework's components: macOS look (SF-style type, translucent materials, grouped lists, switches,
|
|
3
|
+
* segmented controls). Everything is driven by CSS variables so hosts, users (Settings → Appearance) and plugin themes can
|
|
4
|
+
* restyle it: --flux-font, --flux-font-size, --flux-line, --flux-radius, --flux-accent, --flux-bg/-surface/-fg/-muted/-border …
|
|
5
|
+
*/
|
|
6
|
+
export declare const baseCss = "\n: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;\n--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)}\n@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)}}\n: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)}\n: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)}\n: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}\n:root[data-motion=\"reduced\"] *{transition:none!important;animation:none!important}\nhtml{font-family:var(--flux-font);font-size:var(--flux-font-size);line-height:var(--flux-line);-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}\nbody{font-family:var(--flux-font);font-size:1em;line-height:var(--flux-line);color:var(--flux-fg);background:var(--flux-bg)}\nbutton,input,select,textarea{font-family:inherit;font-size:inherit}\n.flux-icon{flex:none;vertical-align:middle}\n.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}\n.flux-empty,.flux-not-found,.flux-forbidden,.flux-loading{padding:24px;color:var(--flux-muted)}\n.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}\n.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)}\n/* ---- materials (vibrancy) */\n.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)}\n: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}\n/* ---- buttons / switch / segmented */\n.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}\n.flux-btn:hover{background:var(--flux-surface)}.flux-btn:active{transform:scale(.97)}.flux-btn:disabled{opacity:.5;cursor:default}\n.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)}\n.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)}\n.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}\n.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}\n.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}\n.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}\n.flux-seg{display:inline-flex;background:var(--flux-surface-2);border-radius:9px;padding:2px;gap:2px}\n.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}\n.flux-seg button[data-on]{background:var(--flux-bg);box-shadow:0 1px 3px rgba(0,0,0,.18)}\n.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)}\n.flux-select{background:var(--flux-bg);color:inherit;border:1px solid var(--flux-border);border-radius:7px;padding:3px 6px}\n/* ---- grouped lists */\n.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}\n.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}\n.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))}\n.flux-row+.flux-row::before{content:\"\";position:absolute;top:0;left:14px;right:0;height:1px;background:var(--flux-border)}\n.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)}\n.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}\n.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)}\n.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}\n/* ---- title bar */\n.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}\n.flux-titlebar[data-electron][data-os=\"mac\"]{padding-left:82px}\n.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)}\n.flux-titlebar button,.flux-titlebar input,.flux-titlebar select,.flux-titlebar .flux-dropdown{-webkit-app-region:no-drag}\n.flux-tb-left,.flux-tb-right{display:flex;align-items:center;gap:2px;min-width:0}.flux-tb-right{justify-content:flex-end}\n.flux-brand{display:flex;align-items:center;gap:8px;font-weight:650;margin-right:8px;white-space:nowrap}\n.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}\n.flux-tb-btn:hover,.flux-tb-btn.is-open{background:color-mix(in srgb,var(--flux-fg) 9%,transparent)}\n.flux-menubar{display:flex}.flux-menu{position:relative}\n.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}\n.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}\n.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}\n.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)}\n.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)}\n.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}\n.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}\n.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)}\n.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}\n.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)}\n.flux-link{background:none;border:0;color:var(--flux-accent);cursor:pointer;font:inherit;font-size:.88em;padding:0}\n.flux-toasts{position:fixed;top:48px;right:14px;display:flex;flex-direction:column;gap:8px;z-index:40;pointer-events:none}\n.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}\n.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)}\n/* ---- command palette (Spotlight) */\n.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}\n.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}\n.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}\n.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}\n.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}\n/* ---- sidebar */\n.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}\n.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}\n.flux-nav-group{position:relative}\n.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}\n.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}\n.flux-nav-icon{display:inline-flex;width:22px;justify-content:center;flex:none}.flux-nav-label{flex:1;overflow:hidden;text-overflow:ellipsis}\n.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)}\n.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))}\n.flux-side[data-collapsed] .flux-nav{justify-content:center;padding:9px 0}\n.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}\n.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)}\n.flux-side-footer{font-size:.78em;color:var(--flux-muted);padding:6px 10px}\n/* ---- settings */\n.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}\n.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)}\n.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)}\n.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}\n.flux-range{width:180px;accent-color:var(--flux-accent)}\n/* ---- plugin pages */\n.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}\n.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}\n.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)}\n.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}\n.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}\n.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}\n.flux-pm h3.sec{font-size:1.3em;font-weight:700;letter-spacing:-.015em;margin:26px 0 10px}\n.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)}\n.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}\n.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}\n.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}\n.flux-fields label{display:flex;gap:12px;align-items:center;justify-content:space-between;padding:10px 14px}\n/* ---- Markdown: app-store style typography */\n.flux-md{font-size:1.04em;line-height:1.68;color:var(--flux-fg);letter-spacing:-.004em;max-width:70ch;overflow-wrap:anywhere}\n.flux-md>:first-child{margin-top:0}.flux-md p{margin:.85em 0}\n.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}\n.flux-md a{color:var(--flux-accent);text-decoration:none}.flux-md a:hover{text-decoration:underline;text-underline-offset:3px}\n.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}\n.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}\n.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}\n.flux-md blockquote{margin:1.1em 0;padding:.2em 1em;border-left:3px solid var(--flux-border);color:var(--flux-muted)}\n.flux-md hr{border:0;height:1px;background:var(--flux-border);margin:2em 0}\n.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)}\n.flux-md-img{max-width:100%;height:auto;border-radius:14px;border:1px solid var(--flux-border);margin:.9em 0;display:block}\n.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))}\n.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}\n";
|
|
@@ -8,3 +8,8 @@ export declare class HttpBridgeError extends Error {
|
|
|
8
8
|
* (development, headless E2E tests, screenshots). In Electron the preload bridge is used instead.
|
|
9
9
|
*/
|
|
10
10
|
export declare function createHttpBridge(baseUrl?: string): FluxBridge;
|
|
11
|
+
/**
|
|
12
|
+
* Bridge for a static website export (`fluxplugin export`): reads `bootstrap.json` and has no main process behind it.
|
|
13
|
+
* Renderer-only plugins work; IPC calls reject with a clear message.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createStaticBridge(bootstrapUrl?: string): FluxBridge;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React, { type ReactNode } from "react";
|
|
2
|
+
import type { RendererFramework } from "./runtime.js";
|
|
3
|
+
export interface LanguageDef {
|
|
4
|
+
code: string;
|
|
5
|
+
label: string;
|
|
6
|
+
nativeLabel?: string;
|
|
7
|
+
rtl?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface MessagesDef {
|
|
10
|
+
locale: string;
|
|
11
|
+
messages: Record<string, string>;
|
|
12
|
+
}
|
|
13
|
+
export type TFunction = (key: string, fallback?: string, params?: Record<string, string | number>) => string;
|
|
14
|
+
interface I18nCtx {
|
|
15
|
+
locale: string;
|
|
16
|
+
setLocale(code: string): void;
|
|
17
|
+
languages: LanguageDef[];
|
|
18
|
+
t: TFunction;
|
|
19
|
+
}
|
|
20
|
+
/** Declares the extension points plugins use to add languages and translations. Called by `declareHostPoints`. */
|
|
21
|
+
export declare function declareI18nPoints(framework: RendererFramework): void;
|
|
22
|
+
/**
|
|
23
|
+
* Translations come from plugins (extension point `i18n`): `languages` lists what the user can pick, `messages` maps keys to text.
|
|
24
|
+
* Components call `useT()("key", "English fallback")`, so an app without any language plugin simply shows the fallback.
|
|
25
|
+
*/
|
|
26
|
+
export declare function I18nProvider({ children, defaultLocale, storageKey }: {
|
|
27
|
+
children: ReactNode;
|
|
28
|
+
defaultLocale?: string;
|
|
29
|
+
storageKey?: string;
|
|
30
|
+
}): React.ReactElement;
|
|
31
|
+
export declare const useI18n: () => I18nCtx;
|
|
32
|
+
export declare const useT: () => TFunction;
|
|
33
|
+
export {};
|
|
@@ -9,3 +9,8 @@ export * from "./icons.js";
|
|
|
9
9
|
export * from "./markdown.js";
|
|
10
10
|
export * from "./theme.js";
|
|
11
11
|
export * from "./titlebar.js";
|
|
12
|
+
export * from "./ui.js";
|
|
13
|
+
export * from "./media.js";
|
|
14
|
+
export * from "./i18n.js";
|
|
15
|
+
export * from "./sidebar.js";
|
|
16
|
+
export * from "./settings.js";
|
|
@@ -3,6 +3,7 @@ import React from "react";
|
|
|
3
3
|
* Small, safe Markdown renderer for plugin descriptions: headings, paragraphs, lists, quotes, code blocks, rules,
|
|
4
4
|
* bold/italic/inline code and https links. Raw HTML is never interpreted (it is shown as text) and images are not loaded.
|
|
5
5
|
*/
|
|
6
|
-
export declare function Markdown({ source }: {
|
|
6
|
+
export declare function Markdown({ source, baseUrl }: {
|
|
7
7
|
source: string;
|
|
8
|
+
baseUrl?: string;
|
|
8
9
|
}): React.ReactElement;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { VideoInfo } from "../core/index.js";
|
|
3
|
+
export type ImageInput = string | Blob | ArrayBuffer | Uint8Array | null | undefined;
|
|
4
|
+
export interface ResolvedImage {
|
|
5
|
+
src: string;
|
|
6
|
+
/** Call when the image is no longer used (revokes object URLs). */
|
|
7
|
+
release?: () => void;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Turns any supported image source into something an <img> can show:
|
|
11
|
+
* https/http URLs, `data:image/*;base64,…`, raw base64 text, `blob:` URLs, Blob/File/ArrayBuffer objects, `file://` URLs,
|
|
12
|
+
* absolute local paths (C:\… or /…), and relative paths (resolved against `base`, e.g. a plugin's asset URL).
|
|
13
|
+
* Anything else (javascript:, other data: types…) returns undefined.
|
|
14
|
+
*/
|
|
15
|
+
export declare function toImageSrc(input: ImageInput, opts?: {
|
|
16
|
+
base?: string;
|
|
17
|
+
}): ResolvedImage | undefined;
|
|
18
|
+
/** <img> that accepts any source accepted by `toImageSrc`, with a graceful placeholder when it fails to load. */
|
|
19
|
+
export declare function Img({ src, base, alt, fallback, className, style, ...rest }: {
|
|
20
|
+
src: ImageInput;
|
|
21
|
+
base?: string;
|
|
22
|
+
alt?: string;
|
|
23
|
+
fallback?: React.ReactNode;
|
|
24
|
+
} & Omit<React.ImgHTMLAttributes<HTMLImageElement>, "src" | "alt">): React.ReactElement;
|
|
25
|
+
/**
|
|
26
|
+
* Video player. Hosted players (YouTube, Vimeo, Dailymotion, Loom, Streamable) use a sandboxed iframe; direct media files
|
|
27
|
+
* (mp4/webm/ogg/mov, https or shipped inside a plugin) use the native HTML5 player; anything else becomes a link.
|
|
28
|
+
*/
|
|
29
|
+
export declare function VideoPlayer({ video, poster, title }: {
|
|
30
|
+
video: VideoInfo;
|
|
31
|
+
poster?: string;
|
|
32
|
+
title?: string;
|
|
33
|
+
}): React.ReactElement;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* macOS "System Settings"-style screen. Sections are contributed to the `settings` extension point by the host and by
|
|
4
|
+
* plugins (language, appearance, your own…). Register the route `/settings/:section` (and `/settings`) with this component.
|
|
5
|
+
*/
|
|
6
|
+
export declare function SettingsPage(): React.ReactElement;
|