@lapis-notes/workspace 0.2.0 → 0.3.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +108 -9
  2. package/README.md +4 -0
  3. package/dist/WorkspaceShell.svelte +19 -2
  4. package/dist/WorkspaceShell.svelte.d.ts +3 -1
  5. package/dist/desktop/DesktopRecoveryBanner.css +46 -0
  6. package/dist/desktop/DesktopRecoveryBanner.svelte +70 -0
  7. package/dist/desktop/DesktopRecoveryBanner.svelte.d.ts +15 -0
  8. package/dist/desktop/DesktopVaultLauncher.svelte +278 -0
  9. package/dist/desktop/DesktopVaultLauncher.svelte.d.ts +17 -0
  10. package/dist/desktop/DesktopVaultLauncherDialogs.svelte +136 -0
  11. package/dist/desktop/DesktopVaultLauncherDialogs.svelte.d.ts +9 -0
  12. package/dist/desktop/DesktopWorkspaceStartup.css +8 -0
  13. package/dist/desktop/DesktopWorkspaceStartup.svelte +34 -0
  14. package/dist/desktop/DesktopWorkspaceStartup.svelte.d.ts +9 -0
  15. package/dist/desktop/desktop-vault-launcher-controls.css +142 -0
  16. package/dist/desktop/desktop-vault-launcher.css +181 -0
  17. package/dist/desktop/desktop-vault-launcher.d.ts +30 -0
  18. package/dist/desktop/desktop-vault-launcher.js +26 -0
  19. package/dist/desktop/desktop-vault-launcher.svelte.d.ts +36 -0
  20. package/dist/desktop/desktop-vault-launcher.svelte.js +140 -0
  21. package/dist/desktop/lapis-logo.svg +471 -0
  22. package/dist/file-conflicts/ConflictReview.svelte +145 -0
  23. package/dist/file-conflicts/ConflictReview.svelte.d.ts +8 -0
  24. package/dist/file-conflicts/ConflictReviewControls.svelte +66 -0
  25. package/dist/file-conflicts/ConflictReviewControls.svelte.d.ts +12 -0
  26. package/dist/file-conflicts/conflict-review-state.svelte.d.ts +22 -0
  27. package/dist/file-conflicts/conflict-review-state.svelte.js +59 -0
  28. package/dist/file-conflicts/conflict-review-view.d.ts +19 -0
  29. package/dist/file-conflicts/conflict-review-view.js +50 -0
  30. package/dist/file-conflicts/register-conflict-reviews.d.ts +7 -0
  31. package/dist/file-conflicts/register-conflict-reviews.js +132 -0
  32. package/dist/index.d.ts +6 -0
  33. package/dist/index.js +4 -0
  34. package/dist/plugin-management/CorePluginsSettingsPage.svelte +47 -16
  35. package/dist/plugin-management/PluginBrowser.svelte +63 -7
  36. package/dist/plugin-management/PluginRegistryToolbar.svelte.d.ts +1 -1
  37. package/dist/plugin-management/plugin-browser-model.d.ts +1 -0
  38. package/dist/plugin-management/plugin-browser-model.js +11 -2
  39. package/dist/plugin-management/plugin-browser-navigation.d.ts +1 -1
  40. package/dist/plugin-management/plugin-browser-navigation.js +4 -2
  41. package/dist/plugin-management/plugin-registry-controller.svelte.js +5 -1
  42. package/dist/plugin-management/plugins-view.svelte.d.ts +2 -1
  43. package/dist/plugin-management/plugins-view.svelte.js +6 -2
  44. package/dist/plugin-management/register-plugin-management-settings.js +7 -6
  45. package/package.json +5 -4
@@ -94,7 +94,9 @@
94
94
  sidebarMaxWidth: 440,
95
95
  }),
96
96
  );
97
- let selectedVersion = $state<string>();
97
+ const selectedVersion = $derived(
98
+ navigation.route.page === "plugin" ? navigation.route.version : undefined,
99
+ );
98
100
  const resolved = $derived(
99
101
  selected && session.detail
100
102
  ? communityDetail(selected, session.detail)
@@ -115,6 +117,15 @@
115
117
  );
116
118
  const installAction = $derived.by<RegistryInstallAction>(() => {
117
119
  if (!selected) return { state: "disabled" };
120
+ if (
121
+ ownedApp.pluginDistribution
122
+ .getCompatibility?.(selected.id)
123
+ ?.reasons.includes("desktop-only")
124
+ )
125
+ return {
126
+ state: "disabled",
127
+ reason: "This plugin is available in the desktop app.",
128
+ };
118
129
  const feedback =
119
130
  registry.actionFeedback?.pluginId === selected.id
120
131
  ? registry.actionFeedback
@@ -128,16 +139,40 @@
128
139
  return { state: "rejected", reason: feedback.message };
129
140
  if (bundledIds.has(selected.id))
130
141
  return { state: "installed", label: "Bundled" };
131
- if (update?.canUpdate) return { state: "available", label: "Update" };
132
- if (installedIds.has(selected.id)) return { state: "installed" };
142
+ const installed = registry.installed.find(
143
+ (record) => record.pluginId === selected.id,
144
+ );
145
+ const requestedVersion = selectedVersion ?? selected.latestVersion;
146
+ if (installed?.installedVersion === requestedVersion)
147
+ return {
148
+ state: "installed",
149
+ installedVersion: installed.installedVersion,
150
+ };
151
+ if (update?.canUpdate && requestedVersion === selected.latestVersion)
152
+ return {
153
+ state: "available",
154
+ label: "Update",
155
+ installedVersion: installed?.installedVersion,
156
+ };
157
+ if (installed && requestedVersion === selected.latestVersion)
158
+ return {
159
+ state: "installed",
160
+ installedVersion: installed.installedVersion,
161
+ };
133
162
  const disabled = reason(selected);
134
163
  return disabled
135
164
  ? { state: "disabled", reason: disabled }
136
- : { state: "available" };
165
+ : { state: "available", installedVersion: installed?.installedVersion };
137
166
  });
138
167
  const downloads = (id: string) =>
139
168
  registry.downloadStats?.periods["30d"].plugins[id]?.total ?? 0;
140
169
  function reason(entry: PluginCatalogEntry): string | undefined {
170
+ if (
171
+ ownedApp.pluginDistribution
172
+ .getCompatibility?.(entry.id)
173
+ ?.reasons.includes("desktop-only")
174
+ )
175
+ return "This plugin is available in the desktop app.";
141
176
  if (entry.status && entry.status !== "active")
142
177
  return `This plugin is ${entry.status}.`;
143
178
  if (
@@ -171,7 +206,6 @@
171
206
  $effect(() => {
172
207
  if (selected && previousDetail !== selected.id) {
173
208
  previousDetail = selected.id;
174
- selectedVersion = undefined;
175
209
  void session.select(selected);
176
210
  }
177
211
  if (navigation.route.page !== "plugin") {
@@ -412,6 +446,19 @@
412
446
  onOpenPlugin={open}
413
447
  onOpenCategory={(category) => go("directory", category)}
414
448
  />
449
+ {:else if selected && resolved && selectedVersion !== undefined && !Object.hasOwn(resolved.detail.versions, selectedVersion)}<PluginRegistryContentState
450
+ kind="empty"
451
+ heading="Plugin version unavailable"
452
+ description={`Version ${selectedVersion} is not available from your enabled registry.`}
453
+ actionLabel="View latest release"
454
+ onaction={() => {
455
+ navigation.route = {
456
+ page: "plugin",
457
+ pluginId: selected.id,
458
+ };
459
+ persist();
460
+ }}
461
+ />
415
462
  {:else if selected && resolved}<PluginBrowserDetail
416
463
  {resolved}
417
464
  downloadSummary={registry.downloadStats
@@ -430,14 +477,23 @@
430
477
  ? changelogContent
431
478
  : undefined}
432
479
  onInstall={(request) =>
433
- update?.canUpdate
480
+ update?.canUpdate &&
481
+ request.version === update.targetVersion &&
482
+ request.version === selected.latestVersion
434
483
  ? registry.update(update)
435
484
  : registry.install(selected, request.version)}
436
485
  onOpenPlugin={open}
437
486
  onOpenCategory={(category) => go("directory", category)}
438
487
  onOpenPlatform={(platform) =>
439
488
  go("directory", undefined, platform)}
440
- onSelectVersion={(version) => (selectedVersion = version)}
489
+ onSelectVersion={(version) => {
490
+ navigation.route = {
491
+ page: "plugin",
492
+ pluginId: selected.id,
493
+ version,
494
+ };
495
+ persist();
496
+ }}
441
497
  />
442
498
  {:else if selected && session.loading}<PluginRegistryContentState
443
499
  kind="loading"
@@ -16,6 +16,6 @@ type $$ComponentProps = {
16
16
  resultCount: number;
17
17
  onreset: () => void;
18
18
  };
19
- declare const PluginRegistryToolbar: import("svelte").Component<$$ComponentProps, {}, "search" | "provenance" | "sort" | "channel" | "category" | "platform" | "compatibleOnly" | "installedState" | "enabledState" | "updateState">;
19
+ declare const PluginRegistryToolbar: import("svelte").Component<$$ComponentProps, {}, "search" | "sort" | "provenance" | "channel" | "category" | "platform" | "compatibleOnly" | "installedState" | "enabledState" | "updateState">;
20
20
  type PluginRegistryToolbar = ReturnType<typeof PluginRegistryToolbar>;
21
21
  export default PluginRegistryToolbar;
@@ -4,6 +4,7 @@ export type PluginBrowserRoute = {
4
4
  } | {
5
5
  page: "plugin";
6
6
  pluginId: string;
7
+ version?: string;
7
8
  };
8
9
  export interface PluginDirectoryState {
9
10
  query: string;
@@ -67,8 +67,17 @@ export function restoreBrowserState(value) {
67
67
  const routeValue = value.route;
68
68
  const route = routeValue?.page === "plugin" &&
69
69
  typeof routeValue.pluginId === "string" &&
70
- routeValue.pluginId.length > 0
71
- ? { page: "plugin", pluginId: routeValue.pluginId }
70
+ routeValue.pluginId.length > 0 &&
71
+ (routeValue.version === undefined ||
72
+ (typeof routeValue.version === "string" &&
73
+ /^[A-Za-z0-9][A-Za-z0-9.+-]{0,127}$/u.test(routeValue.version)))
74
+ ? {
75
+ page: "plugin",
76
+ pluginId: routeValue.pluginId,
77
+ ...(routeValue.version !== undefined
78
+ ? { version: routeValue.version }
79
+ : {}),
80
+ }
72
81
  : { page: routeValue?.page === "directory" ? "directory" : "landing" };
73
82
  const directory = defaultDirectoryState();
74
83
  const input = value.directory;
@@ -1,3 +1,3 @@
1
1
  import type { App } from "@lapis-notes/api";
2
2
  /** Reveal the registered singleton browser, preserving its workspace placement. */
3
- export declare function openPluginBrowser(app: App, pluginId?: string, isActive?: () => boolean): Promise<void>;
3
+ export declare function openPluginBrowser(app: App, pluginId?: string, isActive?: () => boolean, version?: string): Promise<void>;
@@ -1,7 +1,7 @@
1
1
  import { PluginsView } from "./plugins-view.svelte";
2
2
  const pending = new WeakMap();
3
3
  /** Reveal the registered singleton browser, preserving its workspace placement. */
4
- export function openPluginBrowser(app, pluginId, isActive = () => true) {
4
+ export function openPluginBrowser(app, pluginId, isActive = () => true, version) {
5
5
  const previous = pending.get(app) ?? Promise.resolve();
6
6
  const next = previous
7
7
  .catch(() => { })
@@ -19,7 +19,9 @@ export function openPluginBrowser(app, pluginId, isActive = () => true) {
19
19
  return;
20
20
  if (!(leaf.view instanceof PluginsView))
21
21
  throw new Error("The Plugins view is not registered in this workspace.");
22
- if (pluginId)
22
+ if (pluginId && version !== undefined)
23
+ leaf.view.showPlugin(pluginId, version);
24
+ else if (pluginId)
23
25
  leaf.view.showPlugin(pluginId);
24
26
  else
25
27
  leaf.view.home();
@@ -37,7 +37,11 @@ export class PluginRegistryController {
37
37
  }
38
38
  retain() {
39
39
  if (this.references++ === 0) {
40
- for (const name of ["plugin-enabled", "plugin-disabled"]) {
40
+ for (const name of [
41
+ "plugin-enabled",
42
+ "plugin-disabled",
43
+ "plugin-unloaded",
44
+ ]) {
41
45
  const ref = this.app.plugins.on(name, () => {
42
46
  this.pluginLifecycleRevision += 1;
43
47
  });
@@ -15,6 +15,7 @@ export declare class PluginsView extends ItemView {
15
15
  } | {
16
16
  page: "plugin";
17
17
  pluginId: string;
18
+ version?: string;
18
19
  };
19
20
  directory: {
20
21
  query: string;
@@ -31,7 +32,7 @@ export declare class PluginsView extends ItemView {
31
32
  scrollTop: number;
32
33
  };
33
34
  setState(state: Record<string, unknown>): Promise<void>;
34
- showPlugin(pluginId: string): void;
35
+ showPlugin(pluginId: string, version?: string): void;
35
36
  home(): void;
36
37
  load(): void;
37
38
  unload(): void;
@@ -61,10 +61,14 @@ export class PluginsView extends ItemView {
61
61
  ? Math.max(0, state.scrollTop)
62
62
  : 0;
63
63
  }
64
- showPlugin(pluginId) {
64
+ showPlugin(pluginId, version) {
65
65
  if (this.navigation.route.page !== "plugin")
66
66
  this.navigation.returnPage = this.navigation.route.page;
67
- this.navigation.route = { page: "plugin", pluginId };
67
+ this.navigation.route = {
68
+ page: "plugin",
69
+ pluginId,
70
+ ...(version !== undefined ? { version } : {}),
71
+ };
68
72
  this.app.workspace.requestSaveLayout();
69
73
  }
70
74
  home() {
@@ -4,12 +4,13 @@ import CorePluginsSettingsPage from "./CorePluginsSettingsPage.svelte";
4
4
  import PluginRegistrySettingsPage from "./PluginRegistrySettingsPage.svelte";
5
5
  import { clearPluginManagementContext, PluginManagementRevealState, setPluginManagementContext, } from "./plugin-management-context.svelte";
6
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],
7
+ const { managedPlugins } = getWorkspaceHostBinding(app.workspace).controller;
8
+ return managedPlugins.states.map((entry) => ({
9
+ id: `core:${entry.sourceId === "lapis" ? entry.id : entry.key}`,
10
+ title: entry.name,
11
+ description: entry.description,
12
+ keywords: [entry.id, entry.distribution, "plugin"],
13
+ path: ["Core plugins", entry.name],
13
14
  }));
14
15
  }
15
16
  function communitySearchEntries(app) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lapis-notes/workspace",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Lapis Notes workspace shell backed by design-core",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -38,9 +38,10 @@
38
38
  }
39
39
  },
40
40
  "dependencies": {
41
- "@lapis-notes/api": "^0.1.2",
42
- "@lapismd/design-core": "^0.3.2",
43
- "@lucide/svelte": "^0.523.0"
41
+ "@lapis-notes/api": "^0.2.0",
42
+ "@lapismd/design-core": "^0.7.0",
43
+ "@lucide/svelte": "^0.523.0",
44
+ "@lapis-notes/ui": "^0.1.4"
44
45
  },
45
46
  "peerDependencies": {
46
47
  "svelte": "^5.0.0"