@lapis-notes/workspace 0.1.0
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/CHANGELOG.md +8 -0
- package/LICENSE.md +4 -0
- package/README.md +36 -0
- package/dist/WorkspaceShell.svelte +59 -0
- package/dist/WorkspaceShell.svelte.d.ts +14 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +5 -0
- package/dist/plugin-management/CommunityPluginsSettingsPage.svelte +182 -0
- package/dist/plugin-management/CommunityPluginsSettingsPage.svelte.d.ts +5 -0
- package/dist/plugin-management/CorePluginsSettingsPage.svelte +111 -0
- package/dist/plugin-management/CorePluginsSettingsPage.svelte.d.ts +5 -0
- package/dist/plugin-management/PluginReadmeRenderer.svelte +47 -0
- package/dist/plugin-management/PluginReadmeRenderer.svelte.d.ts +9 -0
- package/dist/plugin-management/PluginRegistrySettingsPage.svelte +751 -0
- package/dist/plugin-management/PluginRegistrySettingsPage.svelte.d.ts +5 -0
- package/dist/plugin-management/PluginSettingsRow.svelte +152 -0
- package/dist/plugin-management/PluginSettingsRow.svelte.d.ts +24 -0
- package/dist/plugin-management/plugin-management-context.svelte.d.ts +15 -0
- package/dist/plugin-management/plugin-management-context.svelte.js +24 -0
- package/dist/plugin-management/plugin-management.css +477 -0
- package/dist/plugin-management/plugin-readme.d.ts +26 -0
- package/dist/plugin-management/plugin-readme.js +34 -0
- package/dist/plugin-management/plugin-settings-navigation.d.ts +8 -0
- package/dist/plugin-management/plugin-settings-navigation.js +37 -0
- package/dist/plugin-management/register-plugin-management-settings.d.ts +3 -0
- package/dist/plugin-management/register-plugin-management-settings.js +95 -0
- package/dist/workspace-shell.css +18 -0
- package/package.json +69 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
type FetchLike = typeof fetch;
|
|
2
|
+
export type PluginReadmeLoadState = {
|
|
3
|
+
status: "idle";
|
|
4
|
+
} | {
|
|
5
|
+
status: "loading";
|
|
6
|
+
} | {
|
|
7
|
+
status: "loaded";
|
|
8
|
+
url: string;
|
|
9
|
+
markdown: string;
|
|
10
|
+
} | {
|
|
11
|
+
status: "missing";
|
|
12
|
+
} | {
|
|
13
|
+
status: "error";
|
|
14
|
+
message: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function clearPluginReadmeCache(): void;
|
|
17
|
+
export declare function fetchPluginReadmeMarkdown(url: string, options: {
|
|
18
|
+
pluginId: string;
|
|
19
|
+
detailUrl: string;
|
|
20
|
+
}, fetchImpl?: FetchLike): Promise<string>;
|
|
21
|
+
export declare function buildPluginReadmeMarkdownUrl(_readmeUrl: string, options: {
|
|
22
|
+
pluginId: string;
|
|
23
|
+
detailUrl: string;
|
|
24
|
+
}): string;
|
|
25
|
+
export declare function resolveReadmeRelativeUrls(markdown: string, readmeUrl: string): string;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const readmeCache = new Map();
|
|
2
|
+
export function clearPluginReadmeCache() {
|
|
3
|
+
readmeCache.clear();
|
|
4
|
+
}
|
|
5
|
+
export async function fetchPluginReadmeMarkdown(url, options, fetchImpl = fetch) {
|
|
6
|
+
const artifactUrl = buildPluginReadmeMarkdownUrl(url, options);
|
|
7
|
+
let cached = readmeCache.get(artifactUrl);
|
|
8
|
+
if (!cached) {
|
|
9
|
+
cached = fetchMarkdown(artifactUrl, fetchImpl).then((markdown) => resolveReadmeRelativeUrls(markdown, artifactUrl));
|
|
10
|
+
readmeCache.set(artifactUrl, cached);
|
|
11
|
+
}
|
|
12
|
+
return cached;
|
|
13
|
+
}
|
|
14
|
+
export function buildPluginReadmeMarkdownUrl(_readmeUrl, options) {
|
|
15
|
+
return new URL(`../readmes/${encodeURIComponent(options.pluginId)}/README.md`, options.detailUrl).toString();
|
|
16
|
+
}
|
|
17
|
+
export function resolveReadmeRelativeUrls(markdown, readmeUrl) {
|
|
18
|
+
return markdown.replace(/(!?\[[^\]]*\]\()(<)?(?![a-z][a-z0-9+.-]*:|\/\/|\/)([^)\s>]+)(>)?([^)]*\))/gi, (match, prefix, openingAngle, target, closingAngle, suffix) => {
|
|
19
|
+
try {
|
|
20
|
+
const resolved = new URL(target, readmeUrl).toString();
|
|
21
|
+
return `${prefix}${openingAngle ?? ""}${resolved}${closingAngle ?? ""}${suffix}`;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return match;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
async function fetchMarkdown(url, fetchImpl) {
|
|
29
|
+
const response = await fetchImpl(url);
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
throw new Error(`README request failed with HTTP ${response.status}`);
|
|
32
|
+
}
|
|
33
|
+
return response.text();
|
|
34
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { App } from "@lapis-notes/api";
|
|
2
|
+
import type { WorkspaceSettingsController } from "@lapismd/design-core/workspace/settings";
|
|
3
|
+
export declare function pluginSettingsSectionId(controller: WorkspaceSettingsController, pluginId: string): string | null;
|
|
4
|
+
export declare function openPluginSettings(controller: WorkspaceSettingsController, pluginId: string): boolean;
|
|
5
|
+
export declare function pluginDiagnosticRows(app: App, pluginId: string): {
|
|
6
|
+
label: string;
|
|
7
|
+
value: string;
|
|
8
|
+
}[];
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export function pluginSettingsSectionId(controller, pluginId) {
|
|
2
|
+
return (controller.sections.find((section) => section.sourcePluginId === pluginId &&
|
|
3
|
+
section.id !== "core-plugins" &&
|
|
4
|
+
section.id !== "community-plugins")?.id ?? null);
|
|
5
|
+
}
|
|
6
|
+
export function openPluginSettings(controller, pluginId) {
|
|
7
|
+
const sectionId = pluginSettingsSectionId(controller, pluginId);
|
|
8
|
+
return sectionId ? controller.open({ sectionId }) : false;
|
|
9
|
+
}
|
|
10
|
+
export function pluginDiagnosticRows(app, pluginId) {
|
|
11
|
+
const plugin = app.plugins.plugins.get(pluginId);
|
|
12
|
+
const indexed = app.plugins.getCommunityPluginDiagnostics(pluginId);
|
|
13
|
+
const rows = [
|
|
14
|
+
{ label: "State", value: plugin?.state ?? indexed?.state ?? "indexed" },
|
|
15
|
+
{
|
|
16
|
+
label: "Source",
|
|
17
|
+
value: indexed?.source ?? plugin?.source ?? "core",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
label: "Provenance",
|
|
21
|
+
value: indexed?.provenance ?? plugin?.provenance ?? "bundled",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
label: "Activation",
|
|
25
|
+
value: indexed?.activationMode ?? "code",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
label: "Runtime host",
|
|
29
|
+
value: indexed?.selectedRuntimeHost ?? plugin?.hostMode ?? "application",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
label: "Runtime entry",
|
|
33
|
+
value: indexed?.selectedRuntimeEntry ?? "static package",
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
return rows.filter((row) => row.value.length > 0);
|
|
37
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { getWorkspaceHostBinding } from "@lapis-notes/api/workspace-host";
|
|
2
|
+
import CommunityPluginsSettingsPage from "./CommunityPluginsSettingsPage.svelte";
|
|
3
|
+
import CorePluginsSettingsPage from "./CorePluginsSettingsPage.svelte";
|
|
4
|
+
import PluginRegistrySettingsPage from "./PluginRegistrySettingsPage.svelte";
|
|
5
|
+
import { clearPluginManagementContext, PluginManagementRevealState, setPluginManagementContext, } from "./plugin-management-context.svelte";
|
|
6
|
+
function coreSearchEntries(app) {
|
|
7
|
+
return app.plugins.corePluginEntries.map((entry) => ({
|
|
8
|
+
id: `core:${entry.manifest.id}`,
|
|
9
|
+
title: entry.manifest.name,
|
|
10
|
+
description: entry.manifest.description,
|
|
11
|
+
keywords: [entry.manifest.id, entry.distribution, "plugin"],
|
|
12
|
+
path: ["Core plugins", entry.manifest.name],
|
|
13
|
+
}));
|
|
14
|
+
}
|
|
15
|
+
function communitySearchEntries(app) {
|
|
16
|
+
const loaded = app.plugins.communityPlugins.map((plugin) => ({
|
|
17
|
+
id: `community:${plugin.manifest.id}`,
|
|
18
|
+
title: plugin.manifest.name,
|
|
19
|
+
description: plugin.manifest.description,
|
|
20
|
+
keywords: [plugin.manifest.id, plugin.provenance, "plugin"],
|
|
21
|
+
path: ["Community plugins", plugin.manifest.name],
|
|
22
|
+
}));
|
|
23
|
+
const loadedIds = new Set(app.plugins.communityPlugins.map((plugin) => plugin.manifest.id));
|
|
24
|
+
const indexed = app.plugins.communityPluginDiagnostics
|
|
25
|
+
.filter((entry) => !loadedIds.has(entry.pluginId))
|
|
26
|
+
.map((entry) => ({
|
|
27
|
+
id: `community:${entry.pluginId}`,
|
|
28
|
+
title: entry.name ?? entry.pluginId,
|
|
29
|
+
description: entry.description ?? undefined,
|
|
30
|
+
keywords: [entry.pluginId, entry.provenance, "plugin"],
|
|
31
|
+
path: ["Community plugins", entry.name ?? entry.pluginId],
|
|
32
|
+
}));
|
|
33
|
+
return [...loaded, ...indexed];
|
|
34
|
+
}
|
|
35
|
+
function registrySearchEntries(app) {
|
|
36
|
+
try {
|
|
37
|
+
return app.pluginDistribution.search({ channel: "all" }).map((entry) => ({
|
|
38
|
+
id: `registry:${entry.id}`,
|
|
39
|
+
title: entry.name,
|
|
40
|
+
description: entry.description,
|
|
41
|
+
keywords: [entry.id, entry.author, entry.channel, ...entry.categories],
|
|
42
|
+
path: ["Plugin registry", "Browse", entry.name],
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function registerSearchableSection(controller, section) {
|
|
50
|
+
return controller.registerSection(section);
|
|
51
|
+
}
|
|
52
|
+
/** Register the three Lapis Notes-owned plugin settings pages. */
|
|
53
|
+
export function registerPluginManagementSettings(app) {
|
|
54
|
+
const { controller } = getWorkspaceHostBinding(app.workspace);
|
|
55
|
+
const settings = controller.settings;
|
|
56
|
+
const reveal = new PluginManagementRevealState();
|
|
57
|
+
setPluginManagementContext(settings, { app, reveal });
|
|
58
|
+
const disposers = [
|
|
59
|
+
registerSearchableSection(settings, {
|
|
60
|
+
id: "plugin-registry",
|
|
61
|
+
title: "Plugin registry",
|
|
62
|
+
description: "Browse verified plugins, manage installed provenance, and review updates.",
|
|
63
|
+
icon: "package",
|
|
64
|
+
order: 94,
|
|
65
|
+
page: PluginRegistrySettingsPage,
|
|
66
|
+
searchEntries: () => registrySearchEntries(app),
|
|
67
|
+
revealSearchEntry: (entryId) => reveal.request("plugin-registry", entryId),
|
|
68
|
+
}),
|
|
69
|
+
registerSearchableSection(settings, {
|
|
70
|
+
id: "core-plugins",
|
|
71
|
+
title: "Core plugins",
|
|
72
|
+
description: "Enable, inspect, and configure this app's static plugins.",
|
|
73
|
+
icon: "blocks",
|
|
74
|
+
order: 100,
|
|
75
|
+
page: CorePluginsSettingsPage,
|
|
76
|
+
searchEntries: () => coreSearchEntries(app),
|
|
77
|
+
revealSearchEntry: (entryId) => reveal.request("core-plugins", entryId),
|
|
78
|
+
}),
|
|
79
|
+
registerSearchableSection(settings, {
|
|
80
|
+
id: "community-plugins",
|
|
81
|
+
title: "Community plugins",
|
|
82
|
+
description: "Review installed plugins, workspace trust, and runtime diagnostics.",
|
|
83
|
+
icon: "puzzle",
|
|
84
|
+
order: 101,
|
|
85
|
+
page: CommunityPluginsSettingsPage,
|
|
86
|
+
searchEntries: () => communitySearchEntries(app),
|
|
87
|
+
revealSearchEntry: (entryId) => reveal.request("community-plugins", entryId),
|
|
88
|
+
}),
|
|
89
|
+
];
|
|
90
|
+
return () => {
|
|
91
|
+
for (const dispose of disposers.reverse())
|
|
92
|
+
dispose();
|
|
93
|
+
clearPluginManagementContext(settings);
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
.lapis-workspace-shell {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
display: grid;
|
|
4
|
+
width: 100%;
|
|
5
|
+
height: 100%;
|
|
6
|
+
min-width: 0;
|
|
7
|
+
min-height: 0;
|
|
8
|
+
overflow: hidden;
|
|
9
|
+
color: var(--ui-workspace-foreground, var(--foreground));
|
|
10
|
+
background: var(--ui-workspace-background, var(--background));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.lapis-workspace-shell :global([data-ui-component="app-shell"]) {
|
|
14
|
+
width: 100%;
|
|
15
|
+
height: 100%;
|
|
16
|
+
min-width: 0;
|
|
17
|
+
min-height: 0;
|
|
18
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lapis-notes/workspace",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lapis Notes workspace shell backed by design-core",
|
|
5
|
+
"license": "AGPL-3.0-or-later",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/lapismd/lapis-notes.git",
|
|
9
|
+
"directory": "packages/workspace"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/lapismd/lapis-notes/tree/main/packages/workspace#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/lapismd/lapis-notes/issues"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"files": [
|
|
20
|
+
"CHANGELOG.md",
|
|
21
|
+
"dist",
|
|
22
|
+
"LICENSE.md",
|
|
23
|
+
"README.md",
|
|
24
|
+
"!dist/**/*.test.*",
|
|
25
|
+
"!dist/**/*.spec.*"
|
|
26
|
+
],
|
|
27
|
+
"sideEffects": [
|
|
28
|
+
"**/*.css"
|
|
29
|
+
],
|
|
30
|
+
"svelte": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"svelte": "./dist/index.js",
|
|
36
|
+
"import": "./dist/index.js",
|
|
37
|
+
"default": "./dist/index.js"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@lapis-notes/api": "^0.1.0",
|
|
42
|
+
"@lapismd/design-core": "^0.1.0",
|
|
43
|
+
"@lucide/svelte": "^0.523.0"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"svelte": "^5.0.0"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@sveltejs/package": "^2.5.0",
|
|
50
|
+
"@sveltejs/vite-plugin-svelte": "^5.1.1",
|
|
51
|
+
"@tsconfig/svelte": "^5.0.5",
|
|
52
|
+
"fake-indexeddb": "^6.2.5",
|
|
53
|
+
"jsdom": "^26.1.0",
|
|
54
|
+
"publint": "^0.3.12",
|
|
55
|
+
"svelte": "^5.38.2",
|
|
56
|
+
"svelte-check": "^4.3.1",
|
|
57
|
+
"tslib": "^2.8.1",
|
|
58
|
+
"typescript": "~5.8.3",
|
|
59
|
+
"vite": "^6.4.2",
|
|
60
|
+
"vitest": "^4.1.8"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"build": "pnpm run prepack",
|
|
64
|
+
"check": "pnpm --dir ../.. check:no-tailwind packages/workspace/src/lib && pnpm check:svelte && pnpm check:types",
|
|
65
|
+
"check:svelte": "svelte-check --tsconfig ./tsconfig.app.json --fail-on-warnings --output human",
|
|
66
|
+
"check:types": "tsc -p tsconfig.node.json --noEmit",
|
|
67
|
+
"test": "vitest run"
|
|
68
|
+
}
|
|
69
|
+
}
|