@lapis-notes/api 0.1.0 → 0.1.2
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 +18 -0
- package/README.md +5 -0
- package/dist/app-database-host.d.ts +1 -0
- package/dist/app-database-host.js +1 -0
- package/dist/browser-sync-security.d.ts +45 -0
- package/dist/browser-sync-security.js +189 -0
- package/dist/build-tools/vite.d.ts +15 -0
- package/dist/build-tools/vite.js +36 -0
- package/dist/cache.svelte.d.ts +1 -1
- package/dist/cache.svelte.js +4 -1
- package/dist/components/editor/editor.css +54 -2
- package/dist/components/editor/extensions/lint/lapis-lint-tooltip.svelte +18 -5
- package/dist/components/editor/extensions/lint/lint-tooltip-actions.d.ts +0 -2
- package/dist/components/editor/extensions/lint/lint-tooltip-actions.js +0 -14
- package/dist/components/editor/language-service/index.js +23 -2
- package/dist/components/icon/icon-list.svelte.d.ts +1 -1
- package/dist/components/select/select.svelte.d.ts +1 -1
- package/dist/context.svelte.d.ts +11 -1
- package/dist/context.svelte.js +38 -1
- package/dist/docs-api.d.ts +2 -0
- package/dist/docs-api.js +1 -0
- package/dist/generated/plugin-host-modules.schema.generated.d.ts +443 -13
- package/dist/generated/plugin-host-modules.schema.generated.js +970 -306
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/language-service/manager.d.ts +5 -0
- package/dist/language-service/manager.js +58 -14
- package/dist/lapis-extension.d.ts +1 -1
- package/dist/lapis-extension.js +12 -0
- package/dist/nostr-signer.d.ts +68 -0
- package/dist/nostr-signer.js +49 -0
- package/dist/plugin-asset-server.d.ts +1 -0
- package/dist/plugin-asset-server.js +3 -0
- package/dist/plugin-capability-facade.d.ts +1 -1
- package/dist/plugin-capability-facade.js +6 -0
- package/dist/plugin-distribution/catalog-media.d.ts +4 -0
- package/dist/plugin-distribution/catalog-media.js +40 -0
- package/dist/plugin-distribution/download-stats.d.ts +4 -0
- package/dist/plugin-distribution/download-stats.js +19 -0
- package/dist/plugin-distribution/index.d.ts +4 -0
- package/dist/plugin-distribution/index.js +4 -0
- package/dist/plugin-distribution/installer.d.ts +3 -0
- package/dist/plugin-distribution/installer.js +9 -2
- package/dist/plugin-distribution/manager.d.ts +71 -5
- package/dist/plugin-distribution/manager.js +376 -37
- package/dist/plugin-distribution/nostr-registry-adapter.d.ts +29 -0
- package/dist/plugin-distribution/nostr-registry-adapter.js +152 -0
- package/dist/plugin-distribution/plugin-bundle-verifier.d.ts +43 -0
- package/dist/plugin-distribution/plugin-bundle-verifier.js +64 -1
- package/dist/plugin-distribution/plugin-bundle.d.ts +14 -0
- package/dist/plugin-distribution/plugin-bundle.js +46 -7
- package/dist/plugin-distribution/plugin-markdown.d.ts +6 -0
- package/dist/plugin-distribution/plugin-markdown.js +94 -0
- package/dist/plugin-distribution/registry-client.d.ts +6 -1
- package/dist/plugin-distribution/registry-client.js +32 -10
- package/dist/plugin-distribution/registry-source-state.d.ts +15 -0
- package/dist/plugin-distribution/registry-source-state.js +42 -0
- package/dist/plugin-distribution/schemas.d.ts +2511 -2824
- package/dist/plugin-distribution/schemas.js +111 -42
- package/dist/plugin-distribution/types.d.ts +116 -5
- package/dist/plugin-distribution-host.d.ts +4 -0
- package/dist/plugin-distribution-host.js +2 -0
- package/dist/plugin-install-progress.js +1 -1
- package/dist/plugin-manager.d.ts +3 -0
- package/dist/plugin-manager.js +21 -13
- package/dist/plugin-settings.d.ts +21 -0
- package/dist/plugin-settings.js +41 -0
- package/dist/plugin.d.ts +13 -0
- package/dist/plugin.js +56 -0
- package/dist/registry-schema/catalog-v1.d.ts +4143 -0
- package/dist/registry-schema/catalog-v1.js +172 -0
- package/dist/registry-schema/catalog-v2.d.ts +5275 -0
- package/dist/registry-schema/catalog-v2.js +137 -0
- package/dist/registry-schema/index.d.ts +1 -0
- package/dist/registry-schema/index.js +1 -0
- package/dist/registry-schema/release-notes.d.ts +5 -0
- package/dist/registry-schema/release-notes.js +21 -0
- package/dist/server-host.d.ts +28 -0
- package/dist/server-host.js +17 -0
- package/dist/storage/app-database.d.ts +37 -2
- package/dist/storage/app-database.js +41 -1
- package/dist/storage/browser-coordinated-app-database.d.ts +3 -1
- package/dist/storage/browser-coordinated-app-database.js +8 -0
- package/dist/storage/browser.d.ts +4 -1
- package/dist/storage/browser.js +84 -0
- package/dist/storage/desktop-native.d.ts +4 -2
- package/dist/storage/desktop-native.js +90 -1
- package/dist/storage/fs.d.ts +17 -0
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.js +1 -0
- package/dist/storage/memory-vault-adapter.d.ts +3 -1
- package/dist/storage/memory-vault-adapter.js +60 -0
- package/dist/storage/remote-app-database.d.ts +19 -0
- package/dist/storage/remote-app-database.js +130 -0
- package/dist/storage/task-projection.d.ts +2 -1
- package/dist/storage/task-projection.js +7 -2
- package/dist/storage/turso-app-database.d.ts +3 -1
- package/dist/storage/turso-app-database.js +45 -0
- package/dist/storage/vault-session.js +11 -0
- package/dist/storage/vault-state.d.ts +1 -1
- package/dist/storage/vault.svelte.d.ts +3 -1
- package/dist/storage/vault.svelte.js +76 -3
- package/dist/sync-host-security.d.ts +285 -0
- package/dist/sync-host-security.js +2237 -0
- package/dist/sync-host.d.ts +179 -0
- package/dist/sync-host.js +769 -0
- package/dist/workspace.svelte.d.ts +1 -1
- package/dist/workspace.svelte.js +1 -1
- package/package.json +48 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @lapis-notes/api
|
|
2
2
|
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Own the transport-neutral registry schema and portable Turso build helper,
|
|
8
|
+
removing the Community dependency from static registry producers and hosts.
|
|
9
|
+
|
|
10
|
+
- Expose host-owned secure sync capabilities and controlled declarative plugin settings.
|
|
11
|
+
|
|
12
|
+
- Allow indexed consumers to select metadata fingerprint inputs. Preserve native
|
|
13
|
+
filesystem error codes and reconcile files removed during startup as deletions
|
|
14
|
+
while retaining unexpected read failures.
|
|
15
|
+
|
|
16
|
+
- Support verified static V2 catalogs alongside Nostr sources, retain installation
|
|
17
|
+
source provenance, and render themed registry images and structured release notes.
|
|
18
|
+
- Updated dependencies []:
|
|
19
|
+
- @lapis-notes/ui@0.1.1
|
|
20
|
+
|
|
3
21
|
## 0.1.0
|
|
4
22
|
|
|
5
23
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -19,6 +19,11 @@ or product routing.
|
|
|
19
19
|
- Peer dependencies for UI, Svelte, CodeMirror, Mira, and Design Core so hosts
|
|
20
20
|
retain singleton control over their runtime stack.
|
|
21
21
|
|
|
22
|
+
`@lapis-notes/api/registry/schema` provides catalog schemas, types, normalization,
|
|
23
|
+
media resolution, and structured release-note extraction for registry producers
|
|
24
|
+
and consumers. It imports only Zod and does not boot the application.
|
|
25
|
+
`@lapis-notes/api/build/vite` provides the portable Turso browser-build helper.
|
|
26
|
+
|
|
22
27
|
## Usage
|
|
23
28
|
|
|
24
29
|
```ts
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { SyncCryptoStatus } from "./sync-host-security";
|
|
2
|
+
import type { SyncCryptoEnrollmentRequest } from "./sync-host-security";
|
|
3
|
+
import type { SyncHostService } from "./sync-host";
|
|
4
|
+
import type { AppDatabase } from "./storage/app-database";
|
|
5
|
+
export interface BrowserSyncPrfCredential {
|
|
6
|
+
version: 1;
|
|
7
|
+
credentialId: string;
|
|
8
|
+
prfInput: string;
|
|
9
|
+
hkdfSalt: string;
|
|
10
|
+
}
|
|
11
|
+
export interface BrowserStoragePersistenceStatus {
|
|
12
|
+
supported: boolean;
|
|
13
|
+
persisted: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface BrowserSyncUnlockInteraction {
|
|
16
|
+
confirmPasskeyEnrollment(pluginId: string): Promise<boolean>;
|
|
17
|
+
requestPassphrase(pluginId: string, mode: "enroll" | "unlock"): Promise<string | null>;
|
|
18
|
+
}
|
|
19
|
+
export declare function supportsBrowserWebAuthnPrf(): Promise<boolean>;
|
|
20
|
+
export declare function requestBrowserStoragePersistence(storage?: Pick<StorageManager, "persist" | "persisted"> | undefined): Promise<BrowserStoragePersistenceStatus>;
|
|
21
|
+
export declare function createBrowserSyncPrfCredential(options: {
|
|
22
|
+
rpName: string;
|
|
23
|
+
rpId?: string;
|
|
24
|
+
userId: Uint8Array;
|
|
25
|
+
userName: string;
|
|
26
|
+
credentials?: CredentialsContainer;
|
|
27
|
+
}): Promise<{
|
|
28
|
+
credential: BrowserSyncPrfCredential;
|
|
29
|
+
wrappingKey: Uint8Array;
|
|
30
|
+
}>;
|
|
31
|
+
export declare function unlockBrowserSyncPrfCredential(credential: BrowserSyncPrfCredential, credentials?: CredentialsContainer | undefined): Promise<Uint8Array>;
|
|
32
|
+
/**
|
|
33
|
+
* Build the host-owned browser unlock callback used by SyncHostService.
|
|
34
|
+
* The callback returns only status to plugins; wrapping material stays in the
|
|
35
|
+
* host flow and is zeroed immediately after enrollment or unlock.
|
|
36
|
+
*/
|
|
37
|
+
export declare function createBrowserSyncUnlockHandler(options: {
|
|
38
|
+
database: AppDatabase;
|
|
39
|
+
getSyncHost(): SyncHostService;
|
|
40
|
+
interaction: BrowserSyncUnlockInteraction;
|
|
41
|
+
rpName: string;
|
|
42
|
+
rpId?: string;
|
|
43
|
+
userName?: string;
|
|
44
|
+
credentials?: CredentialsContainer;
|
|
45
|
+
}): (pluginId: string, status: SyncCryptoStatus, enrollment?: SyncCryptoEnrollmentRequest) => Promise<void>;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { hkdf } from "@noble/hashes/hkdf.js";
|
|
2
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
3
|
+
import { randomBytes } from "@noble/hashes/utils.js";
|
|
4
|
+
const BROWSER_CREDENTIAL_PREFIX = "sync-host:v1:browser-prf:";
|
|
5
|
+
export async function supportsBrowserWebAuthnPrf() {
|
|
6
|
+
if (typeof PublicKeyCredential === "undefined")
|
|
7
|
+
return false;
|
|
8
|
+
const getCapabilities = PublicKeyCredential.getClientCapabilities;
|
|
9
|
+
if (!getCapabilities)
|
|
10
|
+
return false;
|
|
11
|
+
try {
|
|
12
|
+
const capabilities = await getCapabilities();
|
|
13
|
+
return capabilities["extension:prf"] === true;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export async function requestBrowserStoragePersistence(storage = typeof navigator === "undefined" ? undefined : navigator.storage) {
|
|
20
|
+
if (!storage?.persist || !storage.persisted) {
|
|
21
|
+
return { supported: false, persisted: false };
|
|
22
|
+
}
|
|
23
|
+
if (await storage.persisted()) {
|
|
24
|
+
return { supported: true, persisted: true };
|
|
25
|
+
}
|
|
26
|
+
return { supported: true, persisted: await storage.persist() };
|
|
27
|
+
}
|
|
28
|
+
export async function createBrowserSyncPrfCredential(options) {
|
|
29
|
+
const credentials = options.credentials ??
|
|
30
|
+
(typeof navigator === "undefined" ? undefined : navigator.credentials);
|
|
31
|
+
if (!credentials)
|
|
32
|
+
throw new Error("WebAuthn is unavailable in this host");
|
|
33
|
+
const prfInput = randomBytes(32);
|
|
34
|
+
const hkdfSalt = randomBytes(32);
|
|
35
|
+
const created = await credentials.create({
|
|
36
|
+
publicKey: {
|
|
37
|
+
challenge: randomBytes(32),
|
|
38
|
+
rp: { name: options.rpName, id: options.rpId },
|
|
39
|
+
user: {
|
|
40
|
+
id: options.userId,
|
|
41
|
+
name: options.userName,
|
|
42
|
+
displayName: options.userName,
|
|
43
|
+
},
|
|
44
|
+
pubKeyCredParams: [
|
|
45
|
+
{ type: "public-key", alg: -7 },
|
|
46
|
+
{ type: "public-key", alg: -257 },
|
|
47
|
+
],
|
|
48
|
+
authenticatorSelection: {
|
|
49
|
+
residentKey: "required",
|
|
50
|
+
userVerification: "required",
|
|
51
|
+
},
|
|
52
|
+
extensions: {
|
|
53
|
+
prf: { eval: { first: prfInput } },
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
if (!(created instanceof PublicKeyCredential)) {
|
|
58
|
+
throw new Error("WebAuthn credential creation was cancelled");
|
|
59
|
+
}
|
|
60
|
+
const results = created.getClientExtensionResults();
|
|
61
|
+
if (results.prf?.enabled !== true) {
|
|
62
|
+
throw new Error("The selected authenticator does not support WebAuthn PRF");
|
|
63
|
+
}
|
|
64
|
+
const credential = {
|
|
65
|
+
version: 1,
|
|
66
|
+
credentialId: toBase64Url(new Uint8Array(created.rawId)),
|
|
67
|
+
prfInput: toBase64Url(prfInput),
|
|
68
|
+
hkdfSalt: toBase64Url(hkdfSalt),
|
|
69
|
+
};
|
|
70
|
+
return {
|
|
71
|
+
credential,
|
|
72
|
+
wrappingKey: await unlockBrowserSyncPrfCredential(credential, credentials),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export async function unlockBrowserSyncPrfCredential(credential, credentials = typeof navigator === "undefined" ? undefined : navigator.credentials) {
|
|
76
|
+
if (!credentials)
|
|
77
|
+
throw new Error("WebAuthn is unavailable in this host");
|
|
78
|
+
if (credential.version !== 1) {
|
|
79
|
+
throw new Error("Unsupported browser sync credential version");
|
|
80
|
+
}
|
|
81
|
+
const assertion = await credentials.get({
|
|
82
|
+
publicKey: {
|
|
83
|
+
challenge: randomBytes(32),
|
|
84
|
+
allowCredentials: [
|
|
85
|
+
{
|
|
86
|
+
id: fromBase64Url(credential.credentialId),
|
|
87
|
+
type: "public-key",
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
userVerification: "required",
|
|
91
|
+
extensions: {
|
|
92
|
+
prf: { eval: { first: fromBase64Url(credential.prfInput) } },
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
if (!(assertion instanceof PublicKeyCredential)) {
|
|
97
|
+
throw new Error("WebAuthn unlock was cancelled");
|
|
98
|
+
}
|
|
99
|
+
const results = assertion.getClientExtensionResults();
|
|
100
|
+
const output = results.prf?.results?.first;
|
|
101
|
+
if (!output || output.byteLength < 32) {
|
|
102
|
+
throw new Error("WebAuthn PRF did not return wrapping material");
|
|
103
|
+
}
|
|
104
|
+
return hkdf(sha256, new Uint8Array(output), fromBase64Url(credential.hkdfSalt), new TextEncoder().encode("lapis-sync-v1:browser-wrapping-key"), 32);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Build the host-owned browser unlock callback used by SyncHostService.
|
|
108
|
+
* The callback returns only status to plugins; wrapping material stays in the
|
|
109
|
+
* host flow and is zeroed immediately after enrollment or unlock.
|
|
110
|
+
*/
|
|
111
|
+
export function createBrowserSyncUnlockHandler(options) {
|
|
112
|
+
return async (pluginId, status, enrollment) => {
|
|
113
|
+
if (status.state === "unlocked")
|
|
114
|
+
return;
|
|
115
|
+
const recordKey = `${BROWSER_CREDENTIAL_PREFIX}${encodeKeyPart(pluginId)}`;
|
|
116
|
+
const stored = (await options.database.getMeta(recordKey)) ??
|
|
117
|
+
null;
|
|
118
|
+
if (stored) {
|
|
119
|
+
const wrappingKey = await unlockBrowserSyncPrfCredential(stored, options.credentials);
|
|
120
|
+
try {
|
|
121
|
+
if (status.state === "unenrolled") {
|
|
122
|
+
await options.getSyncHost().enrollPluginCryptoWithWrappingKey(pluginId, wrappingKey, {
|
|
123
|
+
name: "external",
|
|
124
|
+
provider: "webauthn-prf",
|
|
125
|
+
keyId: stored.credentialId,
|
|
126
|
+
}, enrollment);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
await options
|
|
130
|
+
.getSyncHost()
|
|
131
|
+
.unlockPluginCryptoWithWrappingKey(pluginId, wrappingKey);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
wrappingKey.fill(0);
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (status.state === "unenrolled" &&
|
|
140
|
+
(await supportsBrowserWebAuthnPrf()) &&
|
|
141
|
+
(await options.interaction.confirmPasskeyEnrollment(pluginId))) {
|
|
142
|
+
const userId = randomBytes(32);
|
|
143
|
+
const created = await createBrowserSyncPrfCredential({
|
|
144
|
+
rpName: options.rpName,
|
|
145
|
+
rpId: options.rpId,
|
|
146
|
+
userId,
|
|
147
|
+
userName: options.userName ?? `sync:${pluginId}`,
|
|
148
|
+
credentials: options.credentials,
|
|
149
|
+
});
|
|
150
|
+
try {
|
|
151
|
+
await options.getSyncHost().enrollPluginCryptoWithWrappingKey(pluginId, created.wrappingKey, {
|
|
152
|
+
name: "external",
|
|
153
|
+
provider: "webauthn-prf",
|
|
154
|
+
keyId: created.credential.credentialId,
|
|
155
|
+
}, enrollment);
|
|
156
|
+
await options.database.setMeta(recordKey, created.credential);
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
created.wrappingKey.fill(0);
|
|
160
|
+
userId.fill(0);
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const passphrase = await options.interaction.requestPassphrase(pluginId, status.state === "unenrolled" ? "enroll" : "unlock");
|
|
165
|
+
if (passphrase == null)
|
|
166
|
+
throw new Error("Sync unlock was cancelled");
|
|
167
|
+
if (status.state === "unenrolled") {
|
|
168
|
+
await options
|
|
169
|
+
.getSyncHost()
|
|
170
|
+
.enrollPluginCrypto(pluginId, passphrase, enrollment);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
await options.getSyncHost().unlockPluginCrypto(pluginId, passphrase);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function toBase64Url(bytes) {
|
|
178
|
+
let binary = "";
|
|
179
|
+
for (const byte of bytes)
|
|
180
|
+
binary += String.fromCharCode(byte);
|
|
181
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
|
|
182
|
+
}
|
|
183
|
+
function encodeKeyPart(value) {
|
|
184
|
+
return toBase64Url(new TextEncoder().encode(value));
|
|
185
|
+
}
|
|
186
|
+
function fromBase64Url(value) {
|
|
187
|
+
const padded = value.replaceAll("-", "+").replaceAll("_", "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
|
|
188
|
+
return Uint8Array.from(atob(padded), (character) => character.charCodeAt(0));
|
|
189
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turso's prebuilt browser bundle already embeds its WASM as a complete data
|
|
3
|
+
* URL. Removing the redundant URL constructor prevents Vite's worker scanner
|
|
4
|
+
* from tokenizing the generated 15 MB module recursively during production
|
|
5
|
+
* builds, without changing the URL passed to fetch at runtime.
|
|
6
|
+
*/
|
|
7
|
+
export declare function unwrapTursoBundleDataUrl(code: string, id: string): string;
|
|
8
|
+
export declare function tursoBundleBuildPlugin(): {
|
|
9
|
+
name: string;
|
|
10
|
+
enforce: "pre";
|
|
11
|
+
transform(this: import("rollup").TransformPluginContext, code: string, id: string): {
|
|
12
|
+
code: string;
|
|
13
|
+
map: null;
|
|
14
|
+
} | null;
|
|
15
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const TURSO_BUNDLE_SUFFIX = "/node_modules/@tursodatabase/database-wasm/bundle/main.es.js";
|
|
2
|
+
const DATA_URL_PREFIX = 'new URL("data:application/wasm;base64,';
|
|
3
|
+
const URL_WRAPPER_PREFIX = "new URL(";
|
|
4
|
+
const URL_WRAPPER_SUFFIX = ", import.meta.url).href";
|
|
5
|
+
/**
|
|
6
|
+
* Turso's prebuilt browser bundle already embeds its WASM as a complete data
|
|
7
|
+
* URL. Removing the redundant URL constructor prevents Vite's worker scanner
|
|
8
|
+
* from tokenizing the generated 15 MB module recursively during production
|
|
9
|
+
* builds, without changing the URL passed to fetch at runtime.
|
|
10
|
+
*/
|
|
11
|
+
export function unwrapTursoBundleDataUrl(code, id) {
|
|
12
|
+
const normalizedId = id.split("?", 1)[0]?.replaceAll("\\", "/") ?? id;
|
|
13
|
+
if (!normalizedId.endsWith(TURSO_BUNDLE_SUFFIX))
|
|
14
|
+
return code;
|
|
15
|
+
const expressionStart = code.indexOf(DATA_URL_PREFIX);
|
|
16
|
+
if (expressionStart < 0) {
|
|
17
|
+
return code.replaceAll("new Worker(", "new globalThis.Worker(");
|
|
18
|
+
}
|
|
19
|
+
const wrapperEnd = code.indexOf(URL_WRAPPER_SUFFIX, expressionStart);
|
|
20
|
+
if (wrapperEnd < 0) {
|
|
21
|
+
return code.replaceAll("new Worker(", "new globalThis.Worker(");
|
|
22
|
+
}
|
|
23
|
+
return (code.slice(0, expressionStart) +
|
|
24
|
+
code.slice(expressionStart + URL_WRAPPER_PREFIX.length, wrapperEnd) +
|
|
25
|
+
code.slice(wrapperEnd + URL_WRAPPER_SUFFIX.length)).replaceAll("new Worker(", "new globalThis.Worker(");
|
|
26
|
+
}
|
|
27
|
+
export function tursoBundleBuildPlugin() {
|
|
28
|
+
return {
|
|
29
|
+
name: "lapis-api-turso-bundle-data-url",
|
|
30
|
+
enforce: "pre",
|
|
31
|
+
transform(code, id) {
|
|
32
|
+
const transformed = unwrapTursoBundleDataUrl(code, id);
|
|
33
|
+
return transformed === code ? null : { code: transformed, map: null };
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
package/dist/cache.svelte.d.ts
CHANGED
|
@@ -535,7 +535,7 @@ export declare class MetadataCache extends EventDispatcher<{
|
|
|
535
535
|
private loadPrimarySnapshot;
|
|
536
536
|
private hydrateDatabaseFromSnapshot;
|
|
537
537
|
private reconcileSnapshotWithVault;
|
|
538
|
-
getReconciliationFingerprint(): string;
|
|
538
|
+
getReconciliationFingerprint(acceptsFile?: (file: TFile) => boolean): string;
|
|
539
539
|
getFileCache(file: TFile): CachedMetadata | null;
|
|
540
540
|
getCache(path: string): CachedMetadata | null;
|
|
541
541
|
getFileCacheAsync(file: TFile | string): Promise<CachedMetadata | null>;
|
package/dist/cache.svelte.js
CHANGED
|
@@ -586,12 +586,15 @@ export class MetadataCache extends EventDispatcher {
|
|
|
586
586
|
/**
|
|
587
587
|
* Return the current vault/parser manifest fingerprint used to validate the
|
|
588
588
|
* durable metadata index and disposable indexed-consumer snapshots.
|
|
589
|
+
* Consumers may restrict the fingerprint to their accepted metadata files.
|
|
589
590
|
*/
|
|
590
|
-
getReconciliationFingerprint() {
|
|
591
|
+
getReconciliationFingerprint(acceptsFile) {
|
|
591
592
|
const fingerprint = new StreamingManifestFingerprint();
|
|
592
593
|
for (const file of this.app.vault.iterateFiles()) {
|
|
593
594
|
if (!this.processors.has(file.extension.toLowerCase()))
|
|
594
595
|
continue;
|
|
596
|
+
if (acceptsFile && !acceptsFile(file))
|
|
597
|
+
continue;
|
|
595
598
|
fingerprint.add([
|
|
596
599
|
file.path,
|
|
597
600
|
file.stat.mtime,
|
|
@@ -377,12 +377,14 @@
|
|
|
377
377
|
|
|
378
378
|
[data-ui-component="editor"][data-ui-part="lint-rule"] {
|
|
379
379
|
font: inherit;
|
|
380
|
-
color: var(--
|
|
380
|
+
color: var(--foreground);
|
|
381
|
+
text-decoration: underline;
|
|
382
|
+
text-decoration-color: var(--ui-editor-lint-accent);
|
|
381
383
|
text-underline-offset: 2px;
|
|
382
384
|
}
|
|
383
385
|
|
|
384
386
|
[data-ui-component="editor"][data-ui-part="lint-rule"]:hover {
|
|
385
|
-
text-decoration:
|
|
387
|
+
text-decoration-thickness: 2px;
|
|
386
388
|
}
|
|
387
389
|
|
|
388
390
|
[data-ui-component="editor"][data-ui-part="lint-copy"] {
|
|
@@ -444,6 +446,56 @@
|
|
|
444
446
|
text-decoration: underline;
|
|
445
447
|
}
|
|
446
448
|
|
|
449
|
+
.ui-editor-lint-quick-fix-menu {
|
|
450
|
+
z-index: calc(var(--ui-workspace-overlay-z-index, 50) + 10);
|
|
451
|
+
min-width: 13rem;
|
|
452
|
+
max-width: min(24rem, calc(100vw - 1rem));
|
|
453
|
+
border: 1px solid var(--ui-workspace-border, var(--border));
|
|
454
|
+
border-radius: var(--ui-workspace-radius-medium, 0.375rem);
|
|
455
|
+
padding: 0.25rem;
|
|
456
|
+
color: var(--ui-workspace-popover-foreground, var(--foreground));
|
|
457
|
+
background: var(--ui-workspace-popover, var(--popover, var(--secondary)));
|
|
458
|
+
box-shadow: var(
|
|
459
|
+
--ui-workspace-shadow,
|
|
460
|
+
0 10px 24px rgb(0 0 0 / 0.18)
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.ui-editor-lint-quick-fix-menu__item {
|
|
465
|
+
display: flex;
|
|
466
|
+
min-height: 2rem;
|
|
467
|
+
width: 100%;
|
|
468
|
+
align-items: center;
|
|
469
|
+
gap: 0.5rem;
|
|
470
|
+
border-radius: var(--ui-workspace-radius-small, 0.25rem);
|
|
471
|
+
padding: 0.375rem 0.5rem;
|
|
472
|
+
color: inherit;
|
|
473
|
+
font-family: var(--ui-editor-font-sans);
|
|
474
|
+
font-size: 0.875rem;
|
|
475
|
+
line-height: 1.25rem;
|
|
476
|
+
outline: none;
|
|
477
|
+
user-select: none;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
.ui-editor-lint-quick-fix-menu__item:focus,
|
|
481
|
+
.ui-editor-lint-quick-fix-menu__item[data-highlighted] {
|
|
482
|
+
background: var(--ui-workspace-muted, var(--muted));
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.ui-editor-lint-quick-fix-menu__item > svg {
|
|
486
|
+
width: 1rem;
|
|
487
|
+
height: 1rem;
|
|
488
|
+
flex: 0 0 1rem;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.ui-editor-lint-quick-fix-menu__label {
|
|
492
|
+
min-width: 0;
|
|
493
|
+
flex: 1 1 auto;
|
|
494
|
+
overflow: hidden;
|
|
495
|
+
text-overflow: ellipsis;
|
|
496
|
+
white-space: nowrap;
|
|
497
|
+
}
|
|
498
|
+
|
|
447
499
|
/* Inline diagnostic expansion created by the lint tooltip's View Problem action. */
|
|
448
500
|
[data-ui-component="editor"][data-ui-part="inline-problem"] {
|
|
449
501
|
position: relative;
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Button } from "@lapismd/design-core/shadcn/button";
|
|
3
3
|
import { WorkspaceIcon } from "@lapismd/design-core/workspace/icon";
|
|
4
|
-
import { WorkspaceMenuItems } from "@lapismd/design-core/workspace/menu";
|
|
5
4
|
import { DropdownMenu } from "bits-ui";
|
|
6
5
|
import {
|
|
7
|
-
createLintQuickFixMenu,
|
|
8
6
|
splitLintTooltipActions,
|
|
9
7
|
type LintTooltipAction,
|
|
10
8
|
} from "./lint-tooltip-actions";
|
|
@@ -35,7 +33,6 @@
|
|
|
35
33
|
const { viewProblem, quickFixActions } = $derived(
|
|
36
34
|
splitLintTooltipActions(actions),
|
|
37
35
|
);
|
|
38
|
-
const quickFixMenu = $derived(createLintQuickFixMenu(quickFixActions));
|
|
39
36
|
const showFooter = $derived(
|
|
40
37
|
Boolean(viewProblem) || quickFixActions.length > 0,
|
|
41
38
|
);
|
|
@@ -54,6 +51,10 @@
|
|
|
54
51
|
function stopRowActivation(event: Event) {
|
|
55
52
|
event.stopPropagation();
|
|
56
53
|
}
|
|
54
|
+
|
|
55
|
+
function selectQuickFix(action: LintTooltipAction, event: MouseEvent) {
|
|
56
|
+
action.onClick(event);
|
|
57
|
+
}
|
|
57
58
|
</script>
|
|
58
59
|
|
|
59
60
|
<div
|
|
@@ -139,7 +140,7 @@
|
|
|
139
140
|
</DropdownMenu.Trigger>
|
|
140
141
|
<DropdownMenu.Portal>
|
|
141
142
|
<DropdownMenu.Content
|
|
142
|
-
class="ui-
|
|
143
|
+
class="ui-editor-lint-quick-fix-menu"
|
|
143
144
|
data-ui-component="workspace-menu"
|
|
144
145
|
data-ui-part="content"
|
|
145
146
|
data-lint-quick-fix-menu=""
|
|
@@ -150,7 +151,19 @@
|
|
|
150
151
|
collisionBoundary={[]}
|
|
151
152
|
onpointerdown={stopRowActivation}
|
|
152
153
|
>
|
|
153
|
-
|
|
154
|
+
{#each quickFixActions as action, index (`${action.name}-${index}`)}
|
|
155
|
+
<DropdownMenu.Item
|
|
156
|
+
class="ui-editor-lint-quick-fix-menu__item"
|
|
157
|
+
data-ui-component="workspace-menu"
|
|
158
|
+
data-ui-part="item"
|
|
159
|
+
onclick={(event) => selectQuickFix(action, event)}
|
|
160
|
+
>
|
|
161
|
+
<WorkspaceIcon name="lightbulb" />
|
|
162
|
+
<span class="ui-editor-lint-quick-fix-menu__label">
|
|
163
|
+
{action.name}
|
|
164
|
+
</span>
|
|
165
|
+
</DropdownMenu.Item>
|
|
166
|
+
{/each}
|
|
154
167
|
</DropdownMenu.Content>
|
|
155
168
|
</DropdownMenu.Portal>
|
|
156
169
|
</DropdownMenu.Root>
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { WorkspaceMenu } from "@lapismd/design-core/workspace/core";
|
|
2
1
|
export declare const VIEW_PROBLEM_ACTION = "View Problem";
|
|
3
2
|
export type LintTooltipAction = {
|
|
4
3
|
name: string;
|
|
@@ -8,4 +7,3 @@ export declare function splitLintTooltipActions(actions: readonly LintTooltipAct
|
|
|
8
7
|
viewProblem: LintTooltipAction | undefined;
|
|
9
8
|
quickFixActions: LintTooltipAction[];
|
|
10
9
|
};
|
|
11
|
-
export declare function createLintQuickFixMenu(actions: readonly LintTooltipAction[]): WorkspaceMenu;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { WorkspaceMenu } from "@lapismd/design-core/workspace/core";
|
|
2
1
|
export const VIEW_PROBLEM_ACTION = "View Problem";
|
|
3
2
|
export function splitLintTooltipActions(actions) {
|
|
4
3
|
return {
|
|
@@ -6,16 +5,3 @@ export function splitLintTooltipActions(actions) {
|
|
|
6
5
|
quickFixActions: actions.filter((action) => action.name !== VIEW_PROBLEM_ACTION),
|
|
7
6
|
};
|
|
8
7
|
}
|
|
9
|
-
export function createLintQuickFixMenu(actions) {
|
|
10
|
-
const menu = new WorkspaceMenu();
|
|
11
|
-
for (const action of actions) {
|
|
12
|
-
menu.addItem((item) => item
|
|
13
|
-
.setTitle(action.name)
|
|
14
|
-
.setIcon("lightbulb")
|
|
15
|
-
.setSection("fix")
|
|
16
|
-
.onClick((event) => {
|
|
17
|
-
action.onClick(event instanceof MouseEvent ? event : new MouseEvent("click"));
|
|
18
|
-
}));
|
|
19
|
-
}
|
|
20
|
-
return menu;
|
|
21
|
-
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Facet } from "@codemirror/state";
|
|
1
|
+
import { Facet, StateEffect, } from "@codemirror/state";
|
|
2
2
|
import { hoverTooltip, ViewPlugin, } from "@codemirror/view";
|
|
3
3
|
import { editorViewField, fromPosition, toPosition, } from "../../../editor.svelte";
|
|
4
4
|
import { lapisCodeMirrorAutocomplete } from "../extensions/autocomplete";
|
|
5
|
-
import { lapisCodeMirrorLint, lintGutter, mapToLapisLintDiagnostic, markdownlintRuleUrl, setDiagnostics, } from "../extensions/lint";
|
|
5
|
+
import { lapisCodeMirrorLint, lintGutter, mapToLapisLintDiagnostic, markdownlintRuleUrl, setDiagnostics, forceLinting, } from "../extensions/lint";
|
|
6
6
|
import { createLanguageServiceHoverDom } from "./dom";
|
|
7
7
|
export const languageServiceDocumentContext = Facet.define({
|
|
8
8
|
combine(values) {
|
|
@@ -10,6 +10,7 @@ export const languageServiceDocumentContext = Facet.define({
|
|
|
10
10
|
},
|
|
11
11
|
});
|
|
12
12
|
const trackedOpenDocuments = new WeakMap();
|
|
13
|
+
const diagnosticsInvalidatedEffect = StateEffect.define();
|
|
13
14
|
export function languageServiceExtensions(options = {}) {
|
|
14
15
|
const extensions = [];
|
|
15
16
|
const wantsDiagnostics = options.diagnostics ?? true;
|
|
@@ -18,6 +19,9 @@ export function languageServiceExtensions(options = {}) {
|
|
|
18
19
|
extensions.push(openDocumentTrackingExtension(options), ...lapisCodeMirrorLint({
|
|
19
20
|
source: async (view) => collectLanguageServiceDiagnostics(view, options),
|
|
20
21
|
gutter: wantsGutter ? undefined : false,
|
|
22
|
+
lint: {
|
|
23
|
+
needsRefresh: (update) => update.transactions.some((transaction) => transaction.effects.some((effect) => effect.is(diagnosticsInvalidatedEffect))),
|
|
24
|
+
},
|
|
21
25
|
}));
|
|
22
26
|
}
|
|
23
27
|
else if (wantsGutter) {
|
|
@@ -33,10 +37,26 @@ export function languageServiceExtensions(options = {}) {
|
|
|
33
37
|
}
|
|
34
38
|
function openDocumentTrackingExtension(options) {
|
|
35
39
|
return ViewPlugin.define((view) => {
|
|
40
|
+
let subscribedManager;
|
|
41
|
+
let unsubscribeInvalidation;
|
|
36
42
|
const sync = (nextView) => {
|
|
37
43
|
const resolved = resolveDocumentContext(nextView, options.languageId);
|
|
38
44
|
if (!resolved)
|
|
39
45
|
return;
|
|
46
|
+
const manager = resolved.info.app.languageServices;
|
|
47
|
+
if (subscribedManager !== manager) {
|
|
48
|
+
unsubscribeInvalidation?.();
|
|
49
|
+
subscribedManager = manager;
|
|
50
|
+
unsubscribeInvalidation = manager.onDiagnosticsInvalidated((languages) => {
|
|
51
|
+
const current = resolveDocumentContext(view, options.languageId);
|
|
52
|
+
if (current && languages.includes(current.document.languageId)) {
|
|
53
|
+
view.dispatch({
|
|
54
|
+
effects: diagnosticsInvalidatedEffect.of(undefined),
|
|
55
|
+
});
|
|
56
|
+
forceLinting(view);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
40
60
|
retainOpenDocument(nextView, resolved.document.uri, resolved.info.app.languageServices);
|
|
41
61
|
};
|
|
42
62
|
sync(view);
|
|
@@ -45,6 +65,7 @@ function openDocumentTrackingExtension(options) {
|
|
|
45
65
|
sync(update.view);
|
|
46
66
|
},
|
|
47
67
|
destroy() {
|
|
68
|
+
unsubscribeInvalidation?.();
|
|
48
69
|
trackedOpenDocuments.get(view)?.release();
|
|
49
70
|
trackedOpenDocuments.delete(view);
|
|
50
71
|
},
|
|
@@ -6,6 +6,6 @@ type $$ComponentProps = ButtonProps & {
|
|
|
6
6
|
ref?: HTMLButtonElement | null;
|
|
7
7
|
label?: string;
|
|
8
8
|
};
|
|
9
|
-
declare const IconList: import("svelte").Component<$$ComponentProps, {}, "
|
|
9
|
+
declare const IconList: import("svelte").Component<$$ComponentProps, {}, "value" | "ref">;
|
|
10
10
|
type IconList = ReturnType<typeof IconList>;
|
|
11
11
|
export default IconList;
|
|
@@ -20,6 +20,6 @@ export type MultipleSelectProps = SharedSelectProps & {
|
|
|
20
20
|
onValueChange?: (value: string[]) => void;
|
|
21
21
|
};
|
|
22
22
|
export type SelectProps = SingleSelectProps | MultipleSelectProps;
|
|
23
|
-
declare const Select: import("svelte").Component<SelectProps, {}, "
|
|
23
|
+
declare const Select: import("svelte").Component<SelectProps, {}, "value" | "ref">;
|
|
24
24
|
type Select = ReturnType<typeof Select>;
|
|
25
25
|
export default Select;
|
package/dist/context.svelte.d.ts
CHANGED
|
@@ -30,6 +30,8 @@ import { DailyDocumentProviderRegistry } from "./daily-document-provider";
|
|
|
30
30
|
import { AppToolRegistry } from "./agent-tools";
|
|
31
31
|
import { MarkdownExtensionRegistry } from "./markdown-extension-registry";
|
|
32
32
|
import { MarkdownFileSurfaceRegistry } from "./markdown-file-surface-registry";
|
|
33
|
+
import { NostrSignerHost, type NostrSignerBroker } from "./nostr-signer";
|
|
34
|
+
import { SyncHostService, type SyncHostServiceOptions } from "./sync-host";
|
|
33
35
|
export interface AppWorkspaceShellApplicationProperties {
|
|
34
36
|
/** Application name shown by the shell's About surface. */
|
|
35
37
|
name?: string;
|
|
@@ -60,9 +62,14 @@ export type AppStateProperties = {
|
|
|
60
62
|
createCommunityPluginDependencyResolver?: PluginDependencyResolverFactory;
|
|
61
63
|
pluginAssetServer?: PluginAssetServer;
|
|
62
64
|
pluginDistributionOptions?: AppPluginDistributionOptions;
|
|
65
|
+
/** Host-owned Nostr account, keychain, and remote-signer implementation. */
|
|
66
|
+
nostrSignerBroker?: NostrSignerBroker;
|
|
67
|
+
syncHost?: SyncHostServiceOptions;
|
|
63
68
|
workspaceShell?: AppWorkspaceShellProperties;
|
|
64
69
|
markdownRenderer: (markdown: string, el: HTMLElement, sourcePath: string, component: Component) => Promise<void>;
|
|
65
70
|
};
|
|
71
|
+
/** Renderer-safe application properties exposed to plugins and views. */
|
|
72
|
+
export type AppPublicProperties = Pick<AppStateProperties, "version" | "configPath" | "workspaceShell" | "markdownRenderer">;
|
|
66
73
|
export type AppPluginDistributionOptions = Omit<DefaultPluginDistributionManagerOptions, "adapter" | "appVersion" | "platform" | "workspaceTrusted" | "pluginManager">;
|
|
67
74
|
export type ExtType = Extension | ((context: Record<string, any>) => Extension);
|
|
68
75
|
/**
|
|
@@ -146,7 +153,7 @@ export declare class SecretStorage {
|
|
|
146
153
|
* @public
|
|
147
154
|
*/
|
|
148
155
|
export declare class App {
|
|
149
|
-
readonly props:
|
|
156
|
+
readonly props: AppPublicProperties;
|
|
150
157
|
readonly i18n: LocalizationManager;
|
|
151
158
|
readonly workspace: Workspace;
|
|
152
159
|
readonly vault: Vault;
|
|
@@ -166,6 +173,7 @@ export declare class App {
|
|
|
166
173
|
readonly urls: AppUrlService;
|
|
167
174
|
readonly workspaceTrust: WorkspaceTrustService;
|
|
168
175
|
readonly pluginDistribution: DefaultPluginDistributionManager;
|
|
176
|
+
readonly nostr: NostrSignerHost;
|
|
169
177
|
mardownPostProcessor: Array<MarkdownPostProcessor>;
|
|
170
178
|
mardownCodeBlockPostProcessor: Record<string, Array<MarkdownPostProcessor>>;
|
|
171
179
|
markdownDirectiveRenderers: Record<string, MarkdownDirectiveRenderer>;
|
|
@@ -239,6 +247,8 @@ export declare class App {
|
|
|
239
247
|
setting(id: "options" | "core-plugins" | "community-plugins"): import("./settings.svelte").SettingGroup;
|
|
240
248
|
}
|
|
241
249
|
export type AppState = App;
|
|
250
|
+
/** Transfer the mounting host's sync controller exactly once. */
|
|
251
|
+
export declare function takeHostSyncService(app: App): SyncHostService;
|
|
242
252
|
declare global {
|
|
243
253
|
var app: App;
|
|
244
254
|
}
|