edytor 1.0.0 → 3.0.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.
Files changed (287) hide show
  1. package/README.md +26 -69
  2. package/dist/block/block.svelte.d.ts +6 -4
  3. package/dist/block/block.svelte.js +12 -8
  4. package/dist/block/inlineBlock.svelte.d.ts +6 -2
  5. package/dist/block/inlineBlock.svelte.js +7 -2
  6. package/dist/clipboard/htmlFlow.d.ts +20 -7
  7. package/dist/clipboard/htmlFlow.js +23 -15
  8. package/dist/clipboard/serializeClipboardFragment.d.ts +37 -4
  9. package/dist/clipboard/serializeClipboardFragment.js +6 -4
  10. package/dist/clipboard/textBlocks.d.ts +8 -0
  11. package/dist/clipboard/textBlocks.js +6 -2
  12. package/dist/cloudflare/room/admission.js +3 -3
  13. package/dist/cloudflare/room/context.d.ts +7 -1
  14. package/dist/cloudflare/routeDocumentSocket.d.ts +17 -0
  15. package/dist/cloudflare/routeDocumentSocket.js +53 -0
  16. package/dist/collaboration/awarenessSelection.d.ts +22 -1
  17. package/dist/collaboration/awarenessSelection.js +42 -0
  18. package/dist/collaboration/comments/client.d.ts +1 -1
  19. package/dist/collaboration/comments/client.js +1 -1
  20. package/dist/collaboration/remoteSelection.js +3 -3
  21. package/dist/components/Block.svelte +3 -1
  22. package/dist/components/Edytor.svelte +38 -20
  23. package/dist/components/Edytor.svelte.d.ts +9 -4
  24. package/dist/components/InlineBlock.svelte +1 -1
  25. package/dist/crdt/admission.d.ts +5 -10
  26. package/dist/crdt/admission.js +5 -11
  27. package/dist/crdt/data.d.ts +10 -0
  28. package/dist/crdt/data.js +20 -2
  29. package/dist/crdt/doc/funnel.d.ts +7 -6
  30. package/dist/crdt/doc/funnel.js +10 -9
  31. package/dist/crdt/doc/gate.d.ts +0 -17
  32. package/dist/crdt/doc/gate.js +2 -41
  33. package/dist/crdt/doc/history.js +2 -2
  34. package/dist/crdt/doc/reads.d.ts +7 -0
  35. package/dist/crdt/doc/reads.js +11 -1
  36. package/dist/crdt/document.d.ts +13 -14
  37. package/dist/crdt/document.js +17 -19
  38. package/dist/crdt/edytor-doc.d.ts +22 -4
  39. package/dist/crdt/edytor-doc.js +5 -8
  40. package/dist/crdt/index.d.ts +9 -3
  41. package/dist/crdt/index.js +2 -2
  42. package/dist/crdt/operations.d.ts +1 -1
  43. package/dist/crdt/operations.js +1 -0
  44. package/dist/crdt/props.d.ts +32 -0
  45. package/dist/crdt/props.js +172 -0
  46. package/dist/crdt/protocol.d.ts +2 -2
  47. package/dist/crdt/protocol.js +2 -2
  48. package/dist/crdt/providers/index.d.ts +1 -1
  49. package/dist/crdt/providers/room.d.ts +10 -9
  50. package/dist/crdt/providers/room.js +13 -10
  51. package/dist/crdt/providers/websocket.d.ts +1 -1
  52. package/dist/crdt/providers/websocket.js +28 -29
  53. package/dist/crdt/semantics.js +3 -1
  54. package/dist/dnd/autoScroll.d.ts +15 -7
  55. package/dist/dnd/autoScroll.js +10 -15
  56. package/dist/{plugins/blockHandles → dnd}/dropIndicator.d.ts +2 -2
  57. package/dist/{plugins/blockHandles → dnd}/dropIndicator.js +2 -5
  58. package/dist/dnd/external.d.ts +6 -0
  59. package/dist/dnd/external.js +17 -0
  60. package/dist/{plugins/blockHandles → dnd}/geometry.d.ts +4 -2
  61. package/dist/{plugins/blockHandles → dnd}/geometry.js +3 -4
  62. package/dist/dnd/placement.d.ts +200 -0
  63. package/dist/dnd/placement.js +426 -0
  64. package/dist/dnd/pragmatic.d.ts +3 -1
  65. package/dist/dnd/pragmatic.js +12 -2
  66. package/dist/dnd/vendor/pragmatic/UPSTREAM.md +51 -18
  67. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/drop-target-for-external.d.ts +4 -0
  68. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/drop-target-for-external.js +6 -0
  69. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/element-adapter.d.ts +0 -2
  70. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/element-adapter.js +4 -22
  71. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/external-adapter-types.d.ts +26 -0
  72. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/external-adapter.d.ts +30 -0
  73. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/external-adapter.js +203 -0
  74. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/get-available-items.js +13 -0
  75. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/get-available-types.js +9 -0
  76. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/is-an-available-type.js +19 -0
  77. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/monitor-for-external.d.ts +4 -0
  78. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/monitor-for-external.js +6 -0
  79. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/honey-pot-fix/make-honey-pot-fix.js +4 -16
  80. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/internal-types.d.ts +1 -8
  81. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/make-adapter/make-drop-target.js +5 -6
  82. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/make-adapter/make-monitor.js +2 -3
  83. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/element/custom-native-drag-preview/set-custom-native-drag-preview.d.ts +1 -1
  84. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/element/custom-native-drag-preview/set-custom-native-drag-preview.js +6 -24
  85. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/external/contains-files.d.ts +2 -0
  86. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/external/contains-files.js +5 -0
  87. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/external/get-files.d.ts +3 -0
  88. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/external/get-files.js +12 -0
  89. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/external/native-types.d.ts +12 -0
  90. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/changing-window/is-entering-window-in-safari.js +10 -0
  91. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/changing-window/is-entering-window.js +55 -0
  92. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/entry-point/external.d.ts +4 -0
  93. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/entry-point/external.js +8 -0
  94. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/internal-types.d.ts +0 -4
  95. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/over-element/get-scroll-by.js +16 -72
  96. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/over-element/try-scroll.js +1 -9
  97. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/configuration.js +4 -29
  98. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/get-scroll-change.js +2 -3
  99. package/dist/edytor.svelte.d.ts +19 -9
  100. package/dist/edytor.svelte.js +28 -33
  101. package/dist/events/beforeInputCommands.d.ts +2 -2
  102. package/dist/events/beforeInputCommands.js +8 -4
  103. package/dist/events/onBeforeInput.js +1 -1
  104. package/dist/events/onDrop.js +25 -10
  105. package/dist/events/onInput.d.ts +0 -12
  106. package/dist/events/onInput.js +1 -67
  107. package/dist/events/onPaste.d.ts +2 -1
  108. package/dist/events/onPaste.js +8 -3
  109. package/dist/export/forms.d.ts +627 -0
  110. package/dist/export/forms.js +749 -0
  111. package/dist/export/index.d.ts +170 -0
  112. package/dist/export/index.js +161 -0
  113. package/dist/export/markdown.d.ts +41 -0
  114. package/dist/export/markdown.js +585 -0
  115. package/dist/export/markdownWriter.d.ts +9 -0
  116. package/dist/export/markdownWriter.js +133 -0
  117. package/dist/export/mdx.d.ts +15 -0
  118. package/dist/export/mdx.js +33 -0
  119. package/dist/index.d.ts +4 -4
  120. package/dist/index.js +2 -2
  121. package/dist/kinds.js +1 -1
  122. package/dist/labels.d.ts +2 -0
  123. package/dist/labels.js +2 -1
  124. package/dist/plugins/anchored.svelte.d.ts +21 -25
  125. package/dist/plugins/anchored.svelte.js +23 -39
  126. package/dist/plugins/arrowMove/arrowMove.d.ts +1 -1
  127. package/dist/plugins/arrowMove/arrowMove.js +1 -1
  128. package/dist/plugins/blockHandles/BlockHandleController.svelte.d.ts +2 -122
  129. package/dist/plugins/blockHandles/BlockHandleController.svelte.js +48 -414
  130. package/dist/plugins/blockHandles/BlockHandles.svelte +14 -32
  131. package/dist/plugins/blockHandles/blockHandlesPlugin.js +2 -1
  132. package/dist/plugins/blockHandles/dragPreview.d.ts +26 -7
  133. package/dist/plugins/blockHandles/dragPreview.js +33 -0
  134. package/dist/plugins/blockMenu/BlockMenuController.svelte.d.ts +5 -0
  135. package/dist/plugins/blockMenu/BlockMenuController.svelte.js +7 -2
  136. package/dist/plugins/blockMenu/blockMenuPlugin.js +5 -50
  137. package/dist/plugins/chrome.d.ts +27 -5
  138. package/dist/plugins/chrome.js +24 -9
  139. package/dist/plugins/code/CodeHeader.svelte +5 -3
  140. package/dist/plugins/code/CodePlugin.svelte +15 -12
  141. package/dist/plugins/code/header.svelte.js +7 -11
  142. package/dist/plugins/code/languageMenu.svelte.d.ts +5 -2
  143. package/dist/plugins/code/languageMenu.svelte.js +13 -20
  144. package/dist/plugins/code/languages.d.ts +0 -3
  145. package/dist/plugins/code/languages.js +0 -2
  146. package/dist/plugins/columns/ColumnResize.svelte +3 -2
  147. package/dist/plugins/columns/ColumnsPlugin.svelte +8 -21
  148. package/dist/plugins/columns/gaps.d.ts +7 -5
  149. package/dist/plugins/columns/gaps.js +11 -6
  150. package/dist/plugins/columns/resize.svelte.d.ts +5 -6
  151. package/dist/plugins/columns/resize.svelte.js +12 -14
  152. package/dist/plugins/comments/CommentsController.svelte.d.ts +6 -0
  153. package/dist/plugins/comments/CommentsController.svelte.js +20 -0
  154. package/dist/plugins/equation/EquationPlugin.svelte +13 -30
  155. package/dist/plugins/equation/EquationPlugin.svelte.d.ts +1 -0
  156. package/dist/plugins/equation/equation.css +7 -0
  157. package/dist/plugins/equation/equation.svelte.d.ts +7 -15
  158. package/dist/plugins/equation/equation.svelte.js +13 -50
  159. package/dist/plugins/equation/tex.d.ts +17 -0
  160. package/dist/plugins/equation/tex.js +42 -0
  161. package/dist/plugins/fileDrop.d.ts +43 -0
  162. package/dist/plugins/fileDrop.js +122 -0
  163. package/dist/plugins/find/FindController.svelte.d.ts +5 -0
  164. package/dist/plugins/find/FindController.svelte.js +14 -14
  165. package/dist/plugins/find/findPlugin.js +1 -15
  166. package/dist/plugins/image/ImageChrome.svelte +8 -2
  167. package/dist/plugins/image/ImagePlugin.svelte +56 -84
  168. package/dist/plugins/image/ImagePlugin.svelte.d.ts +13 -1
  169. package/dist/plugins/image/controls.svelte.js +16 -2
  170. package/dist/plugins/image/image.css +17 -0
  171. package/dist/plugins/image/toolbar.svelte.d.ts +5 -4
  172. package/dist/plugins/image/toolbar.svelte.js +21 -31
  173. package/dist/plugins/index.d.ts +4 -5
  174. package/dist/plugins/index.js +0 -1
  175. package/dist/plugins/list.svelte.d.ts +9 -0
  176. package/dist/plugins/list.svelte.js +18 -0
  177. package/dist/plugins/markdownShortcuts.js +5 -5
  178. package/dist/plugins/marquee/marqueePlugin.js +0 -4
  179. package/dist/plugins/media/AudioPlugin.svelte +13 -1
  180. package/dist/plugins/media/AudioPlugin.svelte.d.ts +11 -1
  181. package/dist/plugins/media/BookmarkPlugin.svelte +6 -97
  182. package/dist/plugins/media/EmbedPlugin.svelte +5 -90
  183. package/dist/plugins/media/EmptySlot.svelte +36 -20
  184. package/dist/plugins/media/EmptySlot.svelte.d.ts +6 -15
  185. package/dist/plugins/media/FilePlugin.svelte +12 -20
  186. package/dist/plugins/media/FilePlugin.svelte.d.ts +9 -0
  187. package/dist/plugins/media/MediaEmpty.svelte +2 -55
  188. package/dist/plugins/media/MediaEmpty.svelte.d.ts +18 -14
  189. package/dist/plugins/media/SourceBlock.svelte +178 -52
  190. package/dist/plugins/media/SourceBlock.svelte.d.ts +13 -6
  191. package/dist/plugins/media/UploadPreview.svelte +32 -0
  192. package/dist/plugins/media/UploadPreview.svelte.d.ts +11 -0
  193. package/dist/plugins/media/VideoPlugin.svelte +13 -1
  194. package/dist/plugins/media/VideoPlugin.svelte.d.ts +11 -1
  195. package/dist/plugins/media/empty.svelte.d.ts +13 -4
  196. package/dist/plugins/media/empty.svelte.js +32 -26
  197. package/dist/plugins/media/media.d.ts +9 -63
  198. package/dist/plugins/media/media.js +1 -194
  199. package/dist/plugins/media/sources.d.ts +60 -0
  200. package/dist/plugins/media/sources.js +165 -0
  201. package/dist/plugins/media/upload.css +23 -0
  202. package/dist/plugins/media/urlPaste.svelte.d.ts +2 -1
  203. package/dist/plugins/media/urlPaste.svelte.js +12 -21
  204. package/dist/plugins/mention/MentionPlugin.svelte +50 -38
  205. package/dist/plugins/mention/MentionPlugin.svelte.d.ts +11 -2
  206. package/dist/plugins/mention/mention.css +6 -0
  207. package/dist/plugins/page/PagePlugin.svelte +2 -9
  208. package/dist/plugins/page/page.js +1 -1
  209. package/dist/plugins/pageLink/PageLinkPlugin.svelte +126 -28
  210. package/dist/plugins/pageLink/PageLinkPlugin.svelte.d.ts +13 -1
  211. package/dist/plugins/pageLink/pageLink.css +6 -0
  212. package/dist/plugins/richtext/RichTextPlugin.svelte +99 -204
  213. package/dist/plugins/richtext/RichTextPlugin.svelte.d.ts +1 -0
  214. package/dist/plugins/richtext/body.js +1 -1
  215. package/dist/plugins/richtext/calloutIcons.svelte.d.ts +2 -2
  216. package/dist/plugins/richtext/calloutIcons.svelte.js +5 -7
  217. package/dist/plugins/richtext/richTextOperations.d.ts +1 -19
  218. package/dist/plugins/richtext/richTextOperations.js +3 -49
  219. package/dist/plugins/richtext/richtext.css +6 -0
  220. package/dist/plugins/slashMenu/SlashMenuController.svelte.d.ts +2 -0
  221. package/dist/plugins/slashMenu/SlashMenuController.svelte.js +7 -0
  222. package/dist/plugins/slashMenu/slashMenuPlugin.js +5 -14
  223. package/dist/plugins/table/TableChrome.svelte +2 -3
  224. package/dist/plugins/table/TablePlugin.svelte +20 -54
  225. package/dist/plugins/table/chrome.svelte.d.ts +5 -2
  226. package/dist/plugins/table/chrome.svelte.js +33 -56
  227. package/dist/plugins/table/table.js +3 -3
  228. package/dist/plugins/toc/TocPlugin.svelte +2 -4
  229. package/dist/plugins/toolbar/ToolbarController.svelte.d.ts +7 -0
  230. package/dist/plugins/toolbar/ToolbarController.svelte.js +20 -13
  231. package/dist/plugins/toolbar/toolbarPlugin.js +19 -53
  232. package/dist/plugins/triggers/TriggerController.svelte.d.ts +7 -2
  233. package/dist/plugins/triggers/TriggerController.svelte.js +11 -4
  234. package/dist/plugins/triggers/place.js +1 -2
  235. package/dist/plugins/triggers/triggers.d.ts +6 -1
  236. package/dist/plugins/triggers/triggers.js +12 -13
  237. package/dist/plugins/uploads.svelte.d.ts +48 -32
  238. package/dist/plugins/uploads.svelte.js +119 -43
  239. package/dist/plugins.d.ts +66 -14
  240. package/dist/plugins.js +0 -24
  241. package/dist/selection/replaceSelection.js +2 -2
  242. package/dist/selection/selection.svelte.d.ts +4 -10
  243. package/dist/selection/selection.svelte.js +7 -11
  244. package/dist/session/attempt.d.ts +16 -6
  245. package/dist/session/attempt.js +1 -0
  246. package/dist/session/bindings.js +35 -32
  247. package/dist/session/commands.d.ts +7 -11
  248. package/dist/session/commands.js +15 -19
  249. package/dist/session/history.js +1 -0
  250. package/dist/session/inputRules.d.ts +6 -3
  251. package/dist/session/inputRules.js +10 -6
  252. package/dist/session/moves.d.ts +33 -0
  253. package/dist/session/moves.js +71 -1
  254. package/dist/session/navigation.d.ts +7 -0
  255. package/dist/session/navigation.js +20 -11
  256. package/dist/session/props.d.ts +28 -0
  257. package/dist/session/props.js +65 -0
  258. package/dist/session/suggestions.svelte.d.ts +0 -11
  259. package/dist/session/suggestions.svelte.js +15 -3
  260. package/dist/session/uploads.d.ts +29 -0
  261. package/dist/session/uploads.js +10 -0
  262. package/dist/surface/lines.js +1 -3
  263. package/dist/surface/observer.svelte.d.ts +17 -1
  264. package/dist/surface/observer.svelte.js +72 -17
  265. package/dist/surface/overlay.d.ts +0 -14
  266. package/dist/surface/overlay.js +0 -25
  267. package/dist/surface/projector.svelte.js +1 -1
  268. package/dist/text/text.svelte.js +3 -13
  269. package/dist/themes/notion.css +9 -0
  270. package/dist/utils/sanitize.d.ts +38 -0
  271. package/dist/utils/sanitize.js +82 -0
  272. package/dist/utils.d.ts +2 -0
  273. package/dist/utils.js +5 -0
  274. package/package.json +18 -1
  275. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/max-z-index.js +0 -3
  276. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/supports-popover.js +0 -16
  277. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/is-axis-allowed.js +0 -3
  278. package/dist/plugins/code/LanguageMenu.svelte +0 -25
  279. package/dist/plugins/code/LanguageMenu.svelte.d.ts +0 -9
  280. package/dist/plugins/equation/EquationEditor.svelte +0 -27
  281. package/dist/plugins/equation/EquationEditor.svelte.d.ts +0 -9
  282. package/dist/plugins/image/ImageToolbarHost.svelte +0 -37
  283. package/dist/plugins/image/ImageToolbarHost.svelte.d.ts +0 -9
  284. package/dist/plugins/richtext/CalloutIconPicker.svelte +0 -26
  285. package/dist/plugins/richtext/CalloutIconPicker.svelte.d.ts +0 -9
  286. /package/dist/{plugins/columns → dnd}/stacking.d.ts +0 -0
  287. /package/dist/{plugins/columns → dnd}/stacking.js +0 -0
@@ -1,10 +1,9 @@
1
- import { onPress, takeKeys } from '../../events/onFocus.js';
2
- import { pin } from '../../surface/overlay.js';
1
+ import { takeKeys } from '../../events/onFocus.js';
3
2
  import { imageAlignOf, imageAltOf } from './image.js';
4
3
  import { imageControls } from './controls.svelte.js';
5
- import { identify, keepFocus } from '../chrome.js';
4
+ import { identify, keepFocus, onPressOutside, virtualAnchor } from '../chrome.js';
6
5
  import { all, listen } from '../../utils/listen.js';
7
- import ImageToolbarHost from './ImageToolbarHost.svelte';
6
+ import { keysOf } from '../list.svelte.js';
8
7
  /** Where a press keeps the alt field open: the toolbar (its Alt button toggles it) and the field's own. */
9
8
  const ALT_CHROME = '[data-edytor-image-alt-panel], [data-edytor-image-toolbar], [data-edytor-image-alt]';
10
9
  /**
@@ -15,11 +14,22 @@ const ALT_CHROME = '[data-edytor-image-alt-panel], [data-edytor-image-toolbar],
15
14
  */
16
15
  export class ImageToolbar {
17
16
  controls;
17
+ snippet;
18
+ /** @internal */
18
19
  constructor(
19
20
  /** @internal */
20
- controls) {
21
+ controls,
22
+ /** The app's markup. @internal */
23
+ snippet) {
21
24
  this.controls = controls;
25
+ this.snippet = snippet;
22
26
  }
27
+ /**
28
+ * Where the toolbar stands (`ChromeAnchor`): the image the chrome is
29
+ * on, whose top right your `toolbar` places itself in.
30
+ */
31
+ anchor = virtualAnchor(() => this.#image()?.getBoundingClientRect(), () => this.#image());
32
+ #image = () => this.#block()?.node?.querySelector('[data-edytor-image] img');
23
33
  /** The measured image the toolbar is on (`null`: none). */
24
34
  get box() {
25
35
  return this.controls.box;
@@ -59,20 +69,6 @@ export class ImageToolbar {
59
69
  this.editing = this.editing ? null : (this.box?.id ?? null);
60
70
  this.#edytor.overlay.invalidate();
61
71
  };
62
- /**
63
- * A press anywhere (`onPress`, capture): one outside the alt panel and the
64
- * toolbar (whose Alt button toggles it) closes the field, so the chrome
65
- * follows the pointer again.
66
- * @internal
67
- */
68
- pressed = (event) => {
69
- const target = event.target;
70
- if (this.editing === null)
71
- return;
72
- if (target?.closest?.(ALT_CHROME))
73
- return;
74
- this.closeAlt();
75
- };
76
72
  /** Focus left the alt field: for somewhere outside its panel, the field closes. @internal */
77
73
  blurred = (event) => {
78
74
  const to = event.relatedTarget;
@@ -106,13 +102,8 @@ export class ImageToolbar {
106
102
  identify(node, this.#edytor.popups.idOf('image-alt'));
107
103
  node.setAttribute('data-edytor-image-alt', '');
108
104
  node.focus({ preventScroll: true });
109
- const keydown = (event) => {
110
- if (event.isComposing || (event.key !== 'Enter' && event.key !== 'Escape'))
111
- return;
112
- event.preventDefault();
113
- this.closeAlt(true);
114
- };
115
- return all(listen(node, 'keydown', keydown), listen(node, 'focusout', this.blurred));
105
+ const close = () => this.closeAlt(true);
106
+ return all(listen(node, 'keydown', keysOf({ Enter: close, Escape: close })), listen(node, 'focusout', this.blurred));
116
107
  };
117
108
  /** Close the alt field; with `keys`, the editor takes the keys back. */
118
109
  closeAlt = (keys = false) => {
@@ -135,11 +126,10 @@ export const createImageToolbarPlugin = ({ toolbar }) => (edytor) => ({
135
126
  const controls = imageControls.get(edytor);
136
127
  if (!controls || controls.toolbar)
137
128
  return;
138
- const bar = (controls.toolbar = new ImageToolbar(controls));
129
+ const bar = (controls.toolbar = new ImageToolbar(controls, toolbar));
139
130
  return all(
140
- // A press outside the alt panel closes it (WebKit's lone `mousedown` too).
141
- onPress(edytor, node.ownerDocument, bar.pressed, true), edytor.overlay.mount(ImageToolbarHost, { toolbar: bar, snippet: toolbar }, 'edytor-image-toolbar-host',
142
- // With the image chrome: above the block handles and the column bands.
143
- 7, (host) => () => pin(host)), () => (controls.toolbar = undefined));
131
+ // A press outside the alt panel and the toolbar (whose Alt button toggles it)
132
+ // closes it (WebKit's lone `mousedown` too).
133
+ onPressOutside(edytor, node.ownerDocument, ALT_CHROME, () => bar.closeAlt()), () => (controls.toolbar = undefined));
144
134
  }
145
135
  });
@@ -1,8 +1,7 @@
1
1
  export { createRichTextPlugin, richTextOperations, richTextPlaceholder, type RichTextPluginOptions, type TodoCheckboxPayload, type CalloutIconPayload, type EmptyBodyPayload } from './richtext/RichTextPlugin.svelte';
2
2
  export { CALLOUT_ICONS, createCalloutIconsPlugin, type CalloutIconPicker, type CalloutIconsOptions } from './richtext/calloutIcons.svelte.js';
3
- export { createUploadsPlugin, type UploadsOptions } from './uploads.svelte.js';
4
3
  export { createArrowMovePlugin } from './arrowMove/arrowMove.js';
5
- export type { MenuItemPayload } from './chrome.js';
4
+ export type { ChromeAnchor, MenuItemPayload } from './chrome.js';
6
5
  export { BLOCK_ACTIVATE_EVENT, BLOCK_ADD_EVENT, type BlockActivation, type BlockAddition } from './blockHandles/BlockHandleController.svelte.js';
7
6
  export { createBlockHandlesPlugin, type BlockHandlesOptions, type BlockHandleSnippetPayload } from './blockHandles/blockHandlesPlugin.js';
8
7
  export { createCodePlugin, CODE_LANGUAGES, loadCodeLanguage, type CodeLanguage, type CodePluginOptions } from './code/CodePlugin.svelte';
@@ -21,12 +20,12 @@ export { createBlockMenuPlugin } from './blockMenu/blockMenuPlugin.js';
21
20
  export { createSuggestionsPlugin, type SuggestionBarOptions, type SuggestionBarPayload } from './suggestions/suggestionsPlugin.js';
22
21
  export { createSuggestionBarPlugin } from './suggestions/SuggestionBars.svelte';
23
22
  export type { BlockMenuOptions, BlockMenuController, BlockMenuAction, BlockMenuColor } from './blockMenu/BlockMenuController.svelte.js';
24
- export { createImagePlugin, safeImageSrc, storableImageSrc, oversizedInlineImage, MAX_INLINE_IMAGE_BYTES, MIN_IMAGE_WIDTH, type ImageData, type ImageAlign } from './image/ImagePlugin.svelte';
23
+ export { createImagePlugin, type ImagePluginOptions, safeImageSrc, storableImageSrc, oversizedInlineImage, MAX_INLINE_IMAGE_BYTES, MIN_IMAGE_WIDTH, type ImageData, type ImageAlign } from './image/ImagePlugin.svelte';
25
24
  export { createEmbedPlugin, EMBED_PROVIDERS, EMBED_SANDBOX, EMBED_ALLOW, embedSourceOf, type EmbedProvider, type EmbedPluginOptions, type EmbedFallbackPayload } from './media/EmbedPlugin.svelte';
26
25
  export { createBookmarkPlugin, type BookmarkPluginOptions, type BookmarkPreview, type BookmarkCardPayload } from './media/BookmarkPlugin.svelte';
27
26
  export { createFilePlugin, type FilePluginOptions, type FileRowPayload } from './media/FilePlugin.svelte';
28
- export { createVideoPlugin } from './media/VideoPlugin.svelte';
29
- export { createAudioPlugin } from './media/AudioPlugin.svelte';
27
+ export { createVideoPlugin, type VideoPluginOptions } from './media/VideoPlugin.svelte';
28
+ export { createAudioPlugin, type AudioPluginOptions } from './media/AudioPlugin.svelte';
30
29
  export { safeWebUrl, safeMediaSrc, type MediaKind } from './media/media.js';
31
30
  export type { MediaEmptyController, MediaEmptyFailure } from './media/empty.svelte.js';
32
31
  export { createMediaEmptyPlugin, type MediaEmptyOptions } from './media/MediaEmpty.svelte';
@@ -1,6 +1,5 @@
1
1
  export { createRichTextPlugin, richTextOperations, richTextPlaceholder } from './richtext/RichTextPlugin.svelte';
2
2
  export { CALLOUT_ICONS, createCalloutIconsPlugin } from './richtext/calloutIcons.svelte.js';
3
- export { createUploadsPlugin } from './uploads.svelte.js';
4
3
  export { createArrowMovePlugin } from './arrowMove/arrowMove.js';
5
4
  export { BLOCK_ACTIVATE_EVENT, BLOCK_ADD_EVENT } from './blockHandles/BlockHandleController.svelte.js';
6
5
  export { createBlockHandlesPlugin } from './blockHandles/blockHandlesPlugin.js';
@@ -36,6 +36,13 @@ type Steps = {
36
36
  ends?: boolean;
37
37
  usable?: (index: number) => boolean;
38
38
  };
39
+ /**
40
+ * A field's or a control's keys (`keydown`): `keys[event.key]` runs and the
41
+ * key's default is prevented (with `stop`, it goes no further), unless the
42
+ * handler answers `false`; a key an IME composes, or one already prevented,
43
+ * is left alone.
44
+ */
45
+ export declare const keysOf: (keys: Partial<Record<string, (event: KeyboardEvent) => unknown>>, stop?: boolean) => ((event: KeyboardEvent) => void);
39
46
  /**
40
47
  * A key on a menu that holds the keyboard itself, not a field (WAI-ARIA's
41
48
  * menu keys): the arrows, Home and End move its row (`stepRow`), Enter and
@@ -112,6 +119,8 @@ export declare abstract class ChromeList {
112
119
  role: 'listbox' | 'menu', open?: (node: HTMLElement) => Omit<Popup, 'id'> | false | null);
113
120
  /** How many rows the keyboard walks. */
114
121
  protected abstract get count(): number;
122
+ /** The view is readonly (reactive): the list closes. */
123
+ get readonly(): boolean;
115
124
  /** The list's element id (page-unique), for `aria-controls`. */
116
125
  get listId(): string;
117
126
  /** Row `index`'s element id (page-unique), for `aria-activedescendant`. */
@@ -70,6 +70,20 @@ export const stepRow = (key, at, count, { columns = 0, ends = false, usable = ()
70
70
  if (ends && length && (key === 'Home' || key === 'End'))
71
71
  return rows[key === 'Home' ? 0 : length - 1];
72
72
  };
73
+ /**
74
+ * A field's or a control's keys (`keydown`): `keys[event.key]` runs and the
75
+ * key's default is prevented (with `stop`, it goes no further), unless the
76
+ * handler answers `false`; a key an IME composes, or one already prevented,
77
+ * is left alone.
78
+ */
79
+ export const keysOf = (keys, stop = false) => (event) => {
80
+ const run = keys[event.key];
81
+ if (event.defaultPrevented || event.isComposing || !run || run(event) === false)
82
+ return;
83
+ event.preventDefault();
84
+ if (stop)
85
+ event.stopPropagation();
86
+ };
73
87
  /**
74
88
  * A key on a menu that holds the keyboard itself, not a field (WAI-ARIA's
75
89
  * menu keys): the arrows, Home and End move its row (`stepRow`), Enter and
@@ -192,6 +206,10 @@ export class ChromeList {
192
206
  this.role = role;
193
207
  this.popup = listPopup(edytor, owner, role, open);
194
208
  }
209
+ /** The view is readonly (reactive): the list closes. */
210
+ get readonly() {
211
+ return this.edytor.readonly;
212
+ }
195
213
  /** The list's element id (page-unique), for `aria-controls`. */
196
214
  get listId() {
197
215
  return this.edytor.popups.idOf(this.owner);
@@ -91,12 +91,12 @@ export const createMarkdownShortcutsPlugin = () => (edytor) => {
91
91
  if (!text)
92
92
  return false;
93
93
  const { id } = block;
94
- // The segment's start, in block offsets: the caret, less the text before it.
95
- const at = to - before.length + found.start;
94
+ // The segment's start, in block offsets: the caret (after the typed text), less the text before it.
95
+ const at = to - match.input.length + found.start;
96
96
  const { facade, dispatcher } = edytor;
97
- const plan = () => facade.compose(facade.prepare.formatRange(id, at + found.open, found.content, { [found.mark]: true }), ...(found.closing
98
- ? [facade.prepare.deleteText(id, at + found.open + found.content, found.closing)]
99
- : []), facade.prepare.deleteText(id, at, found.open));
97
+ const plan = () => facade.compose(facade.prepare.formatRange(id, at + found.open, found.content, { [found.mark]: true }),
98
+ // The closing marker, the typed character with it.
99
+ facade.prepare.deleteText(id, at + found.open + found.content, found.closing + typed.length), facade.prepare.deleteText(id, at, found.open));
100
100
  const payload = { value: typed, start: before.length, end: before.length };
101
101
  const result = dispatcher.plan('inlineMarkdown', payload, plan, block, text);
102
102
  if (result.status !== 'applied')
@@ -3,9 +3,6 @@ import MarqueeBox from './MarqueeBox.svelte';
3
3
  import { MarqueeController } from './MarqueeController.svelte.js';
4
4
  /** The handle gutter: a block handle's own box, around its buttons (never a button). */
5
5
  const HANDLE_HOST = '[data-edytor-block-handle-host]';
6
- const controllers = new WeakMap();
7
- /** The marquee's controller of `edytor`; `undefined` when the plugin is not listed. @internal */
8
- export const marqueeController = (edytor) => controllers.get(edytor);
9
6
  /**
10
7
  * Block selection with a rectangle (Notion's rubber band), opt-in: a press
11
8
  * in the editor's empty area — the margins beside the blocks (the handle
@@ -18,7 +15,6 @@ export const marqueeController = (edytor) => controllers.get(edytor);
18
15
  */
19
16
  export const createMarqueePlugin = (options = {}) => (edytor) => {
20
17
  const controller = new MarqueeController(edytor, options.threshold ?? 4);
21
- controllers.set(edytor, controller);
22
18
  return {
23
19
  onEdytorAttached: ({ node }) => {
24
20
  const layer = edytor.overlay.layer;
@@ -1,11 +1,23 @@
1
1
  <script module lang="ts">
2
2
  import type { Plugin } from '../../plugins.js';
3
3
  import { sourcePlugin } from './SourceBlock.svelte';
4
+ import type { Uploader } from '../uploads.svelte.js';
5
+
6
+ export type AudioPluginOptions = {
7
+ /**
8
+ * Upload a audio file and answer its URL: an empty block's Upload, and
9
+ * the pasted and dropped audio files, each placed as a block at once
10
+ * (one undo step), showing its name and progress, then filled with the
11
+ * URL this answers. Without it, audios are only linked.
12
+ */
13
+ upload?: Uploader;
14
+ };
4
15
 
5
16
  /**
6
17
  * Notion's audio block: an audio file (an `http(s)` link, or an upload
7
18
  * through `upload`) in the browser's player, with an editable caption.
8
19
  * Void: its only text is the caption.
9
20
  */
10
- export const createAudioPlugin = (): Plugin => sourcePlugin('audio', ['sound', 'music', 'mp3']);
21
+ export const createAudioPlugin = ({ upload }: AudioPluginOptions = {}): Plugin =>
22
+ sourcePlugin('audio', ['sound', 'music', 'mp3'], {}, upload);
11
23
  </script>
@@ -1,10 +1,20 @@
1
1
  import type { Plugin } from '../../plugins.js';
2
+ import type { Uploader } from '../uploads.svelte.js';
3
+ export type AudioPluginOptions = {
4
+ /**
5
+ * Upload a audio file and answer its URL: an empty block's Upload, and
6
+ * the pasted and dropped audio files, each placed as a block at once
7
+ * (one undo step), showing its name and progress, then filled with the
8
+ * URL this answers. Without it, audios are only linked.
9
+ */
10
+ upload?: Uploader;
11
+ };
2
12
  /**
3
13
  * Notion's audio block: an audio file (an `http(s)` link, or an upload
4
14
  * through `upload`) in the browser's player, with an editable caption.
5
15
  * Void: its only text is the caption.
6
16
  */
7
- export declare const createAudioPlugin: () => Plugin;
17
+ export declare const createAudioPlugin: ({ upload }?: AudioPluginOptions) => Plugin;
8
18
  interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
9
19
  new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
10
20
  $$bindings?: Bindings;
@@ -1,21 +1,11 @@
1
1
  <script module lang="ts">
2
- import type { Plugin, BlockSnippetPayload } from '../../plugins.js';
2
+ import { mediaForms } from '../../export/forms.js';
3
+ import type { Plugin } from '../../plugins.js';
3
4
  import type { Block } from '../../block/block.svelte.js';
4
- import EmptySlot from './EmptySlot.svelte';
5
- import Control from '../Control.svelte';
5
+ import { previewData, source } from './SourceBlock.svelte';
6
6
  import type { Snippet } from 'svelte';
7
7
  import { urlPaste, type UrlPasteController } from './urlPaste.svelte.js';
8
- import {
9
- MEDIA_ICONS,
10
- escapeHtml,
11
- mediaControl,
12
- mediaLink,
13
- mediaPlugin,
14
- mediaView,
15
- safeWebUrl,
16
- LINK_REL,
17
- type BookmarkCardPayload
18
- } from './media.js';
8
+ import { MEDIA_ICONS, mediaPlugin, safeWebUrl, type BookmarkCardPayload } from './media.js';
19
9
 
20
10
  export type { BookmarkCardPayload };
21
11
 
@@ -54,21 +44,6 @@
54
44
  card?: Snippet<[BookmarkCardPayload]>;
55
45
  };
56
46
 
57
- /** A preview's text field, trimmed and capped: it is stored in the document. */
58
- const text = (value: unknown, max: number) =>
59
- typeof value === 'string' && value.trim() ? value.trim().slice(0, max) : undefined;
60
-
61
- /** The fields of `preview` a bookmark stores: text capped, images {@link safeWebUrl} only. */
62
- const previewData = (preview: BookmarkPreview | null | undefined) => {
63
- const fields = {
64
- title: text(preview?.title, 300),
65
- description: text(preview?.description, 1000),
66
- image: safeWebUrl(preview?.image) ?? undefined,
67
- icon: safeWebUrl(preview?.icon) ?? undefined
68
- };
69
- return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined));
70
- };
71
-
72
47
  /**
73
48
  * The unfurl's writes: background bookkeeping, not a gesture, so no
74
49
  * view's history tracks this origin (`crdt/document.ts`, History). The
@@ -77,9 +52,6 @@
77
52
  */
78
53
  const UNFURL_ORIGIN = Symbol('edytor.bookmark.unfurl');
79
54
 
80
- /** The view's card markup, if the app gave one. */
81
- const cardOf = mediaControl('bookmark', 'card');
82
-
83
55
  /**
84
56
  * Notion's web bookmark: a card linking to a page, with its title,
85
57
  * description and images when your `unfurl` answers them, and an
@@ -135,72 +107,9 @@
135
107
  options.menu
136
108
  ),
137
109
  record: {
138
- snippet: bookmark,
139
- html: (block, caption) => {
140
- const url = safeWebUrl(block.data?.url);
141
- const title = text(block.data?.title, 300) ?? url;
142
- const link = url ? `<a href="${escapeHtml(url)}">${escapeHtml(title!)}</a>` : '';
143
- return `<figure data-edytor-bookmark>${link}<figcaption>${caption}</figcaption></figure>`;
144
- },
145
- plain: (block, caption) =>
146
- [safeWebUrl(block.data?.url), caption].filter(Boolean).join('\n'),
147
- // Its own export: a `figure[data-edytor-bookmark]` holding the link.
148
- parse: (el) => {
149
- if (el.localName !== 'figure' || !el.hasAttribute('data-edytor-bookmark')) return;
150
- const anchor = el.querySelector('a[href]');
151
- const url = safeWebUrl(anchor?.getAttribute('href'));
152
- const title = text(anchor?.textContent, 300);
153
- return url ? { url, ...(title && title !== url ? { title } : {}) } : undefined;
154
- }
110
+ snippet: source,
111
+ ...mediaForms().bookmark
155
112
  }
156
113
  };
157
114
  });
158
115
  </script>
159
-
160
- {#snippet bookmark({ block, content }: BlockSnippetPayload<Record<string, unknown>>)}
161
- {@const url = safeWebUrl(block.data.url)}
162
- {#if url}
163
- <div use:block.void data-edytor-bookmark>
164
- <Control
165
- own={cardOf}
166
- {initial}
167
- payload={{
168
- block: block.handle,
169
- url,
170
- title: text(block.data.title, 300),
171
- description: text(block.data.description, 1000),
172
- image: safeWebUrl(block.data.image) ?? undefined,
173
- icon: safeWebUrl(block.data.icon) ?? undefined,
174
- link: mediaLink({ draggable: 'false' })
175
- }}
176
- />
177
- </div>
178
- {:else}
179
- <div use:block.void data-edytor-media-empty>
180
- <EmptySlot
181
- block={block.handle}
182
- kind="bookmark"
183
- viewOf={(edytor) => mediaView(edytor, 'bookmark')}
184
- />
185
- </div>
186
- {/if}
187
- <!-- The core renders the kind's <figure> around this markup. -->
188
- <!-- svelte-ignore a11y_figcaption_parent -->
189
- <figcaption>{@render content()}</figcaption>
190
- {/snippet}
191
-
192
- <!-- Notion's card: the page's title, description and URL (with its icon) beside its cover. -->
193
- {#snippet initial({ url, title, description, image, icon, link }: BookmarkCardPayload)}
194
- <a href={url} target="_blank" rel={LINK_REL} draggable="false" {@attach link}>
195
- <span data-edytor-bookmark-text>
196
- <span data-edytor-bookmark-title>{title ?? url}</span>
197
- {#if description}<span data-edytor-bookmark-description>{description}</span>{/if}
198
- <span data-edytor-bookmark-url>
199
- {#if icon}<img src={icon} alt="" draggable="false" />{/if}<span>{url}</span>
200
- </span>
201
- </span>
202
- {#if image}
203
- <span data-edytor-bookmark-cover><img src={image} alt="" draggable="false" /></span>
204
- {/if}
205
- </a>
206
- {/snippet}
@@ -1,8 +1,7 @@
1
1
  <script module lang="ts">
2
- import type { Plugin, BlockSnippetPayload } from '../../plugins.js';
3
- import type { Block } from '../../block/block.svelte.js';
4
- import EmptySlot from './EmptySlot.svelte';
5
- import Control from '../Control.svelte';
2
+ import { mediaForms } from '../../export/forms.js';
3
+ import type { Plugin } from '../../plugins.js';
4
+ import { source } from './SourceBlock.svelte';
6
5
  import type { Snippet } from 'svelte';
7
6
  import { urlPaste, type UrlPasteController } from './urlPaste.svelte.js';
8
7
  import {
@@ -10,14 +9,8 @@
10
9
  EMBED_PROVIDERS,
11
10
  EMBED_SANDBOX,
12
11
  MEDIA_ICONS,
13
- claimed,
14
12
  embedSourceOf,
15
- escapeHtml,
16
- mediaControl,
17
- mediaLink,
18
- LINK_REL,
19
13
  mediaPlugin,
20
- mediaView,
21
14
  safeWebUrl,
22
15
  type EmbedFallbackPayload,
23
16
  type EmbedProvider
@@ -54,16 +47,6 @@
54
47
  fallback?: Snippet<[EmbedFallbackPayload]>;
55
48
  };
56
49
 
57
- /** The view's fallback markup, if the app gave one. */
58
- const fallbackOf = mediaControl('embed', 'fallback');
59
-
60
- /**
61
- * The view's allowlist: the first embed plugin listed in it (first wins,
62
- * as for the kind). Never a broader list: no entry plays nothing.
63
- */
64
- const providers = (block: Block | undefined) =>
65
- mediaView(block?.edytor, 'embed')?.providers ?? [];
66
-
67
50
  /**
68
51
  * Notion's embed block: an "Embed a link" panel until it has a link, then
69
52
  * the provider's player in a sandboxed frame, with an editable caption.
@@ -98,78 +81,10 @@
98
81
  options.menu
99
82
  ),
100
83
  record: {
101
- snippet: embed,
102
- html: (block, caption) => {
103
- const player = embedSourceOf(block.data?.url, allowed);
104
- const url = safeWebUrl(block.data?.url);
105
- const media = player
106
- ? `<iframe src="${escapeHtml(player.src)}" title="${escapeHtml(player.provider.name)}" sandbox="${EMBED_SANDBOX}" allowfullscreen></iframe>`
107
- : url
108
- ? `<a href="${escapeHtml(url)}">${escapeHtml(url)}</a>`
109
- : '';
110
- return `<figure>${media}<figcaption>${caption}</figcaption></figure>`;
111
- },
112
- plain: (block, caption) =>
113
- [safeWebUrl(block.data?.url), caption].filter(Boolean).join('\n'),
114
- // An allowlisted `iframe` (bare, or in a `figure`): its `src` is the link.
115
- parse: (el) => {
116
- const src = claimed(el, 'iframe')?.getAttribute('src');
117
- const url = embedSourceOf(src, allowed) && safeWebUrl(src);
118
- return url ? { url } : undefined;
119
- }
84
+ snippet: source,
85
+ ...mediaForms(allowed).embed
120
86
  }
121
87
  })
122
88
  );
123
89
  };
124
90
  </script>
125
-
126
- {#snippet embed({ block, content }: BlockSnippetPayload<{ url?: string }>)}
127
- {@const player = block.handle ? embedSourceOf(block.data.url, providers(block.handle)) : null}
128
- {@const url = safeWebUrl(block.data.url)}
129
- {#if player}
130
- {@const { height, aspectRatio = '16 / 9' } = player.provider}
131
- <div
132
- use:block.void
133
- data-edytor-embed
134
- style:aspect-ratio={height ? undefined : aspectRatio}
135
- style:height={height ? `${height}px` : undefined}
136
- >
137
- <iframe
138
- src={player.src}
139
- title={player.provider.name}
140
- sandbox={EMBED_SANDBOX}
141
- allow={EMBED_ALLOW}
142
- referrerpolicy="strict-origin-when-cross-origin"
143
- loading="lazy"
144
- allowfullscreen
145
- style="display: block; width: 100%; height: 100%; border: 0"
146
- ></iframe>
147
- </div>
148
- {:else if url}
149
- <!-- A link no provider plays (a peer may store any), or a suggestion's preview (no handle, so no
150
- view's allowlist): a plain link, never a frame. -->
151
- <div use:block.void data-edytor-embed-unsupported>
152
- <Control
153
- own={fallbackOf}
154
- {initial}
155
- payload={{ block: block.handle, url, link: mediaLink() }}
156
- />
157
- </div>
158
- {:else}
159
- <div use:block.void data-edytor-media-empty>
160
- <EmptySlot
161
- block={block.handle}
162
- kind="embed"
163
- viewOf={(edytor) => mediaView(edytor, 'embed')}
164
- />
165
- </div>
166
- {/if}
167
- <!-- The core renders the kind's <figure> around this markup. -->
168
- <!-- svelte-ignore a11y_figcaption_parent -->
169
- <figcaption>{@render content()}</figcaption>
170
- {/snippet}
171
-
172
- <!-- The link itself. -->
173
- {#snippet initial({ url, link }: EmbedFallbackPayload)}
174
- <a href={url} target="_blank" rel={LINK_REL} {@attach link}>{url}</a>
175
- {/snippet}
@@ -1,32 +1,29 @@
1
1
  <script lang="ts" module>
2
- import type { Component, Snippet } from 'svelte';
2
+ import type { Snippet } from 'svelte';
3
3
  import type { Block } from '../../block/block.svelte.js';
4
4
  import type { Edytor } from '../../edytor.svelte.js';
5
5
  import type { MediaKind } from './media.js';
6
- import type { EmptyView, MediaEmptyController } from './empty.svelte.js';
6
+ import { MediaEmptyController, type EmptyView } from './empty.svelte.js';
7
7
 
8
- /** What an empty image or media block renders in its view: the media empty plugin's host and snippet. */
9
- type EmptyHost = {
10
- Host: Component<{
11
- block: Block | undefined;
12
- kind: MediaKind | 'image';
13
- viewOf: (edytor: Edytor | undefined) => EmptyView | undefined;
14
- empty: Snippet<[MediaEmptyController]>;
15
- }>;
16
- empty: Snippet<[MediaEmptyController]>;
17
- };
18
-
19
- /** Each view's empty-block chrome (the first media empty plugin listed claims it). */
20
- export const emptyHosts = new WeakMap<object, EmptyHost>();
8
+ /** Each view's empty-block markup (the first media empty plugin listed claims it). */
9
+ export const emptyHosts = new WeakMap<object, Snippet<[MediaEmptyController]>>();
21
10
  </script>
22
11
 
23
12
  <script lang="ts">
24
13
  import { getContext, untrack } from 'svelte';
14
+ import { fileUploads } from '../uploads.svelte.js';
25
15
 
26
16
  /**
27
- * Where an empty image or media block shows its empty state: the view's
28
- * media empty plugin's host, when one is listed (else nothing). The view
29
- * rendering it is the block's, or a suggestion preview's.
17
+ * Where an empty image or media block shows its empty state: a
18
+ * `MediaEmptyController`, which the view's `empty` snippet (the media
19
+ * empty plugin's) draws, when one is listed (else nothing). The view
20
+ * rendering it is the block's, or a suggestion preview's. The kind in the
21
+ * block's view (`viewOf`) stores the link, or an uploaded file's URL
22
+ * (only on a block still empty when it lands). A readonly view, or a
23
+ * suggestion's preview (no `block`), may not write: no control may run
24
+ * `upload`. A pasted or dropped file shows its upload (`pending`); a
25
+ * failed one opens the panel with its error; data answered while the
26
+ * block was not shown fills it as soon as it shows.
30
27
  */
31
28
  let {
32
29
  block,
@@ -35,10 +32,29 @@
35
32
  }: {
36
33
  block: Block | undefined;
37
34
  kind: MediaKind | 'image';
35
+ /** The kind in a view: how it stores a source. */
38
36
  viewOf: (edytor: Edytor | undefined) => EmptyView | undefined;
39
37
  } = $props();
40
38
  const view = getContext<Edytor>('edytor');
41
- const host = untrack(() => emptyHosts.get(block?.edytor ?? view));
39
+ const empty = untrack(() => emptyHosts.get(block?.edytor ?? view));
40
+ const panel = untrack(() => new MediaEmptyController(block, kind, viewOf(block?.edytor)));
41
+ /** The upload of a pasted or dropped file into this block, if any. */
42
+ const pending = $derived(empty && panel.pending);
43
+ // After the flush that shows the block: never inside the transaction that showed it.
44
+ $effect(() => {
45
+ if (pending?.status === 'landed' && block && !block.edytor.readonly) {
46
+ const id = block.id;
47
+ untrack(() => fileUploads(block.edytor)?.fill(id));
48
+ }
49
+ });
50
+ // The panel opens on a failed upload, with its error; the user closes it.
51
+ $effect(() => {
52
+ if (pending?.status === 'failed')
53
+ untrack(() => {
54
+ panel.open = true;
55
+ panel.failed = 'upload';
56
+ });
57
+ });
42
58
  </script>
43
59
 
44
- {#if host}<host.Host {block} {kind} {viewOf} empty={host.empty} />{/if}
60
+ {#if empty}{@render empty(panel)}{/if}
@@ -1,25 +1,16 @@
1
- import type { Component, Snippet } from 'svelte';
1
+ import type { Snippet } from 'svelte';
2
2
  import type { Block } from '../../block/block.svelte.js';
3
3
  import type { Edytor } from '../../edytor.svelte.js';
4
4
  import type { MediaKind } from './media.js';
5
- import type { EmptyView, MediaEmptyController } from './empty.svelte.js';
6
- /** What an empty image or media block renders in its view: the media empty plugin's host and snippet. */
7
- type EmptyHost = {
8
- Host: Component<{
9
- block: Block | undefined;
10
- kind: MediaKind | 'image';
11
- viewOf: (edytor: Edytor | undefined) => EmptyView | undefined;
12
- empty: Snippet<[MediaEmptyController]>;
13
- }>;
14
- empty: Snippet<[MediaEmptyController]>;
15
- };
16
- /** Each view's empty-block chrome (the first media empty plugin listed claims it). */
17
- export declare const emptyHosts: WeakMap<object, EmptyHost>;
5
+ import { MediaEmptyController, type EmptyView } from './empty.svelte.js';
6
+ /** Each view's empty-block markup (the first media empty plugin listed claims it). */
7
+ export declare const emptyHosts: WeakMap<object, Snippet<[MediaEmptyController]>>;
18
8
  type $$ComponentProps = {
19
9
  block: Block | undefined;
20
10
  kind: MediaKind | 'image';
11
+ /** The kind in a view: how it stores a source. */
21
12
  viewOf: (edytor: Edytor | undefined) => EmptyView | undefined;
22
13
  };
23
- declare const EmptySlot: Component<$$ComponentProps, {}, "">;
14
+ declare const EmptySlot: import("svelte").Component<$$ComponentProps, {}, "">;
24
15
  type EmptySlot = ReturnType<typeof EmptySlot>;
25
16
  export default EmptySlot;