@appsoftwareltd/etherpk-extension-api 0.9.5 → 0.10.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 +268 -33
- package/dist/context.d.ts +108 -28
- package/dist/dialogs.d.ts +37 -0
- package/dist/dialogs.js +7 -0
- package/dist/editor.d.ts +236 -0
- package/dist/editor.js +9 -0
- package/dist/embeds.d.ts +33 -0
- package/dist/embeds.js +6 -0
- package/dist/index.d.ts +8 -3
- package/dist/index.js +4 -2
- package/dist/manifest.d.ts +25 -9
- package/dist/manifest.js +12 -2
- package/dist/menus.d.ts +30 -7
- package/dist/views.d.ts +5 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,41 +1,104 @@
|
|
|
1
1
|
# EtherPK Extension API
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
checks an extension's manifest the way the EtherPK Client does.
|
|
3
|
+
This package holds the types an [EtherPK](https://etherpk.com) extension is written against, and
|
|
4
|
+
`readExtensionPackage`, which checks an extension's manifest the way the EtherPK Client does. It
|
|
5
|
+
holds no code that the Client runs. MIT licensed.
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npm install --save-dev @appsoftwareltd/etherpk-extension-api
|
|
9
|
+
```
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
import type { ExtensionContext } from '@appsoftwareltd/etherpk-extension-api'
|
|
11
|
+
It is a development dependency, because the Client supplies everything at run time.
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
## What an extension is
|
|
14
|
+
|
|
15
|
+
An extension is an npm package. Its `package.json` has an `etherpk` manifest, which says what the
|
|
16
|
+
extension is and what it adds, and its main module exports `activate(context)`. The Client calls
|
|
17
|
+
`activate` each time a graph opens, with a context for that graph. When the graph closes, the Client
|
|
18
|
+
removes everything the extension registered through the context, and calls the function `activate`
|
|
19
|
+
returned, if it returned one, for anything else the extension holds.
|
|
16
20
|
|
|
17
21
|
```json
|
|
18
22
|
{
|
|
19
23
|
"name": "etherpk-extension-hello",
|
|
20
24
|
"version": "1.0.0",
|
|
25
|
+
"type": "module",
|
|
26
|
+
"files": ["dist"],
|
|
21
27
|
"etherpk": {
|
|
22
28
|
"id": "hello",
|
|
23
29
|
"displayName": "Hello",
|
|
24
|
-
"publisher": "
|
|
25
|
-
"minClientVersion": "0.
|
|
30
|
+
"publisher": "you",
|
|
31
|
+
"minClientVersion": "0.10.0",
|
|
26
32
|
"main": "./dist/main.js"
|
|
27
33
|
}
|
|
28
34
|
}
|
|
29
35
|
```
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
```ts
|
|
38
|
+
import type { ExtensionContext } from '@appsoftwareltd/etherpk-extension-api'
|
|
39
|
+
|
|
40
|
+
export function activate(context: ExtensionContext): void {
|
|
41
|
+
context.commands.register('hello.greet', () => context.notify('Hello from an extension.'))
|
|
42
|
+
context.commandMenu.register({ id: 'hello.greet', title: 'Say hello', command: 'hello.greet' })
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Stable and Proposed parts
|
|
47
|
+
|
|
48
|
+
Each declaration in this package, and each member of one, is either Stable or Proposed. Its
|
|
49
|
+
documentation says which, with `@public` for Stable and `@beta` for Proposed.
|
|
50
|
+
|
|
51
|
+
- **A Stable part never breaks.** A change can add to it. Anything a change replaces is marked
|
|
52
|
+
deprecated and kept working.
|
|
53
|
+
- **A Proposed part may change in any release.** Only the extensions that come with EtherPK may use
|
|
54
|
+
it. In an extension installed from the Extension Directory, a call to a Proposed member throws an
|
|
55
|
+
error that names it.
|
|
56
|
+
|
|
57
|
+
An extension listed in the Extension Directory uses only the Stable part.
|
|
58
|
+
[`etc/etherpk-extension-api.public.api.md`](etc/etherpk-extension-api.public.api.md) lists the
|
|
59
|
+
Stable part, and [`etc/etherpk-extension-api.beta.api.md`](etc/etherpk-extension-api.beta.api.md)
|
|
60
|
+
lists everything. A Proposed part becomes Stable when an extension from another publisher needs it.
|
|
61
|
+
To ask for one, open an issue on
|
|
62
|
+
[the EtherPK Client repository](https://github.com/appsoftwareltd/etherpk-client/issues).
|
|
63
|
+
|
|
64
|
+
## The manifest
|
|
34
65
|
|
|
35
|
-
|
|
66
|
+
| Field | What it is |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| `id` | The extension's id: lowercase letters, digits and hyphens, starting with a letter. The ids of everything it registers start with it. An extension in the Extension Directory can't use `map`, `math`, `mermaid` or `etherpk-cipher`, the words of blocks the Client draws itself. |
|
|
69
|
+
| `displayName` | The name Settings shows, and the name a dialog it opens gives. |
|
|
70
|
+
| `publisher` | Who publishes the extension. |
|
|
71
|
+
| `minClientVersion` | The oldest Client version the extension runs on, as `x.y.z`. An extension in the Extension Directory must give one. |
|
|
72
|
+
| `main` | The main module, an ES module. |
|
|
73
|
+
| `styles` | The extension's stylesheets (see *Styles* below). |
|
|
74
|
+
| `isolation` | `"shadow"`, the default, or `"page"` (see *Styles* below). |
|
|
75
|
+
| `icons` | Icons by name, each the SVG elements of a 16 by 16 picture. A Command Menu row or a View refers to an icon by its name. |
|
|
76
|
+
| `views` | The View kinds the extension supplies (see *Views* below). |
|
|
77
|
+
| `settings` | What the person can set (see *Settings* below). |
|
|
36
78
|
|
|
37
|
-
|
|
38
|
-
|
|
79
|
+
`readExtensionPackage(packageJson)` checks a manifest as the Client does, and lists every problem
|
|
80
|
+
it finds.
|
|
81
|
+
|
|
82
|
+
## What an extension can do
|
|
83
|
+
|
|
84
|
+
These are the Stable parts of the context.
|
|
85
|
+
|
|
86
|
+
### Commands and the `/` menu
|
|
87
|
+
|
|
88
|
+
`context.commands.register(id, handler)` adds a Command, whose id starts with the extension's id
|
|
89
|
+
(`hello.greet`). `context.commandMenu.register(item)` adds a row to the `/` menu that runs a Command.
|
|
90
|
+
A row can have a `detail` line, an `icon`, `keywords` that find it, a `group` and an `order`. Its
|
|
91
|
+
`when(menu)` says whether it is offered at the caret, from what `menu` reports: `bodyWritable`,
|
|
92
|
+
`inTable` and `protectedDocument`.
|
|
93
|
+
|
|
94
|
+
### Notices
|
|
95
|
+
|
|
96
|
+
`context.notify(text)` shows a short notice, as the Client's own notices do.
|
|
97
|
+
|
|
98
|
+
### Settings
|
|
99
|
+
|
|
100
|
+
A value the person sets, such as a key for a service, is declared in the manifest. The Client draws
|
|
101
|
+
it under the extension's entry in Settings, in the Extensions area.
|
|
39
102
|
|
|
40
103
|
```json
|
|
41
104
|
"settings": [
|
|
@@ -50,25 +113,197 @@ draws it under the extension's entry in Settings and Extensions → Extensions:
|
|
|
50
113
|
]
|
|
51
114
|
```
|
|
52
115
|
|
|
53
|
-
`type` is `text`, `secret` or `boolean`. A secret stays
|
|
54
|
-
`boolean` is a switch,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
person
|
|
59
|
-
|
|
60
|
-
devices can read
|
|
116
|
+
`type` is `text`, `secret` or `boolean`. A secret stays hidden until the person shows it. A
|
|
117
|
+
`boolean` is a switch, and `"default": true` starts it on. The person can change settings while the
|
|
118
|
+
extension is switched off.
|
|
119
|
+
|
|
120
|
+
The extension reads a setting with `context.settings.get('api-key')`, which is undefined until the
|
|
121
|
+
person sets it, and hears every change with `context.settings.subscribe`. A switch always reads
|
|
122
|
+
`'true'` or `'false'`. Settings belong to the person, not to a graph. Once their account syncs a
|
|
123
|
+
graph, the settings follow them to their other devices, encrypted so that only those devices can read
|
|
124
|
+
them.
|
|
125
|
+
|
|
126
|
+
### Storage
|
|
127
|
+
|
|
128
|
+
`context.storage` keeps the extension's own values on the device, as JSON, with `get`, `set` and
|
|
129
|
+
`remove`. It is for what the extension works out or caches, not for what the person sets.
|
|
130
|
+
|
|
131
|
+
### Fence widgets
|
|
132
|
+
|
|
133
|
+
A fence widget draws over a fenced block in a document, in place of its text. The extension
|
|
134
|
+
registers one for an info word, which is either its id or `<id>.<name>`:
|
|
135
|
+
|
|
136
|
+
- **Its id** (```` ```hello ````) makes an ordinary fence. Without the extension it shows as a code
|
|
137
|
+
block, so the document reads well anywhere.
|
|
138
|
+
- **`<id>.<name>`** (```` ```hello.board ````) makes an Extension Fence, which holds the extension's
|
|
139
|
+
data. EtherPK never searches its lines and never publishes them as code. The caret passes over it,
|
|
140
|
+
and where the extension is not running, a note says so.
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
context.editor.registerFence('hello', {
|
|
144
|
+
height: () => 40,
|
|
145
|
+
mount(host, initial) {
|
|
146
|
+
const line = document.createElement('p')
|
|
147
|
+
const show = (fence: { body: readonly string[] }) => {
|
|
148
|
+
line.textContent = `${fence.body.length} lines`
|
|
149
|
+
}
|
|
150
|
+
show(initial)
|
|
151
|
+
host.append(line)
|
|
152
|
+
return { update: show, destroy: () => line.remove() }
|
|
153
|
+
},
|
|
154
|
+
})
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`height` must answer before the widget is drawn, so the lines below never move as it loads. The
|
|
158
|
+
context a widget is given has the fence's lines (`body`), whether the document can be written
|
|
159
|
+
(`writable`), and the theme. It can also:
|
|
160
|
+
|
|
161
|
+
- Change the lines (`edit`), as one change that Mod+Z undoes.
|
|
162
|
+
- Show the fence as text (`editAsText`), or delete it (`remove`).
|
|
163
|
+
- Open the fence in a tab of its own, or over the whole window.
|
|
164
|
+
|
|
165
|
+
The third argument to `registerFence` adds options:
|
|
166
|
+
|
|
167
|
+
- `title`, what the fence is called in its tab.
|
|
168
|
+
- `fromPaste`, which turns text pasted on a line of its own into the fence. A line it answers with
|
|
169
|
+
that would end the fence is changed so it can't.
|
|
170
|
+
- `publish`, which says how a published site shows the fence, as a picture or a link card. An SVG
|
|
171
|
+
picture is cleaned of scripts, event handlers and links that run code before the site gets it.
|
|
172
|
+
- `identify`, which finds a fence again by an id in its body, so it can open in a tab.
|
|
173
|
+
- `keepsHistory`, which leaves Mod+Z to the widget while it has the keyboard.
|
|
174
|
+
|
|
175
|
+
### A block at the caret
|
|
176
|
+
|
|
177
|
+
`context.editor.insertBlock(text, { focus })` puts lines at the caret, as a code block or a table
|
|
178
|
+
goes in. It returns false where nothing went in, such as a document that cannot be written, a
|
|
179
|
+
Protected Document, or a caret in the frontmatter, in a fence or in a table.
|
|
180
|
+
|
|
181
|
+
### Dialogs
|
|
182
|
+
|
|
183
|
+
`context.dialogs.prompt(options)` asks for one line of text in a dialog the Client draws. Its
|
|
184
|
+
`validate` function can refuse an answer by returning a message, and the dialog stays open with the
|
|
185
|
+
text kept. It resolves to the text, trimmed, or undefined when the person cancels. The dialog names
|
|
186
|
+
the extension, and shows every string it is given as text.
|
|
187
|
+
|
|
188
|
+
### Frames
|
|
189
|
+
|
|
190
|
+
`context.embeds.frame({ src, title })` builds a frame showing a page from a host the Client allows,
|
|
191
|
+
and throws for any other host. `context.embeds.frameHosts` lists the hosts: the video players of
|
|
192
|
+
YouTube (`www.youtube-nocookie.com`) and Vimeo (`player.vimeo.com`).
|
|
193
|
+
|
|
194
|
+
### Views
|
|
195
|
+
|
|
196
|
+
A View is the content of a tab. The manifest declares each View kind, and the extension supplies it
|
|
197
|
+
with `context.views.register(kind, view)` as the graph opens.
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
"views": [
|
|
201
|
+
{
|
|
202
|
+
"kind": "hello.board",
|
|
203
|
+
"title": "Board: {target}",
|
|
204
|
+
"region": "main",
|
|
205
|
+
"address": { "segment": "board", "target": "concept" }
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`kind` starts with the extension's id. `{target}` in the title stands for the View's target, here a
|
|
211
|
+
concept. `region` is where the kind opens: `main`, `left-sidebar` or `right-sidebar`. A kind with
|
|
212
|
+
one View per graph gives a fixed `target` instead.
|
|
213
|
+
|
|
214
|
+
An `address` gives the View a URL. For an extension from the Extension Directory, the URL is
|
|
215
|
+
`/g/<graph>/x/<extension id>/<segment>`, followed by the concept when `target` is `concept`. A link
|
|
216
|
+
to it opens the View. On a device without the extension, it opens a page that names the extension
|
|
217
|
+
and offers to install it, if the directory lists it.
|
|
218
|
+
|
|
219
|
+
`context.layout.openView({ kind, target })` opens a View. `context.layout.isDesktop()` says whether
|
|
220
|
+
the desktop layout is showing, which it is at 1024 pixels wide and more.
|
|
221
|
+
|
|
222
|
+
## Styles
|
|
223
|
+
|
|
224
|
+
The manifest's `styles` are the extension's stylesheets. By default, each widget and View the
|
|
225
|
+
extension draws is mounted in a shadow root with them, so the extension's styles and the Client's
|
|
226
|
+
stay apart.
|
|
227
|
+
|
|
228
|
+
Some libraries can't run in a shadow root. An extension that uses one can set
|
|
229
|
+
`"isolation": "page"` to mount in the page instead. Then every rule in its stylesheets must style
|
|
230
|
+
only elements with a class that starts with the extension's id, or elements inside one. The Client
|
|
231
|
+
refuses any other stylesheet. The colours of the Client's theme are CSS variables (`--gk-*`) that
|
|
232
|
+
reach into a shadow root as well, so an extension can follow the light and dark themes either way.
|
|
233
|
+
An extension's stylesheet can't register one of them with `@property`.
|
|
234
|
+
|
|
235
|
+
Either way, the shadow root and the check keep styles apart, and nothing more. They don't limit
|
|
236
|
+
what the extension's code can do in the page.
|
|
61
237
|
|
|
62
238
|
## Writing to the page
|
|
63
239
|
|
|
64
|
-
The Client enforces
|
|
65
|
-
and extension
|
|
240
|
+
The Client enforces
|
|
241
|
+
[Trusted Types](https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API), and extension
|
|
242
|
+
code runs under them.
|
|
66
243
|
|
|
67
|
-
- **An extension cannot create a Trusted Types policy.** The Client's Content Security Policy names
|
|
68
|
-
the policies it allows, and each belongs to the Client or a library it ships.
|
|
69
244
|
- **Write no HTML.** Build elements with the DOM (`createElement`, `createElementNS`,
|
|
70
245
|
`textContent`). A string written to an HTML sink such as `innerHTML` is sanitized by the Client's
|
|
71
246
|
default policy, which may remove what you wrote.
|
|
72
|
-
- **
|
|
73
|
-
|
|
74
|
-
|
|
247
|
+
- **An extension can't create a Trusted Types policy.** The Client's Content Security Policy names
|
|
248
|
+
the policies it allows, and each one belongs to the Client or to a library it ships.
|
|
249
|
+
- **Icons come from the manifest.** The Client sanitizes the icons a manifest declares as SVG, so
|
|
250
|
+
they can hold only SVG.
|
|
251
|
+
|
|
252
|
+
## Proposed parts
|
|
253
|
+
|
|
254
|
+
The Proposed parts are for the extensions that come with EtherPK, and may change in any release.
|
|
255
|
+
They include:
|
|
256
|
+
|
|
257
|
+
- Context Menu rows (`contextMenu`), key chords (`keybindings`) and Graph Sidebar buttons
|
|
258
|
+
(`graphSidebar`).
|
|
259
|
+
- Events (`events`), questions to the graph's index (`index`) and the rules for naming a concept
|
|
260
|
+
(`concepts`).
|
|
261
|
+
- The theme (`theme`), the Client's icons (`icons`) and a View that lives in a Sidebar (`resident`).
|
|
262
|
+
- Running another extension's Commands (`commands.execute`) and opening documents
|
|
263
|
+
(`layout.openDocument`).
|
|
264
|
+
|
|
265
|
+
`contributions`, which reaches the Client's own registries, stays Proposed for good, since its
|
|
266
|
+
shape is the Client's internals.
|
|
267
|
+
|
|
268
|
+
### Graph Sidebar buttons
|
|
269
|
+
|
|
270
|
+
A button in the Graph Sidebar, below Search and today's date, runs a Command. These rules keep the
|
|
271
|
+
buttons readable together:
|
|
272
|
+
|
|
273
|
+
- **`title`** is short. Two buttons sit side by side in the sidebar at its default width of 304
|
|
274
|
+
pixels, and "Graph Kanban", with its icon, is about the widest that fits.
|
|
275
|
+
- **`description`** says what the button does, such as "Open a map of every place and route in this
|
|
276
|
+
graph". The Client shows it as the button's help text. Don't write a keyboard shortcut in it: the
|
|
277
|
+
Client adds the key chords bound to the button's Command itself.
|
|
278
|
+
- **`icon`** is clear on its own. On a phone the buttons show their icons only.
|
|
279
|
+
- **`when`** hides the button where it does not apply. A button for the desktop alone passes
|
|
280
|
+
`when: () => context.layout.isDesktop()`. A `when` that throws hides the button too.
|
|
281
|
+
- **`order`** places the button among the others. The Graph Map's is 20 and the Graph Kanban's is
|
|
282
|
+
30.
|
|
283
|
+
|
|
284
|
+
## Trying an extension
|
|
285
|
+
|
|
286
|
+
To try an extension, build it, and start a development copy of the EtherPK Client with the
|
|
287
|
+
extension's folder named in `ETHERPK_DEV_EXTENSIONS`. Separate several folders with commas. The
|
|
288
|
+
Client loads each one as it loads the extensions it comes with, and one with the same id replaces
|
|
289
|
+
it.
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
ETHERPK_DEV_EXTENSIONS=/path/to/my-extension pnpm dev
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
[The EtherPK Client repository](https://github.com/appsoftwareltd/etherpk-client) says how to run
|
|
296
|
+
the Client.
|
|
297
|
+
|
|
298
|
+
## Listing an extension
|
|
299
|
+
|
|
300
|
+
The [Extension Directory](https://github.com/appsoftwareltd/etherpk-extension-directory) lists the
|
|
301
|
+
extensions people can install from inside EtherPK. Each release is a pull request there. It is
|
|
302
|
+
rebuilt from its tagged source and compared with its npm package, and a person reviews it before it
|
|
303
|
+
is listed. Installed extensions update by themselves to the newest listed release the person's
|
|
304
|
+
EtherPK can run. The directory's contributing guide says what a release needs, including the size
|
|
305
|
+
limits.
|
|
306
|
+
|
|
307
|
+
## Licence
|
|
308
|
+
|
|
309
|
+
MIT.
|
package/dist/context.d.ts
CHANGED
|
@@ -4,8 +4,12 @@
|
|
|
4
4
|
* Everything an extension adds through it sits under the extension's id and is removed when the
|
|
5
5
|
* graph closes or the extension is switched off, so an extension never has to undo its own
|
|
6
6
|
* registrations. It holds no document store, no keys and no sync: an extension reads documents
|
|
7
|
-
* only through the index's answers, and changes them only
|
|
7
|
+
* only through the index's answers and its own fences, and changes them only through its fences,
|
|
8
|
+
* the blocks it puts at the caret and the Client's Commands.
|
|
8
9
|
*/
|
|
10
|
+
import type { ExtensionDialogs } from './dialogs';
|
|
11
|
+
import type { ExtensionEditor } from './editor';
|
|
12
|
+
import type { ExtensionEmbeds } from './embeds';
|
|
9
13
|
import type { ExtensionEvents } from './events';
|
|
10
14
|
import type { ExtensionIndex } from './graph-index';
|
|
11
15
|
import type { CommandMenuItem, ContextMenuItem, GraphSidebarButton } from './menus';
|
|
@@ -13,26 +17,34 @@ import type { ViewContribution, ViewRef } from './views';
|
|
|
13
17
|
/**
|
|
14
18
|
* A Command's handler. What it returns, or resolves to, is what `execute` resolves to.
|
|
15
19
|
*
|
|
16
|
-
* @
|
|
20
|
+
* @public
|
|
17
21
|
*/
|
|
18
22
|
export type CommandHandler = (arg?: unknown) => unknown;
|
|
19
23
|
/**
|
|
20
24
|
* Running and adding Commands.
|
|
21
25
|
*
|
|
22
|
-
* @
|
|
26
|
+
* @public
|
|
23
27
|
*/
|
|
24
28
|
export interface ExtensionCommands {
|
|
25
29
|
/** Add a Command, its id under the extension's id. Throws if the id is taken or outside it. */
|
|
26
30
|
register(id: string, handler: CommandHandler): () => void;
|
|
27
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Run any Command, the Client's or an extension's. Rejects if no Command has the id.
|
|
33
|
+
*
|
|
34
|
+
* @beta
|
|
35
|
+
*/
|
|
28
36
|
execute(id: string, arg?: unknown): Promise<unknown>;
|
|
29
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* Whether any Command has the id.
|
|
39
|
+
*
|
|
40
|
+
* @beta
|
|
41
|
+
*/
|
|
30
42
|
has(id: string): boolean;
|
|
31
43
|
}
|
|
32
44
|
/**
|
|
33
45
|
* Supplying the Views the manifest declares.
|
|
34
46
|
*
|
|
35
|
-
* @
|
|
47
|
+
* @public
|
|
36
48
|
*/
|
|
37
49
|
export interface ExtensionViews {
|
|
38
50
|
/**
|
|
@@ -53,14 +65,14 @@ export interface ExtensionContextMenu {
|
|
|
53
65
|
/**
|
|
54
66
|
* Adding Command Menu rows.
|
|
55
67
|
*
|
|
56
|
-
* @
|
|
68
|
+
* @public
|
|
57
69
|
*/
|
|
58
70
|
export interface ExtensionCommandMenu {
|
|
59
71
|
/** Add a row, its id under the extension's id. */
|
|
60
72
|
register(item: CommandMenuItem): () => void;
|
|
61
73
|
}
|
|
62
74
|
/**
|
|
63
|
-
* Adding buttons to the Graph Sidebar,
|
|
75
|
+
* Adding buttons to the Graph Sidebar's group of action buttons, after Search and today's date.
|
|
64
76
|
*
|
|
65
77
|
* @beta
|
|
66
78
|
*/
|
|
@@ -95,7 +107,7 @@ export interface ExtensionKeybindings {
|
|
|
95
107
|
/**
|
|
96
108
|
* Opening Views and documents in the open graph's Layout.
|
|
97
109
|
*
|
|
98
|
-
* @
|
|
110
|
+
* @public
|
|
99
111
|
*/
|
|
100
112
|
export interface ExtensionLayout {
|
|
101
113
|
/**
|
|
@@ -107,19 +119,29 @@ export interface ExtensionLayout {
|
|
|
107
119
|
inPaneOf?: string;
|
|
108
120
|
beside?: string;
|
|
109
121
|
}): void;
|
|
110
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* Bring a Sidebar resident to the front of its expanded Sidebar.
|
|
124
|
+
*
|
|
125
|
+
* @beta
|
|
126
|
+
*/
|
|
111
127
|
reveal(kind: string): void;
|
|
112
128
|
/**
|
|
113
129
|
* Open the document of a concept, under the name it resolves to, as following a wikilink
|
|
114
130
|
* does. `line` scrolls to and selects that line, `inPaneOf` and `beside` place it as for
|
|
115
131
|
* {@link ExtensionLayout.openView}.
|
|
132
|
+
*
|
|
133
|
+
* @beta
|
|
116
134
|
*/
|
|
117
135
|
openDocument(concept: string, options?: {
|
|
118
136
|
line?: number;
|
|
119
137
|
inPaneOf?: string;
|
|
120
138
|
beside?: string;
|
|
121
139
|
}): void;
|
|
122
|
-
/**
|
|
140
|
+
/**
|
|
141
|
+
* The document the user is working in, or null.
|
|
142
|
+
*
|
|
143
|
+
* @beta
|
|
144
|
+
*/
|
|
123
145
|
activeDocument(): string | null;
|
|
124
146
|
/** True while the desktop presenter is showing, 1024 pixels wide and more. */
|
|
125
147
|
isDesktop(): boolean;
|
|
@@ -139,7 +161,7 @@ export interface ExtensionConcepts {
|
|
|
139
161
|
* The extension's own storage on this device, under `etherpk-<extension id>:` in the browser's
|
|
140
162
|
* localStorage. Values are stored as JSON.
|
|
141
163
|
*
|
|
142
|
-
* @
|
|
164
|
+
* @public
|
|
143
165
|
*/
|
|
144
166
|
export interface ExtensionStorage {
|
|
145
167
|
/** The value stored under `key`, or undefined when there is none or it cannot be read. */
|
|
@@ -155,7 +177,7 @@ export interface ExtensionStorage {
|
|
|
155
177
|
* keeps them with the person: on this device, and in their account where it syncs a graph, so they
|
|
156
178
|
* follow the person to their other devices. The extension only reads them.
|
|
157
179
|
*
|
|
158
|
-
* @
|
|
180
|
+
* @public
|
|
159
181
|
*/
|
|
160
182
|
export interface ExtensionSettings {
|
|
161
183
|
/**
|
|
@@ -232,54 +254,112 @@ export interface ExtensionContributions {
|
|
|
232
254
|
/**
|
|
233
255
|
* What an extension is started with, once for each graph that opens.
|
|
234
256
|
*
|
|
235
|
-
* @
|
|
257
|
+
* @public
|
|
236
258
|
*/
|
|
237
259
|
export interface ExtensionContext {
|
|
238
|
-
/**
|
|
260
|
+
/**
|
|
261
|
+
* The extension's id, from its manifest.
|
|
262
|
+
*
|
|
263
|
+
* @beta
|
|
264
|
+
*/
|
|
239
265
|
readonly extensionId: string;
|
|
240
|
-
/**
|
|
266
|
+
/**
|
|
267
|
+
* The open graph.
|
|
268
|
+
*
|
|
269
|
+
* @beta
|
|
270
|
+
*/
|
|
241
271
|
readonly graphId: string;
|
|
242
|
-
/**
|
|
272
|
+
/**
|
|
273
|
+
* The Client's version, `x.y.z`.
|
|
274
|
+
*
|
|
275
|
+
* @beta
|
|
276
|
+
*/
|
|
243
277
|
readonly clientVersion: string;
|
|
244
|
-
/**
|
|
278
|
+
/**
|
|
279
|
+
* True in a development build of the Client, where tests may reach in.
|
|
280
|
+
*
|
|
281
|
+
* @beta
|
|
282
|
+
*/
|
|
245
283
|
readonly dev: boolean;
|
|
246
284
|
/** Supplying the Views the manifest declares. */
|
|
247
285
|
readonly views: ExtensionViews;
|
|
248
286
|
/** Running and adding Commands. */
|
|
249
287
|
readonly commands: ExtensionCommands;
|
|
250
|
-
/**
|
|
288
|
+
/**
|
|
289
|
+
* Adding Context Menu rows.
|
|
290
|
+
*
|
|
291
|
+
* @beta
|
|
292
|
+
*/
|
|
251
293
|
readonly contextMenu: ExtensionContextMenu;
|
|
252
294
|
/** Adding Command Menu rows. */
|
|
253
295
|
readonly commandMenu: ExtensionCommandMenu;
|
|
254
|
-
/**
|
|
296
|
+
/**
|
|
297
|
+
* Adding buttons to the Graph Sidebar.
|
|
298
|
+
*
|
|
299
|
+
* @beta
|
|
300
|
+
*/
|
|
255
301
|
readonly graphSidebar: ExtensionGraphSidebar;
|
|
256
|
-
/**
|
|
302
|
+
/**
|
|
303
|
+
* Adding key chords.
|
|
304
|
+
*
|
|
305
|
+
* @beta
|
|
306
|
+
*/
|
|
257
307
|
readonly keybindings: ExtensionKeybindings;
|
|
258
|
-
/**
|
|
308
|
+
/**
|
|
309
|
+
* Every Contribution Point by kind, for the kinds the parts above do not cover.
|
|
310
|
+
*
|
|
311
|
+
* @beta
|
|
312
|
+
*/
|
|
259
313
|
readonly contributions: ExtensionContributions;
|
|
260
|
-
/**
|
|
314
|
+
/**
|
|
315
|
+
* Listening to Events.
|
|
316
|
+
*
|
|
317
|
+
* @beta
|
|
318
|
+
*/
|
|
261
319
|
readonly events: ExtensionEvents;
|
|
262
|
-
/**
|
|
320
|
+
/**
|
|
321
|
+
* Questions to the open graph's index.
|
|
322
|
+
*
|
|
323
|
+
* @beta
|
|
324
|
+
*/
|
|
263
325
|
readonly index: ExtensionIndex;
|
|
264
326
|
/** Opening Views and documents. */
|
|
265
327
|
readonly layout: ExtensionLayout;
|
|
266
|
-
/**
|
|
328
|
+
/**
|
|
329
|
+
* The rules for naming a concept.
|
|
330
|
+
*
|
|
331
|
+
* @beta
|
|
332
|
+
*/
|
|
267
333
|
readonly concepts: ExtensionConcepts;
|
|
268
334
|
/** The extension's own storage on this device. */
|
|
269
335
|
readonly storage: ExtensionStorage;
|
|
270
336
|
/** The person's answers to the settings the manifest declares. */
|
|
271
337
|
readonly settings: ExtensionSettings;
|
|
272
|
-
/**
|
|
338
|
+
/**
|
|
339
|
+
* The Client's light or dark theme.
|
|
340
|
+
*
|
|
341
|
+
* @beta
|
|
342
|
+
*/
|
|
273
343
|
readonly theme: ExtensionTheme;
|
|
274
|
-
/**
|
|
344
|
+
/**
|
|
345
|
+
* The Client's icons and the extension's own.
|
|
346
|
+
*
|
|
347
|
+
* @beta
|
|
348
|
+
*/
|
|
275
349
|
readonly icons: ExtensionIcons;
|
|
350
|
+
/** Widgets over the extension's own fences, and blocks put at the caret. */
|
|
351
|
+
readonly editor: ExtensionEditor;
|
|
352
|
+
/** Dialogs the Client draws. */
|
|
353
|
+
readonly dialogs: ExtensionDialogs;
|
|
354
|
+
/** Frames showing content from the hosts the Client allows. */
|
|
355
|
+
readonly embeds: ExtensionEmbeds;
|
|
276
356
|
/** Say something in passing, as the Client's own notices do: "Coordinates copied." */
|
|
277
357
|
notify(text: string): void;
|
|
278
358
|
}
|
|
279
359
|
/**
|
|
280
360
|
* What an extension's main module exports.
|
|
281
361
|
*
|
|
282
|
-
* @
|
|
362
|
+
* @public
|
|
283
363
|
*/
|
|
284
364
|
export interface ExtensionModule {
|
|
285
365
|
/**
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dialogs the Client draws for an extension, so every extension's questions look and behave like the
|
|
3
|
+
* Client's own: the keyboard stays inside while one is open, Enter answers, Esc cancels, and the
|
|
4
|
+
* keyboard returns where it was when one closes. Each dialog names the extension that asks, and
|
|
5
|
+
* shows every string it is given as plain text, never as markup.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* A question with one line of text for an answer.
|
|
9
|
+
*
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
export interface PromptOptions {
|
|
13
|
+
/** The dialog's heading. */
|
|
14
|
+
title: string;
|
|
15
|
+
/** The text field's label. */
|
|
16
|
+
label: string;
|
|
17
|
+
/** Example text shown in the empty field. */
|
|
18
|
+
placeholder?: string;
|
|
19
|
+
/** The field's text when the dialog opens. */
|
|
20
|
+
value?: string;
|
|
21
|
+
/** The button that answers. "OK" when left out. */
|
|
22
|
+
submitLabel?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Checks the answer when it is given: a message saying what is wrong, shown at the field, or
|
|
25
|
+
* undefined when it is fine. The dialog stays open, with the text kept, until it is fine.
|
|
26
|
+
*/
|
|
27
|
+
validate?(value: string): string | undefined;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Dialogs.
|
|
31
|
+
*
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
export interface ExtensionDialogs {
|
|
35
|
+
/** Ask for one line of text. Resolves to the text, trimmed, or undefined when it was cancelled. */
|
|
36
|
+
prompt(options: PromptOptions): Promise<string | undefined>;
|
|
37
|
+
}
|
package/dist/dialogs.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dialogs the Client draws for an extension, so every extension's questions look and behave like the
|
|
3
|
+
* Client's own: the keyboard stays inside while one is open, Enter answers, Esc cancels, and the
|
|
4
|
+
* keyboard returns where it was when one closes. Each dialog names the extension that asks, and
|
|
5
|
+
* shows every string it is given as plain text, never as markup.
|
|
6
|
+
*/
|
|
7
|
+
export {};
|
package/dist/editor.d.ts
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an extension may do in the document editor: draw a widget over its own kind of fenced block,
|
|
3
|
+
* and put a block at the caret.
|
|
4
|
+
*
|
|
5
|
+
* A widget never sees the editor. It is told the fence's body as lines of text and asks for changes
|
|
6
|
+
* to those lines, which the Client writes as ordinary editor changes: undone with the editor's undo,
|
|
7
|
+
* synced and merged like typing, and refused where the document cannot be written.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* One change to a fence's body, by line number from 0. A replacement or a removal names the text the
|
|
11
|
+
* widget last read on that line, and applies only while the line still says it, so a widget acting on
|
|
12
|
+
* an old reading never lands on someone else's edit.
|
|
13
|
+
*
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
16
|
+
export type FenceEdit = {
|
|
17
|
+
kind: 'insert';
|
|
18
|
+
line: number;
|
|
19
|
+
text: string;
|
|
20
|
+
} | {
|
|
21
|
+
kind: 'replace';
|
|
22
|
+
line: number;
|
|
23
|
+
expect: string;
|
|
24
|
+
text: string;
|
|
25
|
+
} | {
|
|
26
|
+
kind: 'remove';
|
|
27
|
+
line: number;
|
|
28
|
+
expect: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* What a widget is told about its fence.
|
|
32
|
+
*
|
|
33
|
+
* @public
|
|
34
|
+
*/
|
|
35
|
+
export interface FenceInfo {
|
|
36
|
+
/** The fence's info word, the one the widget was registered for. */
|
|
37
|
+
readonly info: string;
|
|
38
|
+
/** The fence's body: its lines between the fences, less their indentation. */
|
|
39
|
+
readonly body: readonly string[];
|
|
40
|
+
/** The document the fence is in, by its concept, or null outside a workspace. */
|
|
41
|
+
readonly document: string | null;
|
|
42
|
+
/**
|
|
43
|
+
* How many fences with this info word come before this one in the document. It changes when one
|
|
44
|
+
* is added or removed above, so it keys only what lasts a moment.
|
|
45
|
+
*/
|
|
46
|
+
readonly ordinal: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Where a widget is drawn: in the document, in a tab of its own, or filling the EtherPK window.
|
|
50
|
+
*
|
|
51
|
+
* @public
|
|
52
|
+
*/
|
|
53
|
+
export type FencePresentation = 'inline' | 'tab' | 'window';
|
|
54
|
+
/**
|
|
55
|
+
* What a mounted widget can see and ask for.
|
|
56
|
+
*
|
|
57
|
+
* @public
|
|
58
|
+
*/
|
|
59
|
+
export interface FenceContext extends FenceInfo {
|
|
60
|
+
/** Whether the document accepts the widget's changes now: not read-only, not locked. */
|
|
61
|
+
readonly writable: boolean;
|
|
62
|
+
/** Whether the dark theme shows. */
|
|
63
|
+
readonly dark: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Whether anyone can see the widget: false while its tab is behind another, its Pane is collapsed
|
|
66
|
+
* or the browser tab is hidden. A widget holding something costly lets it go while this is false.
|
|
67
|
+
*/
|
|
68
|
+
readonly onScreen: boolean;
|
|
69
|
+
/** Where the widget is drawn. */
|
|
70
|
+
readonly presentation: FencePresentation;
|
|
71
|
+
/**
|
|
72
|
+
* Change the body, as one undoable change: one edit, or several applied together. False when it
|
|
73
|
+
* was refused (the document cannot be written, the fence is gone, or a line no longer says what an
|
|
74
|
+
* edit expects), and then nothing changed. The widget hears the current body in an `update`.
|
|
75
|
+
*/
|
|
76
|
+
edit(change: FenceEdit | readonly FenceEdit[]): boolean;
|
|
77
|
+
/** Show the fence's text in the editor, with the caret on its first body line. */
|
|
78
|
+
editAsText(): void;
|
|
79
|
+
/** Delete the fence, as one undoable change. False when it was refused. */
|
|
80
|
+
remove(): boolean;
|
|
81
|
+
/** The widget's height changed: the editor asks `height` again. */
|
|
82
|
+
resized(): void;
|
|
83
|
+
/**
|
|
84
|
+
* True once, when the widget is to take the keyboard: for the widget of a fence `insertBlock` put
|
|
85
|
+
* in with `focus`, and for a widget drawn in a tab or over the window as it opens. False otherwise.
|
|
86
|
+
*/
|
|
87
|
+
takeFocus(): boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Open the fence in a tab of its own, which draws the same widget with `presentation` `tab`. Only
|
|
90
|
+
* for a fence the registration can `identify`.
|
|
91
|
+
*/
|
|
92
|
+
openInTab(): void;
|
|
93
|
+
/**
|
|
94
|
+
* Draw the widget again over the whole EtherPK window, with `presentation` `window`, and a Return
|
|
95
|
+
* button the Client draws. Only for a fence the registration can `identify`.
|
|
96
|
+
*/
|
|
97
|
+
openWindow(): void;
|
|
98
|
+
/** Close the window this widget fills. Does nothing for a widget drawn elsewhere. */
|
|
99
|
+
closeWindow(): void;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* A widget the extension has drawn, as the Client drives it.
|
|
103
|
+
*
|
|
104
|
+
* @public
|
|
105
|
+
*/
|
|
106
|
+
export interface MountedFence {
|
|
107
|
+
/** The body, the theme, whether it can be written or whether it is on screen changed. */
|
|
108
|
+
update(context: FenceContext): void;
|
|
109
|
+
/** The widget leaves: scrolled far away, its fence deleted, its tab or the editor closed. */
|
|
110
|
+
destroy(): void;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* What an extension registers for one info word.
|
|
114
|
+
*
|
|
115
|
+
* @public
|
|
116
|
+
*/
|
|
117
|
+
export interface FenceWidget {
|
|
118
|
+
/**
|
|
119
|
+
* The widget's height in CSS pixels, before and after it mounts. The editor reserves it so the
|
|
120
|
+
* lines below never move while the widget loads. It is asked again after `resized`. Not asked for
|
|
121
|
+
* a widget in a tab or a window, which fills what it is given.
|
|
122
|
+
*/
|
|
123
|
+
height(info: FenceInfo): number;
|
|
124
|
+
/** Draw the widget into `host`, which the Client owns and places. */
|
|
125
|
+
mount(host: HTMLElement, context: FenceContext): MountedFence;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* A fence as the publisher hands it to the extension: already public content.
|
|
129
|
+
*
|
|
130
|
+
* @public
|
|
131
|
+
*/
|
|
132
|
+
export interface PublishFenceRequest {
|
|
133
|
+
/** The fence's info word. */
|
|
134
|
+
readonly info: string;
|
|
135
|
+
/** The fence's body lines. */
|
|
136
|
+
readonly body: readonly string[];
|
|
137
|
+
/** The document being published, by its concept. */
|
|
138
|
+
readonly document: string;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* How a published site shows a fence: a picture the site keeps, or a link with an optional picture
|
|
142
|
+
* from the web. The publisher writes the HTML, so an extension never writes markup into a site.
|
|
143
|
+
*
|
|
144
|
+
* @public
|
|
145
|
+
*/
|
|
146
|
+
export type PublishedFence = {
|
|
147
|
+
kind: 'picture';
|
|
148
|
+
/** The picture's type. */
|
|
149
|
+
type: 'image/svg+xml' | 'image/png' | 'image/webp';
|
|
150
|
+
/** The picture's bytes, written into the site as a file of its own. */
|
|
151
|
+
bytes: Uint8Array;
|
|
152
|
+
width: number;
|
|
153
|
+
height: number;
|
|
154
|
+
/** What the picture shows, for anyone who cannot see it. */
|
|
155
|
+
alt: string;
|
|
156
|
+
} | {
|
|
157
|
+
kind: 'link';
|
|
158
|
+
/** Where the link goes: an `https` address. */
|
|
159
|
+
href: string;
|
|
160
|
+
/** The link's text. */
|
|
161
|
+
text: string;
|
|
162
|
+
/** A picture from the web shown in the link: an `https` address. */
|
|
163
|
+
image?: {
|
|
164
|
+
src: string;
|
|
165
|
+
alt: string;
|
|
166
|
+
width?: number;
|
|
167
|
+
height?: number;
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* Options for a fence registration.
|
|
172
|
+
*
|
|
173
|
+
* @public
|
|
174
|
+
*/
|
|
175
|
+
export interface FenceOptions {
|
|
176
|
+
/**
|
|
177
|
+
* A short name for what the fence holds, such as "Drawing": a tab's title and the full window's
|
|
178
|
+
* heading start with it.
|
|
179
|
+
*/
|
|
180
|
+
title?: string;
|
|
181
|
+
/**
|
|
182
|
+
* What text pasted as plain text on an empty line or bullet becomes: the body lines of a fence of
|
|
183
|
+
* this kind, or null to leave the paste as it is. A link to a video, say, gives the link as the
|
|
184
|
+
* one line. The text is the clipboard's own, untrimmed. The Client asks each registration in
|
|
185
|
+
* turn, and the first that answers takes the paste. Mod+Z straight after it gives back the
|
|
186
|
+
* text that was pasted.
|
|
187
|
+
*
|
|
188
|
+
* The lines are written inside the fence as they are, except that a line which would end the
|
|
189
|
+
* fence, or start another, is changed so it cannot. A line break inside a line splits it in two.
|
|
190
|
+
*/
|
|
191
|
+
fromPaste?(text: string): readonly string[] | null;
|
|
192
|
+
/**
|
|
193
|
+
* How a published site shows the fence, or null to leave it out. Called only for public content,
|
|
194
|
+
* in a publish run in the browser. Where it is not called (the Headless Client, or the extension
|
|
195
|
+
* off), an Extension Fence is left out and an ordinary fence is published as code.
|
|
196
|
+
*/
|
|
197
|
+
publish?(fence: PublishFenceRequest): PublishedFence | null | Promise<PublishedFence | null>;
|
|
198
|
+
/**
|
|
199
|
+
* The fence's own id, read from its body, or null. A fence with an id can be opened in a tab or a
|
|
200
|
+
* window, where the Client finds it again by the id after every change.
|
|
201
|
+
*/
|
|
202
|
+
identify?(body: readonly string[]): string | null;
|
|
203
|
+
/**
|
|
204
|
+
* True when the widget keeps its own undo: Mod+Z and Mod+Y pressed inside it are its own, rather
|
|
205
|
+
* than the editor's.
|
|
206
|
+
*/
|
|
207
|
+
keepsHistory?: boolean;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* The document editor, as far as an extension may reach it.
|
|
211
|
+
*
|
|
212
|
+
* @public
|
|
213
|
+
*/
|
|
214
|
+
export interface ExtensionEditor {
|
|
215
|
+
/**
|
|
216
|
+
* Draw `widget` over every complete fence whose info word is `info`. The word is the extension's
|
|
217
|
+
* id (an ordinary fence, whose text stays text) or `<id>.<name>` (an Extension Fence, whose body
|
|
218
|
+
* is the extension's data and never searched or shown as text). Throws for any other word, or one
|
|
219
|
+
* that is taken.
|
|
220
|
+
*
|
|
221
|
+
* An extension listed in the Extension Directory may not have an id that is one of the words the
|
|
222
|
+
* Client draws blocks for itself: `map`, `math`, `mermaid` and `etherpk-cipher`.
|
|
223
|
+
*/
|
|
224
|
+
registerFence(info: string, widget: FenceWidget, options?: FenceOptions): () => void;
|
|
225
|
+
/**
|
|
226
|
+
* Put a block at the caret of the editor the person is working in, as a code block or a table
|
|
227
|
+
* goes in: an empty bullet takes it as its content, an empty line is replaced, and after any other
|
|
228
|
+
* line it goes on new lines beneath at the line's content column. `text` is the block's lines
|
|
229
|
+
* without indentation. A block that is a fence leaves the caret outside it, and with `focus` its
|
|
230
|
+
* widget is told to `takeFocus`. False where nothing was put in: no editor, a body that cannot be
|
|
231
|
+
* written, or a caret in the frontmatter, a fence or a table.
|
|
232
|
+
*/
|
|
233
|
+
insertBlock(text: string, options?: {
|
|
234
|
+
focus?: boolean;
|
|
235
|
+
}): boolean;
|
|
236
|
+
}
|
package/dist/editor.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an extension may do in the document editor: draw a widget over its own kind of fenced block,
|
|
3
|
+
* and put a block at the caret.
|
|
4
|
+
*
|
|
5
|
+
* A widget never sees the editor. It is told the fence's body as lines of text and asks for changes
|
|
6
|
+
* to those lines, which the Client writes as ordinary editor changes: undone with the editor's undo,
|
|
7
|
+
* synced and merged like typing, and refused where the document cannot be written.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
package/dist/embeds.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content from another site shown inside the Client (ADR 0137). The Client allows frames from a short
|
|
3
|
+
* list of hosts only, and builds every frame itself, with the referrer policy, sandbox and
|
|
4
|
+
* permissions those hosts need, so an extension never writes an `<iframe>`.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* A frame to build.
|
|
8
|
+
*
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
export interface FrameOptions {
|
|
12
|
+
/**
|
|
13
|
+
* The address to show: an `https` address on a host the Client allows, which today are
|
|
14
|
+
* `https://www.youtube-nocookie.com` and `https://player.vimeo.com`.
|
|
15
|
+
*/
|
|
16
|
+
src: string;
|
|
17
|
+
/** What the frame shows, read out to anyone using a screen reader. Plain text, never markup. */
|
|
18
|
+
title: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Frames.
|
|
22
|
+
*
|
|
23
|
+
* @public
|
|
24
|
+
*/
|
|
25
|
+
export interface ExtensionEmbeds {
|
|
26
|
+
/** The hosts a frame may show, as origins such as `https://player.vimeo.com`. */
|
|
27
|
+
readonly frameHosts: readonly string[];
|
|
28
|
+
/**
|
|
29
|
+
* A frame the Client built for `src`, to put in the extension's own element. It loads once it is
|
|
30
|
+
* in the page. Throws for an address on a host the Client does not allow.
|
|
31
|
+
*/
|
|
32
|
+
frame(options: FrameOptions): HTMLIFrameElement;
|
|
33
|
+
}
|
package/dist/embeds.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content from another site shown inside the Client (ADR 0137). The Client allows frames from a short
|
|
3
|
+
* list of hosts only, and builds every frame itself, with the referrer policy, sandbox and
|
|
4
|
+
* permissions those hosts need, so an extension never writes an `<iframe>`.
|
|
5
|
+
*/
|
|
6
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -7,15 +7,20 @@
|
|
|
7
7
|
* manifest and of the contract a View follows, and {@link readExtensionPackage}, which checks a
|
|
8
8
|
* manifest the way the Client does. It holds none of the Client's implementation.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* A declaration, or a member of one, is Stable (`@public`) or Proposed (`@beta`). A Stable one never
|
|
11
|
+
* breaks: it is deprecated and kept. A Proposed one may change in any release, and only the
|
|
12
|
+
* extensions that come with EtherPK may use it. An extension from the Extension Directory uses the
|
|
13
|
+
* Stable part alone, which `etc/etherpk-extension-api.public.api.md` lists.
|
|
12
14
|
*
|
|
13
15
|
* @packageDocumentation
|
|
14
16
|
*/
|
|
15
17
|
export type { CommandHandler, ExtensionCommandMenu, ExtensionCommands, ExtensionConcepts, ExtensionContext, ExtensionContextMenu, ExtensionContributions, ExtensionGraphSidebar, ExtensionIcons, ExtensionKeybindings, ExtensionLayout, ExtensionModule, ExtensionSettings, ExtensionStorage, ExtensionTheme, ExtensionViews, IconMarkup, Keybinding, } from './context';
|
|
18
|
+
export type { ExtensionDialogs, PromptOptions } from './dialogs';
|
|
19
|
+
export type { ExtensionEditor, FenceContext, FenceEdit, FenceInfo, FenceOptions, FencePresentation, FenceWidget, MountedFence, PublishedFence, PublishFenceRequest, } from './editor';
|
|
20
|
+
export type { ExtensionEmbeds, FrameOptions } from './embeds';
|
|
16
21
|
export type { ExtensionEventName, ExtensionEventPayloads, ExtensionEvents } from './events';
|
|
17
22
|
export type { ConceptCandidate, ConceptKind, ExtensionIndex, IndexUpdate, LinkGraph, LinkGraphConcept, LinkGraphLink } from './graph-index';
|
|
18
|
-
export type { ExtensionManifest, ExtensionPackage, ReadExtensionPackageResult, SettingDeclaration, ViewDeclaration, ViewRegion, } from './manifest';
|
|
23
|
+
export type { ExtensionIsolation, ExtensionManifest, ExtensionPackage, ReadExtensionPackageResult, SettingDeclaration, ViewDeclaration, ViewRegion, } from './manifest';
|
|
19
24
|
export { readExtensionPackage } from './manifest';
|
|
20
25
|
export type { CommandMenuContext, CommandMenuItem, ContextMenuItem, DocumentMenuTarget, ExtensionMenuTarget, GraphSidebarButton, MenuTarget, TabMenuTarget, WikilinkMenuTarget, } from './menus';
|
|
21
26
|
export type { MountedView, ViewContribution, ViewMountProps, ViewRef, ViewVisibility } from './views';
|
package/dist/index.js
CHANGED
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
* manifest and of the contract a View follows, and {@link readExtensionPackage}, which checks a
|
|
8
8
|
* manifest the way the Client does. It holds none of the Client's implementation.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* A declaration, or a member of one, is Stable (`@public`) or Proposed (`@beta`). A Stable one never
|
|
11
|
+
* breaks: it is deprecated and kept. A Proposed one may change in any release, and only the
|
|
12
|
+
* extensions that come with EtherPK may use it. An extension from the Extension Directory uses the
|
|
13
|
+
* Stable part alone, which `etc/etherpk-extension-api.public.api.md` lists.
|
|
12
14
|
*
|
|
13
15
|
* @packageDocumentation
|
|
14
16
|
*/
|
package/dist/manifest.d.ts
CHANGED
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
/**
|
|
7
7
|
* Where a View kind opens when nothing says otherwise.
|
|
8
8
|
*
|
|
9
|
-
* @
|
|
9
|
+
* @public
|
|
10
10
|
*/
|
|
11
11
|
export type ViewRegion = 'main' | 'left-sidebar' | 'right-sidebar';
|
|
12
12
|
/**
|
|
13
13
|
* One View kind an extension adds, declared so the Client can show its tabs, titles and loading
|
|
14
14
|
* state before any of the extension's code has run.
|
|
15
15
|
*
|
|
16
|
-
* @
|
|
16
|
+
* @public
|
|
17
17
|
*/
|
|
18
18
|
export interface ViewDeclaration {
|
|
19
19
|
/** The kind, `<extension id>.<name>`: the key every saved Layout stores it under. */
|
|
@@ -35,15 +35,18 @@ export interface ViewDeclaration {
|
|
|
35
35
|
/**
|
|
36
36
|
* The View lives in a Sidebar from the moment a graph opens, beside the Client's own residents,
|
|
37
37
|
* and the Sidebar's toggle brings it back if it was closed. `desktopOnly` keeps it off a phone.
|
|
38
|
+
*
|
|
39
|
+
* @beta
|
|
38
40
|
*/
|
|
39
41
|
resident?: {
|
|
40
42
|
side: 'left' | 'right';
|
|
41
43
|
desktopOnly?: boolean;
|
|
42
44
|
};
|
|
43
45
|
/**
|
|
44
|
-
* A short address of the View's own
|
|
45
|
-
*
|
|
46
|
-
*
|
|
46
|
+
* A short address of the View's own, followed by the target when `target` is `concept`: for an
|
|
47
|
+
* extension from the Extension Directory `/g/<graph>/x/<extension id>/<segment>`, and for one that
|
|
48
|
+
* comes with EtherPK `/g/<graph>/<segment>`. A View kind whose address takes a concept follows
|
|
49
|
+
* that concept through a rename.
|
|
47
50
|
*/
|
|
48
51
|
address?: {
|
|
49
52
|
segment: string;
|
|
@@ -57,7 +60,7 @@ export interface ViewDeclaration {
|
|
|
57
60
|
* a graph, so it follows them to their other devices. The extension reads it through
|
|
58
61
|
* `context.settings`.
|
|
59
62
|
*
|
|
60
|
-
* @
|
|
63
|
+
* @public
|
|
61
64
|
*/
|
|
62
65
|
export interface SettingDeclaration {
|
|
63
66
|
/** Unique within the extension: lowercase letters, digits and hyphens, such as `mapbox-token`. */
|
|
@@ -81,10 +84,20 @@ export interface SettingDeclaration {
|
|
|
81
84
|
url: string;
|
|
82
85
|
};
|
|
83
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Where a loaded extension's Views and fence widgets mount. `shadow`, the default, puts each in a
|
|
89
|
+
* shadow root holding the extension's stylesheets, so neither side's CSS reaches the other. `page`
|
|
90
|
+
* mounts them in the page and has the document adopt the stylesheets, for a library that cannot work
|
|
91
|
+
* in a shadow root. The Client then checks every rule is scoped under a class whose name starts with
|
|
92
|
+
* the extension's id, and refuses to start the extension otherwise.
|
|
93
|
+
*
|
|
94
|
+
* @public
|
|
95
|
+
*/
|
|
96
|
+
export type ExtensionIsolation = 'shadow' | 'page';
|
|
84
97
|
/**
|
|
85
98
|
* The `etherpk` field of an extension's package.json.
|
|
86
99
|
*
|
|
87
|
-
* @
|
|
100
|
+
* @public
|
|
88
101
|
*/
|
|
89
102
|
export interface ExtensionManifest {
|
|
90
103
|
/**
|
|
@@ -101,10 +114,13 @@ export interface ExtensionManifest {
|
|
|
101
114
|
/** The module that exports `activate`, relative to the package's root. */
|
|
102
115
|
main: string;
|
|
103
116
|
/**
|
|
104
|
-
* Stylesheets adopted into the shadow root of each of the extension's Views
|
|
105
|
-
* package's root. Only a loaded extension
|
|
117
|
+
* Stylesheets adopted into the shadow root of each of the extension's Views and fence widgets, or
|
|
118
|
+
* by the document with `isolation` `page`, relative to the package's root. Only a loaded extension
|
|
119
|
+
* has them: a compiled-in one shares the Client's.
|
|
106
120
|
*/
|
|
107
121
|
styles?: string[];
|
|
122
|
+
/** Where a loaded extension's Views and fence widgets mount. `shadow` when left out. */
|
|
123
|
+
isolation?: ExtensionIsolation;
|
|
108
124
|
/** Icons by name: the inner markup of a 16 by 16 SVG drawn with `currentColor` strokes. */
|
|
109
125
|
icons?: Record<string, string>;
|
|
110
126
|
/** The View kinds the extension adds. */
|
package/dist/manifest.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
const NAME = /^[a-z][a-z0-9-]*$/;
|
|
7
7
|
const VERSION = /^\d+\.\d+\.\d+$/;
|
|
8
8
|
const REGIONS = ['main', 'left-sidebar', 'right-sidebar'];
|
|
9
|
+
/** The longest an icon's markup may be: a 16 by 16 picture needs a few hundred characters. */
|
|
10
|
+
const ICON_MARKUP_LIMIT = 4096;
|
|
9
11
|
/**
|
|
10
12
|
* Read an extension's package.json, already parsed, and check its manifest.
|
|
11
13
|
*
|
|
@@ -44,6 +46,9 @@ export function readExtensionPackage(packageJson) {
|
|
|
44
46
|
if (field.styles !== undefined && (!Array.isArray(field.styles) || !field.styles.every((path) => typeof path === 'string' && relativePath(path)))) {
|
|
45
47
|
errors.push('"etherpk.styles" must be a list of paths inside the package.');
|
|
46
48
|
}
|
|
49
|
+
if (field.isolation !== undefined && field.isolation !== 'shadow' && field.isolation !== 'page') {
|
|
50
|
+
errors.push('"etherpk.isolation" must be "shadow" or "page".');
|
|
51
|
+
}
|
|
47
52
|
if (field.icons !== undefined) {
|
|
48
53
|
if (!isRecord(field.icons))
|
|
49
54
|
errors.push('"etherpk.icons" must map names to SVG markup.');
|
|
@@ -53,6 +58,8 @@ export function readExtensionPackage(packageJson) {
|
|
|
53
58
|
errors.push(`Icon "${iconName}" must be lowercase letters, digits and hyphens.`);
|
|
54
59
|
if (typeof markup !== 'string' || markup.trim() === '')
|
|
55
60
|
errors.push(`Icon "${iconName}" has no markup.`);
|
|
61
|
+
else if (markup.length > ICON_MARKUP_LIMIT)
|
|
62
|
+
errors.push(`Icon "${iconName}" is longer than ${ICON_MARKUP_LIMIT} characters.`);
|
|
56
63
|
}
|
|
57
64
|
}
|
|
58
65
|
const views = field.views === undefined ? [] : field.views;
|
|
@@ -164,9 +171,12 @@ function checkView(view, index, id, kinds, segments, errors) {
|
|
|
164
171
|
errors.push(`${where}.address.target may only be "concept".`);
|
|
165
172
|
}
|
|
166
173
|
}
|
|
167
|
-
/**
|
|
174
|
+
/**
|
|
175
|
+
* A path that stays inside the package: relative, never climbing out of it, and never an address or
|
|
176
|
+
* a drive (`https:`, `data:`, `C:`), which a URL would read as somewhere else.
|
|
177
|
+
*/
|
|
168
178
|
function relativePath(path) {
|
|
169
|
-
return path !== '' && !path.startsWith('/') && !path.includes('\\') && !path.split('/').includes('..');
|
|
179
|
+
return path !== '' && !path.startsWith('/') && !path.includes('\\') && !/^[a-z][a-z0-9+.-]*:/i.test(path) && !path.split('/').includes('..');
|
|
170
180
|
}
|
|
171
181
|
function isRecord(value) {
|
|
172
182
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
package/dist/menus.d.ts
CHANGED
|
@@ -87,12 +87,16 @@ export interface ContextMenuItem {
|
|
|
87
87
|
/**
|
|
88
88
|
* What a Command Menu row's `when` may read about the editor it was opened in.
|
|
89
89
|
*
|
|
90
|
-
* @
|
|
90
|
+
* @public
|
|
91
91
|
*/
|
|
92
92
|
export interface CommandMenuContext {
|
|
93
93
|
/** The caret is in a table. */
|
|
94
94
|
inTable: boolean;
|
|
95
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* A table may be inserted at the caret.
|
|
97
|
+
*
|
|
98
|
+
* @beta
|
|
99
|
+
*/
|
|
96
100
|
tableInsertable: boolean;
|
|
97
101
|
/** The document's body may be written: false in a locked Protected Document. */
|
|
98
102
|
bodyWritable: boolean;
|
|
@@ -106,13 +110,15 @@ export interface CommandMenuContext {
|
|
|
106
110
|
* caret line's links, the links above it in the outline, the document's own concept, then the
|
|
107
111
|
* scopes in its name. Names are as written, an alias as the alias. Worked out only when a row
|
|
108
112
|
* reads it. Absent reads as none.
|
|
113
|
+
*
|
|
114
|
+
* @beta
|
|
109
115
|
*/
|
|
110
116
|
readonly conceptsAtCaret?: readonly string[];
|
|
111
117
|
}
|
|
112
118
|
/**
|
|
113
119
|
* One row of the Command Menu, the menu `/` opens in the editor.
|
|
114
120
|
*
|
|
115
|
-
* @
|
|
121
|
+
* @public
|
|
116
122
|
*/
|
|
117
123
|
export interface CommandMenuItem {
|
|
118
124
|
/** Under the extension's id. */
|
|
@@ -136,24 +142,41 @@ export interface CommandMenuItem {
|
|
|
136
142
|
when?: (context: CommandMenuContext) => boolean;
|
|
137
143
|
/** The Command the row runs. */
|
|
138
144
|
command: string;
|
|
139
|
-
/**
|
|
145
|
+
/**
|
|
146
|
+
* What the Command is given.
|
|
147
|
+
*
|
|
148
|
+
* @beta
|
|
149
|
+
*/
|
|
140
150
|
args?: unknown;
|
|
141
151
|
}
|
|
142
152
|
/**
|
|
143
|
-
* A button in the Graph Sidebar,
|
|
144
|
-
* the whole graph, such as the Graph Map
|
|
153
|
+
* A button in the Graph Sidebar's group of action buttons, after Search and today's date: a way in
|
|
154
|
+
* to what the extension shows for the whole graph, such as the Graph Map. Pressing it runs
|
|
155
|
+
* `command`.
|
|
145
156
|
*
|
|
146
157
|
* @beta
|
|
147
158
|
*/
|
|
148
159
|
export interface GraphSidebarButton {
|
|
149
160
|
/** Under the extension's id. */
|
|
150
161
|
id: string;
|
|
151
|
-
/** The button's text. */
|
|
162
|
+
/** The button's text: short, because the Sidebar fits two buttons side by side. */
|
|
152
163
|
title: string;
|
|
164
|
+
/**
|
|
165
|
+
* What pressing the button does, shown as its help text: "Open a map of every place and route
|
|
166
|
+
* in this graph". The Client adds the key chords bound to `command`, so it names none. Without
|
|
167
|
+
* one, the help text is the title.
|
|
168
|
+
*/
|
|
169
|
+
description?: string;
|
|
153
170
|
/** An icon drawn before the title: the extension's own or one of the Client's. */
|
|
154
171
|
icon?: string;
|
|
155
172
|
/** Lower lists earlier. A button without one lists after every button that has one. */
|
|
156
173
|
order?: number;
|
|
174
|
+
/**
|
|
175
|
+
* Whether the button shows here, asked each time the Sidebar draws, which it does afresh when
|
|
176
|
+
* the layout changes between desktop and phone. Absent means everywhere. A `when` that throws
|
|
177
|
+
* hides the button.
|
|
178
|
+
*/
|
|
179
|
+
when?: () => boolean;
|
|
157
180
|
/** The Command the button runs. */
|
|
158
181
|
command: string;
|
|
159
182
|
/** What the Command is given. */
|
package/dist/views.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* Which View: its kind and what it is about. A View with no subject has an empty target.
|
|
7
7
|
*
|
|
8
|
-
* @
|
|
8
|
+
* @public
|
|
9
9
|
*/
|
|
10
10
|
export interface ViewRef {
|
|
11
11
|
/** The View kind, `<extension id>.<name>` for an extension's. */
|
|
@@ -18,7 +18,7 @@ export interface ViewRef {
|
|
|
18
18
|
* The desktop keeps every tab mounted, so a View that does work while shown (drawing, reading the
|
|
19
19
|
* index) stops it while this is false.
|
|
20
20
|
*
|
|
21
|
-
* @
|
|
21
|
+
* @public
|
|
22
22
|
*/
|
|
23
23
|
export interface ViewVisibility {
|
|
24
24
|
/** True while the View can be seen. */
|
|
@@ -29,7 +29,7 @@ export interface ViewVisibility {
|
|
|
29
29
|
/**
|
|
30
30
|
* What a View is mounted with.
|
|
31
31
|
*
|
|
32
|
-
* @
|
|
32
|
+
* @public
|
|
33
33
|
*/
|
|
34
34
|
export interface ViewMountProps {
|
|
35
35
|
/** The View being mounted. */
|
|
@@ -45,7 +45,7 @@ export interface ViewMountProps {
|
|
|
45
45
|
/**
|
|
46
46
|
* A View the extension has drawn, as the Client drives it.
|
|
47
47
|
*
|
|
48
|
-
* @
|
|
48
|
+
* @public
|
|
49
49
|
*/
|
|
50
50
|
export interface MountedView {
|
|
51
51
|
/** The same View with new props: a rename re-keyed its target, or it moved to another Pane. */
|
|
@@ -56,7 +56,7 @@ export interface MountedView {
|
|
|
56
56
|
/**
|
|
57
57
|
* What an extension registers for a View kind its manifest declares.
|
|
58
58
|
*
|
|
59
|
-
* @
|
|
59
|
+
* @public
|
|
60
60
|
*/
|
|
61
61
|
export interface ViewContribution {
|
|
62
62
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appsoftwareltd/etherpk-extension-api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "The API an EtherPK extension is written against: the context it is started with, its manifest, and the contract its Views follow.",
|
|
6
6
|
"type": "module",
|