@andesine/extensions 0.0.0-stage → 0.6.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 +65 -0
- package/LICENSE +21 -0
- package/README.md +104 -3
- package/dist/index.d.ts +289 -0
- package/dist/index.js +173 -0
- package/dist/index.js.map +1 -0
- package/dist/solid.d.ts +397 -0
- package/dist/solid.js +558 -0
- package/dist/solid.js.map +1 -0
- package/package.json +60 -3
package/dist/solid.js
ADDED
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
import { createRenderer } from "solid-js/universal";
|
|
2
|
+
import { catchError, createContext, createSignal, useContext } from "solid-js";
|
|
3
|
+
import { createStore, reconcile } from "solid-js/store";
|
|
4
|
+
//#region src/solid/renderer.ts
|
|
5
|
+
const MAX_PATCHES_PER_MESSAGE = 1e3;
|
|
6
|
+
const MAX_JSON_DEPTH = 8;
|
|
7
|
+
const callbacks = /* @__PURE__ */ new Map();
|
|
8
|
+
const removedNodes = /* @__PURE__ */ new Set();
|
|
9
|
+
let nextID$1 = 1;
|
|
10
|
+
let queue = [];
|
|
11
|
+
let flushScheduled = false;
|
|
12
|
+
let sendPatches = () => {};
|
|
13
|
+
const releaseCallbacks = (node) => {
|
|
14
|
+
for (const callback of node.callbacks.values()) callbacks.delete(callback);
|
|
15
|
+
node.callbacks.clear();
|
|
16
|
+
node.children.forEach(releaseCallbacks);
|
|
17
|
+
};
|
|
18
|
+
const flush = () => {
|
|
19
|
+
for (const node of removedNodes) {
|
|
20
|
+
if (node.parent) continue;
|
|
21
|
+
releaseCallbacks(node);
|
|
22
|
+
queue.push({
|
|
23
|
+
op: "remove",
|
|
24
|
+
id: node.id
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
removedNodes.clear();
|
|
28
|
+
const patches = queue;
|
|
29
|
+
flushScheduled = false;
|
|
30
|
+
queue = [];
|
|
31
|
+
for (let index = 0; index < patches.length; index += MAX_PATCHES_PER_MESSAGE) sendPatches(patches.slice(index, index + MAX_PATCHES_PER_MESSAGE));
|
|
32
|
+
};
|
|
33
|
+
const scheduleFlush = () => {
|
|
34
|
+
if (flushScheduled) return;
|
|
35
|
+
flushScheduled = true;
|
|
36
|
+
queueMicrotask(flush);
|
|
37
|
+
};
|
|
38
|
+
const queuePatch = (patch) => {
|
|
39
|
+
queue.push(patch);
|
|
40
|
+
scheduleFlush();
|
|
41
|
+
};
|
|
42
|
+
const createNode = (type) => {
|
|
43
|
+
return {
|
|
44
|
+
id: nextID$1++,
|
|
45
|
+
type,
|
|
46
|
+
children: [],
|
|
47
|
+
callbacks: /* @__PURE__ */ new Map()
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
const toJSONValue = (value, depth = 0) => {
|
|
51
|
+
if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) return value;
|
|
52
|
+
if (depth >= MAX_JSON_DEPTH) throw new TypeError("Extension props are nested too deeply");
|
|
53
|
+
if (Array.isArray(value)) return value.map((item) => toJSONValue(item, depth + 1));
|
|
54
|
+
if (Object.getPrototypeOf(value) === Object.prototype) return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, toJSONValue(item, depth + 1)]));
|
|
55
|
+
throw new TypeError("Extension props must be JSON values or functions");
|
|
56
|
+
};
|
|
57
|
+
const detach = (node) => {
|
|
58
|
+
const siblings = node.parent?.children;
|
|
59
|
+
if (siblings) siblings.splice(siblings.indexOf(node), 1);
|
|
60
|
+
node.parent = void 0;
|
|
61
|
+
};
|
|
62
|
+
const renderer = createRenderer({
|
|
63
|
+
createElement(type) {
|
|
64
|
+
const node = createNode(type);
|
|
65
|
+
queuePatch({
|
|
66
|
+
op: "create",
|
|
67
|
+
id: node.id,
|
|
68
|
+
component: type
|
|
69
|
+
});
|
|
70
|
+
return node;
|
|
71
|
+
},
|
|
72
|
+
createTextNode(value) {
|
|
73
|
+
const node = createNode("#text");
|
|
74
|
+
queuePatch({
|
|
75
|
+
op: "text",
|
|
76
|
+
id: node.id,
|
|
77
|
+
value: String(value)
|
|
78
|
+
});
|
|
79
|
+
return node;
|
|
80
|
+
},
|
|
81
|
+
replaceText(node, value) {
|
|
82
|
+
queuePatch({
|
|
83
|
+
op: "setText",
|
|
84
|
+
id: node.id,
|
|
85
|
+
value: String(value)
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
isTextNode(node) {
|
|
89
|
+
return node.type === "#text";
|
|
90
|
+
},
|
|
91
|
+
setProperty(node, name, value) {
|
|
92
|
+
const previous = node.callbacks.get(name);
|
|
93
|
+
if (previous) {
|
|
94
|
+
callbacks.delete(previous);
|
|
95
|
+
node.callbacks.delete(name);
|
|
96
|
+
}
|
|
97
|
+
if (typeof value === "function") {
|
|
98
|
+
const callback = nextID$1++;
|
|
99
|
+
callbacks.set(callback, value);
|
|
100
|
+
node.callbacks.set(name, callback);
|
|
101
|
+
queuePatch({
|
|
102
|
+
op: "callback",
|
|
103
|
+
id: node.id,
|
|
104
|
+
name,
|
|
105
|
+
callback
|
|
106
|
+
});
|
|
107
|
+
} else if (value === void 0) queuePatch({
|
|
108
|
+
op: "unset",
|
|
109
|
+
id: node.id,
|
|
110
|
+
name
|
|
111
|
+
});
|
|
112
|
+
else queuePatch({
|
|
113
|
+
op: "prop",
|
|
114
|
+
id: node.id,
|
|
115
|
+
name,
|
|
116
|
+
value: toJSONValue(value)
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
insertNode(parent, node, anchor) {
|
|
120
|
+
const index = anchor ? parent.children.indexOf(anchor) : -1;
|
|
121
|
+
detach(node);
|
|
122
|
+
node.parent = parent;
|
|
123
|
+
if (index >= 0) parent.children.splice(index, 0, node);
|
|
124
|
+
else parent.children.push(node);
|
|
125
|
+
queuePatch({
|
|
126
|
+
op: "insert",
|
|
127
|
+
parent: parent.id,
|
|
128
|
+
id: node.id,
|
|
129
|
+
before: anchor?.id ?? null
|
|
130
|
+
});
|
|
131
|
+
},
|
|
132
|
+
removeNode(_parent, node) {
|
|
133
|
+
detach(node);
|
|
134
|
+
removedNodes.add(node);
|
|
135
|
+
scheduleFlush();
|
|
136
|
+
},
|
|
137
|
+
getParentNode(node) {
|
|
138
|
+
return node.parent;
|
|
139
|
+
},
|
|
140
|
+
getFirstChild(node) {
|
|
141
|
+
return node.children[0];
|
|
142
|
+
},
|
|
143
|
+
getNextSibling(node) {
|
|
144
|
+
const siblings = node.parent?.children;
|
|
145
|
+
return siblings?.[siblings.indexOf(node) + 1];
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
const createViewRoot = (viewID) => {
|
|
149
|
+
const node = createNode("#root");
|
|
150
|
+
queuePatch({
|
|
151
|
+
op: "root",
|
|
152
|
+
id: node.id,
|
|
153
|
+
viewID
|
|
154
|
+
});
|
|
155
|
+
return node;
|
|
156
|
+
};
|
|
157
|
+
const removeViewRoot = (node) => {
|
|
158
|
+
releaseCallbacks(node);
|
|
159
|
+
queuePatch({
|
|
160
|
+
op: "remove",
|
|
161
|
+
id: node.id
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
const setPatchSender = (sender) => {
|
|
165
|
+
sendPatches = sender;
|
|
166
|
+
};
|
|
167
|
+
const getCallback = (callback) => {
|
|
168
|
+
return callbacks.get(callback);
|
|
169
|
+
};
|
|
170
|
+
const { render, effect, memo, createComponent, createElement, createTextNode, insertNode, insert, spread, setProp, mergeProps, use } = renderer;
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/solid/components.ts
|
|
173
|
+
const hostComponent = (name) => {
|
|
174
|
+
return (props) => {
|
|
175
|
+
const node = createElement(name);
|
|
176
|
+
spread(node, props);
|
|
177
|
+
return node;
|
|
178
|
+
};
|
|
179
|
+
};
|
|
180
|
+
const Stack = hostComponent("Stack");
|
|
181
|
+
const Grid = hostComponent("Grid");
|
|
182
|
+
const Box = hostComponent("Box");
|
|
183
|
+
const Divider = hostComponent("Divider");
|
|
184
|
+
const ScrollArea = hostComponent("ScrollArea");
|
|
185
|
+
const Text = hostComponent("Text");
|
|
186
|
+
const Heading = hostComponent("Heading");
|
|
187
|
+
const Code = hostComponent("Code");
|
|
188
|
+
const Link = hostComponent("Link");
|
|
189
|
+
const Badge = hostComponent("Badge");
|
|
190
|
+
const Icon = hostComponent("Icon");
|
|
191
|
+
const Button = hostComponent("Button");
|
|
192
|
+
const IconButton = hostComponent("IconButton");
|
|
193
|
+
const Input = hostComponent("Input");
|
|
194
|
+
const Textarea = hostComponent("Textarea");
|
|
195
|
+
const Select = hostComponent("Select");
|
|
196
|
+
const Combobox = hostComponent("Combobox");
|
|
197
|
+
const Checkbox = hostComponent("Checkbox");
|
|
198
|
+
const Toggle = hostComponent("Toggle");
|
|
199
|
+
const ToggleGroup = hostComponent("ToggleGroup");
|
|
200
|
+
const ColorInput = hostComponent("ColorInput");
|
|
201
|
+
const Tooltip = hostComponent("Tooltip");
|
|
202
|
+
const Dialog = hostComponent("Dialog");
|
|
203
|
+
const Menu = hostComponent("Menu");
|
|
204
|
+
const MenuTrigger = hostComponent("MenuTrigger");
|
|
205
|
+
const MenuItem = hostComponent("MenuItem");
|
|
206
|
+
const MenuGroup = hostComponent("MenuGroup");
|
|
207
|
+
const MenuSeparator = hostComponent("MenuSeparator");
|
|
208
|
+
/** A custom menu item: any content, e.g. a result or a form. */
|
|
209
|
+
const MenuContent = hostComponent("MenuContent");
|
|
210
|
+
const Setting = hostComponent("Setting");
|
|
211
|
+
const List = hostComponent("List");
|
|
212
|
+
const Card = hostComponent("Card");
|
|
213
|
+
const Tree = hostComponent("Tree");
|
|
214
|
+
const Tabs = hostComponent("Tabs");
|
|
215
|
+
const Tab = hostComponent("Tab");
|
|
216
|
+
const Disclosure = hostComponent("Disclosure");
|
|
217
|
+
const Callout = hostComponent("Callout");
|
|
218
|
+
const Steps = hostComponent("Steps");
|
|
219
|
+
const Step = hostComponent("Step");
|
|
220
|
+
const Figure = hostComponent("Figure");
|
|
221
|
+
const CodeBlock = hostComponent("CodeBlock");
|
|
222
|
+
const TimeAgo = hostComponent("TimeAgo");
|
|
223
|
+
const Spinner = hostComponent("Spinner");
|
|
224
|
+
const Skeleton = hostComponent("Skeleton");
|
|
225
|
+
/** Where an element view shows the element's editable content (element views only). */
|
|
226
|
+
const ContentSlot = hostComponent("ContentSlot");
|
|
227
|
+
//#endregion
|
|
228
|
+
//#region src/solid/requests.ts
|
|
229
|
+
const pending = /* @__PURE__ */ new Map();
|
|
230
|
+
let nextID = 1;
|
|
231
|
+
let sendRequest = null;
|
|
232
|
+
/** A host request failed; `code` says why, for example `user_interaction_required`. */
|
|
233
|
+
var ExtensionRequestError = class extends Error {
|
|
234
|
+
code;
|
|
235
|
+
constructor(code, message) {
|
|
236
|
+
super(message);
|
|
237
|
+
this.code = code;
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
const setRequestSender = (sender) => {
|
|
241
|
+
sendRequest = sender;
|
|
242
|
+
};
|
|
243
|
+
const request = (method, params) => {
|
|
244
|
+
if (!sendRequest) return Promise.reject(new ExtensionRequestError("unavailable", "The extension is not running"));
|
|
245
|
+
const id = nextID++;
|
|
246
|
+
const send = sendRequest;
|
|
247
|
+
return new Promise((resolve, reject) => {
|
|
248
|
+
pending.set(id, {
|
|
249
|
+
resolve,
|
|
250
|
+
reject
|
|
251
|
+
});
|
|
252
|
+
send(id, method, params);
|
|
253
|
+
});
|
|
254
|
+
};
|
|
255
|
+
const settleRequest = (id, result, error) => {
|
|
256
|
+
const request = pending.get(id);
|
|
257
|
+
if (!request) return;
|
|
258
|
+
pending.delete(id);
|
|
259
|
+
if (error) request.reject(new ExtensionRequestError(error.code, error.message));
|
|
260
|
+
else request.resolve(result);
|
|
261
|
+
};
|
|
262
|
+
//#endregion
|
|
263
|
+
//#region src/solid/editor.ts
|
|
264
|
+
const ViewContext = createContext();
|
|
265
|
+
/** Edits the document as the member; invalid edits reject with `ExtensionRequestError`. */
|
|
266
|
+
const useEditor = () => {
|
|
267
|
+
const viewID = useContext(ViewContext) ?? "";
|
|
268
|
+
const asJSON = (value) => JSON.parse(JSON.stringify(value));
|
|
269
|
+
return {
|
|
270
|
+
replace: async (content) => {
|
|
271
|
+
await request("editor.replace", {
|
|
272
|
+
viewID,
|
|
273
|
+
content: asJSON(content)
|
|
274
|
+
});
|
|
275
|
+
},
|
|
276
|
+
insertAfter: async (content) => {
|
|
277
|
+
await request("editor.insertAfter", {
|
|
278
|
+
viewID,
|
|
279
|
+
content: asJSON(content)
|
|
280
|
+
});
|
|
281
|
+
},
|
|
282
|
+
setElementProps: async (props) => {
|
|
283
|
+
await request("editor.setElementProps", {
|
|
284
|
+
viewID,
|
|
285
|
+
props: asJSON(props)
|
|
286
|
+
});
|
|
287
|
+
},
|
|
288
|
+
close: async () => {
|
|
289
|
+
await request("view.close", { viewID });
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
};
|
|
293
|
+
//#endregion
|
|
294
|
+
//#region src/solid/context.ts
|
|
295
|
+
/** The origin `apiFetch` accepts; use it as the SDK `baseURL`. */
|
|
296
|
+
const EXTENSION_API_URL = "https://api.andesine.invalid";
|
|
297
|
+
const SESSION_WAIT = 1e4;
|
|
298
|
+
const [permissions, setPermissions] = createSignal([]);
|
|
299
|
+
const [backendURL, setBackendURL] = createSignal(null);
|
|
300
|
+
const [session, setSession] = createSignal(null);
|
|
301
|
+
const [configuration, setConfiguration] = createSignal({});
|
|
302
|
+
let identity = {
|
|
303
|
+
extensionID: "",
|
|
304
|
+
instance: ""
|
|
305
|
+
};
|
|
306
|
+
const updateContext = (context) => {
|
|
307
|
+
setPermissions(context.permissions);
|
|
308
|
+
setBackendURL(context.backend);
|
|
309
|
+
setConfiguration(context.configuration);
|
|
310
|
+
};
|
|
311
|
+
const updateSession = (message) => {
|
|
312
|
+
identity = {
|
|
313
|
+
extensionID: message.extensionID,
|
|
314
|
+
instance: message.instance
|
|
315
|
+
};
|
|
316
|
+
setSession(message.token);
|
|
317
|
+
};
|
|
318
|
+
/** The non-secret configuration values with defaults (reactive); only the backend reads secrets. */
|
|
319
|
+
const useConfiguration = () => {
|
|
320
|
+
return configuration;
|
|
321
|
+
};
|
|
322
|
+
/** The member's effective permissions for this extension (reactive), e.g. `read:entries`. */
|
|
323
|
+
const usePermissions = () => permissions;
|
|
324
|
+
/** Whether the effective permissions include `permission`; a write permission includes its read. */
|
|
325
|
+
const hasPermission = (permission) => {
|
|
326
|
+
const write = permission.startsWith("read:") ? permission.slice(5) : null;
|
|
327
|
+
return permissions().some((granted) => granted === permission || granted === write);
|
|
328
|
+
};
|
|
329
|
+
/**
|
|
330
|
+
* SDK `fetch` that calls Andesine with the member's session; forbidden operations return HTTP 403.
|
|
331
|
+
* @example createClient({ baseURL: EXTENSION_API_URL, fetch: apiFetch })
|
|
332
|
+
*/
|
|
333
|
+
const apiFetch = async (input, init) => {
|
|
334
|
+
const outgoing = new Request(input, init);
|
|
335
|
+
const url = new URL(outgoing.url);
|
|
336
|
+
const hasBody = !["GET", "HEAD"].includes(outgoing.method);
|
|
337
|
+
if (url.origin !== "https://api.andesine.invalid") throw new TypeError(`apiFetch only accepts ${EXTENSION_API_URL} URLs`);
|
|
338
|
+
try {
|
|
339
|
+
const result = await request("api.fetch", {
|
|
340
|
+
method: outgoing.method,
|
|
341
|
+
path: `${url.pathname}${url.search}`,
|
|
342
|
+
...hasBody && { body: await outgoing.text() }
|
|
343
|
+
});
|
|
344
|
+
return new Response(result.body, {
|
|
345
|
+
status: result.status,
|
|
346
|
+
headers: { "content-type": result.contentType }
|
|
347
|
+
});
|
|
348
|
+
} catch (error) {
|
|
349
|
+
if (!(error instanceof ExtensionRequestError) || error.code !== "forbidden") throw error;
|
|
350
|
+
return Response.json({
|
|
351
|
+
code: "FORBIDDEN",
|
|
352
|
+
message: "The extension cannot call this operation"
|
|
353
|
+
}, { status: 403 });
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
const waitForSession = () => {
|
|
357
|
+
return new Promise((resolve, reject) => {
|
|
358
|
+
const started = Date.now();
|
|
359
|
+
const check = () => {
|
|
360
|
+
const token = session();
|
|
361
|
+
if (token) resolve(token);
|
|
362
|
+
else if (Date.now() - started > SESSION_WAIT) reject(new ExtensionRequestError("unavailable", "No backend session is available"));
|
|
363
|
+
else setTimeout(check, 100);
|
|
364
|
+
};
|
|
365
|
+
check();
|
|
366
|
+
});
|
|
367
|
+
};
|
|
368
|
+
/** The extension's own backend, as declared in the manifest. */
|
|
369
|
+
const backend = {
|
|
370
|
+
/**
|
|
371
|
+
* Fetches a backend path with `Authorization`, `Andesine-Extension`, and `Andesine-Instance`
|
|
372
|
+
* headers (CORS must allow them); verify them with `verifySession` of `@andesine/sdk/extensions`.
|
|
373
|
+
*/
|
|
374
|
+
async fetch(path, init) {
|
|
375
|
+
const base = backendURL();
|
|
376
|
+
if (!base) throw new ExtensionRequestError("unavailable", "The extension has no backend");
|
|
377
|
+
const root = base.endsWith("/") ? base : `${base}/`;
|
|
378
|
+
const url = new URL(path.replace(/^\/+/, ""), root);
|
|
379
|
+
if (!url.href.startsWith(root)) throw new TypeError("The path is outside the backend URL");
|
|
380
|
+
const headers = new Headers(init?.headers);
|
|
381
|
+
headers.set("Authorization", `Bearer ${await waitForSession()}`);
|
|
382
|
+
headers.set("Andesine-Extension", identity.extensionID);
|
|
383
|
+
headers.set("Andesine-Instance", identity.instance);
|
|
384
|
+
return fetch(url, {
|
|
385
|
+
...init,
|
|
386
|
+
headers,
|
|
387
|
+
credentials: "omit",
|
|
388
|
+
referrerPolicy: "no-referrer"
|
|
389
|
+
});
|
|
390
|
+
} };
|
|
391
|
+
//#endregion
|
|
392
|
+
//#region src/solid/runtime.ts
|
|
393
|
+
const PROTOCOL = 1;
|
|
394
|
+
const ViewProvider = ViewContext.Provider;
|
|
395
|
+
const views = /* @__PURE__ */ new Map();
|
|
396
|
+
let started = false;
|
|
397
|
+
const getErrorMessage = (error) => {
|
|
398
|
+
return String(error instanceof Error ? error.message : error).slice(0, 500);
|
|
399
|
+
};
|
|
400
|
+
/** Starts the frontend worker; `entries` maps manifest view entries to Solid components. */
|
|
401
|
+
const startExtension = (entries) => {
|
|
402
|
+
if (started) return;
|
|
403
|
+
started = true;
|
|
404
|
+
globalThis.addEventListener("message", function handleInit(event) {
|
|
405
|
+
if (!(event.data?.type === "andesine:init" && event.ports.length === 1)) return;
|
|
406
|
+
const port = event.ports[0];
|
|
407
|
+
const generation = event.data.generation;
|
|
408
|
+
let sequence = 0;
|
|
409
|
+
const send = (body) => {
|
|
410
|
+
port.postMessage({
|
|
411
|
+
...body,
|
|
412
|
+
protocol: PROTOCOL,
|
|
413
|
+
generation,
|
|
414
|
+
sequence
|
|
415
|
+
});
|
|
416
|
+
sequence += 1;
|
|
417
|
+
};
|
|
418
|
+
const sendError = (error, viewID) => {
|
|
419
|
+
send({
|
|
420
|
+
type: "error",
|
|
421
|
+
viewID,
|
|
422
|
+
message: getErrorMessage(error)
|
|
423
|
+
});
|
|
424
|
+
};
|
|
425
|
+
const createView = (viewID, entry, props) => {
|
|
426
|
+
const component = Object.hasOwn(entries, entry) ? entries[entry] : void 0;
|
|
427
|
+
if (!component || views.has(viewID)) {
|
|
428
|
+
sendError(`Unknown or duplicate view: ${entry}`, viewID);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const root = createViewRoot(viewID);
|
|
432
|
+
const [viewProps, setViewProps] = createStore({ ...props });
|
|
433
|
+
const dispose = render(() => {
|
|
434
|
+
return catchError(() => {
|
|
435
|
+
return createComponent(ViewProvider, {
|
|
436
|
+
value: viewID,
|
|
437
|
+
get children() {
|
|
438
|
+
return createComponent(component, viewProps);
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
}, (error) => sendError(error, viewID));
|
|
442
|
+
}, root);
|
|
443
|
+
views.set(viewID, {
|
|
444
|
+
root,
|
|
445
|
+
setProps: (next) => setViewProps(reconcile(next)),
|
|
446
|
+
dispose
|
|
447
|
+
});
|
|
448
|
+
};
|
|
449
|
+
const disposeView = (viewID) => {
|
|
450
|
+
const view = views.get(viewID);
|
|
451
|
+
if (!view) return;
|
|
452
|
+
views.delete(viewID);
|
|
453
|
+
view.dispose();
|
|
454
|
+
removeViewRoot(view.root);
|
|
455
|
+
};
|
|
456
|
+
const handleMessage = (message) => {
|
|
457
|
+
if (message.type === "heartbeat") send({
|
|
458
|
+
type: "heartbeat",
|
|
459
|
+
id: message.id
|
|
460
|
+
});
|
|
461
|
+
else if (message.type === "view.create") createView(message.viewID, message.entry, message.props);
|
|
462
|
+
else if (message.type === "view.update") views.get(message.viewID)?.setProps(message.props);
|
|
463
|
+
else if (message.type === "view.dispose") disposeView(message.viewID);
|
|
464
|
+
else if (message.type === "context") updateContext(message);
|
|
465
|
+
else if (message.type === "session") updateSession(message);
|
|
466
|
+
else if (message.type === "response") settleRequest(message.id, message.result, message.error);
|
|
467
|
+
else if (message.type === "callback") getCallback(message.callback)?.(...message.args);
|
|
468
|
+
else if (message.type === "dispose") {
|
|
469
|
+
[...views.keys()].forEach(disposeView);
|
|
470
|
+
port.close();
|
|
471
|
+
globalThis.close();
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
globalThis.removeEventListener("message", handleInit);
|
|
475
|
+
globalThis.addEventListener("error", (error) => sendError(error.message));
|
|
476
|
+
globalThis.addEventListener("unhandledrejection", (event) => sendError(event.reason));
|
|
477
|
+
setPatchSender((patches) => send({
|
|
478
|
+
type: "patch",
|
|
479
|
+
patches
|
|
480
|
+
}));
|
|
481
|
+
setRequestSender((id, method, params) => send({
|
|
482
|
+
type: "request",
|
|
483
|
+
id,
|
|
484
|
+
method,
|
|
485
|
+
params
|
|
486
|
+
}));
|
|
487
|
+
port.addEventListener("message", (event) => {
|
|
488
|
+
try {
|
|
489
|
+
handleMessage(event.data);
|
|
490
|
+
} catch (error) {
|
|
491
|
+
sendError(error);
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
port.start();
|
|
495
|
+
send({ type: "ready" });
|
|
496
|
+
});
|
|
497
|
+
};
|
|
498
|
+
//#endregion
|
|
499
|
+
//#region src/solid/actions.ts
|
|
500
|
+
/** Copies text; the host shows a fallback dialog if the browser refuses. */
|
|
501
|
+
const copyText = async (text) => {
|
|
502
|
+
await request("host.copyText", { text });
|
|
503
|
+
};
|
|
504
|
+
/** Downloads a text file, for example `downloadFile("andesine.config.ts", source)`. */
|
|
505
|
+
const downloadFile = async (name, text, type) => {
|
|
506
|
+
await request("host.downloadFile", type ? {
|
|
507
|
+
name,
|
|
508
|
+
text,
|
|
509
|
+
type
|
|
510
|
+
} : {
|
|
511
|
+
name,
|
|
512
|
+
text
|
|
513
|
+
});
|
|
514
|
+
};
|
|
515
|
+
/** Opens an `https:` URL in a new tab; URLs outside the declared URLs need confirmation. */
|
|
516
|
+
const openURL = async (url) => {
|
|
517
|
+
await request("host.openURL", { url });
|
|
518
|
+
};
|
|
519
|
+
/** Shows a short notification, e.g. the result of a block action without UI; it needs no interaction. */
|
|
520
|
+
const notify = async (text, type) => {
|
|
521
|
+
await request("host.notify", type ? {
|
|
522
|
+
text,
|
|
523
|
+
type
|
|
524
|
+
} : { text });
|
|
525
|
+
};
|
|
526
|
+
//#endregion
|
|
527
|
+
//#region src/solid/storage.ts
|
|
528
|
+
/**
|
|
529
|
+
* JSON key-value storage shared with the backend; the last write wins.
|
|
530
|
+
* Limits: 256-character keys, 128 KiB values, 10,000 entries, and 10 MiB in total.
|
|
531
|
+
*/
|
|
532
|
+
const storage = {
|
|
533
|
+
/** The entry, or null when the key is not set. */
|
|
534
|
+
async get(key) {
|
|
535
|
+
return await request("storage.get", { key }) ?? null;
|
|
536
|
+
},
|
|
537
|
+
/** Creates or replaces an entry; resolves with its update time. */
|
|
538
|
+
async set(key, value) {
|
|
539
|
+
const { updatedAt } = await request("storage.set", {
|
|
540
|
+
key,
|
|
541
|
+
value
|
|
542
|
+
});
|
|
543
|
+
return { updatedAt };
|
|
544
|
+
},
|
|
545
|
+
/** Resolves with whether the key was set. */
|
|
546
|
+
async delete(key) {
|
|
547
|
+
const { deleted } = await request("storage.delete", { key });
|
|
548
|
+
return deleted;
|
|
549
|
+
},
|
|
550
|
+
/** Entries in key order; a page can have fewer entries than `limit` when values are large. */
|
|
551
|
+
async list(options = {}) {
|
|
552
|
+
return await request("storage.list", { ...options });
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
//#endregion
|
|
556
|
+
export { Badge, Box, Button, Callout, Card, Checkbox, Code, CodeBlock, ColorInput, Combobox, ContentSlot, Dialog, Disclosure, Divider, EXTENSION_API_URL, ExtensionRequestError, Figure, Grid, Heading, Icon, IconButton, Input, Link, List, Menu, MenuContent, MenuGroup, MenuItem, MenuSeparator, MenuTrigger, ScrollArea, Select, Setting, Skeleton, Spinner, Stack, Step, Steps, Tab, Tabs, Text, Textarea, TimeAgo, Toggle, ToggleGroup, Tooltip, Tree, apiFetch, backend, copyText, createComponent, createElement, createTextNode, downloadFile, effect, hasPermission, insert, insertNode, memo, mergeProps, notify, openURL, render, setProp, spread, startExtension, storage, use, useConfiguration, useEditor, usePermissions };
|
|
557
|
+
|
|
558
|
+
//# sourceMappingURL=solid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"solid.js","names":["nextID"],"sources":["../src/solid/renderer.ts","../src/solid/components.ts","../src/solid/requests.ts","../src/solid/editor.ts","../src/solid/context.ts","../src/solid/runtime.ts","../src/solid/actions.ts","../src/solid/storage.ts"],"sourcesContent":["import { createRenderer } from \"solid-js/universal\";\nimport type { JSONValue, ViewPatch } from \"./protocol\";\n\n/** A node of the serialized view tree; it mirrors the host tree, which has the real DOM. */\ninterface ViewNode {\n id: number;\n type: string;\n parent?: ViewNode;\n children: ViewNode[];\n callbacks: Map<string, number>;\n}\n\ntype PatchSender = (patches: ViewPatch[]) => void;\n\nconst MAX_PATCHES_PER_MESSAGE = 1000;\nconst MAX_JSON_DEPTH = 8;\nconst callbacks = new Map<number, (...args: unknown[]) => unknown>();\n// Solid moves nodes by removal and insertion; removals wait for the flush to skip reinserted nodes.\nconst removedNodes = new Set<ViewNode>();\n\nlet nextID = 1;\nlet queue: ViewPatch[] = [];\nlet flushScheduled = false;\nlet sendPatches: PatchSender = () => {};\n\nconst releaseCallbacks = (node: ViewNode): void => {\n for (const callback of node.callbacks.values()) callbacks.delete(callback);\n\n node.callbacks.clear();\n node.children.forEach(releaseCallbacks);\n};\nconst flush = (): void => {\n for (const node of removedNodes) {\n if (node.parent) continue;\n\n releaseCallbacks(node);\n queue.push({ op: \"remove\", id: node.id });\n }\n\n removedNodes.clear();\n\n const patches = queue;\n\n flushScheduled = false;\n queue = [];\n\n for (let index = 0; index < patches.length; index += MAX_PATCHES_PER_MESSAGE) {\n sendPatches(patches.slice(index, index + MAX_PATCHES_PER_MESSAGE));\n }\n};\nconst scheduleFlush = (): void => {\n if (flushScheduled) return;\n\n flushScheduled = true;\n queueMicrotask(flush);\n};\nconst queuePatch = (patch: ViewPatch): void => {\n queue.push(patch);\n scheduleFlush();\n};\nconst createNode = (type: string): ViewNode => {\n return { id: nextID++, type, children: [], callbacks: new Map() };\n};\nconst toJSONValue = (value: unknown, depth = 0): JSONValue => {\n const isPrimitive =\n value === null ||\n typeof value === \"string\" ||\n typeof value === \"boolean\" ||\n (typeof value === \"number\" && Number.isFinite(value));\n\n if (isPrimitive) return value as JSONValue;\n\n if (depth >= MAX_JSON_DEPTH) throw new TypeError(\"Extension props are nested too deeply\");\n\n if (Array.isArray(value)) return value.map((item) => toJSONValue(item, depth + 1));\n\n if (Object.getPrototypeOf(value) === Object.prototype) {\n return Object.fromEntries(\n Object.entries(value as object).map(([key, item]) => [key, toJSONValue(item, depth + 1)])\n );\n }\n\n throw new TypeError(\"Extension props must be JSON values or functions\");\n};\nconst detach = (node: ViewNode): void => {\n const siblings = node.parent?.children;\n\n if (siblings) siblings.splice(siblings.indexOf(node), 1);\n\n node.parent = undefined;\n};\nconst renderer = createRenderer<ViewNode>({\n createElement(type) {\n const node = createNode(type);\n\n queuePatch({ op: \"create\", id: node.id, component: type });\n\n return node;\n },\n // Solid passes numbers from JSX expressions as they are; the protocol carries strings.\n createTextNode(value) {\n const node = createNode(\"#text\");\n\n queuePatch({ op: \"text\", id: node.id, value: String(value) });\n\n return node;\n },\n replaceText(node, value) {\n queuePatch({ op: \"setText\", id: node.id, value: String(value) });\n },\n isTextNode(node) {\n return node.type === \"#text\";\n },\n setProperty(node, name, value) {\n const previous = node.callbacks.get(name);\n\n if (previous) {\n callbacks.delete(previous);\n node.callbacks.delete(name);\n }\n\n if (typeof value === \"function\") {\n const callback = nextID++;\n\n callbacks.set(callback, value as (...args: unknown[]) => unknown);\n node.callbacks.set(name, callback);\n queuePatch({ op: \"callback\", id: node.id, name, callback });\n } else if (value === undefined) {\n queuePatch({ op: \"unset\", id: node.id, name });\n } else {\n queuePatch({ op: \"prop\", id: node.id, name, value: toJSONValue(value) });\n }\n },\n insertNode(parent, node, anchor) {\n const index = anchor ? parent.children.indexOf(anchor) : -1;\n\n detach(node);\n node.parent = parent;\n\n if (index >= 0) {\n parent.children.splice(index, 0, node);\n } else {\n parent.children.push(node);\n }\n\n queuePatch({ op: \"insert\", parent: parent.id, id: node.id, before: anchor?.id ?? null });\n },\n removeNode(_parent, node) {\n detach(node);\n removedNodes.add(node);\n scheduleFlush();\n },\n getParentNode(node) {\n return node.parent;\n },\n getFirstChild(node) {\n return node.children[0];\n },\n getNextSibling(node) {\n const siblings = node.parent?.children;\n\n return siblings?.[siblings.indexOf(node) + 1];\n }\n});\nconst createViewRoot = (viewID: string): ViewNode => {\n const node = createNode(\"#root\");\n\n queuePatch({ op: \"root\", id: node.id, viewID });\n\n return node;\n};\nconst removeViewRoot = (node: ViewNode): void => {\n releaseCallbacks(node);\n queuePatch({ op: \"remove\", id: node.id });\n};\nconst setPatchSender = (sender: PatchSender): void => {\n sendPatches = sender;\n};\nconst getCallback = (callback: number): ((...args: unknown[]) => unknown) | undefined => {\n return callbacks.get(callback);\n};\n\nexport const {\n render,\n effect,\n memo,\n createComponent,\n createElement,\n createTextNode,\n insertNode,\n insert,\n spread,\n setProp,\n mergeProps,\n use\n} = renderer;\nexport { createViewRoot, removeViewRoot, setPatchSender, getCallback };\nexport type { ViewNode };\n","import type { JSX } from \"solid-js\";\nimport { createElement, spread } from \"./renderer\";\n\ninterface ParentProps {\n children?: JSX.Element;\n}\ninterface StyleProps {\n /** Utility classes of the Andesine UnoCSS preset; the extension build generates their CSS. */\n class?: string;\n}\ninterface ButtonBaseProps {\n /** Defaults to `secondary` for buttons and `ghost` for icon buttons. */\n variant?: \"primary\" | \"secondary\" | \"ghost\" | \"link\" | \"danger\";\n size?: \"small\" | \"medium\";\n disabled?: boolean;\n onClick?(): void;\n}\ninterface FieldProps {\n value?: string;\n placeholder?: string;\n label?: string;\n disabled?: boolean;\n onInput?(value: string): void;\n}\ninterface Option {\n value: string;\n label: string;\n}\ninterface ChoiceProps {\n value?: string;\n options: Option[];\n placeholder?: string;\n disabled?: boolean;\n onChange?(value: string): void;\n}\ninterface ToggleProps {\n checked?: boolean;\n label?: string;\n disabled?: boolean;\n onChange?(checked: boolean): void;\n}\ninterface StackProps extends ParentProps, StyleProps {\n direction?: \"row\" | \"column\";\n gap?: Gap;\n align?: \"start\" | \"center\" | \"end\" | \"stretch\";\n justify?: \"start\" | \"center\" | \"end\" | \"between\";\n wrap?: boolean;\n}\ninterface GridProps extends ParentProps, StyleProps {\n columns?: number;\n gap?: Gap;\n}\ninterface BoxProps extends ParentProps, StyleProps {\n padding?: Gap;\n background?: \"none\" | \"soft\" | \"contrast\";\n border?: boolean;\n}\ninterface ScrollAreaProps extends ParentProps {\n maxHeight?: Size;\n}\ninterface TextProps extends ParentProps, StyleProps {\n size?: \"xs\" | \"sm\" | \"base\" | \"lg\";\n weight?: \"normal\" | \"medium\" | \"semibold\";\n tone?: Tone;\n}\ninterface HeadingProps extends ParentProps, StyleProps {\n level?: 1 | 2 | 3;\n}\ninterface LinkProps extends ParentProps {\n /** An `https:` URL within the extension's declared request URLs. */\n href: string;\n}\ninterface BadgeProps extends ParentProps {\n color?: \"base\" | \"primary\" | \"danger\" | \"success\";\n}\ninterface IconProps {\n /** An icon class, e.g. `i-lucide:rocket`, or `i-acme:logo?bg` for a colored icon. */\n name: string;\n size?: Size;\n tone?: Tone;\n}\ninterface ButtonProps extends ButtonBaseProps, ParentProps {\n loading?: boolean;\n}\ninterface IconButtonProps extends ButtonBaseProps {\n icon: string;\n label: string;\n}\ninterface InputProps extends FieldProps {\n type?: \"text\" | \"url\" | \"number\";\n onEnter?(): void;\n}\ninterface TextareaProps extends FieldProps {\n rows?: number;\n}\ninterface ToggleGroupProps {\n value?: string;\n options: Option[];\n disabled?: boolean;\n onChange?(value: string): void;\n}\ninterface ColorInputProps {\n /** A hex color, e.g. `#ff3617`. */\n value?: string;\n disabled?: boolean;\n onInput?(value: string): void;\n}\ninterface TooltipProps extends ParentProps {\n content: string;\n}\n/**\n * A modal, shown while rendered. Outside block actions, render it right after an interaction,\n * e.g. from `onClick`; otherwise it isn't shown. In a block action, closing it closes the action.\n */\ninterface DialogProps extends ParentProps {\n title: string;\n description?: string;\n size?: Size;\n /** Whether Escape, an outside click, or the close button call `onClose` (default true). */\n dismissible?: boolean;\n onClose?(): void;\n}\n/**\n * A dropdown menu of `MenuItem`, `MenuGroup`, `MenuSeparator`, and `MenuContent` children,\n * opened by its `MenuTrigger`, or with `contextMenu` by a right click on it. At a block action\n * view's root it needs no trigger: it opens where the block menu was, and closing it closes the action.\n */\ninterface MenuProps extends ParentProps {\n /** The title of the bottom sheet on small screens. */\n title?: string;\n /** Controls the open state; without it, the menu opens and closes on its own. */\n opened?: boolean;\n contextMenu?: boolean;\n placement?:\n \"bottom-start\" | \"bottom-end\" | \"top-start\" | \"top-end\" | \"right-start\" | \"left-start\";\n size?: \"small\" | \"medium\";\n onOpenChange?(opened: boolean): void;\n}\n/** Nested items make a submenu, which replaces `onSelect`. */\ninterface MenuItemProps extends ParentProps {\n label: string;\n icon?: string;\n /** Shown next to the label; handle the keys yourself. */\n shortcut?: string;\n color?: \"base\" | \"danger\";\n disabled?: boolean;\n /** A spinner replaces the icon, and the item is disabled. */\n loading?: boolean;\n selected?: boolean;\n closeOnSelect?: boolean;\n onSelect?(): void;\n}\ninterface MenuGroupProps extends ParentProps {\n label?: string;\n}\ninterface SettingProps extends ParentProps {\n label: string;\n description?: string;\n}\ninterface ListProps extends ParentProps {\n addLabel?: string;\n emptyLabel?: string;\n disabled?: boolean;\n onAdd?(): void;\n onRemove?(index: number): void;\n onMove?(from: number, to: number): void;\n}\ninterface CardProps extends ParentProps {\n color?: \"base\" | \"contrast\" | \"soft\";\n}\ninterface TreeItem {\n id: string;\n label: string;\n icon?: string;\n /** The parent item ID; top level when unset. */\n parent?: string;\n}\ninterface TreeProps {\n items: TreeItem[];\n selected?: string;\n emptyLabel?: string;\n onSelect?(id: string): void;\n}\ninterface TabsProps extends ParentProps {\n value?: string;\n onChange?(value: string): void;\n}\ninterface TabProps extends ParentProps {\n value: string;\n label: string;\n}\ninterface DisclosureProps extends ParentProps {\n label: string;\n open?: boolean;\n}\ninterface CalloutProps extends ParentProps {\n tone?: \"info\" | \"warning\" | \"danger\" | \"success\";\n title?: string;\n}\ninterface StepProps extends ParentProps {\n title: string;\n}\ninterface FigureProps {\n /** An `https:` image URL within the extension's declared request URLs. */\n src: string;\n alt: string;\n caption?: string;\n}\ninterface CodeBlockProps {\n code: string;\n language?: string;\n}\ninterface TimeAgoProps {\n /** An ISO date-time. */\n date: string;\n}\ninterface SpinnerProps {\n size?: Size;\n}\ninterface SkeletonProps {\n height?: Size;\n}\n\ntype Gap = \"none\" | \"small\" | \"medium\" | \"large\";\ntype Size = \"small\" | \"medium\" | \"large\";\ntype Tone = \"default\" | \"muted\" | \"danger\" | \"success\";\ntype EmptyProps = Record<string, never>;\n\n// Host components are serialized nodes; the host renders the matching Andesine component.\nconst hostComponent = <P extends object>(name: string) => {\n return (props: P): JSX.Element => {\n const node = createElement(name);\n\n spread(node, props);\n\n return node as unknown as JSX.Element;\n };\n};\nconst Stack = hostComponent<StackProps>(\"Stack\");\nconst Grid = hostComponent<GridProps>(\"Grid\");\nconst Box = hostComponent<BoxProps>(\"Box\");\nconst Divider = hostComponent<EmptyProps>(\"Divider\");\nconst ScrollArea = hostComponent<ScrollAreaProps>(\"ScrollArea\");\nconst Text = hostComponent<TextProps>(\"Text\");\nconst Heading = hostComponent<HeadingProps>(\"Heading\");\nconst Code = hostComponent<ParentProps>(\"Code\");\nconst Link = hostComponent<LinkProps>(\"Link\");\nconst Badge = hostComponent<BadgeProps>(\"Badge\");\nconst Icon = hostComponent<IconProps>(\"Icon\");\nconst Button = hostComponent<ButtonProps>(\"Button\");\nconst IconButton = hostComponent<IconButtonProps>(\"IconButton\");\nconst Input = hostComponent<InputProps>(\"Input\");\nconst Textarea = hostComponent<TextareaProps>(\"Textarea\");\nconst Select = hostComponent<ChoiceProps>(\"Select\");\nconst Combobox = hostComponent<ChoiceProps>(\"Combobox\");\nconst Checkbox = hostComponent<ToggleProps>(\"Checkbox\");\nconst Toggle = hostComponent<ToggleProps>(\"Toggle\");\nconst ToggleGroup = hostComponent<ToggleGroupProps>(\"ToggleGroup\");\nconst ColorInput = hostComponent<ColorInputProps>(\"ColorInput\");\nconst Tooltip = hostComponent<TooltipProps>(\"Tooltip\");\nconst Dialog = hostComponent<DialogProps>(\"Dialog\");\nconst Menu = hostComponent<MenuProps>(\"Menu\");\nconst MenuTrigger = hostComponent<ParentProps>(\"MenuTrigger\");\nconst MenuItem = hostComponent<MenuItemProps>(\"MenuItem\");\nconst MenuGroup = hostComponent<MenuGroupProps>(\"MenuGroup\");\nconst MenuSeparator = hostComponent<EmptyProps>(\"MenuSeparator\");\n/** A custom menu item: any content, e.g. a result or a form. */\nconst MenuContent = hostComponent<ParentProps>(\"MenuContent\");\nconst Setting = hostComponent<SettingProps>(\"Setting\");\nconst List = hostComponent<ListProps>(\"List\");\nconst Card = hostComponent<CardProps>(\"Card\");\nconst Tree = hostComponent<TreeProps>(\"Tree\");\nconst Tabs = hostComponent<TabsProps>(\"Tabs\");\nconst Tab = hostComponent<TabProps>(\"Tab\");\nconst Disclosure = hostComponent<DisclosureProps>(\"Disclosure\");\nconst Callout = hostComponent<CalloutProps>(\"Callout\");\nconst Steps = hostComponent<ParentProps>(\"Steps\");\nconst Step = hostComponent<StepProps>(\"Step\");\nconst Figure = hostComponent<FigureProps>(\"Figure\");\nconst CodeBlock = hostComponent<CodeBlockProps>(\"CodeBlock\");\nconst TimeAgo = hostComponent<TimeAgoProps>(\"TimeAgo\");\nconst Spinner = hostComponent<SpinnerProps>(\"Spinner\");\nconst Skeleton = hostComponent<SkeletonProps>(\"Skeleton\");\n/** Where an element view shows the element's editable content (element views only). */\nconst ContentSlot = hostComponent<EmptyProps>(\"ContentSlot\");\n\nexport {\n Stack,\n Grid,\n Box,\n Divider,\n ScrollArea,\n Text,\n Heading,\n Code,\n Link,\n Badge,\n Icon,\n Button,\n IconButton,\n Input,\n Textarea,\n Select,\n Combobox,\n Checkbox,\n Toggle,\n ToggleGroup,\n ColorInput,\n Tooltip,\n Dialog,\n Menu,\n MenuTrigger,\n MenuItem,\n MenuGroup,\n MenuSeparator,\n MenuContent,\n Setting,\n List,\n Card,\n Tree,\n Tabs,\n Tab,\n Disclosure,\n Callout,\n Steps,\n Step,\n Figure,\n CodeBlock,\n TimeAgo,\n Spinner,\n Skeleton,\n ContentSlot\n};\nexport type {\n StackProps,\n GridProps,\n BoxProps,\n ScrollAreaProps,\n TextProps,\n HeadingProps,\n ParentProps,\n LinkProps,\n BadgeProps,\n IconProps,\n ButtonProps,\n IconButtonProps,\n InputProps,\n TextareaProps,\n ChoiceProps,\n ToggleProps,\n ToggleGroupProps,\n ColorInputProps,\n TooltipProps,\n DialogProps,\n MenuProps,\n MenuItemProps,\n MenuGroupProps,\n SettingProps,\n ListProps,\n CardProps,\n TreeItem,\n TreeProps,\n TabsProps,\n TabProps,\n DisclosureProps,\n CalloutProps,\n StepProps,\n FigureProps,\n CodeBlockProps,\n TimeAgoProps,\n SpinnerProps,\n SkeletonProps,\n EmptyProps,\n StyleProps,\n Option\n};\n","import type { JSONValue, RequestError, RequestErrorCode, RequestMethod } from \"./protocol\";\n\ninterface PendingRequest {\n resolve(result: JSONValue | undefined): void;\n reject(error: ExtensionRequestError): void;\n}\n\ntype RequestSender = (id: number, method: RequestMethod, params: JSONValue) => void;\n\nconst pending = new Map<number, PendingRequest>();\n\nlet nextID = 1;\nlet sendRequest: RequestSender | null = null;\n\n/** A host request failed; `code` says why, for example `user_interaction_required`. */\nclass ExtensionRequestError extends Error {\n constructor(\n readonly code: RequestErrorCode,\n message: string\n ) {\n super(message);\n }\n}\n\nconst setRequestSender = (sender: RequestSender): void => {\n sendRequest = sender;\n};\nconst request = (method: RequestMethod, params: JSONValue): Promise<JSONValue | undefined> => {\n if (!sendRequest) {\n return Promise.reject(new ExtensionRequestError(\"unavailable\", \"The extension is not running\"));\n }\n\n const id = nextID++;\n const send = sendRequest;\n\n return new Promise((resolve, reject) => {\n pending.set(id, { resolve, reject });\n send(id, method, params);\n });\n};\nconst settleRequest = (id: number, result: JSONValue | undefined, error?: RequestError): void => {\n const request = pending.get(id);\n\n if (!request) return;\n\n pending.delete(id);\n\n if (error) {\n request.reject(new ExtensionRequestError(error.code, error.message));\n } else {\n request.resolve(result);\n }\n};\n\nexport { ExtensionRequestError, request, setRequestSender, settleRequest };\n","import { createContext, useContext } from \"solid-js\";\nimport type { JSONValue } from \"./protocol\";\nimport { request } from \"./requests\";\n\n/** Editor JSON block, e.g. `{ type: \"paragraph\", content: [{ type: \"text\", text: \"Hi\" }] }`. */\ninterface ContentNode {\n type: string;\n attrs?: Record<string, JSONValue>;\n content?: ContentNode[];\n marks?: Array<{ type: string; attrs?: Record<string, JSONValue> }>;\n text?: string;\n}\ninterface ViewEditor {\n /** Block action views: replaces the blocks the action was opened for. */\n replace(content: ContentNode[]): Promise<void>;\n /** Block action views: inserts blocks after those the action was opened for. */\n insertAfter(content: ContentNode[]): Promise<void>;\n /** Element views: updates the element's props; call it from a user interaction. */\n setElementProps(props: Record<string, JSONValue>): Promise<void>;\n /** Block action views: closes the view. */\n close(): Promise<void>;\n}\n\nconst ViewContext = createContext<string>();\n\n/** Edits the document as the member; invalid edits reject with `ExtensionRequestError`. */\nconst useEditor = (): ViewEditor => {\n const viewID = useContext(ViewContext) ?? \"\";\n // Content built from view props holds store proxies, which `postMessage` cannot clone.\n const asJSON = (value: unknown) => JSON.parse(JSON.stringify(value)) as JSONValue;\n\n return {\n replace: async (content) => {\n await request(\"editor.replace\", { viewID, content: asJSON(content) });\n },\n insertAfter: async (content) => {\n await request(\"editor.insertAfter\", { viewID, content: asJSON(content) });\n },\n setElementProps: async (props) => {\n await request(\"editor.setElementProps\", {\n viewID,\n props: asJSON(props) as Record<string, JSONValue>\n });\n },\n close: async () => {\n await request(\"view.close\", { viewID });\n }\n };\n};\n\nexport { ViewContext, useEditor };\nexport type { ContentNode, ViewEditor };\n","import { createSignal } from \"solid-js\";\nimport { type JSONValue } from \"./protocol\";\nimport { ExtensionRequestError, request } from \"./requests\";\n\ninterface ExtensionContext {\n permissions: string[];\n backend: string | null;\n configuration: Record<string, JSONValue>;\n}\ninterface SessionMessage {\n token: string | null;\n extensionID: string;\n instance: string;\n}\ninterface APIFetchResult {\n status: number;\n contentType: string;\n body: string;\n}\n\n/** The origin `apiFetch` accepts; use it as the SDK `baseURL`. */\nconst EXTENSION_API_URL = \"https://api.andesine.invalid\";\nconst SESSION_WAIT = 10_000;\nconst [permissions, setPermissions] = createSignal<string[]>([]);\nconst [backendURL, setBackendURL] = createSignal<string | null>(null);\nconst [session, setSession] = createSignal<string | null>(null);\nconst [configuration, setConfiguration] = createSignal<Record<string, JSONValue>>({});\n\nlet identity = { extensionID: \"\", instance: \"\" };\n\nconst updateContext = (context: ExtensionContext): void => {\n setPermissions(context.permissions);\n setBackendURL(context.backend);\n setConfiguration(context.configuration);\n};\nconst updateSession = (message: SessionMessage): void => {\n identity = { extensionID: message.extensionID, instance: message.instance };\n setSession(message.token);\n};\n/** The non-secret configuration values with defaults (reactive); only the backend reads secrets. */\nconst useConfiguration = <T extends Record<string, JSONValue> = Record<string, JSONValue>>() => {\n return configuration as () => Partial<T>;\n};\n/** The member's effective permissions for this extension (reactive), e.g. `read:entries`. */\nconst usePermissions = () => permissions;\n/** Whether the effective permissions include `permission`; a write permission includes its read. */\nconst hasPermission = (permission: string): boolean => {\n const write = permission.startsWith(\"read:\") ? permission.slice(5) : null;\n\n return permissions().some((granted) => granted === permission || granted === write);\n};\n/**\n * SDK `fetch` that calls Andesine with the member's session; forbidden operations return HTTP 403.\n * @example createClient({ baseURL: EXTENSION_API_URL, fetch: apiFetch })\n */\nconst apiFetch: typeof fetch = async (input, init) => {\n const outgoing = new Request(input, init);\n const url = new URL(outgoing.url);\n const hasBody = ![\"GET\", \"HEAD\"].includes(outgoing.method);\n\n if (url.origin !== EXTENSION_API_URL) {\n throw new TypeError(`apiFetch only accepts ${EXTENSION_API_URL} URLs`);\n }\n\n try {\n const result = (await request(\"api.fetch\", {\n method: outgoing.method,\n path: `${url.pathname}${url.search}`,\n ...(hasBody && { body: await outgoing.text() })\n })) as unknown as APIFetchResult;\n\n return new Response(result.body, {\n status: result.status,\n headers: { \"content-type\": result.contentType }\n });\n } catch (error) {\n if (!(error instanceof ExtensionRequestError) || error.code !== \"forbidden\") throw error;\n\n return Response.json(\n { code: \"FORBIDDEN\", message: \"The extension cannot call this operation\" },\n { status: 403 }\n );\n }\n};\nconst waitForSession = (): Promise<string> => {\n return new Promise((resolve, reject) => {\n const started = Date.now();\n const check = () => {\n const token = session();\n\n if (token) {\n resolve(token);\n } else if (Date.now() - started > SESSION_WAIT) {\n reject(new ExtensionRequestError(\"unavailable\", \"No backend session is available\"));\n } else {\n setTimeout(check, 100);\n }\n };\n\n check();\n });\n};\n/** The extension's own backend, as declared in the manifest. */\nconst backend = {\n /**\n * Fetches a backend path with `Authorization`, `Andesine-Extension`, and `Andesine-Instance`\n * headers (CORS must allow them); verify them with `verifySession` of `@andesine/sdk/extensions`.\n */\n async fetch(path: string, init?: RequestInit): Promise<Response> {\n const base = backendURL();\n\n if (!base) throw new ExtensionRequestError(\"unavailable\", \"The extension has no backend\");\n\n const root = base.endsWith(\"/\") ? base : `${base}/`;\n const url = new URL(path.replace(/^\\/+/, \"\"), root);\n\n if (!url.href.startsWith(root)) throw new TypeError(\"The path is outside the backend URL\");\n\n const headers = new Headers(init?.headers);\n\n headers.set(\"Authorization\", `Bearer ${await waitForSession()}`);\n headers.set(\"Andesine-Extension\", identity.extensionID);\n headers.set(\"Andesine-Instance\", identity.instance);\n\n return fetch(url, { ...init, headers, credentials: \"omit\", referrerPolicy: \"no-referrer\" });\n }\n};\n\nexport {\n EXTENSION_API_URL,\n apiFetch,\n backend,\n hasPermission,\n updateContext,\n updateSession,\n useConfiguration,\n usePermissions\n};\n","import { catchError, type Component } from \"solid-js\";\nimport { ViewContext } from \"./editor\";\nimport { createStore, reconcile } from \"solid-js/store\";\nimport {\n createComponent,\n createViewRoot,\n getCallback,\n removeViewRoot,\n render,\n setPatchSender,\n type ViewNode\n} from \"./renderer\";\nimport type { Envelope, HostMessageBody, WorkerMessageBody } from \"./protocol\";\nimport { setRequestSender, settleRequest } from \"./requests\";\nimport { updateContext, updateSession } from \"./context\";\n\ninterface RunningView {\n root: ViewNode;\n setProps(props: Record<string, unknown>): void;\n dispose(): void;\n}\n\ntype ExtensionViews = Record<string, Component<never>>;\n\nconst PROTOCOL: Envelope[\"protocol\"] = 1;\n// Context providers return their children, which are view nodes in this renderer.\nconst ViewProvider = ViewContext.Provider as unknown as (props: {\n value: string;\n children: ViewNode;\n}) => ViewNode;\nconst views = new Map<string, RunningView>();\n\nlet started = false;\n\nconst getErrorMessage = (error: unknown): string => {\n return String(error instanceof Error ? error.message : error).slice(0, 500);\n};\n/** Starts the frontend worker; `entries` maps manifest view entries to Solid components. */\nconst startExtension = (entries: ExtensionViews): void => {\n if (started) return;\n\n started = true;\n globalThis.addEventListener(\"message\", function handleInit(event: MessageEvent) {\n const isInit = event.data?.type === \"andesine:init\" && event.ports.length === 1;\n\n if (!isInit) return;\n\n const port = event.ports[0];\n const generation = event.data.generation as number;\n\n let sequence = 0;\n\n // The host expects consecutive sequences, so a message that fails to clone must not use one.\n const send = (body: WorkerMessageBody): void => {\n port.postMessage({ ...body, protocol: PROTOCOL, generation, sequence });\n sequence += 1;\n };\n const sendError = (error: unknown, viewID?: string): void => {\n send({ type: \"error\", viewID, message: getErrorMessage(error) });\n };\n const createView = (viewID: string, entry: string, props: Record<string, unknown>): void => {\n // Solid components return JSX elements, which are view nodes in this renderer.\n const component = Object.hasOwn(entries, entry)\n ? (entries[entry] as unknown as (props: object) => ViewNode)\n : undefined;\n\n if (!component || views.has(viewID)) {\n sendError(`Unknown or duplicate view: ${entry}`, viewID);\n\n return;\n }\n\n const root = createViewRoot(viewID);\n const [viewProps, setViewProps] = createStore({ ...props });\n const dispose = render(() => {\n return catchError(\n () => {\n return createComponent(ViewProvider, {\n value: viewID,\n get children() {\n return createComponent(component, viewProps);\n }\n });\n },\n (error) => sendError(error, viewID)\n )!;\n }, root);\n\n views.set(viewID, {\n root,\n setProps: (next) => setViewProps(reconcile(next)),\n dispose\n });\n };\n const disposeView = (viewID: string): void => {\n const view = views.get(viewID);\n\n if (!view) return;\n\n views.delete(viewID);\n view.dispose();\n removeViewRoot(view.root);\n };\n const handleMessage = (message: HostMessageBody): void => {\n if (message.type === \"heartbeat\") {\n send({ type: \"heartbeat\", id: message.id });\n } else if (message.type === \"view.create\") {\n createView(message.viewID, message.entry, message.props);\n } else if (message.type === \"view.update\") {\n views.get(message.viewID)?.setProps(message.props);\n } else if (message.type === \"view.dispose\") {\n disposeView(message.viewID);\n } else if (message.type === \"context\") {\n updateContext(message);\n } else if (message.type === \"session\") {\n updateSession(message);\n } else if (message.type === \"response\") {\n settleRequest(message.id, message.result, message.error);\n } else if (message.type === \"callback\") {\n // Callbacks of removed nodes are released; late calls are ignored.\n getCallback(message.callback)?.(...message.args);\n } else if (message.type === \"dispose\") {\n [...views.keys()].forEach(disposeView);\n port.close();\n globalThis.close();\n }\n };\n\n globalThis.removeEventListener(\"message\", handleInit);\n globalThis.addEventListener(\"error\", (error) => sendError(error.message));\n globalThis.addEventListener(\"unhandledrejection\", (event) => sendError(event.reason));\n setPatchSender((patches) => send({ type: \"patch\", patches }));\n setRequestSender((id, method, params) => send({ type: \"request\", id, method, params }));\n port.addEventListener(\"message\", (event: MessageEvent<HostMessageBody>) => {\n try {\n handleMessage(event.data);\n } catch (error) {\n sendError(error);\n }\n });\n port.start();\n send({ type: \"ready\" });\n });\n};\n\nexport { startExtension };\n","import { request } from \"./requests\";\n\n// Host actions need a recent user interaction, e.g. `onClick`; errors are `ExtensionRequestError`.\n\n/** Copies text; the host shows a fallback dialog if the browser refuses. */\nconst copyText = async (text: string): Promise<void> => {\n await request(\"host.copyText\", { text });\n};\n/** Downloads a text file, for example `downloadFile(\"andesine.config.ts\", source)`. */\nconst downloadFile = async (name: string, text: string, type?: string): Promise<void> => {\n await request(\"host.downloadFile\", type ? { name, text, type } : { name, text });\n};\n/** Opens an `https:` URL in a new tab; URLs outside the declared URLs need confirmation. */\nconst openURL = async (url: string): Promise<void> => {\n await request(\"host.openURL\", { url });\n};\n\n/** Shows a short notification, e.g. the result of a block action without UI; it needs no interaction. */\nconst notify = async (text: string, type?: \"success\" | \"error\"): Promise<void> => {\n await request(\"host.notify\", type ? { text, type } : { text });\n};\n\nexport { copyText, downloadFile, openURL, notify };\n","import { type JSONValue } from \"./protocol\";\nimport { request } from \"./requests\";\n\ninterface StorageEntry<T extends JSONValue = JSONValue> {\n key: string;\n value: T;\n /** An ISO date-time. */\n updatedAt: string;\n}\ninterface StorageListOptions {\n /** Only keys that start with the prefix. */\n prefix?: string;\n /** Entries after this key, from the previous page. */\n after?: string;\n /** At most 100 (the default). */\n limit?: number;\n}\ninterface StoragePage {\n data: StorageEntry[];\n hasMore: boolean;\n}\n\n/**\n * JSON key-value storage shared with the backend; the last write wins.\n * Limits: 256-character keys, 128 KiB values, 10,000 entries, and 10 MiB in total.\n */\nconst storage = {\n /** The entry, or null when the key is not set. */\n async get<T extends JSONValue = JSONValue>(key: string): Promise<StorageEntry<T> | null> {\n return ((await request(\"storage.get\", { key })) ?? null) as StorageEntry<T> | null;\n },\n /** Creates or replaces an entry; resolves with its update time. */\n async set(key: string, value: JSONValue): Promise<{ updatedAt: string }> {\n const { updatedAt } = (await request(\"storage.set\", { key, value })) as { updatedAt: string };\n\n return { updatedAt };\n },\n /** Resolves with whether the key was set. */\n async delete(key: string): Promise<boolean> {\n const { deleted } = (await request(\"storage.delete\", { key })) as { deleted: boolean };\n\n return deleted;\n },\n /** Entries in key order; a page can have fewer entries than `limit` when values are large. */\n async list(options: StorageListOptions = {}): Promise<StoragePage> {\n return (await request(\"storage.list\", { ...options })) as unknown as StoragePage;\n }\n};\n\nexport { storage };\nexport type { StorageEntry, StorageListOptions, StoragePage };\n"],"mappings":"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"}
|