@react-x11/components 0.7.1 → 0.9.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 +295 -51
- package/dist/charts/node.d.ts +1 -1
- package/dist/charts/node.d.ts.map +1 -1
- package/dist/charts/node.js +6 -2
- package/dist/charts/node.js.map +1 -1
- package/dist/color-picker/ColorPicker.d.ts.map +1 -1
- package/dist/color-picker/ColorPicker.js +43 -33
- package/dist/color-picker/ColorPicker.js.map +1 -1
- package/dist/embed/client.d.ts +48 -3
- package/dist/embed/client.d.ts.map +1 -1
- package/dist/embed/client.js +79 -5
- package/dist/embed/client.js.map +1 -1
- package/dist/embed/index.d.ts +1 -1
- package/dist/embed/index.d.ts.map +1 -1
- package/dist/embed/index.js +1 -1
- package/dist/embed/index.js.map +1 -1
- package/dist/html/node.d.ts +3 -1
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +7 -5
- package/dist/html/node.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/{markdown → internal/markdown}/ast.d.ts +12 -0
- package/dist/internal/markdown/ast.d.ts.map +1 -0
- package/dist/internal/markdown/ast.js +22 -0
- package/dist/internal/markdown/ast.js.map +1 -0
- package/dist/internal/markdown/parse.d.ts.map +1 -0
- package/dist/{markdown → internal/markdown}/parse.js +112 -22
- package/dist/internal/markdown/parse.js.map +1 -0
- package/dist/internal/markdown/stringify.d.ts +4 -0
- package/dist/internal/markdown/stringify.d.ts.map +1 -0
- package/dist/internal/markdown/stringify.js +684 -0
- package/dist/internal/markdown/stringify.js.map +1 -0
- package/dist/internal/markdown/tags.d.ts.map +1 -0
- package/dist/internal/markdown/tags.js.map +1 -0
- package/dist/internal/scroll.d.ts +3 -2
- package/dist/internal/scroll.d.ts.map +1 -1
- package/dist/internal/scroll.js +33 -15
- package/dist/internal/scroll.js.map +1 -1
- package/dist/internal/units.d.ts +2 -0
- package/dist/internal/units.d.ts.map +1 -0
- package/dist/internal/units.js +35 -0
- package/dist/internal/units.js.map +1 -0
- package/dist/internal/window.d.ts.map +1 -1
- package/dist/internal/window.js +4 -2
- package/dist/internal/window.js.map +1 -1
- package/dist/maps/anchors.d.ts +98 -0
- package/dist/maps/anchors.d.ts.map +1 -0
- package/dist/maps/anchors.js +531 -0
- package/dist/maps/anchors.js.map +1 -0
- package/dist/maps/controller.d.ts +283 -0
- package/dist/maps/controller.d.ts.map +1 -0
- package/dist/maps/controller.js +743 -0
- package/dist/maps/controller.js.map +1 -0
- package/dist/maps/gl/buckets.d.ts +95 -0
- package/dist/maps/gl/buckets.d.ts.map +1 -0
- package/dist/maps/gl/buckets.js +362 -0
- package/dist/maps/gl/buckets.js.map +1 -0
- package/dist/maps/gl/build-worker.d.ts +13 -0
- package/dist/maps/gl/build-worker.d.ts.map +1 -0
- package/dist/maps/gl/build-worker.js +44 -0
- package/dist/maps/gl/build-worker.js.map +1 -0
- package/dist/maps/gl/color.d.ts +8 -0
- package/dist/maps/gl/color.d.ts.map +1 -0
- package/dist/maps/gl/color.js +57 -0
- package/dist/maps/gl/color.js.map +1 -0
- package/dist/maps/gl/cover.d.ts +51 -0
- package/dist/maps/gl/cover.d.ts.map +1 -0
- package/dist/maps/gl/cover.js +124 -0
- package/dist/maps/gl/cover.js.map +1 -0
- package/dist/maps/gl/index.d.ts +3 -0
- package/dist/maps/gl/index.d.ts.map +1 -0
- package/dist/maps/gl/index.js +20 -0
- package/dist/maps/gl/index.js.map +1 -0
- package/dist/maps/gl/markers.d.ts +28 -0
- package/dist/maps/gl/markers.d.ts.map +1 -0
- package/dist/maps/gl/markers.js +60 -0
- package/dist/maps/gl/markers.js.map +1 -0
- package/dist/maps/gl/overlays.d.ts +68 -0
- package/dist/maps/gl/overlays.d.ts.map +1 -0
- package/dist/maps/gl/overlays.js +300 -0
- package/dist/maps/gl/overlays.js.map +1 -0
- package/dist/maps/gl/pane.d.ts +12 -0
- package/dist/maps/gl/pane.d.ts.map +1 -0
- package/dist/maps/gl/pane.js +37 -0
- package/dist/maps/gl/pane.js.map +1 -0
- package/dist/maps/gl/placement.d.ts +111 -0
- package/dist/maps/gl/placement.d.ts.map +1 -0
- package/dist/maps/gl/placement.js +567 -0
- package/dist/maps/gl/placement.js.map +1 -0
- package/dist/maps/gl/renderer.d.ts +352 -0
- package/dist/maps/gl/renderer.d.ts.map +1 -0
- package/dist/maps/gl/renderer.js +1127 -0
- package/dist/maps/gl/renderer.js.map +1 -0
- package/dist/maps/gl/shaders.d.ts +120 -0
- package/dist/maps/gl/shaders.d.ts.map +1 -0
- package/dist/maps/gl/shaders.js +451 -0
- package/dist/maps/gl/shaders.js.map +1 -0
- package/dist/maps/gl/store.d.ts +115 -0
- package/dist/maps/gl/store.d.ts.map +1 -0
- package/dist/maps/gl/store.js +469 -0
- package/dist/maps/gl/store.js.map +1 -0
- package/dist/maps/gl/text.d.ts +154 -0
- package/dist/maps/gl/text.d.ts.map +1 -0
- package/dist/maps/gl/text.js +382 -0
- package/dist/maps/gl/text.js.map +1 -0
- package/dist/maps/gl/view.d.ts +54 -0
- package/dist/maps/gl/view.d.ts.map +1 -0
- package/dist/maps/gl/view.js +974 -0
- package/dist/maps/gl/view.js.map +1 -0
- package/dist/maps/index.d.ts +1 -1
- package/dist/maps/index.d.ts.map +1 -1
- package/dist/maps/index.js +216 -52
- package/dist/maps/index.js.map +1 -1
- package/dist/maps/labels.d.ts +37 -16
- package/dist/maps/labels.d.ts.map +1 -1
- package/dist/maps/labels.js +188 -119
- package/dist/maps/labels.js.map +1 -1
- package/dist/maps/node.d.ts +162 -56
- package/dist/maps/node.d.ts.map +1 -1
- package/dist/maps/node.js +607 -515
- package/dist/maps/node.js.map +1 -1
- package/dist/maps/overlay.d.ts +72 -9
- package/dist/maps/overlay.d.ts.map +1 -1
- package/dist/maps/overlay.js +108 -28
- package/dist/maps/overlay.js.map +1 -1
- package/dist/maps/paint.d.ts +3 -0
- package/dist/maps/paint.d.ts.map +1 -1
- package/dist/maps/paint.js.map +1 -1
- package/dist/maps/proj.d.ts +14 -0
- package/dist/maps/proj.d.ts.map +1 -1
- package/dist/maps/proj.js +28 -0
- package/dist/maps/proj.js.map +1 -1
- package/dist/maps/renderer.d.ts +69 -0
- package/dist/maps/renderer.d.ts.map +1 -0
- package/dist/maps/renderer.js +66 -0
- package/dist/maps/renderer.js.map +1 -0
- package/dist/maps/sources.d.ts +25 -3
- package/dist/maps/sources.d.ts.map +1 -1
- package/dist/maps/sources.js +17 -0
- package/dist/maps/sources.js.map +1 -1
- package/dist/maps/theme.d.ts +19 -0
- package/dist/maps/theme.d.ts.map +1 -0
- package/dist/maps/theme.js +44 -0
- package/dist/maps/theme.js.map +1 -0
- package/dist/maps/tiles.d.ts +117 -17
- package/dist/maps/tiles.d.ts.map +1 -1
- package/dist/maps/tiles.js +163 -30
- package/dist/maps/tiles.js.map +1 -1
- package/dist/maps/types.d.ts +188 -20
- package/dist/maps/types.d.ts.map +1 -1
- package/dist/markdown/expressions.d.ts +1 -1
- package/dist/markdown/expressions.d.ts.map +1 -1
- package/dist/markdown/expressions.js.map +1 -1
- package/dist/markdown/index.d.ts +7 -6
- package/dist/markdown/index.d.ts.map +1 -1
- package/dist/markdown/index.js +9 -7
- package/dist/markdown/index.js.map +1 -1
- package/dist/markdown/spans.d.ts +1 -1
- package/dist/markdown/spans.d.ts.map +1 -1
- package/dist/media-player/index.d.ts +11 -3
- package/dist/media-player/index.d.ts.map +1 -1
- package/dist/media-player/index.js +13 -2
- package/dist/media-player/index.js.map +1 -1
- package/dist/rich-text-editor/clipboard.d.ts +23 -0
- package/dist/rich-text-editor/clipboard.d.ts.map +1 -0
- package/dist/rich-text-editor/clipboard.js +307 -0
- package/dist/rich-text-editor/clipboard.js.map +1 -0
- package/dist/rich-text-editor/collab.d.ts +32 -0
- package/dist/rich-text-editor/collab.d.ts.map +1 -0
- package/dist/rich-text-editor/collab.js +55 -0
- package/dist/rich-text-editor/collab.js.map +1 -0
- package/dist/rich-text-editor/commands.d.ts +50 -0
- package/dist/rich-text-editor/commands.d.ts.map +1 -0
- package/dist/rich-text-editor/commands.js +376 -0
- package/dist/rich-text-editor/commands.js.map +1 -0
- package/dist/rich-text-editor/drag.d.ts +68 -0
- package/dist/rich-text-editor/drag.d.ts.map +1 -0
- package/dist/rich-text-editor/drag.js +148 -0
- package/dist/rich-text-editor/drag.js.map +1 -0
- package/dist/rich-text-editor/html.d.ts +145 -0
- package/dist/rich-text-editor/html.d.ts.map +1 -0
- package/dist/rich-text-editor/html.js +516 -0
- package/dist/rich-text-editor/html.js.map +1 -0
- package/dist/rich-text-editor/index.d.ts +263 -0
- package/dist/rich-text-editor/index.d.ts.map +1 -0
- package/dist/rich-text-editor/index.js +822 -0
- package/dist/rich-text-editor/index.js.map +1 -0
- package/dist/rich-text-editor/inline.d.ts +94 -0
- package/dist/rich-text-editor/inline.d.ts.map +1 -0
- package/dist/rich-text-editor/inline.js +252 -0
- package/dist/rich-text-editor/inline.js.map +1 -0
- package/dist/rich-text-editor/keymap.d.ts +71 -0
- package/dist/rich-text-editor/keymap.d.ts.map +1 -0
- package/dist/rich-text-editor/keymap.js +393 -0
- package/dist/rich-text-editor/keymap.js.map +1 -0
- package/dist/rich-text-editor/keys.d.ts +29 -0
- package/dist/rich-text-editor/keys.d.ts.map +1 -0
- package/dist/rich-text-editor/keys.js +93 -0
- package/dist/rich-text-editor/keys.js.map +1 -0
- package/dist/rich-text-editor/look.d.ts +71 -0
- package/dist/rich-text-editor/look.d.ts.map +1 -0
- package/dist/rich-text-editor/look.js +342 -0
- package/dist/rich-text-editor/look.js.map +1 -0
- package/dist/rich-text-editor/markdown.d.ts +18 -0
- package/dist/rich-text-editor/markdown.d.ts.map +1 -0
- package/dist/rich-text-editor/markdown.js +496 -0
- package/dist/rich-text-editor/markdown.js.map +1 -0
- package/dist/rich-text-editor/nodes.d.ts +135 -0
- package/dist/rich-text-editor/nodes.d.ts.map +1 -0
- package/dist/rich-text-editor/nodes.js +397 -0
- package/dist/rich-text-editor/nodes.js.map +1 -0
- package/dist/rich-text-editor/render.d.ts +68 -0
- package/dist/rich-text-editor/render.d.ts.map +1 -0
- package/dist/rich-text-editor/render.js +500 -0
- package/dist/rich-text-editor/render.js.map +1 -0
- package/dist/rich-text-editor/schema.d.ts +16 -0
- package/dist/rich-text-editor/schema.d.ts.map +1 -0
- package/dist/rich-text-editor/schema.js +386 -0
- package/dist/rich-text-editor/schema.js.map +1 -0
- package/dist/rich-text-editor/store.d.ts +11 -0
- package/dist/rich-text-editor/store.d.ts.map +1 -0
- package/dist/rich-text-editor/store.js +43 -0
- package/dist/rich-text-editor/store.js.map +1 -0
- package/dist/rich-text-editor/suggest.d.ts +127 -0
- package/dist/rich-text-editor/suggest.d.ts.map +1 -0
- package/dist/rich-text-editor/suggest.js +0 -0
- package/dist/rich-text-editor/suggest.js.map +1 -0
- package/dist/rich-text-editor/tables.d.ts +41 -0
- package/dist/rich-text-editor/tables.d.ts.map +1 -0
- package/dist/rich-text-editor/tables.js +204 -0
- package/dist/rich-text-editor/tables.js.map +1 -0
- package/dist/rich-text-editor/toolbar.d.ts +58 -0
- package/dist/rich-text-editor/toolbar.d.ts.map +1 -0
- package/dist/rich-text-editor/toolbar.js +548 -0
- package/dist/rich-text-editor/toolbar.js.map +1 -0
- package/dist/rich-text-editor/view.d.ts +368 -0
- package/dist/rich-text-editor/view.d.ts.map +1 -0
- package/dist/rich-text-editor/view.js +1968 -0
- package/dist/rich-text-editor/view.js.map +1 -0
- package/dist/rich-text-editor/virtual.d.ts +54 -0
- package/dist/rich-text-editor/virtual.d.ts.map +1 -0
- package/dist/rich-text-editor/virtual.js +262 -0
- package/dist/rich-text-editor/virtual.js.map +1 -0
- package/dist/richtext/node.d.ts +25 -2
- package/dist/richtext/node.d.ts.map +1 -1
- package/dist/richtext/node.js +46 -24
- package/dist/richtext/node.js.map +1 -1
- package/dist/table/index.d.ts.map +1 -1
- package/dist/table/index.js +14 -5
- package/dist/table/index.js.map +1 -1
- package/dist/terminal/backends.d.ts +4 -1
- package/dist/terminal/backends.d.ts.map +1 -1
- package/dist/terminal/backends.js.map +1 -1
- package/dist/terminal/index.d.ts +18 -7
- package/dist/terminal/index.d.ts.map +1 -1
- package/dist/terminal/index.js +28 -11
- package/dist/terminal/index.js.map +1 -1
- package/dist/terminal/vt/node.d.ts +56 -3
- package/dist/terminal/vt/node.d.ts.map +1 -1
- package/dist/terminal/vt/node.js +106 -26
- package/dist/terminal/vt/node.js.map +1 -1
- package/dist/terminal/vt/renderer.d.ts +4 -0
- package/dist/terminal/vt/renderer.d.ts.map +1 -1
- package/dist/terminal/vt/renderer.js +14 -5
- package/dist/terminal/vt/renderer.js.map +1 -1
- package/dist/tree/index.d.ts.map +1 -1
- package/dist/tree/index.js +6 -4
- package/dist/tree/index.js.map +1 -1
- package/package.json +23 -4
- package/src/charts/node.ts +6 -2
- package/src/color-picker/ColorPicker.ts +107 -81
- package/src/embed/client.ts +102 -8
- package/src/embed/index.ts +2 -0
- package/src/html/node.ts +7 -5
- package/src/index.ts +2 -0
- package/src/{markdown → internal/markdown}/ast.ts +24 -7
- package/src/{markdown → internal/markdown}/parse.ts +134 -24
- package/src/internal/markdown/stringify.ts +772 -0
- package/src/internal/scroll.ts +39 -18
- package/src/internal/units.ts +34 -0
- package/src/internal/window.ts +4 -2
- package/src/maps/anchors.ts +607 -0
- package/src/maps/controller.ts +999 -0
- package/src/maps/gl/buckets.ts +457 -0
- package/src/maps/gl/build-worker.ts +55 -0
- package/src/maps/gl/color.ts +62 -0
- package/src/maps/gl/cover.ts +170 -0
- package/src/maps/gl/index.ts +23 -0
- package/src/maps/gl/markers.ts +83 -0
- package/src/maps/gl/overlays.ts +379 -0
- package/src/maps/gl/pane.ts +42 -0
- package/src/maps/gl/placement.ts +816 -0
- package/src/maps/gl/renderer.ts +1576 -0
- package/src/maps/gl/shaders.ts +486 -0
- package/src/maps/gl/store.ts +566 -0
- package/src/maps/gl/text.ts +495 -0
- package/src/maps/gl/view.ts +1224 -0
- package/src/maps/index.ts +270 -81
- package/src/maps/labels.ts +214 -121
- package/src/maps/node.ts +763 -629
- package/src/maps/overlay.ts +148 -43
- package/src/maps/paint.ts +3 -0
- package/src/maps/proj.ts +31 -0
- package/src/maps/renderer.ts +155 -0
- package/src/maps/sources.ts +38 -3
- package/src/maps/theme.ts +51 -0
- package/src/maps/tiles.ts +191 -33
- package/src/maps/types.ts +188 -20
- package/src/markdown/expressions.ts +5 -1
- package/src/markdown/index.ts +14 -11
- package/src/markdown/spans.ts +1 -1
- package/src/media-player/index.ts +18 -4
- package/src/rich-text-editor/clipboard.ts +439 -0
- package/src/rich-text-editor/collab.ts +91 -0
- package/src/rich-text-editor/commands.ts +448 -0
- package/src/rich-text-editor/dom-globals.d.ts +23 -0
- package/src/rich-text-editor/drag.ts +211 -0
- package/src/rich-text-editor/html.ts +619 -0
- package/src/rich-text-editor/index.ts +1331 -0
- package/src/rich-text-editor/inline.ts +345 -0
- package/src/rich-text-editor/keymap.ts +510 -0
- package/src/rich-text-editor/keys.ts +119 -0
- package/src/rich-text-editor/look.ts +427 -0
- package/src/rich-text-editor/markdown.ts +653 -0
- package/src/rich-text-editor/nodes.ts +497 -0
- package/src/rich-text-editor/render.ts +800 -0
- package/src/rich-text-editor/schema.ts +431 -0
- package/src/rich-text-editor/store.ts +39 -0
- package/src/rich-text-editor/suggest.ts +597 -0
- package/src/rich-text-editor/tables.ts +272 -0
- package/src/rich-text-editor/toolbar.ts +724 -0
- package/src/rich-text-editor/view.ts +2287 -0
- package/src/rich-text-editor/virtual.ts +347 -0
- package/src/richtext/node.ts +59 -30
- package/src/table/index.ts +14 -5
- package/src/terminal/backends.ts +4 -1
- package/src/terminal/index.ts +43 -15
- package/src/terminal/vt/node.ts +128 -29
- package/src/terminal/vt/renderer.ts +27 -5
- package/src/tree/index.ts +6 -4
- package/dist/markdown/ast.d.ts.map +0 -1
- package/dist/markdown/ast.js +0 -17
- package/dist/markdown/ast.js.map +0 -1
- package/dist/markdown/parse.d.ts.map +0 -1
- package/dist/markdown/parse.js.map +0 -1
- package/dist/markdown/tags.d.ts.map +0 -1
- package/dist/markdown/tags.js.map +0 -1
- /package/dist/{markdown → internal/markdown}/parse.d.ts +0 -0
- /package/dist/{markdown → internal/markdown}/tags.d.ts +0 -0
- /package/dist/{markdown → internal/markdown}/tags.js +0 -0
- /package/src/{markdown → internal/markdown}/tags.ts +0 -0
package/src/embed/client.ts
CHANGED
|
@@ -12,8 +12,12 @@
|
|
|
12
12
|
// `onReady` — because core's `<foreign>` already owns the protocol
|
|
13
13
|
// (react-x11#269, ntk#246): the reparent, the save set, `_XEMBED_INFO`, the
|
|
14
14
|
// synthetic ConfigureNotify, and handing the client back on unmount without
|
|
15
|
-
// ever destroying it.
|
|
15
|
+
// ever destroying it. The one thing asked of the connection directly is
|
|
16
|
+
// whether it has that protocol at all (`canHostXEmbed`), because on a backend
|
|
17
|
+
// that does not, `onReady` is no evidence either way: the Cocoa backend's
|
|
18
|
+
// `<foreign>` still calls it, with `windowId: undefined`.
|
|
16
19
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
20
|
+
import { useApp } from 'react-x11';
|
|
17
21
|
|
|
18
22
|
import { nodeProcessHost } from './host.js';
|
|
19
23
|
import type { ExitInfo, ProcessHost, SpawnedProcess } from './host.js';
|
|
@@ -25,7 +29,9 @@ import type { ExitInfo, ProcessHost, SpawnedProcess } from './host.js';
|
|
|
25
29
|
* with no terminal emulator installed, or no mpv, is an ordinary machine.
|
|
26
30
|
* Both components render their `fallback` on it, the way
|
|
27
31
|
* `useDesktopCalendarEvents` reports "no bus, no ical.js" rather than
|
|
28
|
-
* throwing.
|
|
32
|
+
* throwing. So is an app on a react-x11 backend that cannot embed another
|
|
33
|
+
* program's window at all — the native macOS one — and `error` says which of
|
|
34
|
+
* the two it is: a `BackendUnavailableError` or an `EmbedUnsupportedError`.
|
|
29
35
|
*/
|
|
30
36
|
export type EmbedStatus =
|
|
31
37
|
'idle' | 'starting' | 'running' | 'exited' | 'unavailable';
|
|
@@ -51,6 +57,65 @@ export class BackendUnavailableError extends Error {
|
|
|
51
57
|
}
|
|
52
58
|
}
|
|
53
59
|
|
|
60
|
+
/**
|
|
61
|
+
* This app cannot host another program's window at all.
|
|
62
|
+
*
|
|
63
|
+
* Embedding is XEmbed, and XEmbed is X protocol — a reparent into a
|
|
64
|
+
* container, the save set, ClientMessages — which only react-x11's X11
|
|
65
|
+
* backend has. The native macOS backend has no cross-process window
|
|
66
|
+
* embedding (react-x11 `docs/macos.md`, "What this is, and is not"), so there
|
|
67
|
+
* is no window to hand a program and nothing to spawn.
|
|
68
|
+
*
|
|
69
|
+
* `useEmbeddedClient` reports it as `status: 'unavailable'`, the status a
|
|
70
|
+
* `BackendUnavailableError` gets, and for the same reason: it is an ordinary
|
|
71
|
+
* state of a healthy app, and the component renders its `fallback`. The class
|
|
72
|
+
* is what tells the two apart, because "install xterm" is the wrong advice
|
|
73
|
+
* here.
|
|
74
|
+
*/
|
|
75
|
+
export class EmbedUnsupportedError extends Error {
|
|
76
|
+
constructor() {
|
|
77
|
+
super(
|
|
78
|
+
'@react-x11/components: this react-x11 backend cannot embed another ' +
|
|
79
|
+
"program's window — XEmbed needs the X11 backend, and the native " +
|
|
80
|
+
'macOS one has no cross-process window embedding at all. Pass ' +
|
|
81
|
+
'`fallback` to render something else.',
|
|
82
|
+
);
|
|
83
|
+
this.name = 'EmbedUnsupportedError';
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** The slice of the app `canHostXEmbed` reads. `useApp()` is the escape
|
|
88
|
+
* hatch, so the shape is written out rather than imported — the call
|
|
89
|
+
* `../tray-host/manager.ts` makes for the slice it needs. */
|
|
90
|
+
interface XEmbedHostApp {
|
|
91
|
+
X?: { SetSelectionOwner?: unknown; ReparentWindow?: unknown } | null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Can this app host another program's window?
|
|
96
|
+
*
|
|
97
|
+
* Asked of the connection, because `<foreign>` cannot be the one to say: on
|
|
98
|
+
* react-x11's Cocoa backend it still calls `onReady`, with an undefined
|
|
99
|
+
* `windowId`, and on the headless mock mounting one throws from the commit.
|
|
100
|
+
* The test is `<TrayHost>`'s — its
|
|
101
|
+
* manager reports `'unavailable'` when `app.X` has no `SetSelectionOwner`,
|
|
102
|
+
* which is what core's Cocoa `X` lacks (react-x11 `src/cocoa/app.js`: a stub
|
|
103
|
+
* carrying only the few requests core makes there) and what the headless
|
|
104
|
+
* mock's `X` lacks too. `ReparentWindow` is asked as well, because it is the
|
|
105
|
+
* request an embed is actually made of.
|
|
106
|
+
*
|
|
107
|
+
* Takes the app rather than calling `useApp()`, so it can be asked off the
|
|
108
|
+
* render path — of `createRoot()`'s result, before there is a tree.
|
|
109
|
+
*/
|
|
110
|
+
export function canHostXEmbed(app: unknown): boolean {
|
|
111
|
+
const X = (app as XEmbedHostApp | null | undefined)?.X;
|
|
112
|
+
return (
|
|
113
|
+
!!X &&
|
|
114
|
+
typeof X.SetSelectionOwner === 'function' &&
|
|
115
|
+
typeof X.ReparentWindow === 'function'
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
54
119
|
/** What to run, and what to do with it once it is running. */
|
|
55
120
|
export interface LaunchPlan {
|
|
56
121
|
/** Absolute path or a name on `PATH`. */
|
|
@@ -101,13 +166,15 @@ export interface UseEmbeddedClientOptions {
|
|
|
101
166
|
stopSignal?: string;
|
|
102
167
|
onStart?: (info: { process: SpawnedProcess; backend?: string }) => void;
|
|
103
168
|
onExit?: (info: ExitInfo) => void;
|
|
104
|
-
/** Spawn failures
|
|
169
|
+
/** Spawn failures, missing backends, and an app that cannot host an
|
|
170
|
+
* embedded window at all (`EmbedUnsupportedError`). */
|
|
105
171
|
onError?: (err: Error) => void;
|
|
106
172
|
}
|
|
107
173
|
|
|
108
174
|
export interface EmbeddedClient {
|
|
109
175
|
status: EmbedStatus;
|
|
110
|
-
/** The last failure, including a `BackendUnavailableError
|
|
176
|
+
/** The last failure, including a `BackendUnavailableError` or an
|
|
177
|
+
* `EmbedUnsupportedError`. */
|
|
111
178
|
error: Error | null;
|
|
112
179
|
pid: number | null;
|
|
113
180
|
/** The container window, once `<foreign>` has one. */
|
|
@@ -134,12 +201,21 @@ export interface EmbeddedClient {
|
|
|
134
201
|
* element's props — style, focus, the `onEmbedded`/`onClientGone` handlers a
|
|
135
202
|
* component may want to compose with — belong to the component, not to the
|
|
136
203
|
* lifecycle.
|
|
204
|
+
*
|
|
205
|
+
* **On an app that cannot host XEmbed** (`canHostXEmbed` is false — the
|
|
206
|
+
* native macOS backend), `status` is `'unavailable'` from the first render,
|
|
207
|
+
* `error` is an `EmbedUnsupportedError`, and nothing is probed or spawned.
|
|
208
|
+
* The caller should render no `<foreign>` there either: it has no socket to
|
|
209
|
+
* be.
|
|
137
210
|
*/
|
|
138
211
|
export function useEmbeddedClient(
|
|
139
212
|
options: UseEmbeddedClientOptions,
|
|
140
213
|
): EmbeddedClient {
|
|
141
214
|
const { plan, enabled = true, stopSignal = 'SIGTERM' } = options;
|
|
142
215
|
const host = useMemo(() => options.host ?? nodeProcessHost(), [options.host]);
|
|
216
|
+
// Asked every render: two property reads, and an app does not change
|
|
217
|
+
// backend under a mounted tree.
|
|
218
|
+
const embeddable = canHostXEmbed(useApp());
|
|
143
219
|
|
|
144
220
|
const [windowId, setWindowId] = useState<number | null>(null);
|
|
145
221
|
const [status, setStatus] = useState<EmbedStatus>('idle');
|
|
@@ -158,6 +234,24 @@ export function useEmbeddedClient(
|
|
|
158
234
|
const handlers = useRef(options);
|
|
159
235
|
handlers.current = options;
|
|
160
236
|
|
|
237
|
+
// No XEmbed on this connection means no window for a program to draw into,
|
|
238
|
+
// whatever `<foreign>` reports: the Cocoa backend's calls `onReady` with an
|
|
239
|
+
// undefined `windowId`, which slipped past a `windowId === null` guard and
|
|
240
|
+
// spawned `xterm -into undefined`. So the app decides, and the lifecycle
|
|
241
|
+
// below starts nothing without it. Decided while rendering rather than in
|
|
242
|
+
// an effect, so the first commit can already be the `fallback`, and
|
|
243
|
+
// reported from an effect keyed on the error itself, so `onError` hears it
|
|
244
|
+
// once rather than once per plan. `enabled` false still means "hold off": a
|
|
245
|
+
// pane nobody can see yet reports nothing.
|
|
246
|
+
const unsupported = enabled && !embeddable;
|
|
247
|
+
const unsupportedError = useMemo(
|
|
248
|
+
() => (unsupported ? new EmbedUnsupportedError() : null),
|
|
249
|
+
[unsupported],
|
|
250
|
+
);
|
|
251
|
+
useEffect(() => {
|
|
252
|
+
if (unsupportedError) handlers.current.onError?.(unsupportedError);
|
|
253
|
+
}, [unsupportedError]);
|
|
254
|
+
|
|
161
255
|
const handleReady = useCallback((info: { windowId: number }) => {
|
|
162
256
|
setWindowId(info.windowId);
|
|
163
257
|
}, []);
|
|
@@ -170,7 +264,7 @@ export function useEmbeddedClient(
|
|
|
170
264
|
}, []);
|
|
171
265
|
|
|
172
266
|
useEffect(() => {
|
|
173
|
-
if (!enabled || windowId === null) {
|
|
267
|
+
if (!enabled || !embeddable || windowId === null) {
|
|
174
268
|
setStatus('idle');
|
|
175
269
|
return undefined;
|
|
176
270
|
}
|
|
@@ -271,11 +365,11 @@ export function useEmbeddedClient(
|
|
|
271
365
|
void planned?.dispose?.();
|
|
272
366
|
};
|
|
273
367
|
// `plan` is the restart signal — see the note on the option.
|
|
274
|
-
}, [enabled, windowId, plan, host, stopSignal, generation]);
|
|
368
|
+
}, [enabled, embeddable, windowId, plan, host, stopSignal, generation]);
|
|
275
369
|
|
|
276
370
|
return {
|
|
277
|
-
status,
|
|
278
|
-
error,
|
|
371
|
+
status: unsupported ? 'unavailable' : status,
|
|
372
|
+
error: unsupportedError ?? error,
|
|
279
373
|
pid,
|
|
280
374
|
windowId,
|
|
281
375
|
backend,
|
package/src/embed/index.ts
CHANGED
package/src/html/node.ts
CHANGED
|
@@ -138,7 +138,9 @@ export class HtmlViewNode extends Node {
|
|
|
138
138
|
private _cascade: Cascade | null = null;
|
|
139
139
|
private _tree: BoxTree | null = null;
|
|
140
140
|
private _stale: Stale = Stale.Everything;
|
|
141
|
-
|
|
141
|
+
/** The width the document was last laid out at. Not `_laidOutAt`: core's
|
|
142
|
+
* `Node` has a method of that name, which a field would hide. */
|
|
143
|
+
private _laidOutWidth = -1;
|
|
142
144
|
private _mediaBand = -1;
|
|
143
145
|
private _documentHeight = 0;
|
|
144
146
|
private _documentWidth = 0;
|
|
@@ -369,7 +371,7 @@ export class HtmlViewNode extends Node {
|
|
|
369
371
|
this._sweep();
|
|
370
372
|
if (this._stale < Stale.Style) this._stale = Stale.Style;
|
|
371
373
|
}
|
|
372
|
-
if (this._stale === Stale.Nothing && this.
|
|
374
|
+
if (this._stale === Stale.Nothing && this._laidOutWidth === target) return;
|
|
373
375
|
|
|
374
376
|
if (this._stale >= Stale.Style || !this._cascade) {
|
|
375
377
|
this._restyle(target);
|
|
@@ -398,12 +400,12 @@ export class HtmlViewNode extends Node {
|
|
|
398
400
|
});
|
|
399
401
|
this._textPoints = null;
|
|
400
402
|
this._pointsAreUnits = null;
|
|
401
|
-
this.
|
|
403
|
+
this._laidOutWidth = -1;
|
|
402
404
|
}
|
|
403
405
|
|
|
404
406
|
if (
|
|
405
407
|
this._tree &&
|
|
406
|
-
(this.
|
|
408
|
+
(this._laidOutWidth !== target || this._stale >= Stale.Layout)
|
|
407
409
|
) {
|
|
408
410
|
const result = layoutDocument(
|
|
409
411
|
this._tree,
|
|
@@ -413,7 +415,7 @@ export class HtmlViewNode extends Node {
|
|
|
413
415
|
);
|
|
414
416
|
this._documentWidth = result.width;
|
|
415
417
|
this._documentHeight = result.height;
|
|
416
|
-
this.
|
|
418
|
+
this._laidOutWidth = target;
|
|
417
419
|
this._reportControls();
|
|
418
420
|
}
|
|
419
421
|
this._stale = Stale.Nothing;
|
package/src/index.ts
CHANGED
|
@@ -655,7 +655,9 @@ export type {
|
|
|
655
655
|
// a third `-into WID` wrapper should not have to reimplement it.
|
|
656
656
|
export {
|
|
657
657
|
BackendUnavailableError,
|
|
658
|
+
EmbedUnsupportedError,
|
|
658
659
|
IpcConnectError,
|
|
660
|
+
canHostXEmbed,
|
|
659
661
|
connectWhenReady,
|
|
660
662
|
nodeProcessHost,
|
|
661
663
|
resolveBackend,
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
// The markdown AST `parse.ts` produces
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
1
|
+
// The markdown AST `parse.ts` produces, `<Markdown>` renders and
|
|
2
|
+
// `<RichTextEditor>` edits (it converts to and from ProseMirror nodes, and
|
|
3
|
+
// `stringify.ts` writes the result back out). Deliberately small: it is the
|
|
4
|
+
// GFM constructs the two components draw, not a general mdast — but it is
|
|
5
|
+
// shaped so that growing it stays additive. In particular `component` is the
|
|
6
|
+
// MDX direction: `ComponentBlock` is parsed and rendered (docs/prd-mdx.md,
|
|
7
|
+
// M1), and `ComponentInline` is still reserved — see the note on it for why
|
|
8
|
+
// the inline half needs machinery `<richtext>` does not have yet.
|
|
9
|
+
//
|
|
10
|
+
// It lives in `src/internal/` because two components read it: shared code no
|
|
11
|
+
// app needs on its own yet, per AGENTS.md's "half-step below a shared
|
|
12
|
+
// module". `<Markdown>` still re-exports the parser and the types.
|
|
8
13
|
|
|
9
14
|
/** Inline content, inside a paragraph, heading, list item or table cell. */
|
|
10
15
|
export type InlineNode =
|
|
@@ -56,6 +61,11 @@ export interface LinkInline {
|
|
|
56
61
|
/** True when this was `` — the href is the image source. */
|
|
57
62
|
image: boolean;
|
|
58
63
|
children: InlineNode[];
|
|
64
|
+
/**
|
|
65
|
+
* `[text](href "title")`'s title, when there was one. `<Markdown>` renders
|
|
66
|
+
* none; the editor keeps it, so a document it saves still has it.
|
|
67
|
+
*/
|
|
68
|
+
title?: string;
|
|
59
69
|
}
|
|
60
70
|
|
|
61
71
|
/** A hard line break (two trailing spaces, or a trailing backslash). */
|
|
@@ -144,6 +154,13 @@ export interface CodeBlock {
|
|
|
144
154
|
type: 'code';
|
|
145
155
|
/** First word of the info string, lowercased — `''` when absent. */
|
|
146
156
|
lang: string;
|
|
157
|
+
/**
|
|
158
|
+
* The whole info string, trimmed — present only when it says more than
|
|
159
|
+
* `lang` does (```` ```ts title="a.ts" ````, or a language not written in
|
|
160
|
+
* lowercase), so a fence that is just a language parses to the AST it
|
|
161
|
+
* always did. The editor keeps it through a round trip.
|
|
162
|
+
*/
|
|
163
|
+
info?: string;
|
|
147
164
|
text: string;
|
|
148
165
|
/**
|
|
149
166
|
* False when the closing fence has not arrived. During streaming that is
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
// Markdown → AST, written for this
|
|
2
|
-
// DOM-shaped pipeline
|
|
1
|
+
// Markdown → AST, written for this package rather than adapted from a
|
|
2
|
+
// DOM-shaped pipeline — shared, from `src/internal/`, because two components
|
|
3
|
+
// read it: `<Markdown>` renders what it produces and `<RichTextEditor>` edits
|
|
4
|
+
// it, writing it back out through `./stringify.ts`. GFM-flavoured
|
|
5
|
+
// CommonMark: headings (ATX and setext),
|
|
3
6
|
// paragraphs, fenced and indented code, blockquotes with lazy continuation,
|
|
4
7
|
// nested ordered/bullet/task lists with the tight/loose distinction, tables,
|
|
5
8
|
// thematic breaks; inline emphasis with the real flanking and rule-of-three
|
|
@@ -317,7 +320,8 @@ function parseBlocks(
|
|
|
317
320
|
const start = i;
|
|
318
321
|
const fenceIndent = fence[1].length;
|
|
319
322
|
const marker = fence[2];
|
|
320
|
-
const
|
|
323
|
+
const info = fence[3].trim();
|
|
324
|
+
const lang = (info.split(/[ \t]/, 1)[0] ?? '').toLowerCase();
|
|
321
325
|
const body: string[] = [];
|
|
322
326
|
let closed = false;
|
|
323
327
|
i += 1;
|
|
@@ -342,7 +346,19 @@ function parseBlocks(
|
|
|
342
346
|
);
|
|
343
347
|
i += 1;
|
|
344
348
|
}
|
|
345
|
-
commit(
|
|
349
|
+
commit(
|
|
350
|
+
{
|
|
351
|
+
type: 'code',
|
|
352
|
+
lang,
|
|
353
|
+
// only when it says more than the language does, so a plain
|
|
354
|
+
// ```js fence parses to the AST it always did
|
|
355
|
+
...(info !== lang ? { info } : null),
|
|
356
|
+
text: body.join('\n'),
|
|
357
|
+
closed,
|
|
358
|
+
},
|
|
359
|
+
start,
|
|
360
|
+
i,
|
|
361
|
+
);
|
|
346
362
|
continue;
|
|
347
363
|
}
|
|
348
364
|
|
|
@@ -607,11 +623,27 @@ function parseList(
|
|
|
607
623
|
const contentIndent = indent + m[2].length + pad;
|
|
608
624
|
|
|
609
625
|
const inner: string[] = [rest];
|
|
626
|
+
// The fence open in the item's own content, if one is. A blank line
|
|
627
|
+
// inside a fence is the code's rather than the list's: it neither ends
|
|
628
|
+
// the item nor makes the list loose — and a snippet with an empty line
|
|
629
|
+
// in it, under a numbered step, is the ordinary case.
|
|
630
|
+
// (a task item's box comes off first: `- [ ] ```js` opens a fence too)
|
|
631
|
+
let fence = fenceAfter(null, rest.replace(RE_TASK, ''));
|
|
610
632
|
i += 1;
|
|
611
633
|
let pendingBlanks = 0;
|
|
612
634
|
while (i < n) {
|
|
613
635
|
const l = lines[i];
|
|
614
636
|
if (RE_BLANK.test(l)) {
|
|
637
|
+
if (fence !== null && pendingBlanks === 0) {
|
|
638
|
+
// …as long as the item goes on after it: an unclosed fence must
|
|
639
|
+
// not swallow the paragraph the list is followed by
|
|
640
|
+
let k = i + 1;
|
|
641
|
+
while (k < n && RE_BLANK.test(lines[k])) k += 1;
|
|
642
|
+
if (k < n && indentOf(lines[k]) >= contentIndent) {
|
|
643
|
+
for (; i < k; i += 1) inner.push('');
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
615
647
|
pendingBlanks += 1;
|
|
616
648
|
if (pendingBlanks > 1) break;
|
|
617
649
|
i += 1;
|
|
@@ -623,7 +655,9 @@ function parseList(
|
|
|
623
655
|
loose = true;
|
|
624
656
|
pendingBlanks = 0;
|
|
625
657
|
}
|
|
626
|
-
|
|
658
|
+
const content = l.slice(contentIndent);
|
|
659
|
+
inner.push(content);
|
|
660
|
+
fence = fenceAfter(fence, content);
|
|
627
661
|
i += 1;
|
|
628
662
|
continue;
|
|
629
663
|
}
|
|
@@ -666,6 +700,23 @@ function parseList(
|
|
|
666
700
|
};
|
|
667
701
|
}
|
|
668
702
|
|
|
703
|
+
/**
|
|
704
|
+
* The fence still open after `line`, given the one open before it — the
|
|
705
|
+
* opening run (```` ``` ````, `~~~~`) or null. Indentation is ignored, so a
|
|
706
|
+
* fence in a list nested inside this item is seen too.
|
|
707
|
+
*/
|
|
708
|
+
function fenceAfter(open: string | null, line: string): string | null {
|
|
709
|
+
const m = RE_FENCE_OPEN.exec(line.trimStart());
|
|
710
|
+
if (open === null) {
|
|
711
|
+
if (!m || (m[2][0] === '`' && m[3].includes('`'))) return null;
|
|
712
|
+
return m[2];
|
|
713
|
+
}
|
|
714
|
+
if (m && m[2][0] === open[0] && m[2].length >= open.length && m[3] === '') {
|
|
715
|
+
return null;
|
|
716
|
+
}
|
|
717
|
+
return open;
|
|
718
|
+
}
|
|
719
|
+
|
|
669
720
|
// --- tables ----------------------------------------------------------------
|
|
670
721
|
|
|
671
722
|
/** Split a row on unescaped pipes; `\|` stays literal in the cell. */
|
|
@@ -763,19 +814,45 @@ const NAMED_ENTITIES: Record<string, string> = {
|
|
|
763
814
|
middot: '·',
|
|
764
815
|
};
|
|
765
816
|
|
|
817
|
+
/** The same pattern, anchored — for reading one reference at a position. */
|
|
818
|
+
const RE_ENTITY_AT = new RegExp(RE_ENTITY.source, 'y');
|
|
819
|
+
|
|
820
|
+
function entityText(
|
|
821
|
+
whole: string,
|
|
822
|
+
dec?: string,
|
|
823
|
+
hex?: string,
|
|
824
|
+
named?: string,
|
|
825
|
+
): string {
|
|
826
|
+
if (dec) {
|
|
827
|
+
const cp = parseInt(dec, 10);
|
|
828
|
+
return cp > 0 && cp <= 0x10ffff ? String.fromCodePoint(cp) : '�';
|
|
829
|
+
}
|
|
830
|
+
if (hex) {
|
|
831
|
+
const cp = parseInt(hex, 16);
|
|
832
|
+
return cp > 0 && cp <= 0x10ffff ? String.fromCodePoint(cp) : '�';
|
|
833
|
+
}
|
|
834
|
+
return (named && NAMED_ENTITIES[named]) ?? whole;
|
|
835
|
+
}
|
|
836
|
+
|
|
766
837
|
function decodeEntities(text: string): string {
|
|
767
838
|
if (!text.includes('&')) return text;
|
|
768
|
-
return text.replace(RE_ENTITY,
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
839
|
+
return text.replace(RE_ENTITY, entityText);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/** The whole character ending at `i`, surrogate pair and all — an emoji is
|
|
843
|
+
* a symbol, and half of one is not. */
|
|
844
|
+
function charBefore(text: string, i: number): string | undefined {
|
|
845
|
+
if (i <= 0) return undefined;
|
|
846
|
+
const unit = text.charCodeAt(i - 1);
|
|
847
|
+
return unit >= 0xdc00 && unit <= 0xdfff && i >= 2
|
|
848
|
+
? text.slice(i - 2, i)
|
|
849
|
+
: text[i - 1];
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/** The whole character starting at `i`. */
|
|
853
|
+
function charAt(text: string, i: number): string | undefined {
|
|
854
|
+
const cp = text.codePointAt(i);
|
|
855
|
+
return cp === undefined ? undefined : String.fromCodePoint(cp);
|
|
779
856
|
}
|
|
780
857
|
|
|
781
858
|
function classify(ch: string | undefined): 'ws' | 'punct' | 'other' {
|
|
@@ -797,11 +874,14 @@ export function parseInline(
|
|
|
797
874
|
const items: Item[] = [];
|
|
798
875
|
let buf = '';
|
|
799
876
|
|
|
877
|
+
// References are decoded where they are read (below), not on the flushed
|
|
878
|
+
// buffer: `\&` is an escaped ampersand followed by `amp;`, and
|
|
879
|
+
// decoding afterwards turned it back into `&`.
|
|
800
880
|
const flushText = (): void => {
|
|
801
881
|
if (buf.length === 0) return;
|
|
802
882
|
items.push({
|
|
803
883
|
kind: 'node',
|
|
804
|
-
node: { type: 'text', text:
|
|
884
|
+
node: { type: 'text', text: buf },
|
|
805
885
|
});
|
|
806
886
|
buf = '';
|
|
807
887
|
};
|
|
@@ -957,7 +1037,7 @@ export function parseInline(
|
|
|
957
1037
|
const suffix = parseLinkSuffix(text, i + 1);
|
|
958
1038
|
if (suffix) {
|
|
959
1039
|
flushText();
|
|
960
|
-
closeLink(items, openIdx, suffix.href);
|
|
1040
|
+
closeLink(items, openIdx, suffix.href, suffix.title);
|
|
961
1041
|
i = suffix.end;
|
|
962
1042
|
continue;
|
|
963
1043
|
}
|
|
@@ -988,8 +1068,8 @@ export function parseInline(
|
|
|
988
1068
|
if (ch === '*' || ch === '_' || ch === '~') {
|
|
989
1069
|
let run = 1;
|
|
990
1070
|
while (text[i + run] === ch) run += 1;
|
|
991
|
-
const before = classify(text
|
|
992
|
-
const after = classify(text
|
|
1071
|
+
const before = classify(charBefore(text, i));
|
|
1072
|
+
const after = classify(charAt(text, i + run));
|
|
993
1073
|
const leftFlank =
|
|
994
1074
|
after !== 'ws' && (after !== 'punct' || before !== 'other');
|
|
995
1075
|
const rightFlank =
|
|
@@ -1023,6 +1103,16 @@ export function parseInline(
|
|
|
1023
1103
|
continue;
|
|
1024
1104
|
}
|
|
1025
1105
|
|
|
1106
|
+
if (ch === '&') {
|
|
1107
|
+
RE_ENTITY_AT.lastIndex = i;
|
|
1108
|
+
const ref = RE_ENTITY_AT.exec(text);
|
|
1109
|
+
if (ref) {
|
|
1110
|
+
buf += entityText(ref[0], ref[1], ref[2], ref[3]);
|
|
1111
|
+
i += ref[0].length;
|
|
1112
|
+
continue;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1026
1116
|
buf += ch;
|
|
1027
1117
|
i += 1;
|
|
1028
1118
|
}
|
|
@@ -1072,10 +1162,12 @@ function lastBracket(items: Item[]): number {
|
|
|
1072
1162
|
interface LinkSuffix {
|
|
1073
1163
|
href: string;
|
|
1074
1164
|
end: number;
|
|
1165
|
+
title?: string;
|
|
1075
1166
|
}
|
|
1076
1167
|
|
|
1077
1168
|
/** `](dest "title")` — destination in `<>` or bare with balanced parens.
|
|
1078
|
-
* The title is
|
|
1169
|
+
* The title is kept on the node — nothing renders one, but the editor
|
|
1170
|
+
* writes it back out. */
|
|
1079
1171
|
function parseLinkSuffix(text: string, from: number): LinkSuffix | null {
|
|
1080
1172
|
if (text[from] !== '(') return null;
|
|
1081
1173
|
let i = from + 1;
|
|
@@ -1110,22 +1202,33 @@ function parseLinkSuffix(text: string, from: number): LinkSuffix | null {
|
|
|
1110
1202
|
while (i < len && (text[i] === ' ' || text[i] === '\n' || text[i] === '\t'))
|
|
1111
1203
|
i += 1;
|
|
1112
1204
|
// an optional title in any of the three quote styles
|
|
1205
|
+
let title: string | undefined;
|
|
1113
1206
|
const q = text[i];
|
|
1114
1207
|
if (q === '"' || q === "'" || q === '(') {
|
|
1115
1208
|
const closer = q === '(' ? ')' : q;
|
|
1116
1209
|
let j = i + 1;
|
|
1117
1210
|
while (j < len && text[j] !== closer) j += 1;
|
|
1118
1211
|
if (j >= len) return null;
|
|
1212
|
+
title = decodeEntities(text.slice(i + 1, j));
|
|
1119
1213
|
i = j + 1;
|
|
1120
1214
|
while (i < len && (text[i] === ' ' || text[i] === '\n' || text[i] === '\t'))
|
|
1121
1215
|
i += 1;
|
|
1122
1216
|
}
|
|
1123
1217
|
if (text[i] !== ')') return null;
|
|
1124
|
-
return {
|
|
1218
|
+
return {
|
|
1219
|
+
href: decodeEntities(href),
|
|
1220
|
+
end: i + 1,
|
|
1221
|
+
...(title !== undefined ? { title } : null),
|
|
1222
|
+
};
|
|
1125
1223
|
}
|
|
1126
1224
|
|
|
1127
1225
|
/** Fold everything after the bracket opener at `openIdx` into a link node. */
|
|
1128
|
-
function closeLink(
|
|
1226
|
+
function closeLink(
|
|
1227
|
+
items: Item[],
|
|
1228
|
+
openIdx: number,
|
|
1229
|
+
href: string | null,
|
|
1230
|
+
title?: string,
|
|
1231
|
+
): void {
|
|
1129
1232
|
const bracket = items[openIdx] as { kind: 'bracket'; image: boolean };
|
|
1130
1233
|
const inner = items.splice(openIdx);
|
|
1131
1234
|
inner.shift();
|
|
@@ -1133,7 +1236,14 @@ function closeLink(items: Item[], openIdx: number, href: string | null): void {
|
|
|
1133
1236
|
processEmphasis(inner, href === null);
|
|
1134
1237
|
items.push({
|
|
1135
1238
|
kind: 'node',
|
|
1136
|
-
node: {
|
|
1239
|
+
node: {
|
|
1240
|
+
type: 'link',
|
|
1241
|
+
href,
|
|
1242
|
+
image: bracket.image,
|
|
1243
|
+
children: finish(inner),
|
|
1244
|
+
// only when written, so a link without one keeps its old shape
|
|
1245
|
+
...(title !== undefined ? { title } : null),
|
|
1246
|
+
},
|
|
1137
1247
|
});
|
|
1138
1248
|
}
|
|
1139
1249
|
|