@appsoftwareltd/etherpk-extension-api 0.13.0 → 0.14.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/README.md +165 -13
- package/controls.css +391 -0
- package/dist/assets.d.ts +7 -4
- package/dist/client-commands.d.ts +11 -0
- package/dist/client-commands.js +11 -0
- package/dist/context.d.ts +33 -58
- package/dist/documents.d.ts +10 -7
- package/dist/documents.js +4 -1
- package/dist/editor.d.ts +2 -4
- package/dist/graph-index.d.ts +7 -7
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/manifest.d.ts +3 -5
- package/dist/manifest.js +1 -1
- package/dist/menus.d.ts +11 -14
- package/dist/records.d.ts +5 -3
- package/dist/settings-sections.d.ts +16 -10
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -54,7 +54,8 @@ documentation says which, with `@public` for Stable and `@beta` for Proposed.
|
|
|
54
54
|
it. In an extension installed from the Extension Directory, a call to a Proposed member throws an
|
|
55
55
|
error that names it.
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
Since 0.14.0 every part is Stable but `contributions` (see *Proposed parts* below). An extension
|
|
58
|
+
listed in the Extension Directory uses only the Stable part.
|
|
58
59
|
[`etc/etherpk-extension-api.public.api.md`](etc/etherpk-extension-api.public.api.md) lists the
|
|
59
60
|
Stable part, and [`etc/etherpk-extension-api.beta.api.md`](etc/etherpk-extension-api.beta.api.md)
|
|
60
61
|
lists everything. A Proposed part becomes Stable when an extension from another publisher needs it.
|
|
@@ -91,6 +92,18 @@ A row can have a `detail` line, an `icon`, `keywords` that find it, a `group` an
|
|
|
91
92
|
`when(menu)` says whether it is offered at the caret, from what `menu` reports: `bodyWritable`,
|
|
92
93
|
`inTable` and `protectedDocument`.
|
|
93
94
|
|
|
95
|
+
`context.commands.execute(id, arg)` runs a Command, and `context.commands.has(id)` says whether there
|
|
96
|
+
is one. An extension may run three kinds of Command, by `execute` or as the Command of a row or a
|
|
97
|
+
button it adds:
|
|
98
|
+
|
|
99
|
+
- Its own.
|
|
100
|
+
- Another running extension's, by its id (`kanban.open`).
|
|
101
|
+
- One of the Client's that `CLIENT_COMMANDS` names. Each keeps its id and argument in every release.
|
|
102
|
+
|
|
103
|
+
The Client's other Commands may change, so `execute` rejects for them, and a row or button naming one
|
|
104
|
+
is refused. `CLIENT_COMMANDS` holds `agents.settings` (open Settings at the Agents tab), and more are
|
|
105
|
+
added as extensions need them.
|
|
106
|
+
|
|
94
107
|
### Notices
|
|
95
108
|
|
|
96
109
|
`context.notify(text)` shows a short notice, as the Client's own notices do.
|
|
@@ -212,6 +225,13 @@ keep the buttons readable together:
|
|
|
212
225
|
|
|
213
226
|
- `context.layout.openDocument(concept)` opens a document as following a wikilink does. A day's
|
|
214
227
|
name, `YYYY-MM-DD`, opens its journal entry.
|
|
228
|
+
- `context.layout.activeDocument()` names the document the person is working in, as its tab names
|
|
229
|
+
it, or null.
|
|
230
|
+
- `context.layout.openWindow({ title, mount })` draws something over the whole window, with a heading
|
|
231
|
+
and a close button the Client draws. One is open at a time: opening another closes it, even
|
|
232
|
+
another extension's.
|
|
233
|
+
- `context.layout.reveal(kind)` brings one of the extension's own Sidebar Views to the front (see
|
|
234
|
+
*Views* below). It does nothing for any other kind.
|
|
215
235
|
- `context.theme.isDark()` says whether the dark theme shows, and the `theme:changed` Event reports a
|
|
216
236
|
switch. EtherPK's `--gk-*` colour tokens reach into a shadow root and change with the theme by
|
|
217
237
|
themselves.
|
|
@@ -272,6 +292,91 @@ The third argument to `registerFence` adds options:
|
|
|
272
292
|
goes in. It returns false where nothing went in, such as a document that cannot be written, a
|
|
273
293
|
Protected Document, or a caret in the frontmatter, in a fence or in a table.
|
|
274
294
|
|
|
295
|
+
`context.editor.openPanel({ mount, at })` draws a panel of the extension's own in the document,
|
|
296
|
+
beneath the caret's line, or beneath the document's last line with `at: 'end'`, without changing
|
|
297
|
+
the text. It returns null where no panel can go, by the same rules as `insertBlock`. A panel closes
|
|
298
|
+
when the extension stops.
|
|
299
|
+
|
|
300
|
+
### Context Menu rows and Command Bar buttons
|
|
301
|
+
|
|
302
|
+
`context.contextMenu.register({ id, label, command, when })` adds a row to the menu raised on a
|
|
303
|
+
document's tab, a Favourite, a Recent, an All Documents row, any other tab, or a wikilink. The row's
|
|
304
|
+
Command is given the target, and its `when(target)` says whether it applies. More kinds of target may
|
|
305
|
+
be added, and when the Client stops raising a menu on one, the rows for it are no longer shown, so a
|
|
306
|
+
`when` answers false for any kind it does not check. A row that deletes or removes something for good
|
|
307
|
+
sets `danger: true`, and is drawn in the danger colour.
|
|
308
|
+
|
|
309
|
+
`context.commandBar.register({ id, title, icon, command, pinned })` adds a button to the row of
|
|
310
|
+
buttons across the bottom of a phone's screen: after the editing buttons that scroll, or with
|
|
311
|
+
`pinned`, at the bar's right end, where it never scrolls away.
|
|
312
|
+
|
|
313
|
+
### Key chords
|
|
314
|
+
|
|
315
|
+
`context.keybindings.register({ key, command, label })` binds a chord such as `Alt+M` to one of the
|
|
316
|
+
extension's own Commands. `Mod` is Cmd on Apple devices and Ctrl elsewhere. When the Client or another
|
|
317
|
+
extension binds the chord already, the chord stays theirs: the call still succeeds, the binding is
|
|
318
|
+
left out, and the browser's console says so. So a shortcut the Client adds in a later release never
|
|
319
|
+
stops an extension from starting.
|
|
320
|
+
|
|
321
|
+
### The graph's index and the names of concepts
|
|
322
|
+
|
|
323
|
+
- `context.index.allConcepts()` lists the graph's concepts, `context.index.linkGraph()` gives every
|
|
324
|
+
concept and the links between them, and `context.index.onUpdated(listener)` hears each change to
|
|
325
|
+
the index.
|
|
326
|
+
- `context.concepts.canonicalName(name)` gives the name a concept resolves to: an alias's page, a
|
|
327
|
+
page's own title, or the name itself. `context.concepts.key(name)` gives the key the Client tells
|
|
328
|
+
two names apart by. The way the key is made may change, so compare keys from `key`, and never make
|
|
329
|
+
one another way.
|
|
330
|
+
|
|
331
|
+
### Reading documents
|
|
332
|
+
|
|
333
|
+
`context.documents` reads the graph's documents for the person, for example for a language model they
|
|
334
|
+
use: `list`, `read`, `links`, `search`, `backlinks` and `tasks`. Every answer leaves out each Protected
|
|
335
|
+
Document, its name included, and a document holding an encrypted fence. Answers are bounded. A page
|
|
336
|
+
of a listing holds at most 200 documents or tasks, and a page of a search 50 results, and a
|
|
337
|
+
document's text is cut after 200,000 characters, saying so. `formatGuide()` describes how the
|
|
338
|
+
graph's documents are written, in the words the Client gives agents. Its wording changes as the
|
|
339
|
+
format does, so pass it on rather than read meaning into it.
|
|
340
|
+
|
|
341
|
+
### Records for one graph
|
|
342
|
+
|
|
343
|
+
`context.records` keeps the extension's records for the open graph on this device alone, such as a
|
|
344
|
+
chat the person has not saved: `get`, `set`, `remove` and `keys`. They are never synced, and they go
|
|
345
|
+
when the graph's data leaves the device. A record may hold a `Blob`, an `ArrayBuffer`, a `Date` or
|
|
346
|
+
plain data, at most 4 MB of it, and a larger one is refused. A write the browser refuses, such as on
|
|
347
|
+
a full disk, rejects.
|
|
348
|
+
|
|
349
|
+
### Images as assets
|
|
350
|
+
|
|
351
|
+
`context.assets.prepareImage(blob)` makes an image smaller, as a pasted image is.
|
|
352
|
+
`context.assets.saveImage(blob, name)` keeps it as one of the graph's assets and answers the markdown
|
|
353
|
+
that shows it, such as ``. The file's name ends in a hash of its
|
|
354
|
+
bytes, so two images never take the same name, and the same image kept twice is one file. An asset
|
|
355
|
+
is part of the graph: it syncs, and every member of a shared graph can see it.
|
|
356
|
+
|
|
357
|
+
### Icons
|
|
358
|
+
|
|
359
|
+
`context.icons.svg(name)` draws one of the extension's own icons or one of the Client's, as markup the
|
|
360
|
+
Client trusts. The Client's icons, which every `icon` field may name too, are `alias`, `backlinks`, `bold`, `bullet`, `calendar`, `check`, `clock`, `close`, `code`, `copy`, `download`, `fit`, `flag`, `fold`, `frontmatter`, `globe`, `graph`, `grip`, `headless-client`, `highlight`, `image`, `import`, `indent`, `italic`, `list`, `lock`, `lock-open`, `more`, `move`, `move-down`, `move-up`, `new-page`, `open-external`, `outdent`, `page`, `pageless`, `pin`, `redo`, `rename`, `reset`, `search`, `slash`, `spell-check`, `table`, `table-add-column`, `table-add-row`, `table-format`, `table-remove-column`, `table-remove-row`, `task`, `template`, `text-lines`, `today`, `todays-journal`, `trash`, `undo`, `wikilink-close`, `wikilink-open`, `zoom-in`, `zoom-out`. An
|
|
361
|
+
icon may be added in a later release, never taken away. An unknown name draws a small dot.
|
|
362
|
+
|
|
363
|
+
### Settings sections
|
|
364
|
+
|
|
365
|
+
The main module can export `settingsSections`, for settings that need more than the fields a
|
|
366
|
+
manifest declares, such as a list of accounts with a button that connects one. Each section has an
|
|
367
|
+
`id`, a `tab` and a `mount(element, context)` that draws it. The context holds the extension's
|
|
368
|
+
settings, its records kept with the person, signing in, the theme and `notify`, and nothing from a
|
|
369
|
+
graph.
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
export const settingsSections: SettingsSection[] = [{ id: 'hello.accounts', tab: 'extension', mount: drawAccounts }]
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
`tab: 'extension'` draws the section under the extension's own entry in Settings and Extensions,
|
|
376
|
+
beside its declared settings, and `tab: 'agents'` in the Agents tab. The Client draws an extension's
|
|
377
|
+
sections in a graph's settings while the extension runs there. An extension's code is loaded only
|
|
378
|
+
once a graph opens, so its sections are not drawn on the Graphs page.
|
|
379
|
+
|
|
275
380
|
### Dialogs
|
|
276
381
|
|
|
277
382
|
`context.dialogs.prompt(options)` asks for one line of text in a dialog the Client draws. Its
|
|
@@ -313,6 +418,10 @@ and offers to install it, if the directory lists it.
|
|
|
313
418
|
`context.layout.openView({ kind, target })` opens a View. `context.layout.isDesktop()` says whether
|
|
314
419
|
the desktop layout is showing, which it is at 1024 pixels wide and more.
|
|
315
420
|
|
|
421
|
+
A View kind can live in a Sidebar from the moment a graph opens, beside the Client's own Sidebar
|
|
422
|
+
Views, with `"resident": { "side": "left" }` (or `"right"`, and `"desktopOnly": true` for the desktop
|
|
423
|
+
alone). `context.layout.reveal(kind)` brings it to the front of its Sidebar.
|
|
424
|
+
|
|
316
425
|
## Styles
|
|
317
426
|
|
|
318
427
|
The manifest's `styles` are the extension's stylesheets. By default, each widget and View the
|
|
@@ -329,6 +438,54 @@ An extension's stylesheet can't register one of them with `@property`.
|
|
|
329
438
|
Either way, the shadow root and the check keep styles apart, and nothing more. They don't limit
|
|
330
439
|
what the extension's code can do in the page.
|
|
331
440
|
|
|
441
|
+
## Controls
|
|
442
|
+
|
|
443
|
+
The Client draws its own buttons, fields, links and menus with a set of classes, and gives the same
|
|
444
|
+
stylesheet to every shadow root an extension draws in, ahead of the extension's own stylesheets. An
|
|
445
|
+
extension mounted in the page has it from the Client's own stylesheet. So an element with one of
|
|
446
|
+
these classes looks like the Client's control, in the light and dark themes alike, and keeps
|
|
447
|
+
looking like it as the Client changes. The classes are Stable, and the Client has them from
|
|
448
|
+
EtherPK 0.14.0. The stylesheet is in this package as `controls.css`, for reference and for an
|
|
449
|
+
extension's own tests.
|
|
450
|
+
|
|
451
|
+
| Class | Use it on |
|
|
452
|
+
| --- | --- |
|
|
453
|
+
| `gk-button` | A `<button>` or `<a>`, as the Client's everyday button: a thin border, filled faintly on hover. |
|
|
454
|
+
| `gk-button--primary` | With `gk-button`, the one main action of a form or dialog. |
|
|
455
|
+
| `gk-button--danger` | With `gk-button`, an action that removes or ends something. |
|
|
456
|
+
| `gk-button--quiet` | With `gk-button`, a button with no border, in a toolbar or beside other text. |
|
|
457
|
+
| `gk-button--small` | With `gk-button`, a button on the line of a heading or a row, 24px tall. |
|
|
458
|
+
| `gk-button--raised` | With `gk-button` or `gk-icon-button`, a button over content such as a map, a picture or code: a solid surface and a small shadow. |
|
|
459
|
+
| `gk-icon-button` | A square button holding only an icon. Give it an `aria-label`. |
|
|
460
|
+
| `gk-link` | A `<button>` or `<a>` that reads as a link: the accent colour, underlined. |
|
|
461
|
+
| `gk-field` | An element holding a field's label, its control and any hint or error, stacked. |
|
|
462
|
+
| `gk-label` | A field's `<label>`. |
|
|
463
|
+
| `gk-hint` | Help text under a field. |
|
|
464
|
+
| `gk-error` | An error under a field. Set `aria-invalid="true"` on the control too. |
|
|
465
|
+
| `gk-input` | An `<input>` or `<textarea>`, the full width of its column. |
|
|
466
|
+
| `gk-select` | A `<select>`. Where the browser can, it draws the option list in the menu's colours, with `<optgroup>` labels and a tick on the chosen option. Elsewhere it shows the browser's own list. |
|
|
467
|
+
| `gk-checkbox` | An `<input type="checkbox">`. |
|
|
468
|
+
| `gk-menu` | A list of actions, such as a menu a button opens. |
|
|
469
|
+
| `gk-menu-item` | A row of a `gk-menu`. |
|
|
470
|
+
| `gk-menu-item--danger` | With `gk-menu-item`, a row that removes something. |
|
|
471
|
+
| `gk-menu-separator` | A rule between groups of rows in a `gk-menu`. |
|
|
472
|
+
|
|
473
|
+
A disabled control (`disabled`, or `aria-disabled="true"`) recedes by colour. A toggle button with
|
|
474
|
+
`aria-pressed="true"` is drawn as the primary button while it is on. On a touch screen buttons,
|
|
475
|
+
fields and menu rows are at least 44px tall.
|
|
476
|
+
|
|
477
|
+
```ts
|
|
478
|
+
const save = document.createElement('button')
|
|
479
|
+
save.className = 'gk-button gk-button--primary'
|
|
480
|
+
save.textContent = 'Save'
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
The rules are in the `components` cascade layer, after the layer order Tailwind v4 uses
|
|
484
|
+
(`properties`, `theme`, `base`, `components`, `utilities`). So Tailwind's utilities can adjust a control
|
|
485
|
+
(`gk-button w-full`), and Tailwind's preflight doesn't undo one. A rule of yours outside any layer
|
|
486
|
+
wins over every layered rule, so keep element resets such as `button { color: inherit }` in
|
|
487
|
+
`@layer base`, or they will override the controls.
|
|
488
|
+
|
|
332
489
|
## Writing to the page
|
|
333
490
|
|
|
334
491
|
The Client enforces
|
|
@@ -345,24 +502,19 @@ code runs under them.
|
|
|
345
502
|
|
|
346
503
|
## Proposed parts
|
|
347
504
|
|
|
348
|
-
The Proposed
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
- The Client's icons (`icons`) and a View that lives in a Sidebar (`resident`).
|
|
354
|
-
- Running another extension's Commands (`commands.execute`), and the document the person is working
|
|
355
|
-
in (`layout.activeDocument`).
|
|
356
|
-
|
|
357
|
-
`contributions`, which reaches the Client's own registries, stays Proposed for good, since its
|
|
358
|
-
shape is the Client's internals.
|
|
505
|
+
The one Proposed part is `contributions`, which reaches the Client's own registries by kind. Only
|
|
506
|
+
the extensions that come with EtherPK may use it, and it stays Proposed for good: its kinds and
|
|
507
|
+
their values are the Client's internals, which change between releases, and through it one
|
|
508
|
+
extension could take another's entries. Where an extension from another Publisher needs what a
|
|
509
|
+
registry gives, the Client adds a checked part for it, as it did for fences and Journal Panels.
|
|
359
510
|
|
|
360
511
|
## Trying an extension
|
|
361
512
|
|
|
362
513
|
To try an extension, build it, and start a development copy of the EtherPK Client with the
|
|
363
514
|
extension's folder named in `ETHERPK_DEV_EXTENSIONS`. Separate several folders with commas. The
|
|
364
515
|
Client loads each one as it loads the extensions it comes with, and one with the same id replaces
|
|
365
|
-
it.
|
|
516
|
+
it. An extension loaded this way gets what a Directory release gets, the Stable part alone, unless it
|
|
517
|
+
stands in for one that comes with EtherPK, so a Proposed part it calls fails while you test.
|
|
366
518
|
|
|
367
519
|
```bash
|
|
368
520
|
ETHERPK_DEV_EXTENSIONS=/path/to/my-extension pnpm dev
|
package/controls.css
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* EtherPK's controls: the buttons, fields, links and menus the Client draws, as classes an
|
|
3
|
+
* extension can use (ADR 0151). The Client supplies this stylesheet to every shadow root an
|
|
4
|
+
* extension draws in, ahead of the extension's own stylesheets, and its own pages import it, so a
|
|
5
|
+
* `gk-button` in an extension is the Client's own button, in the light and dark themes alike.
|
|
6
|
+
*
|
|
7
|
+
* Every colour is one of the Client's `--gk-*` tokens, and no text is under 14px. The class names
|
|
8
|
+
* are part of the Stable API: none is renamed or removed.
|
|
9
|
+
*
|
|
10
|
+
* The rules sit in the `components` layer, after declaring the layer order Tailwind v4 uses, so
|
|
11
|
+
* in a shadow root that also holds a Tailwind stylesheet the layers keep Tailwind's order:
|
|
12
|
+
* Tailwind's preflight cannot undo a control, and a utility class can still adjust one
|
|
13
|
+
* (`gk-button w-full`). A rule outside any layer wins over all of them, so an extension's own
|
|
14
|
+
* element resets belong in `@layer base`.
|
|
15
|
+
*/
|
|
16
|
+
@layer properties, theme, base, components, utilities;
|
|
17
|
+
|
|
18
|
+
@layer components {
|
|
19
|
+
/* ── Buttons ─────────────────────────────────────────────────────────────── */
|
|
20
|
+
|
|
21
|
+
/* The everyday button: a thin border, in the control ink, filled faintly on hover. */
|
|
22
|
+
.gk-button {
|
|
23
|
+
display: inline-flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
justify-content: center;
|
|
26
|
+
gap: 0.375rem;
|
|
27
|
+
padding: 0.375rem 0.75rem;
|
|
28
|
+
border: 1px solid var(--gk-control-border);
|
|
29
|
+
border-radius: 0.5rem;
|
|
30
|
+
background: transparent;
|
|
31
|
+
color: var(--gk-control-text);
|
|
32
|
+
font-family: inherit;
|
|
33
|
+
font-size: 0.875rem;
|
|
34
|
+
font-weight: 500;
|
|
35
|
+
line-height: 1.25rem;
|
|
36
|
+
text-align: center;
|
|
37
|
+
text-decoration: none;
|
|
38
|
+
white-space: nowrap;
|
|
39
|
+
cursor: pointer;
|
|
40
|
+
}
|
|
41
|
+
.gk-button:hover {
|
|
42
|
+
background: var(--gk-control-hover);
|
|
43
|
+
}
|
|
44
|
+
.gk-button:focus-visible,
|
|
45
|
+
.gk-icon-button:focus-visible,
|
|
46
|
+
.gk-link:focus-visible {
|
|
47
|
+
outline: 2px solid var(--gk-accent);
|
|
48
|
+
outline-offset: 2px;
|
|
49
|
+
}
|
|
50
|
+
/* A button that does nothing yet keeps its shape, and recedes by colour, never by opacity. */
|
|
51
|
+
.gk-button:disabled,
|
|
52
|
+
.gk-button[aria-disabled='true'] {
|
|
53
|
+
background: transparent;
|
|
54
|
+
color: var(--gk-text-subtle);
|
|
55
|
+
cursor: not-allowed;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* The one main action of a form or dialog: filled in the darkest ink (the lightest, in dark). */
|
|
59
|
+
.gk-button--primary {
|
|
60
|
+
border-color: var(--gk-primary-bg);
|
|
61
|
+
background: var(--gk-primary-bg);
|
|
62
|
+
color: var(--gk-primary-text);
|
|
63
|
+
}
|
|
64
|
+
.gk-button--primary:hover {
|
|
65
|
+
border-color: var(--gk-primary-hover);
|
|
66
|
+
background: var(--gk-primary-hover);
|
|
67
|
+
}
|
|
68
|
+
.gk-button--primary:disabled,
|
|
69
|
+
.gk-button--primary[aria-disabled='true'] {
|
|
70
|
+
border-color: var(--gk-surface-2);
|
|
71
|
+
background: var(--gk-surface-2);
|
|
72
|
+
color: var(--gk-text-muted);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* An action that removes or ends something. */
|
|
76
|
+
.gk-button--danger {
|
|
77
|
+
border-color: var(--gk-danger-border);
|
|
78
|
+
color: var(--gk-text-danger);
|
|
79
|
+
}
|
|
80
|
+
.gk-button--danger:hover {
|
|
81
|
+
background: var(--gk-danger-hover);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* A button with no border, in a toolbar or beside other text. */
|
|
85
|
+
.gk-button--quiet {
|
|
86
|
+
padding: 0.25rem 0.5rem;
|
|
87
|
+
border-color: transparent;
|
|
88
|
+
border-radius: 0.375rem;
|
|
89
|
+
color: var(--gk-text-muted);
|
|
90
|
+
}
|
|
91
|
+
.gk-button--quiet:hover {
|
|
92
|
+
background: var(--gk-menu-hover);
|
|
93
|
+
color: var(--gk-text-strong);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* A button on the line of a heading or a row: 24px tall. */
|
|
97
|
+
.gk-button--small {
|
|
98
|
+
padding: 0.0625rem 0.5rem;
|
|
99
|
+
border-radius: 0.375rem;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* A button or icon button over content, such as a map, a picture or code: a solid surface and
|
|
103
|
+
a small shadow, so what is under it never shows through. The hover tint is laid over the
|
|
104
|
+
surface, since it is translucent. */
|
|
105
|
+
.gk-button--raised,
|
|
106
|
+
.gk-button--raised:disabled,
|
|
107
|
+
.gk-button--raised[aria-disabled='true'] {
|
|
108
|
+
border: 1px solid var(--gk-control-border);
|
|
109
|
+
background: var(--gk-surface-0);
|
|
110
|
+
box-shadow: var(--gk-control-shadow);
|
|
111
|
+
}
|
|
112
|
+
.gk-button--raised:hover {
|
|
113
|
+
background: linear-gradient(var(--gk-control-hover), var(--gk-control-hover)), var(--gk-surface-0);
|
|
114
|
+
}
|
|
115
|
+
.gk-icon-button.gk-button--raised:hover {
|
|
116
|
+
background: linear-gradient(var(--gk-menu-hover), var(--gk-menu-hover)), var(--gk-surface-0);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* A toggle that is on (`aria-pressed="true"`) is drawn as the primary button, as the Client's
|
|
120
|
+
own toggles are. Raised or not: they come after `gk-button--raised`, and outweigh it. */
|
|
121
|
+
.gk-button[aria-pressed='true'],
|
|
122
|
+
.gk-icon-button[aria-pressed='true'] {
|
|
123
|
+
border-color: var(--gk-primary-bg);
|
|
124
|
+
background: var(--gk-primary-bg);
|
|
125
|
+
color: var(--gk-primary-text);
|
|
126
|
+
}
|
|
127
|
+
.gk-button[aria-pressed='true']:hover,
|
|
128
|
+
.gk-icon-button[aria-pressed='true']:hover {
|
|
129
|
+
border-color: var(--gk-primary-hover);
|
|
130
|
+
background: var(--gk-primary-hover);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* A square button holding only an icon. Give it an accessible name (`aria-label`). */
|
|
134
|
+
.gk-icon-button {
|
|
135
|
+
display: inline-flex;
|
|
136
|
+
flex: none;
|
|
137
|
+
align-items: center;
|
|
138
|
+
justify-content: center;
|
|
139
|
+
width: 2rem;
|
|
140
|
+
height: 2rem;
|
|
141
|
+
padding: 0;
|
|
142
|
+
border: 0;
|
|
143
|
+
border-radius: 0.5rem;
|
|
144
|
+
background: transparent;
|
|
145
|
+
color: var(--gk-text-muted);
|
|
146
|
+
cursor: pointer;
|
|
147
|
+
}
|
|
148
|
+
.gk-icon-button:hover {
|
|
149
|
+
background: var(--gk-menu-hover);
|
|
150
|
+
color: var(--gk-text-strong);
|
|
151
|
+
}
|
|
152
|
+
.gk-icon-button:disabled,
|
|
153
|
+
.gk-icon-button[aria-disabled='true'] {
|
|
154
|
+
background: transparent;
|
|
155
|
+
color: var(--gk-text-subtle);
|
|
156
|
+
cursor: not-allowed;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* A button or a link that reads as a link: the accent, underlined, so a link inside a sentence
|
|
160
|
+
is told from the text around it by more than its colour. */
|
|
161
|
+
.gk-link {
|
|
162
|
+
display: inline;
|
|
163
|
+
padding: 0;
|
|
164
|
+
border: 0;
|
|
165
|
+
border-radius: 0.125rem;
|
|
166
|
+
background: none;
|
|
167
|
+
color: var(--gk-accent);
|
|
168
|
+
font: inherit;
|
|
169
|
+
font-weight: 500;
|
|
170
|
+
text-decoration: underline;
|
|
171
|
+
text-underline-offset: 2px;
|
|
172
|
+
cursor: pointer;
|
|
173
|
+
}
|
|
174
|
+
.gk-link:hover {
|
|
175
|
+
color: var(--gk-accent-hover);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* ── Fields ──────────────────────────────────────────────────────────────── */
|
|
179
|
+
|
|
180
|
+
/* A field's name, then its control, then any hint or error, stacked. */
|
|
181
|
+
.gk-field {
|
|
182
|
+
display: grid;
|
|
183
|
+
gap: 0.375rem;
|
|
184
|
+
}
|
|
185
|
+
.gk-label {
|
|
186
|
+
color: var(--gk-text-strong);
|
|
187
|
+
font-size: 0.875rem;
|
|
188
|
+
font-weight: 500;
|
|
189
|
+
line-height: 1.25rem;
|
|
190
|
+
}
|
|
191
|
+
.gk-hint {
|
|
192
|
+
color: var(--gk-text-muted);
|
|
193
|
+
font-size: 0.875rem;
|
|
194
|
+
line-height: 1.25rem;
|
|
195
|
+
}
|
|
196
|
+
.gk-error {
|
|
197
|
+
color: var(--gk-text-danger);
|
|
198
|
+
font-size: 0.875rem;
|
|
199
|
+
line-height: 1.25rem;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* A text field or text area, the full width of its column. */
|
|
203
|
+
.gk-input,
|
|
204
|
+
.gk-select {
|
|
205
|
+
min-width: 0;
|
|
206
|
+
max-width: 100%;
|
|
207
|
+
padding: 0.5rem 0.75rem;
|
|
208
|
+
border: 1px solid var(--gk-control-border);
|
|
209
|
+
border-radius: 0.5rem;
|
|
210
|
+
color: var(--gk-field-text);
|
|
211
|
+
font-family: inherit;
|
|
212
|
+
font-size: 0.875rem;
|
|
213
|
+
line-height: 1.25rem;
|
|
214
|
+
}
|
|
215
|
+
.gk-input {
|
|
216
|
+
display: block;
|
|
217
|
+
width: 100%;
|
|
218
|
+
background: var(--gk-field-bg);
|
|
219
|
+
}
|
|
220
|
+
textarea.gk-input {
|
|
221
|
+
resize: vertical;
|
|
222
|
+
}
|
|
223
|
+
.gk-input::placeholder {
|
|
224
|
+
color: var(--gk-text-muted);
|
|
225
|
+
opacity: 1;
|
|
226
|
+
}
|
|
227
|
+
/* A select's background is solid: the browser's own option list takes the select's colours. */
|
|
228
|
+
.gk-select {
|
|
229
|
+
background-color: var(--gk-menu-bg);
|
|
230
|
+
}
|
|
231
|
+
.gk-input:focus,
|
|
232
|
+
.gk-select:focus {
|
|
233
|
+
border-color: var(--gk-field-focus);
|
|
234
|
+
outline: none;
|
|
235
|
+
box-shadow: 0 0 0 1px var(--gk-field-focus);
|
|
236
|
+
}
|
|
237
|
+
.gk-input[aria-invalid='true'],
|
|
238
|
+
.gk-select[aria-invalid='true'] {
|
|
239
|
+
border-color: var(--gk-text-danger);
|
|
240
|
+
}
|
|
241
|
+
.gk-input:disabled,
|
|
242
|
+
.gk-select:disabled {
|
|
243
|
+
background: var(--gk-surface-1);
|
|
244
|
+
color: var(--gk-text-muted);
|
|
245
|
+
cursor: not-allowed;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.gk-checkbox {
|
|
249
|
+
flex: none;
|
|
250
|
+
width: 1rem;
|
|
251
|
+
height: 1rem;
|
|
252
|
+
margin: 0;
|
|
253
|
+
accent-color: var(--gk-accent);
|
|
254
|
+
cursor: pointer;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/* ── Menus ───────────────────────────────────────────────────────────────── */
|
|
258
|
+
|
|
259
|
+
/* A list of actions, as the Client's context menu draws one. Rows are `gk-menu-item`. */
|
|
260
|
+
.gk-menu {
|
|
261
|
+
min-width: 12rem;
|
|
262
|
+
padding: 0.25rem 0;
|
|
263
|
+
border: 1px solid var(--gk-menu-border);
|
|
264
|
+
border-radius: 0.5rem;
|
|
265
|
+
background: var(--gk-menu-bg);
|
|
266
|
+
color: var(--gk-control-text);
|
|
267
|
+
box-shadow: var(--gk-menu-shadow);
|
|
268
|
+
}
|
|
269
|
+
.gk-menu-item {
|
|
270
|
+
display: flex;
|
|
271
|
+
align-items: center;
|
|
272
|
+
gap: 0.5rem;
|
|
273
|
+
width: 100%;
|
|
274
|
+
padding: 0.375rem 0.75rem;
|
|
275
|
+
border: 0;
|
|
276
|
+
background: transparent;
|
|
277
|
+
color: var(--gk-control-text);
|
|
278
|
+
font-family: inherit;
|
|
279
|
+
font-size: 0.875rem;
|
|
280
|
+
line-height: 1.25rem;
|
|
281
|
+
text-align: left;
|
|
282
|
+
text-decoration: none;
|
|
283
|
+
cursor: pointer;
|
|
284
|
+
}
|
|
285
|
+
.gk-menu-item:hover,
|
|
286
|
+
.gk-menu-item:focus-visible {
|
|
287
|
+
background: var(--gk-menu-hover);
|
|
288
|
+
outline: none;
|
|
289
|
+
}
|
|
290
|
+
.gk-menu-item--danger {
|
|
291
|
+
color: var(--gk-text-danger);
|
|
292
|
+
}
|
|
293
|
+
.gk-menu-separator {
|
|
294
|
+
height: 1px;
|
|
295
|
+
margin: 0.25rem 0;
|
|
296
|
+
border: 0;
|
|
297
|
+
background: var(--gk-menu-border);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/* A touch screen gets rows, buttons and fields a finger can hit. */
|
|
301
|
+
@media (pointer: coarse) {
|
|
302
|
+
.gk-button,
|
|
303
|
+
.gk-menu-item,
|
|
304
|
+
.gk-input,
|
|
305
|
+
.gk-select {
|
|
306
|
+
min-height: 2.75rem;
|
|
307
|
+
}
|
|
308
|
+
.gk-button--small {
|
|
309
|
+
min-height: 2.25rem;
|
|
310
|
+
}
|
|
311
|
+
.gk-icon-button {
|
|
312
|
+
width: 2.75rem;
|
|
313
|
+
height: 2.75rem;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/*
|
|
319
|
+
* A select whose option list is drawn here (the customizable select, `appearance: base-select`),
|
|
320
|
+
* in the menu's colours: each `<optgroup>` label above its rows, and a tick on the row chosen.
|
|
321
|
+
* Keyboard use and where the list opens stay the browser's. A browser without it skips this whole
|
|
322
|
+
* block, since an unknown pseudo-element voids any rule it is in, and shows its own list under the
|
|
323
|
+
* same closed control.
|
|
324
|
+
*/
|
|
325
|
+
@supports (appearance: base-select) {
|
|
326
|
+
@layer components {
|
|
327
|
+
.gk-select,
|
|
328
|
+
.gk-select::picker(select) {
|
|
329
|
+
appearance: base-select;
|
|
330
|
+
}
|
|
331
|
+
.gk-select {
|
|
332
|
+
display: inline-flex;
|
|
333
|
+
align-items: center;
|
|
334
|
+
gap: 0.5rem;
|
|
335
|
+
}
|
|
336
|
+
/* A chevron drawn from two borders, in the muted ink. */
|
|
337
|
+
.gk-select::picker-icon {
|
|
338
|
+
content: '';
|
|
339
|
+
flex: none;
|
|
340
|
+
width: 0.4375rem;
|
|
341
|
+
height: 0.4375rem;
|
|
342
|
+
margin: 0 0.1875rem 0.1875rem auto;
|
|
343
|
+
border-right: 1.5px solid var(--gk-text-muted);
|
|
344
|
+
border-bottom: 1.5px solid var(--gk-text-muted);
|
|
345
|
+
transform: rotate(45deg);
|
|
346
|
+
}
|
|
347
|
+
.gk-select::picker(select) {
|
|
348
|
+
max-block-size: min(20rem, 60vh);
|
|
349
|
+
overflow-y: auto;
|
|
350
|
+
margin-block: 0.25rem;
|
|
351
|
+
padding: 0.25rem 0;
|
|
352
|
+
border: 1px solid var(--gk-menu-border);
|
|
353
|
+
border-radius: 0.5rem;
|
|
354
|
+
background: var(--gk-menu-bg);
|
|
355
|
+
color: var(--gk-control-text);
|
|
356
|
+
box-shadow: var(--gk-menu-shadow);
|
|
357
|
+
}
|
|
358
|
+
.gk-select optgroup {
|
|
359
|
+
padding-block-start: 0.25rem;
|
|
360
|
+
color: var(--gk-text-muted);
|
|
361
|
+
font-weight: 600;
|
|
362
|
+
}
|
|
363
|
+
.gk-select optgroup + optgroup {
|
|
364
|
+
margin-block-start: 0.25rem;
|
|
365
|
+
border-block-start: 1px solid var(--gk-menu-border);
|
|
366
|
+
}
|
|
367
|
+
.gk-select option {
|
|
368
|
+
gap: 0.5rem;
|
|
369
|
+
padding: 0.375rem 0.75rem;
|
|
370
|
+
color: var(--gk-control-text);
|
|
371
|
+
font-weight: 400;
|
|
372
|
+
line-height: 1.25rem;
|
|
373
|
+
cursor: pointer;
|
|
374
|
+
}
|
|
375
|
+
.gk-select option:hover,
|
|
376
|
+
.gk-select option:focus-visible {
|
|
377
|
+
background: var(--gk-menu-hover);
|
|
378
|
+
outline: none;
|
|
379
|
+
}
|
|
380
|
+
.gk-select option:checked {
|
|
381
|
+
font-weight: 600;
|
|
382
|
+
}
|
|
383
|
+
/* The tick, at the end of the row chosen. */
|
|
384
|
+
.gk-select option::checkmark {
|
|
385
|
+
order: 1;
|
|
386
|
+
margin-inline-start: auto;
|
|
387
|
+
color: var(--gk-accent);
|
|
388
|
+
line-height: 1;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
package/dist/assets.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* The open graph's assets, as far as an extension may add to them.
|
|
7
7
|
*
|
|
8
|
-
* @
|
|
8
|
+
* @public
|
|
9
9
|
*/
|
|
10
10
|
export interface ExtensionAssets {
|
|
11
11
|
/**
|
|
@@ -15,9 +15,12 @@ export interface ExtensionAssets {
|
|
|
15
15
|
*/
|
|
16
16
|
prepareImage(image: Blob): Promise<Blob>;
|
|
17
17
|
/**
|
|
18
|
-
* Keep an image as one of the graph's assets,
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* Keep an image as one of the graph's assets, made smaller first as {@link ExtensionAssets.prepareImage}
|
|
19
|
+
* does, and answer the markdown that shows it in a document, such as
|
|
20
|
+
* ``. The file is named from `name` and a short hash of its
|
|
21
|
+
* bytes, so two images never take the same name, and the same image kept twice is one file.
|
|
22
|
+
* An asset is part of the graph: it syncs, and every member of a shared graph can see it. Null
|
|
23
|
+
* where the graph keeps no assets here, or the image was refused.
|
|
21
24
|
*/
|
|
22
25
|
saveImage(image: Blob, name: string): Promise<string | null>;
|
|
23
26
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Client's own Commands that any extension may run (ADR 0120, amended 2026-10-17): through
|
|
3
|
+
* `commands.execute`, or as the Command of a menu row or a button it adds. Each one keeps its id
|
|
4
|
+
* and the argument it takes in every release, as the rest of the Stable API does. An extension may
|
|
5
|
+
* also run its own Commands and those of the other extensions running, by their ids.
|
|
6
|
+
*
|
|
7
|
+
* - `agents.settings`: open Settings and Extensions at the Agents tab. It takes no argument.
|
|
8
|
+
*
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
export declare const CLIENT_COMMANDS: readonly string[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Client's own Commands that any extension may run (ADR 0120, amended 2026-10-17): through
|
|
3
|
+
* `commands.execute`, or as the Command of a menu row or a button it adds. Each one keeps its id
|
|
4
|
+
* and the argument it takes in every release, as the rest of the Stable API does. An extension may
|
|
5
|
+
* also run its own Commands and those of the other extensions running, by their ids.
|
|
6
|
+
*
|
|
7
|
+
* - `agents.settings`: open Settings and Extensions at the Agents tab. It takes no argument.
|
|
8
|
+
*
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
export const CLIENT_COMMANDS = ['agents.settings'];
|