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
@@ -11,8 +11,7 @@ export const caretRect = (edytor) => {
11
11
  if (selection?.rangeCount && editor.contains(selection.anchorNode)) {
12
12
  const range = selection.getRangeAt(0).cloneRange();
13
13
  range.collapse(false);
14
- if (typeof range.getBoundingClientRect === 'function')
15
- rect = range.getBoundingClientRect();
14
+ rect = range.getBoundingClientRect();
16
15
  }
17
16
  if (!rect || (!rect.width && !rect.height)) {
18
17
  rect = edytor.selection.textRange().startText?.node?.getBoundingClientRect();
@@ -9,9 +9,14 @@
9
9
  * none of it.
10
10
  */
11
11
  import type { Edytor } from '../../edytor.svelte.js';
12
- import type { InitializedPlugin, InputRule, Trigger } from '../../plugins.js';
12
+ import type { InitializedPlugin, InputRule, TextRuleContext, Trigger } from '../../plugins.js';
13
13
  import { type TextTriggerController } from './TriggerController.svelte.js';
14
14
  import { type HeldList } from '../list.svelte.js';
15
+ /**
16
+ * Write `atom` where the matched text starts (`ctx.from`: a trigger's pick,
17
+ * an input rule's replacement), the caret after it; whether it was written.
18
+ */
19
+ export declare const placeAtom: ({ edytor, block, from, caret }: TextRuleContext, atom: Parameters<Edytor["insertInline"]>[2]) => boolean;
15
20
  /**
16
21
  * `plugin` with `inputRules` (text typed at the caret that a rule
17
22
  * replaces: markdown, `:smile:` → 😄) and `triggers` (characters that open
@@ -1,7 +1,5 @@
1
1
  import { inputRulesHook } from '../../session/inputRules.js';
2
2
  import { TriggerMenuController } from './TriggerController.svelte.js';
3
- import { caretRect } from './place.js';
4
- import { placeNear } from '../../surface/overlay.js';
5
3
  import { listHotkeys } from '../list.svelte.js';
6
4
  import { mountChrome } from '../chrome.js';
7
5
  /** Two bindings of one chord, `first`'s claim ending the key. */
@@ -11,6 +9,16 @@ const both = (first, then) => (payload) => {
11
9
  };
12
10
  /** The triggers each view numbered so far: a trigger's popup owner name. */
13
11
  const counts = new WeakMap();
12
+ /**
13
+ * Write `atom` where the matched text starts (`ctx.from`: a trigger's pick,
14
+ * an input rule's replacement), the caret after it; whether it was written.
15
+ */
16
+ export const placeAtom = ({ edytor, block, from, caret }, atom) => {
17
+ const written = edytor.insertInline(block.id, from, atom).status === 'applied';
18
+ if (written)
19
+ caret(from + 1);
20
+ return written;
21
+ };
14
22
  /**
15
23
  * `plugin` with `inputRules` (text typed at the caret that a rule
16
24
  * replaces: markdown, `:smile:` → 😄) and `triggers` (characters that open
@@ -47,17 +55,8 @@ export const withRulesAndTriggers = (edytor, plugin, { inputRules = [], triggers
47
55
  : undefined,
48
56
  close: () => controller.close()
49
57
  }),
50
- // Below the caret (above it when there is no room), kept in the viewport.
51
- (controller) => mountChrome(edytor, controller.trigger.menu, controller, () => controller.isOpen, {
52
- name: 'edytor-trigger-menu-host',
53
- zIndex: 50,
54
- measure: (host) => {
55
- if (!controller.isOpen)
56
- return;
57
- const rect = caretRect(edytor);
58
- return rect && placeNear(host, rect, [280, 240]);
59
- }
60
- }, () => controller.close()));
58
+ // Its markup places itself by the controller's `anchor` (the caret).
59
+ (controller) => mountChrome(edytor, controller.trigger.menu, controller, () => controller.isOpen, { name: 'edytor-trigger-menu-host', zIndex: 50 }, () => controller.close()));
61
60
  };
62
61
  /**
63
62
  * `plugin` with trigger menus wired into its hooks (a plugin's `triggers`,
@@ -1,5 +1,8 @@
1
1
  import type { Edytor } from '../edytor.svelte.js';
2
- import type { Plugin, PluginOperations } from '../plugins.js';
2
+ import type { PluginDefinitions, PluginOperations } from '../plugins.js';
3
+ import type { DropAt } from '../session/attempt.js';
4
+ import { type ViewUploads } from '../session/uploads.js';
5
+ import { FileDrag } from './fileDrop.js';
3
6
  /** What an `upload` may report while it runs. */
4
7
  export type UploadReport = {
5
8
  /** The share sent so far, `0` to `1`: the block shows it as a bar. */
@@ -17,8 +20,14 @@ export type Uploader = (file: File, report: UploadReport) => Promise<string>;
17
20
  */
18
21
  export type FileUpload = {
19
22
  readonly status: 'uploading';
20
- /** The file, shown while it uploads (a `blob:` URL; `null` where there is none). */
23
+ /**
24
+ * The file, shown while it uploads: its object URL (`blob:`), this
25
+ * view's only, never stored; `null` where there is none.
26
+ */
21
27
  readonly preview: string | null;
28
+ /** The file's name and size in bytes, shown at once. */
29
+ readonly name: string;
30
+ readonly size: number;
22
31
  /** The share sent, `0` to `1`; `null` until `upload` reports one. */
23
32
  readonly progress: number | null;
24
33
  } | {
@@ -29,10 +38,12 @@ export type FileUpload = {
29
38
  };
30
39
  /**
31
40
  * What files a kind takes (its record's `upload`, `BlockDefinition`): which,
32
- * and the data an upload's answer writes. The view's uploader, if the
33
- * uploads plugin is listed, sends them.
41
+ * who sends them (`send`, its plugin's `upload` option) and the data an
42
+ * upload's answer writes. A kind without it takes no files.
34
43
  */
35
44
  export type KindUpload = {
45
+ /** Upload a file of this kind and answer its URL (its plugin's `upload`). */
46
+ send: Uploader;
36
47
  /** Whether it takes `file`. */
37
48
  accepts: (file: File) => boolean;
38
49
  /** It takes any file no other kind takes (the file block): asked last. */
@@ -41,14 +52,13 @@ export type KindUpload = {
41
52
  data: (file: File, answer: string) => Record<string, unknown> | null;
42
53
  /** Whether the block has a source already (a peer's, a link's): its upload then writes nothing. */
43
54
  filled: (data: Record<string, unknown>) => boolean;
44
- /** Show the file while it uploads (an image's `blob:` preview). */
45
- preview?: boolean;
46
55
  };
47
56
  /**
48
- * The view's uploads (`media.files`), one per view, the uploads plugin's:
49
- * its uploader (`upload`, an empty block's Upload too) and the uploads of
50
- * pasted and dropped files, session state, never in the document. A kind
51
- * takes files by its record's `upload`; a paste or a drop (a drop replays
57
+ * The view's uploads (`media.files`), one per view, claimed by the first
58
+ * media plugin listed with an `upload`: the uploads of pasted, dropped and
59
+ * picked files (an empty block's Upload), session state, never in the
60
+ * document. A kind takes files by its record's `upload`, and sends them
61
+ * with its `send` (its plugin's uploader); a paste or a drop (a drop replays
52
62
  * the paste hooks) of files the kinds take places one empty block per
53
63
  * file, each of the kind that takes it (`fallback` last), in the
54
64
  * clipboard's order, as one paste (one command, one undo step), and each
@@ -62,14 +72,16 @@ export type KindUpload = {
62
72
  * (`fill`, asked by the empty block's render), so a redo brings it back
63
73
  * whole.
64
74
  */
65
- export declare class FileUploads {
75
+ export declare class FileUploads implements ViewUploads {
66
76
  #private;
67
77
  private edytor;
68
- /** The view's uploader. */
69
- readonly upload: Uploader;
70
- constructor(edytor: Edytor,
71
- /** The view's uploader. */
72
- upload: Uploader);
78
+ constructor(edytor: Edytor);
79
+ /** The kind that takes `file`, if any. */
80
+ kindOf: (file: File) => string | undefined;
81
+ /** The types of the kinds that take files. */
82
+ get types(): string[];
83
+ /** Files dragged from outside: the drop indicator and the placement (`fileDrop.ts`). */
84
+ readonly drop: FileDrag;
73
85
  /** The upload of block `id` in this view (reactive), if any. */
74
86
  of: (id: string) => FileUpload | undefined;
75
87
  /**
@@ -78,8 +90,20 @@ export declare class FileUploads {
78
90
  * clipboard).
79
91
  */
80
92
  paste: NonNullable<PluginOperations['onPaste']>;
81
- /** Place a block per file at the selection (one paste), then upload each. */
82
- insert: (files: readonly File[]) => void;
93
+ /**
94
+ * Place a block per file at the selection (one paste), or at a drop's
95
+ * placement (`at`: beside, before, after or inside a block, one command),
96
+ * then upload each.
97
+ */
98
+ insert: (files: readonly File[], at?: DropAt) => void;
99
+ /**
100
+ * Upload `file`, picked on the empty block `id` (its Upload), with its
101
+ * kind's uploader, the block showing it meanwhile, and hand the answer to
102
+ * `land` before the preview goes (so the block never shows nothing
103
+ * between them): `false` when it failed (the block then shows no
104
+ * upload; the caller says why).
105
+ */
106
+ pick: (id: string, file: File, land: (answer: string) => void) => Promise<boolean>;
83
107
  /** Forget the block's upload (a source was embedded: its error, or held data, no longer applies). */
84
108
  clear: (id: string) => void;
85
109
  /**
@@ -91,20 +115,12 @@ export declare class FileUploads {
91
115
  /** Release every preview. */
92
116
  destroy: () => void;
93
117
  }
94
- /** The view's uploads, when it lists the uploads plugin. */
118
+ /** The view's uploads, when a media plugin with an `upload` is listed. */
95
119
  export declare const fileUploads: (edytor: Edytor | undefined) => FileUploads | undefined;
96
- export type UploadsOptions = {
97
- /**
98
- * Upload a file and answer its URL: an empty image or media block's
99
- * Upload, and the pasted and dropped files a kind takes, each placed as a
100
- * block of that kind at once (one undo step) and filled with the URL this
101
- * answers (the block shows the progress it reports).
102
- */
103
- upload: Uploader;
104
- };
105
120
  /**
106
- * File uploads (Notion's): the view's one uploader, for every kind whose
107
- * record takes files (`upload`: the image, file, video and audio kinds).
108
- * Without it, only links are embedded. The first listed claims the view.
121
+ * The hooks of a media plugin given an `upload` (its kind's record carries
122
+ * it, `KindUpload.send`): the first such plugin listed in a view claims the
123
+ * view's one `FileUploads` and runs its paste hook (a drop replays it) and
124
+ * teardown; the others add none.
109
125
  */
110
- export declare const createUploadsPlugin: ({ upload }: UploadsOptions) => Plugin;
126
+ export declare const uploadHooks: (edytor: Edytor | undefined, upload: Uploader | undefined) => PluginOperations & PluginDefinitions;
@@ -2,11 +2,20 @@ import { SvelteMap } from 'svelte/reactivity';
2
2
  import { pasteFlow } from '../clipboard/insertClipboardFragment.js';
3
3
  import { jsonBlockToSpec } from '../utils/json.js';
4
4
  import { id as mint } from '../utils.js';
5
+ import { placeNew } from '../session/moves.js';
6
+ import { claimUploads, viewUploads } from '../session/uploads.js';
7
+ import { FileDrag } from './fileDrop.js';
8
+ /** An upload's object URL, while it uploads, released. */
9
+ const revoke = (upload) => {
10
+ if (upload?.status === 'uploading' && upload.preview)
11
+ URL.revokeObjectURL(upload.preview);
12
+ };
5
13
  /**
6
- * The view's uploads (`media.files`), one per view, the uploads plugin's:
7
- * its uploader (`upload`, an empty block's Upload too) and the uploads of
8
- * pasted and dropped files, session state, never in the document. A kind
9
- * takes files by its record's `upload`; a paste or a drop (a drop replays
14
+ * The view's uploads (`media.files`), one per view, claimed by the first
15
+ * media plugin listed with an `upload`: the uploads of pasted, dropped and
16
+ * picked files (an empty block's Upload), session state, never in the
17
+ * document. A kind takes files by its record's `upload`, and sends them
18
+ * with its `send` (its plugin's uploader); a paste or a drop (a drop replays
10
19
  * the paste hooks) of files the kinds take places one empty block per
11
20
  * file, each of the kind that takes it (`fallback` last), in the
12
21
  * clipboard's order, as one paste (one command, one undo step), and each
@@ -22,13 +31,10 @@ import { id as mint } from '../utils.js';
22
31
  */
23
32
  export class FileUploads {
24
33
  edytor;
25
- upload;
26
34
  #uploads = new SvelteMap();
27
- constructor(edytor,
28
- /** The view's uploader. */
29
- upload) {
35
+ constructor(edytor) {
30
36
  this.edytor = edytor;
31
- this.upload = upload;
37
+ this.drop = new FileDrag(edytor, this);
32
38
  }
33
39
  /** The kinds that take files (their records' `upload`), in registration order. */
34
40
  get #kinds() {
@@ -37,6 +43,14 @@ export class FileUploads {
37
43
  /** The kind that takes `file`, if any. */
38
44
  #kindOf = (file) => this.#kinds.find((kind) => !kind.fallback && kind.accepts(file)) ??
39
45
  this.#kinds.find((kind) => kind.fallback && kind.accepts(file));
46
+ /** The kind that takes `file`, if any. */
47
+ kindOf = (file) => this.#kindOf(file)?.type;
48
+ /** The types of the kinds that take files. */
49
+ get types() {
50
+ return this.#kinds.map((kind) => kind.type);
51
+ }
52
+ /** Files dragged from outside: the drop indicator and the placement (`fileDrop.ts`). */
53
+ drop;
40
54
  /** The upload of block `id` in this view (reactive), if any. */
41
55
  of = (id) => this.#uploads.get(id);
42
56
  /**
@@ -44,15 +58,19 @@ export class FileUploads {
44
58
  * shows text (an office app puts a picture of copied text on the
45
59
  * clipboard).
46
60
  */
47
- paste = ({ e, prevent }) => {
61
+ paste = ({ e, prevent, at }) => {
48
62
  if (this.edytor.readonly)
49
63
  return;
50
64
  const files = Array.from(e.clipboardData?.files ?? []).filter((file) => this.#kindOf(file));
51
65
  if (files.length && !showsText(e.clipboardData?.getData('text/html')))
52
- prevent(() => this.insert(files));
66
+ prevent(() => this.insert(files, at));
53
67
  };
54
- /** Place a block per file at the selection (one paste), then upload each. */
55
- insert = (files) => {
68
+ /**
69
+ * Place a block per file at the selection (one paste), or at a drop's
70
+ * placement (`at`: beside, before, after or inside a block, one command),
71
+ * then upload each.
72
+ */
73
+ insert = (files, at) => {
56
74
  const { edytor } = this;
57
75
  const placed = files.flatMap((file) => {
58
76
  const kind = this.#kindOf(file);
@@ -61,16 +79,38 @@ export class FileUploads {
61
79
  if (!placed.length)
62
80
  return;
63
81
  const lines = placed.map(({ kind, id }) => jsonBlockToSpec({ id, type: kind.type, data: {}, content: [] }));
64
- edytor.dispatcher.run('insertFromPaste', () => pasteFlow(edytor, { lines }));
82
+ const target = at && edytor.idToBlock.get(at.block);
83
+ if (at && target)
84
+ edytor.dispatcher.run('insertFromDrop', () => placeNew(edytor, placed.map(({ kind, id }) => ({ id, type: kind.type })), target, at.position));
85
+ else
86
+ edytor.dispatcher.run('insertFromPaste', () => pasteFlow(edytor, { lines }));
65
87
  for (const { file, kind, id } of placed) {
66
88
  // Refused, or placed as no such block (a code line takes plain lines, `flow.lines`).
67
89
  if (!edytor.facade.isVisibleBlock(id) || edytor.facade.blockTypeOf(id) !== kind.type)
68
90
  continue;
69
- const preview = kind.preview && typeof URL.createObjectURL === 'function'
70
- ? URL.createObjectURL(file)
71
- : null;
72
- this.#uploads.set(id, { status: 'uploading', preview, progress: null });
73
- void this.#send(id, kind, file, preview);
91
+ void this.#send(id, kind, file);
92
+ }
93
+ };
94
+ /**
95
+ * Upload `file`, picked on the empty block `id` (its Upload), with its
96
+ * kind's uploader, the block showing it meanwhile, and hand the answer to
97
+ * `land` before the preview goes (so the block never shows nothing
98
+ * between them): `false` when it failed (the block then shows no
99
+ * upload; the caller says why).
100
+ */
101
+ pick = async (id, file, land) => {
102
+ const kind = this.edytor.idToBlock.get(id)?.definition.upload;
103
+ if (!kind)
104
+ return false;
105
+ try {
106
+ land(await this.#run(id, kind, file));
107
+ return true;
108
+ }
109
+ catch {
110
+ return false;
111
+ }
112
+ finally {
113
+ this.#settle(id);
74
114
  }
75
115
  };
76
116
  /** Forget the block's upload (a source was embedded: its error, or held data, no longer applies). */
@@ -99,12 +139,24 @@ export class FileUploads {
99
139
  };
100
140
  /** Release every preview. */
101
141
  destroy = () => {
102
- for (const upload of this.#uploads.values())
103
- if (upload.status === 'uploading' && upload.preview)
104
- URL.revokeObjectURL(upload.preview);
142
+ this.#uploads.forEach(revoke);
105
143
  this.#uploads.clear();
106
144
  };
107
- #send = async (id, kind, file, preview) => {
145
+ /**
146
+ * Send `file` with the kind's uploader, block `id` showing it meanwhile
147
+ * (`uploading`: its object URL, name, size and progress): the answer.
148
+ * The object URL stays until `#settle` (after the answer is stored, so
149
+ * the block never shows nothing between them).
150
+ */
151
+ #run = (id, kind, file) => {
152
+ const preview = typeof URL.createObjectURL === 'function' ? URL.createObjectURL(file) : null;
153
+ this.#uploads.set(id, {
154
+ status: 'uploading',
155
+ preview,
156
+ name: file.name,
157
+ size: file.size,
158
+ progress: null
159
+ });
108
160
  const report = {
109
161
  progress: (fraction) => {
110
162
  const upload = this.#uploads.get(id);
@@ -113,28 +165,40 @@ export class FileUploads {
113
165
  this.#uploads.set(id, { ...upload, progress: Math.min(1, Math.max(0, fraction)) });
114
166
  }
115
167
  };
168
+ return kind.send(file, report);
169
+ };
170
+ /** The upload of `id` ended: revoke its object URL and forget it, if it still runs. */
171
+ #settle = (id) => {
172
+ const upload = this.#uploads.get(id);
173
+ if (upload?.status !== 'uploading')
174
+ return;
175
+ revoke(upload);
176
+ this.#uploads.delete(id);
177
+ };
178
+ #send = async (id, kind, file) => {
116
179
  let data;
117
180
  try {
118
- data = kind.data(file, await this.upload(file, report));
181
+ data = kind.data(file, await this.#run(id, kind, file));
119
182
  }
120
183
  catch {
121
184
  data = null;
122
185
  }
123
186
  const { edytor } = this;
124
- if (preview)
125
- URL.revokeObjectURL(preview);
126
187
  if (edytor.destroyed)
127
188
  return;
128
189
  const block = edytor.idToBlock.get(id);
190
+ const upload = this.#uploads.get(id);
129
191
  // Given a source meanwhile (a peer, the link field): nothing to fill.
130
192
  if (block?.isInTree && kind.filled(block.data))
131
- return this.#uploads.delete(id);
132
- if (!data)
133
- return this.#uploads.set(id, { status: 'failed' });
193
+ this.#uploads.delete(id);
194
+ else if (!data)
195
+ this.#uploads.set(id, { status: 'failed' });
134
196
  // Not shown (undone, deleted): held until it shows again (`fill`).
135
- if (!block?.isInTree)
136
- return this.#uploads.set(id, { status: 'landed', data });
137
- this.#write(id, data);
197
+ else if (!block?.isInTree)
198
+ this.#uploads.set(id, { status: 'landed', data });
199
+ else
200
+ this.#write(id, data);
201
+ revoke(upload);
138
202
  };
139
203
  /** The data's write, outside the history; refused (readonly), it is held for the next show. */
140
204
  #write = (id, data) => {
@@ -150,18 +214,30 @@ export class FileUploads {
150
214
  const showsText = (html) => !!html &&
151
215
  typeof DOMParser !== 'undefined' &&
152
216
  !!new DOMParser().parseFromString(html, 'text/html').body.textContent?.trim();
153
- const views = new WeakMap();
154
- /** The view's uploads, when it lists the uploads plugin. */
155
- export const fileUploads = (edytor) => edytor && views.get(edytor);
217
+ /** The view's uploads, when a media plugin with an `upload` is listed. */
218
+ export const fileUploads = (edytor) => {
219
+ const uploads = viewUploads(edytor);
220
+ return uploads instanceof FileUploads ? uploads : undefined;
221
+ };
156
222
  /**
157
- * File uploads (Notion's): the view's one uploader, for every kind whose
158
- * record takes files (`upload`: the image, file, video and audio kinds).
159
- * Without it, only links are embedded. The first listed claims the view.
223
+ * The hooks of a media plugin given an `upload` (its kind's record carries
224
+ * it, `KindUpload.send`): the first such plugin listed in a view claims the
225
+ * view's one `FileUploads` and runs its paste hook (a drop replays it) and
226
+ * teardown; the others add none.
160
227
  */
161
- export const createUploadsPlugin = ({ upload }) => (edytor) => {
162
- if (!edytor || views.has(edytor))
228
+ export const uploadHooks = (edytor, upload) => {
229
+ if (!edytor || !upload || viewUploads(edytor))
163
230
  return {};
164
- const uploads = new FileUploads(edytor, upload);
165
- views.set(edytor, uploads);
166
- return { onPaste: uploads.paste, onEdytorAttached: () => uploads.destroy };
231
+ const uploads = new FileUploads(edytor);
232
+ claimUploads(edytor, uploads);
233
+ return {
234
+ onPaste: uploads.paste,
235
+ onEdytorAttached: ({ node }) => {
236
+ const detach = uploads.drop.attach(node);
237
+ return () => {
238
+ detach();
239
+ uploads.destroy();
240
+ };
241
+ }
242
+ };
167
243
  };
package/dist/plugins.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { Edytor } from './edytor.svelte.js';
3
3
  import type { Block } from './block/block.svelte.js';
4
+ import type { BlockMovePosition } from './session/moves.js';
4
5
  import type { JSONBlock, JSONDoc, JSONInlineBlock, JSONText } from './utils/json.js';
5
6
  import type { Text } from './text/text.svelte.js';
6
7
  import type { TextTransform } from './surface/cells.js';
@@ -42,7 +43,12 @@ export type { Prevent };
42
43
  export type BlockView<D = Record<string, any>> = {
43
44
  readonly id: string;
44
45
  readonly type: string;
45
- readonly data: Readonly<D>;
46
+ /**
47
+ * The block's data as plain state (`block.data`): reactive at any depth,
48
+ * and writable, each write a dispatched `patchData`. In a suggestion's
49
+ * preview it is a copy: a write there changes nothing.
50
+ */
51
+ readonly data: D;
46
52
  readonly selected: boolean;
47
53
  readonly focused: boolean;
48
54
  /**
@@ -241,7 +247,10 @@ export type TextRuleContext = {
241
247
  };
242
248
  /** What an input rule's `replace` receives besides the match. */
243
249
  export type InputRuleContext = TextRuleContext & {
244
- /** The typed text that completed the match: part of the match, not in the document. */
250
+ /**
251
+ * The typed text that completed the match: the end of the match, already
252
+ * in the document as its own undo step (`to` is after it).
253
+ */
245
254
  typed: string;
246
255
  /**
247
256
  * Remove the matched text (`from` to `to`) in the same plan as the first
@@ -267,13 +276,14 @@ export type InputRule = {
267
276
  */
268
277
  find: RegExp;
269
278
  /**
270
- * The replacement. Return a string to replace the whole match with it
271
- * (one command and one undo step with the matched text's removal; the
272
- * caret after it), or write it yourself and return `true`, usually
273
- * through `ctx.remove(() => …)`. Return `false` or `null`, writing
274
- * nothing, to decline: the next rule is asked, then the typed text is
275
- * inserted as usual, as it is when the replacement is refused (by the
276
- * document, readonly or a plugin's veto).
279
+ * The replacement, run once the typed text is in the document (its own
280
+ * undo step, so the first undo gives the typed text back). Return a
281
+ * string to replace the whole match with it (one command and one undo
282
+ * step with the matched text's removal; the caret after it), or write it
283
+ * yourself and return `true`, usually through `ctx.remove(() => …)`.
284
+ * Return `false` or `null`, writing nothing, to decline: the next rule is
285
+ * asked, and the typed text stays as typed, as it does when the
286
+ * replacement is refused (by the document, readonly or a plugin's veto).
277
287
  */
278
288
  replace: (match: RegExpExecArray, ctx: InputRuleContext) => string | boolean | null | void;
279
289
  };
@@ -292,6 +302,11 @@ export type TriggerContext = TextRuleContext & {
292
302
  export type Trigger<T = any> = {
293
303
  /** The text that opens the menu (one or two characters: `@`, `[[`). */
294
304
  char: string;
305
+ /**
306
+ * The menu's name, read by its markup as `controller.trigger.name` (the
307
+ * mention plugin's `kind`: one menu snippet can serve several triggers).
308
+ */
309
+ name?: string;
295
310
  /** The rows for `query`, at once or as a promise (a search); the newest query wins. */
296
311
  items: (query: string, ctx: TriggerContext) => readonly T[] | Promise<readonly T[]>;
297
312
  /**
@@ -397,10 +412,18 @@ export type PluginOperations = {
397
412
  prevent: Prevent;
398
413
  e: ClipboardEvent;
399
414
  }) => void;
400
- /** Called when a paste event is detected */
415
+ /**
416
+ * Called when a paste event is detected, and for a drop (its data as the
417
+ * clipboard's). `at`: where files dropped from outside land, the drop
418
+ * indicator's placement (beside, before, after or inside `block`).
419
+ */
401
420
  onPaste?: (payload: {
402
421
  prevent: Prevent;
403
422
  e: ClipboardEvent;
423
+ at?: {
424
+ block: string;
425
+ position: BlockMovePosition;
426
+ };
404
427
  }) => void;
405
428
  };
406
429
  /**
@@ -493,9 +516,11 @@ export type BlockDefinition = {
493
516
  */
494
517
  rendersContent?: boolean;
495
518
  /**
496
- * The files the kind takes (the image's `image/*`, the file block's any):
497
- * with the uploads plugin listed, an empty block's Upload sends one and a
498
- * pasted or dropped file becomes a block of this kind (`media.files`).
519
+ * The files the kind takes (the image's `image/*`, the file block's any)
520
+ * and who sends them (`send`: the media plugins set it from their
521
+ * `upload` option): an empty block's Upload sends one, and a pasted or
522
+ * dropped file becomes a block of this kind (`media.files`). A kind
523
+ * without it takes no files.
499
524
  */
500
525
  upload?: KindUpload;
501
526
  /**
@@ -556,6 +581,15 @@ export type BlockDefinition = {
556
581
  html?: string | ((block: JSONBlock, content: string, children: string) => string);
557
582
  /** Clipboard plain-text form; default content then children, one per line. */
558
583
  plain?: (block: JSONBlock, content: string, children: string) => string;
584
+ /**
585
+ * Markdown form (`toMarkdown`): `content` is the block's text as inline
586
+ * markdown (marks by their own `markdown` forms), `children` its children's
587
+ * markdown, blocks apart by a blank line (indent them yourself:
588
+ * {@link MarkdownContext.indent}). Default: the bundled kind's of that
589
+ * name (`edytor/export` holds them; the bundled records carry none), else
590
+ * the `plain` form when the kind has one, else the content, then the children.
591
+ */
592
+ markdown?: (block: JSONBlock, content: string, children: string, context: MarkdownContext) => string;
559
593
  /**
560
594
  * HTML import: the block's data when `element` is this kind, else
561
595
  * `undefined`. Checked before the tag tables (a preset's export form or
@@ -609,7 +643,8 @@ export type BlockDefinition = {
609
643
  export type InlineBlockView<D = Record<string, any>> = {
610
644
  readonly id: string;
611
645
  readonly type: string;
612
- readonly data: Readonly<D>;
646
+ /** The atom's data as plain state (`atom.data`), writable; a suggested atom's is a copy. */
647
+ readonly data: D;
613
648
  readonly selected: boolean;
614
649
  /** The atom's id-only handle; a suggested atom has none. */
615
650
  readonly handle: InlineBlock | undefined;
@@ -626,6 +661,8 @@ export type InlineBlockDefinition = {
626
661
  * an empty `<span data-edytor-inline-block>`.
627
662
  */
628
663
  html?: (data: JSONInlineBlock['data']) => string;
664
+ /** Markdown form of the atom (`toMarkdown`, escaped by you); default its `plain` form. */
665
+ markdown?: (data: JSONInlineBlock['data']) => string;
629
666
  /**
630
667
  * HTML import: the atom's data when `element`, met in a line's inline
631
668
  * content, is this atom, else `undefined`. Its contents are then not read
@@ -652,6 +689,12 @@ export type MarkDefinition = {
652
689
  * matches its bare `tag` (value `true`).
653
690
  */
654
691
  parse?: (element: HTMLElement) => SerializableContent | undefined;
692
+ /**
693
+ * Markdown form (`toMarkdown`): the delimiters around the marked text
694
+ * (`['**', '**']`), or a function of the value giving them (a link's
695
+ * `['[', '](href)']`). Without one the text is exported unmarked.
696
+ */
697
+ markdown?: readonly [string, string] | ((value: any) => readonly [string, string] | undefined);
655
698
  void?: boolean;
656
699
  /** Whether typing at the mark's edges extends it (`marksForInsertion`); default `inclusive`. */
657
700
  edge?: MarkEdge;
@@ -729,3 +772,12 @@ export type EditorCommand = {
729
772
  searchOnly?: boolean;
730
773
  };
731
774
  export type InitializedPlugin = ReturnType<Plugin>;
775
+ /** What a kind's `markdown` form is given beside its block, content and children. */
776
+ export type MarkdownContext = {
777
+ /** The block's position in its run of same-kind siblings, from 1 (a numbered item's number). */
778
+ index: number;
779
+ /** Every line of `text` prefixed with `prefix` (`' '` under a list item, `'> '` in a quote). */
780
+ indent: (text: string, prefix: string) => string;
781
+ /** Rich text as inline markdown, by the marks' and atoms' forms (a table cell's). */
782
+ inline: (content: JSONBlock['content']) => string;
783
+ };
package/dist/plugins.js CHANGED
@@ -1,25 +1 @@
1
1
  export {};
2
- // Extension-surface note (replaces stale TODOs): the gaps this used to
3
- // list are all covered by the contract above —
4
- // * `onBeforeInput` is a PluginOperations hook (line ~111).
5
- // * Block focus/selection lifecycle is `onFocus`/`onBlur`/`onSelect`/
6
- // `onDeselect` on BlockDefinition.
7
- // * Arrow-key and select-all behavior are hotkeys, not dedicated
8
- // events: `mod+a` select-all lives in hotkeys.ts and arrow handling
9
- // ships in the arrowMove plugin — plugins can override either via
10
- // the `hotkeys` definition map (prevention is first-win).
11
- // A dedicated raw-key event hook does not exist; add one only if a real
12
- // consumer needs more than hotkey overrides.
13
- // ISLAND BLOCKS
14
- // Island blocks are blocks that are independent of the document.
15
- // They are editable
16
- // They can be nested inside other blocks
17
- // But their children can not be nested or merged with other blocks
18
- // Their structure will remain as if or will not.
19
- // In case of a merge operation, the island block will be merged with the parent block and all its children will ne unnest and set to the default block type without being added as children of the merge destination block. The same wil happen to the island's content.
20
- // VOID BLOCKS
21
- // Void blocks are blocks that are not editable
22
- // They can be nested inside other blocks
23
- // They can not accept children nor be merged with other blocks
24
- // If the content of the void block is rendered, it will still be editable. This to allow for rendering caption of the void block. I
25
- // In void blocks the content is treated as an island