@lapis-notes/workspace 0.3.4 → 0.4.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 +57 -0
- package/dist/WorkspaceShell.svelte +14 -1
- package/dist/mobile-workspace.d.ts +8 -0
- package/dist/mobile-workspace.js +134 -0
- package/dist/plugin-management/PluginBrowser.svelte +1 -4
- package/dist/plugin-management/PluginRegistrySettingsPage.svelte +5 -5
- package/dist/plugin-management/plugin-registry-controller.svelte.d.ts +2 -0
- package/dist/plugin-management/plugin-registry-controller.svelte.js +30 -13
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,62 @@
|
|
|
1
1
|
# @lapis-notes/workspace
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#95](https://github.com/lapismd/lapis-notes/pull/95)
|
|
8
|
+
[`8a4d0e0`](https://github.com/lapismd/lapis-notes/commit/8a4d0e02d0642a65ec7441f4fea032739550a6ae)
|
|
9
|
+
Thanks [@stevejuma](https://github.com/stevejuma)! - Honor mobile layout
|
|
10
|
+
settings in application hosts and restore App-owned mobile Search, new-note
|
|
11
|
+
creation and disposable legacy navigation commands through Design Core's
|
|
12
|
+
public mobile actions. Require released Design Core 0.13.0 for the mobile
|
|
13
|
+
navigation contract.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Updated dependencies
|
|
18
|
+
[[`0ce7e30`](https://github.com/lapismd/lapis-notes/commit/0ce7e3078af8b3f9b132e2b06655a0229e72e346),
|
|
19
|
+
[`8a4d0e0`](https://github.com/lapismd/lapis-notes/commit/8a4d0e02d0642a65ec7441f4fea032739550a6ae)]:
|
|
20
|
+
- @lapis-notes/api@0.3.1
|
|
21
|
+
- @lapis-notes/ui@0.1.9
|
|
22
|
+
|
|
23
|
+
## 0.3.6
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- Updated dependencies
|
|
28
|
+
[[`0b7ac5a`](https://github.com/lapismd/lapis-notes/commit/0b7ac5a9d6f07dd8a35d7d9fd7de8d9cb076e07b),
|
|
29
|
+
[`84551f9`](https://github.com/lapismd/lapis-notes/commit/84551f9f10ec73ea02fcb71d53131758a9d2d419),
|
|
30
|
+
[`1d9f1d1`](https://github.com/lapismd/lapis-notes/commit/1d9f1d180b38e8248f49abaf8f523fe0cddc28fd)]:
|
|
31
|
+
- @lapis-notes/api@0.3.0
|
|
32
|
+
|
|
33
|
+
## 0.3.5
|
|
34
|
+
|
|
35
|
+
### Patch Changes
|
|
36
|
+
|
|
37
|
+
- [#85](https://github.com/lapismd/lapis-notes/pull/85)
|
|
38
|
+
[`8325b5e`](https://github.com/lapismd/lapis-notes/commit/8325b5eb7adef358ac577b457278a19637c13571)
|
|
39
|
+
Thanks [@stevejuma](https://github.com/stevejuma)! - Reuse scanned identities
|
|
40
|
+
during unchanged startup, exclude installed plugin assets from content
|
|
41
|
+
polling, avoid eagerly reading lazy ESM chunks, queue plugin operations with
|
|
42
|
+
independent progress and cancellation, and enable bundled AI by default on the
|
|
43
|
+
Rust server.
|
|
44
|
+
|
|
45
|
+
Renew the native AI feature session after disable/re-enable so startup
|
|
46
|
+
recovery cannot reuse a fenced session.
|
|
47
|
+
|
|
48
|
+
- [#82](https://github.com/lapismd/lapis-notes/pull/82)
|
|
49
|
+
[`e7b96fb`](https://github.com/lapismd/lapis-notes/commit/e7b96fb7f901c6ffff7e92815b29a82bea5c5264)
|
|
50
|
+
Thanks
|
|
51
|
+
[@lapismd-dependency-bot](https://github.com/apps/lapismd-dependency-bot)! -
|
|
52
|
+
Refresh first-party Lapis dependencies to the published versions recorded in
|
|
53
|
+
this pull request.
|
|
54
|
+
- Updated dependencies
|
|
55
|
+
[[`8325b5e`](https://github.com/lapismd/lapis-notes/commit/8325b5eb7adef358ac577b457278a19637c13571),
|
|
56
|
+
[`e7b96fb`](https://github.com/lapismd/lapis-notes/commit/e7b96fb7f901c6ffff7e92815b29a82bea5c5264)]:
|
|
57
|
+
- @lapis-notes/api@0.2.7
|
|
58
|
+
- @lapis-notes/ui@0.1.8
|
|
59
|
+
|
|
3
60
|
## 0.3.4
|
|
4
61
|
|
|
5
62
|
### Patch Changes
|
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
import { getWorkspaceHostBinding } from "@lapis-notes/api/workspace-host";
|
|
27
27
|
import { AppShell } from "@lapismd/design-core/workspace/app-shell";
|
|
28
28
|
import { bindFileConflictReviews } from "./file-conflicts/register-conflict-reviews";
|
|
29
|
+
import {
|
|
30
|
+
bindMobileWorkspaceCommands,
|
|
31
|
+
mobileWorkspaceActions,
|
|
32
|
+
} from "./mobile-workspace";
|
|
29
33
|
import "./workspace-shell.css";
|
|
30
34
|
|
|
31
35
|
let {
|
|
@@ -44,6 +48,7 @@
|
|
|
44
48
|
.getConfiguration()
|
|
45
49
|
.get("editor.alwaysFocusNewTabs", false),
|
|
46
50
|
);
|
|
51
|
+
let mobileActions = $derived(mobileWorkspaceActions(app));
|
|
47
52
|
|
|
48
53
|
onMount(() => {
|
|
49
54
|
// AppShell.Root does not own startup. By the time this shell is mounted,
|
|
@@ -51,7 +56,9 @@
|
|
|
51
56
|
void controller.start();
|
|
52
57
|
const stopNative = bindNativeEditingProblems(app);
|
|
53
58
|
const stopConflicts = bindFileConflictReviews(app);
|
|
59
|
+
const stopMobile = bindMobileWorkspaceCommands(app, controller);
|
|
54
60
|
return () => {
|
|
61
|
+
stopMobile();
|
|
55
62
|
stopConflicts();
|
|
56
63
|
stopNative();
|
|
57
64
|
};
|
|
@@ -71,6 +78,12 @@
|
|
|
71
78
|
disposeOnDestroy={false}
|
|
72
79
|
theme="inherit"
|
|
73
80
|
>
|
|
74
|
-
<AppShell.Surface
|
|
81
|
+
<AppShell.Surface
|
|
82
|
+
{displayMode}
|
|
83
|
+
{workspaceLabel}
|
|
84
|
+
{workspaceNavigation}
|
|
85
|
+
mobileSearchAction={mobileActions.search}
|
|
86
|
+
mobileCreateAction={mobileActions.create}
|
|
87
|
+
/>
|
|
75
88
|
</AppShell.Root>
|
|
76
89
|
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type App } from "@lapis-notes/api";
|
|
2
|
+
import type { AppShellController, WorkspaceAction } from "@lapismd/design-core/workspace/core";
|
|
3
|
+
/** Lapis-owned policy; Design Core owns the transient mobile presentation. */
|
|
4
|
+
export declare function bindMobileWorkspaceCommands(app: App, controller: AppShellController): () => void;
|
|
5
|
+
export declare function mobileWorkspaceActions(app: App): {
|
|
6
|
+
search: WorkspaceAction;
|
|
7
|
+
create: WorkspaceAction;
|
|
8
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Notice } from "@lapis-notes/api";
|
|
2
|
+
/** Lapis-owned policy; Design Core owns the transient mobile presentation. */
|
|
3
|
+
export function bindMobileWorkspaceCommands(app, controller) {
|
|
4
|
+
const navigation = controller.renderer.mobileNavigation;
|
|
5
|
+
let disposed = false;
|
|
6
|
+
let previousCreation = Promise.resolve();
|
|
7
|
+
const setMode = (mode) => app.configuration.getConfiguration().update("workspace.mobile.mode", mode);
|
|
8
|
+
const mobileOnly = (action) => {
|
|
9
|
+
if (controller.renderer.displayMode === "mobile")
|
|
10
|
+
action();
|
|
11
|
+
else
|
|
12
|
+
new Notice("This action is only available while the mobile workspace layout is active.", undefined, app);
|
|
13
|
+
};
|
|
14
|
+
const openSidebar = (side) => {
|
|
15
|
+
if (controller.renderer.displayMode === "mobile")
|
|
16
|
+
navigation.openSidebar(side);
|
|
17
|
+
else {
|
|
18
|
+
app.workspace.exitFocusMode();
|
|
19
|
+
(side === "left"
|
|
20
|
+
? app.workspace.leftSplit
|
|
21
|
+
: app.workspace.rightSplit).expand();
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const createNote = () => {
|
|
25
|
+
const previous = previousCreation;
|
|
26
|
+
const operation = app.fileEditingSessions.track(async () => {
|
|
27
|
+
await previous;
|
|
28
|
+
if (disposed)
|
|
29
|
+
throw new Error("The workspace has closed.");
|
|
30
|
+
const path = app.fileManager.getAvailablePathForAttachment("Untitled.md");
|
|
31
|
+
const file = await app.fileManager.createFile(path, "");
|
|
32
|
+
navigation.openEditor();
|
|
33
|
+
await app.openFile(file);
|
|
34
|
+
app.workspace.requestSaveLayout();
|
|
35
|
+
});
|
|
36
|
+
previousCreation = operation.catch(() => undefined);
|
|
37
|
+
return operation;
|
|
38
|
+
};
|
|
39
|
+
const commands = [
|
|
40
|
+
{
|
|
41
|
+
id: "app:new-note",
|
|
42
|
+
name: "Create new note",
|
|
43
|
+
icon: "file-plus",
|
|
44
|
+
callback: createNote,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "workspace:toggle-mobile-layout",
|
|
48
|
+
name: "Workspace: Cycle mobile layout mode",
|
|
49
|
+
icon: "smartphone",
|
|
50
|
+
callback: () => {
|
|
51
|
+
const mode = app.configuration
|
|
52
|
+
.getConfiguration()
|
|
53
|
+
.get("workspace.mobile.mode", "auto");
|
|
54
|
+
return setMode(mode === "auto" ? "always" : mode === "always" ? "never" : "auto");
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: "workspace:use-mobile-layout",
|
|
59
|
+
name: "Workspace: Use mobile layout",
|
|
60
|
+
icon: "smartphone",
|
|
61
|
+
callback: () => setMode("always"),
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: "workspace:use-desktop-layout",
|
|
65
|
+
name: "Workspace: Use desktop layout",
|
|
66
|
+
icon: "monitor",
|
|
67
|
+
callback: () => setMode("never"),
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "workspace:open-mobile-tabs",
|
|
71
|
+
name: "Workspace: Open mobile tabs",
|
|
72
|
+
icon: "panel-top-open",
|
|
73
|
+
callback: () => mobileOnly(() => navigation.openTabs()),
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: "workspace:open-mobile-search",
|
|
77
|
+
name: "Workspace: Open mobile search",
|
|
78
|
+
icon: "search",
|
|
79
|
+
callback: async () => {
|
|
80
|
+
if (controller.renderer.displayMode !== "mobile")
|
|
81
|
+
return mobileOnly(() => { });
|
|
82
|
+
navigation.openEditor();
|
|
83
|
+
await app.commands.executeCommand("search:open-search");
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: "workspace:open-mobile-left-sidebar",
|
|
88
|
+
name: "Workspace: Open mobile left sidebar",
|
|
89
|
+
icon: "panel-left-open",
|
|
90
|
+
callback: () => openSidebar("left"),
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "workspace:open-mobile-right-sidebar",
|
|
94
|
+
name: "Workspace: Open mobile right sidebar",
|
|
95
|
+
icon: "panel-right-open",
|
|
96
|
+
callback: () => openSidebar("right"),
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
const owned = commands.filter((command) => !app.commands.getCommand(command.id));
|
|
100
|
+
owned.forEach((command) => app.commands.registerCommand(command));
|
|
101
|
+
const registered = owned.map((command) => app.commands.getCommand(command.id));
|
|
102
|
+
return () => {
|
|
103
|
+
disposed = true;
|
|
104
|
+
for (const command of registered) {
|
|
105
|
+
if (app.commands.getCommand(command.id) === command)
|
|
106
|
+
app.commands.unregisterCommand(command.id);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
export function mobileWorkspaceActions(app) {
|
|
111
|
+
const execute = async (id) => {
|
|
112
|
+
try {
|
|
113
|
+
await app.commands.executeCommand(id);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
new Notice(error instanceof Error ? error.message : String(error), undefined, app);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
return {
|
|
120
|
+
search: {
|
|
121
|
+
id: "mobile-search",
|
|
122
|
+
label: "Open search sidebar",
|
|
123
|
+
icon: "search",
|
|
124
|
+
disabled: !app.commands.getCommand("search:open-search"),
|
|
125
|
+
onSelect: () => execute("workspace:open-mobile-search"),
|
|
126
|
+
},
|
|
127
|
+
create: {
|
|
128
|
+
id: "mobile-new-note",
|
|
129
|
+
label: "Create new note",
|
|
130
|
+
icon: "plus",
|
|
131
|
+
onSelect: () => execute("app:new-note"),
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -131,10 +131,7 @@
|
|
|
131
131
|
registry.actionFeedback?.pluginId === selected.id
|
|
132
132
|
? registry.actionFeedback
|
|
133
133
|
: null;
|
|
134
|
-
if (
|
|
135
|
-
registry.runningAction === `install:${selected.id}` ||
|
|
136
|
-
registry.runningAction === `update:${selected.id}`
|
|
137
|
-
)
|
|
134
|
+
if (registry.isPluginActionPending(selected.id))
|
|
138
135
|
return { state: "installing" };
|
|
139
136
|
if (feedback?.state === "error")
|
|
140
137
|
return { state: "rejected", reason: feedback.message };
|
|
@@ -771,8 +771,9 @@
|
|
|
771
771
|
size="icon"
|
|
772
772
|
variant="outline"
|
|
773
773
|
aria-label={`Update ${installedName(record)}`}
|
|
774
|
-
disabled={registry.
|
|
775
|
-
|
|
774
|
+
disabled={registry.isPluginActionPending(
|
|
775
|
+
record.pluginId,
|
|
776
|
+
)}
|
|
776
777
|
onclick={() => void updatePlugin(update)}
|
|
777
778
|
><ArrowUpCircle /></Button.Root
|
|
778
779
|
>
|
|
@@ -922,8 +923,7 @@
|
|
|
922
923
|
><Check data-icon="inline-start" />Bundled</Button.Root
|
|
923
924
|
>
|
|
924
925
|
{:else}
|
|
925
|
-
{@const installing =
|
|
926
|
-
registry.runningAction === `install:${entry.id}`}
|
|
926
|
+
{@const installing = registry.isPluginActionPending(entry.id)}
|
|
927
927
|
<Button.Root
|
|
928
928
|
size="sm"
|
|
929
929
|
aria-label={installing
|
|
@@ -1041,7 +1041,7 @@
|
|
|
1041
1041
|
variant="outline"
|
|
1042
1042
|
aria-label={`Update ${update.name}`}
|
|
1043
1043
|
disabled={!update.canUpdate ||
|
|
1044
|
-
registry.
|
|
1044
|
+
registry.isPluginActionPending(update.id)}
|
|
1045
1045
|
onclick={() => void updatePlugin(update)}
|
|
1046
1046
|
><ArrowUpCircle /></Button.Root
|
|
1047
1047
|
>
|
|
@@ -14,6 +14,8 @@ export declare class PluginRegistryController {
|
|
|
14
14
|
initialLoading: boolean;
|
|
15
15
|
refreshing: boolean;
|
|
16
16
|
runningAction: string | null;
|
|
17
|
+
pendingPluginActions: Record<string, true>;
|
|
18
|
+
isPluginActionPending(pluginId: string): boolean;
|
|
17
19
|
actionFeedback: PluginRegistryActionFeedback | null;
|
|
18
20
|
pluginLifecycleRevision: number;
|
|
19
21
|
private references;
|
|
@@ -25,6 +25,10 @@ export class PluginRegistryController {
|
|
|
25
25
|
initialLoading = $state(true);
|
|
26
26
|
refreshing = $state(false);
|
|
27
27
|
runningAction = $state(null);
|
|
28
|
+
pendingPluginActions = $state({});
|
|
29
|
+
isPluginActionPending(pluginId) {
|
|
30
|
+
return this.pendingPluginActions[pluginId] === true;
|
|
31
|
+
}
|
|
28
32
|
actionFeedback = $state(null);
|
|
29
33
|
pluginLifecycleRevision = $state(0);
|
|
30
34
|
references = 0;
|
|
@@ -110,20 +114,32 @@ export class PluginRegistryController {
|
|
|
110
114
|
this.refreshTask = null;
|
|
111
115
|
}
|
|
112
116
|
async runPluginAction(actionId, pluginId, title, action) {
|
|
113
|
-
if (this.
|
|
117
|
+
if (this.isPluginActionPending(pluginId))
|
|
114
118
|
return;
|
|
115
|
-
this.
|
|
116
|
-
|
|
119
|
+
this.pendingPluginActions = {
|
|
120
|
+
...this.pendingPluginActions,
|
|
121
|
+
[pluginId]: true,
|
|
122
|
+
};
|
|
117
123
|
try {
|
|
118
|
-
await withPluginInstallProgress(this.app, { pluginId, title },
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
await withPluginInstallProgress(this.app, { pluginId, title }, async (signal) => {
|
|
125
|
+
this.runningAction = actionId;
|
|
126
|
+
this.actionFeedback = { pluginId, state: "running", message: title };
|
|
127
|
+
try {
|
|
128
|
+
await action(signal);
|
|
129
|
+
await this.refresh(false);
|
|
130
|
+
this.actionFeedback = {
|
|
131
|
+
pluginId,
|
|
132
|
+
state: "success",
|
|
133
|
+
message: actionId.startsWith("update:")
|
|
134
|
+
? "Update installed"
|
|
135
|
+
: "Installed",
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
if (this.runningAction === actionId)
|
|
140
|
+
this.runningAction = null;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
127
143
|
}
|
|
128
144
|
catch (error) {
|
|
129
145
|
if (isAbortError(error)) {
|
|
@@ -139,7 +155,8 @@ export class PluginRegistryController {
|
|
|
139
155
|
new Notice(`Plugin registry action failed: ${registryErrorMessage(error)}`, undefined, this.app);
|
|
140
156
|
}
|
|
141
157
|
finally {
|
|
142
|
-
|
|
158
|
+
const { [pluginId]: _finished, ...pending } = this.pendingPluginActions;
|
|
159
|
+
this.pendingPluginActions = pending;
|
|
143
160
|
}
|
|
144
161
|
}
|
|
145
162
|
install(entry, version) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lapis-notes/workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Lapis Notes workspace shell backed by design-core",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@lapis-notes/api": "^0.
|
|
42
|
-
"@lapis-notes/ui": "^0.1.
|
|
43
|
-
"@lapismd/design-core": "^0.
|
|
41
|
+
"@lapis-notes/api": "^0.3.1",
|
|
42
|
+
"@lapis-notes/ui": "^0.1.9",
|
|
43
|
+
"@lapismd/design-core": "^0.13.0",
|
|
44
44
|
"@lucide/svelte": "^0.523.0"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|