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,9 +1,7 @@
1
- import { placeNear } from '../../surface/overlay.js';
2
- import { onPress } from '../../events/onFocus.js';
3
1
  import { convertToKind, holdsNothing } from '../../kinds.js';
4
2
  import { pastedLink } from '../richtext/richTextOperations.js';
5
3
  import { safeWebUrl } from './media.js';
6
- import { mountChrome } from '../chrome.js';
4
+ import { mountChrome, onPressOutside, virtualAnchor } from '../chrome.js';
7
5
  import { ChromeList, inPage, listHotkeys } from '../list.svelte.js';
8
6
  /**
9
7
  * Pasting a bare URL on an empty line (Notion): the URL lands as a link at
@@ -27,9 +25,6 @@ export class UrlPasteController extends ChromeList {
27
25
  get count() {
28
26
  return this.open?.options.length ?? 0;
29
27
  }
30
- get readonly() {
31
- return this.edytor.readonly;
32
- }
33
28
  /** A custom menu (a media plugin's `menu`): the first one a plugin making an offer passed. */
34
29
  #menu;
35
30
  /** Take `menu` as the view's menu, unless a plugin listed before passed one. @internal */
@@ -70,7 +65,7 @@ export class UrlPasteController extends ChromeList {
70
65
  : {};
71
66
  edytor.dispatcher.run('insertFromPaste', () => {
72
67
  edytor.insertText(block.id, text.segStart, url, marks);
73
- edytor.dispatcher.caret(edytor.idToBlock.get(block.id)?.firstText, url.length);
68
+ edytor.dispatcher.caretIn(edytor.idToBlock.get(block.id)?.firstText, url.length);
74
69
  });
75
70
  if (edytor.dispatcher.last?.status !== 'applied')
76
71
  return;
@@ -122,12 +117,7 @@ export class UrlPasteController extends ChromeList {
122
117
  this.close();
123
118
  },
124
119
  onEdytorAttached: ({ node }) => {
125
- const outside = (event) => {
126
- const target = event.target;
127
- if (!target?.closest?.('[data-edytor-url-paste-host]'))
128
- this.close();
129
- };
130
- const offPress = onPress(this.edytor, node.ownerDocument, outside, true);
120
+ const offPress = onPressOutside(this.edytor, node.ownerDocument, '[data-edytor-url-paste-host]', this.close);
131
121
  const unmount = mountChrome(this.edytor, this.#menu, this, () => this.open, { name: 'edytor-url-paste-host', zIndex: 50, measure: this.measure }, this.close);
132
122
  return () => {
133
123
  offPress();
@@ -136,16 +126,17 @@ export class UrlPasteController extends ChromeList {
136
126
  };
137
127
  }
138
128
  });
139
- /** Under the pasted line, kept in the viewport; a gone line closes the menu. @internal */
140
- measure = (host) => {
129
+ /** A gone line closes the menu (measured in the overlay's frame). @internal */
130
+ measure = () => {
141
131
  const { open, edytor } = this;
142
- if (!open)
143
- return;
144
- const block = edytor.idToBlock.get(open.block);
145
- if (!block?.isInTree)
132
+ if (open && !edytor.idToBlock.get(open.block)?.isInTree)
146
133
  return () => this.close();
147
- const rect = (block.firstText?.node ?? block.node)?.getBoundingClientRect();
148
- return rect && placeNear(host, rect, [220, 120], { gap: 4 });
134
+ };
135
+ /** Where the menu stands (`ChromeAnchor`): the pasted line, which your `menu` places itself under. */
136
+ anchor = virtualAnchor(() => this.#line()?.getBoundingClientRect(), () => this.#line());
137
+ #line = () => {
138
+ const block = this.open && this.edytor.idToBlock.get(this.open.block);
139
+ return block ? (block.firstText?.node ?? block.node) : undefined;
149
140
  };
150
141
  }
151
142
  const controllers = new WeakMap();
@@ -1,10 +1,18 @@
1
1
  <svelte:options runes />
2
2
 
3
3
  <script module lang="ts">
4
+ import { mentionForms } from '../../export/forms.js';
5
+ import './mention.css';
4
6
  import type { Snippet } from 'svelte';
5
- import type { InlineBlockSnippetPayload, Plugin, TriggerContext } from '../../plugins.js';
7
+ import type {
8
+ InlineBlockDefinition,
9
+ InlineBlockSnippetPayload,
10
+ InlineBlockView,
11
+ Plugin,
12
+ TriggerContext
13
+ } from '../../plugins.js';
6
14
  import type { TriggerMenuController } from '../triggers/TriggerController.svelte.js';
7
- import { withRulesAndTriggers } from '../triggers/triggers.js';
15
+ import { placeAtom, withRulesAndTriggers } from '../triggers/triggers.js';
8
16
 
9
17
  /** A person the mention menu offers: what your app's directory answers. */
10
18
  export type MentionItem = {
@@ -18,7 +26,7 @@
18
26
  avatar?: string;
19
27
  };
20
28
 
21
- /** What a `mention` atom stores. */
29
+ /** What a mention atom stores (whatever its kind). */
22
30
  export type MentionData = { id: string; label: string };
23
31
 
24
32
  export type MentionPluginOptions = {
@@ -27,8 +35,15 @@
27
35
  query: string,
28
36
  ctx: TriggerContext
29
37
  ) => readonly MentionItem[] | Promise<readonly MentionItem[]>;
30
- /** The character that opens the menu (default `@`). */
38
+ /** The character that opens the menu, and that the atom shows before its label (default `@`). */
31
39
  char?: string;
40
+ /**
41
+ * The inline kind the atoms are (default `mention`), and the name of the
42
+ * trigger menu (`controller.trigger.name`). List the plugin once per kind
43
+ * to mention several things (`@` people, `$` tickers as `ticker`); two
44
+ * instances of one kind: the first listed defines it.
45
+ */
46
+ kind?: string;
32
47
  /**
33
48
  * The menu's markup and its words; it renders while `controller.isOpen`,
34
49
  * at the caret (without it, nothing shows). Keep its ARIA with `{@attach
@@ -38,61 +53,58 @@
38
53
  menu?: Snippet<[TriggerMenuController<MentionItem>]>;
39
54
  };
40
55
 
56
+ /** A mention kind's record: its `char` read by the atom's markup. */
57
+ type MentionKind = InlineBlockDefinition & { char: string };
58
+
59
+ /** The char of the atom's kind (the first listed instance's); `@` with no view (a suggestion). */
60
+ const charOf = (atom: InlineBlockView) =>
61
+ (atom.handle?.edytor.inlineBlocks.get(atom.type) as Partial<MentionKind> | undefined)?.char ??
62
+ '@';
63
+
41
64
  /**
42
65
  * People mentions, as in Notion: `@` opens a menu of the people your app's
43
66
  * `items(query)` answers; picking one replaces `@query` with a `mention`
44
67
  * atom (`{ id, label }`) and puts the caret after it, in one undo step.
68
+ * `char` and `kind` make another one (`$` tickers as `ticker` atoms).
45
69
  */
46
- export const createMentionPlugin =
47
- (options: MentionPluginOptions): Plugin =>
48
- (edytor) =>
70
+ export const createMentionPlugin = (options: MentionPluginOptions): Plugin => {
71
+ const char = options.char ?? '@';
72
+ const kind = options.kind ?? 'mention';
73
+ const atom: MentionKind = {
74
+ snippet: mention,
75
+ char,
76
+ ...mentionForms(kind, char)
77
+ };
78
+ return (edytor) =>
49
79
  withRulesAndTriggers(
50
80
  edytor,
51
- {
52
- inlineBlocks: {
53
- mention: {
54
- snippet: mention,
55
- plain: (data) => {
56
- const label = (data as Partial<MentionData> | undefined)?.label;
57
- return typeof label === 'string' && label ? `@${label}` : '@';
58
- }
59
- }
60
- }
61
- },
81
+ { inlineBlocks: { [kind]: atom } },
62
82
  {
63
83
  triggers: [
64
84
  {
65
- char: options.char ?? '@',
85
+ char,
86
+ name: kind,
66
87
  items: (query, ctx) => options.items(query, ctx),
67
- label: (person: MentionItem) => person.label,
68
- key: (person: MentionItem) => person.id,
88
+ label: (item: MentionItem) => item.label,
89
+ key: (item: MentionItem) => item.id,
69
90
  menu: options.menu,
70
- onPick: (person: MentionItem, { edytor, block, from, caret }) => {
71
- const data: MentionData = { id: person.id, label: person.label };
72
- const written =
73
- edytor.insertInline(block.id, from, { type: 'mention', data }).status ===
74
- 'applied';
75
- if (written) caret(from + 1);
76
- return written;
77
- }
91
+ onPick: (item: MentionItem, ctx) =>
92
+ placeAtom(ctx, {
93
+ type: kind,
94
+ data: { id: item.id, label: item.label } satisfies MentionData
95
+ })
78
96
  }
79
97
  ]
80
98
  }
81
99
  );
100
+ };
82
101
  </script>
83
102
 
84
103
  {#snippet mention({ block }: InlineBlockSnippetPayload<Partial<MentionData>>)}
85
104
  <span
86
105
  class="edytor-mention"
87
106
  data-selected={block.selected ? 'true' : undefined}
88
- data-edytor-mention
89
- data-mention-id={block.data.id}>@{block.data.label ?? ''}</span
107
+ data-edytor-mention={block.type}
108
+ data-mention-id={block.data.id}>{charOf(block)}{block.data.label ?? ''}</span
90
109
  >
91
110
  {/snippet}
92
-
93
- <style>
94
- /* One unit, on one line; its look is a theme's. */
95
- .edytor-mention {
96
- white-space: nowrap;
97
- }
98
- </style>
@@ -1,3 +1,4 @@
1
+ import './mention.css';
1
2
  import type { Snippet } from 'svelte';
2
3
  import type { Plugin, TriggerContext } from '../../plugins.js';
3
4
  import type { TriggerMenuController } from '../triggers/TriggerController.svelte.js';
@@ -12,7 +13,7 @@ export type MentionItem = {
12
13
  /** An avatar image URL (http, https or data). */
13
14
  avatar?: string;
14
15
  };
15
- /** What a `mention` atom stores. */
16
+ /** What a mention atom stores (whatever its kind). */
16
17
  export type MentionData = {
17
18
  id: string;
18
19
  label: string;
@@ -20,8 +21,15 @@ export type MentionData = {
20
21
  export type MentionPluginOptions = {
21
22
  /** The people matching `query` (the text typed after `@`), at once or as a promise. */
22
23
  items: (query: string, ctx: TriggerContext) => readonly MentionItem[] | Promise<readonly MentionItem[]>;
23
- /** The character that opens the menu (default `@`). */
24
+ /** The character that opens the menu, and that the atom shows before its label (default `@`). */
24
25
  char?: string;
26
+ /**
27
+ * The inline kind the atoms are (default `mention`), and the name of the
28
+ * trigger menu (`controller.trigger.name`). List the plugin once per kind
29
+ * to mention several things (`@` people, `$` tickers as `ticker`); two
30
+ * instances of one kind: the first listed defines it.
31
+ */
32
+ kind?: string;
25
33
  /**
26
34
  * The menu's markup and its words; it renders while `controller.isOpen`,
27
35
  * at the caret (without it, nothing shows). Keep its ARIA with `{@attach
@@ -34,6 +42,7 @@ export type MentionPluginOptions = {
34
42
  * People mentions, as in Notion: `@` opens a menu of the people your app's
35
43
  * `items(query)` answers; picking one replaces `@query` with a `mention`
36
44
  * atom (`{ id, label }`) and puts the caret after it, in one undo step.
45
+ * `char` and `kind` make another one (`$` tickers as `ticker` atoms).
37
46
  */
38
47
  export declare const createMentionPlugin: (options: MentionPluginOptions) => Plugin;
39
48
  declare const MentionPlugin: import("svelte").Component<Record<string, never>, {}, "">;
@@ -0,0 +1,6 @@
1
+ /* Loaded by the plugin's module (a component's own <style> is dropped when only
2
+ its module is used: the plugin factory, never the component). */
3
+ /* One unit, on one line; its look is a theme's. */
4
+ .edytor-mention {
5
+ white-space: nowrap;
6
+ }
@@ -1,4 +1,5 @@
1
1
  <script module lang="ts">
2
+ import { pageForms } from '../../export/forms.js';
2
3
  import type { Plugin, BlockSnippetPayload } from '../../plugins.js';
3
4
  import { pageKinds } from '../../crdt/semantics.js';
4
5
  import { convertToKind, type KindRow } from '../../kinds.js';
@@ -6,7 +7,6 @@
6
7
  import { getSelectedBlocksInDocumentOrder } from '../../selection/replaceSelection.js';
7
8
  import PageLink from './PageLink.svelte';
8
9
  import {
9
- pageHtml,
10
10
  pageIdOf,
11
11
  pageTitleOf,
12
12
  pageOptions as optionsOf,
@@ -63,15 +63,8 @@
63
63
  ...pageKinds.page,
64
64
  snippet: page,
65
65
  empty: { content: [], children: [] },
66
- html: (block) => pageHtml(block.data, own, labels.untitled),
67
- plain: (block) => pageTitleOf(block.data?.title) ?? labels.untitled,
68
66
  // Its own export: a `p[data-edytor-page]` naming the page.
69
- parse: (el) => {
70
- const pageId = pageIdOf(el.getAttribute('data-edytor-page'));
71
- if (el.localName !== 'p' || !pageId) return;
72
- const title = pageTitleOf(el.textContent);
73
- return { pageId, ...(title && { title }) };
74
- }
67
+ ...pageForms(own, labels.untitled).page
75
68
  }
76
69
  },
77
70
  commands: create
@@ -1,4 +1,4 @@
1
- import { sanitizeLinkHref } from '../richtext/richTextOperations.js';
1
+ import { sanitizeLinkHref } from '../../utils/sanitize.js';
2
2
  import { escapeHtml } from '../../clipboard/serializeClipboardFragment.js';
3
3
  /** Each view's page options (the first page plugin listed sets them). */
4
4
  export const pageOptions = new WeakMap();
@@ -1,10 +1,19 @@
1
1
  <svelte:options runes />
2
2
 
3
3
  <script module lang="ts">
4
+ import { pageLinkForms } from '../../export/forms.js';
5
+ import './pageLink.css';
4
6
  import type { Snippet } from 'svelte';
5
- import type { InlineBlockSnippetPayload, Plugin, TriggerContext } from '../../plugins.js';
7
+ import type {
8
+ InlineBlockSnippetPayload,
9
+ Plugin,
10
+ PluginOperations,
11
+ TriggerContext
12
+ } from '../../plugins.js';
13
+ import type { Edytor } from '../../edytor.svelte.js';
14
+ import type { TextAnchor } from '../../selection/selection.svelte.js';
6
15
  import type { TriggerMenuController } from '../triggers/TriggerController.svelte.js';
7
- import { withRulesAndTriggers } from '../triggers/triggers.js';
16
+ import { placeAtom, withRulesAndTriggers } from '../triggers/triggers.js';
8
17
  import { pageLinkEnglish, labelsOf } from '../../labels.js';
9
18
  import { sanitizeLinkHref } from '../richtext/richTextOperations.js';
10
19
 
@@ -31,6 +40,18 @@
31
40
  ) => readonly PageLinkItem[] | Promise<readonly PageLinkItem[]>;
32
41
  /** A page's URL when the search answers none (`/pages/<id>`, say). */
33
42
  href?: (page: PageLinkItem) => string | undefined;
43
+ /**
44
+ * The page a pasted text names (your app's URL for it, `page:roadmap`…),
45
+ * at once or as a promise; `null` when it names none. Asked for a plain
46
+ * text paste at a caret that is one item (trimmed, one line): a page
47
+ * turns the pasted text into a `pageLink` atom, an undo step of its own
48
+ * after the paste, so undo gives the text back. A promise lets the paste
49
+ * land as it would without this option and swaps it when it answers, if
50
+ * the text is still there.
51
+ */
52
+ fromPaste?: (
53
+ text: string
54
+ ) => PageLinkItem | null | undefined | Promise<PageLinkItem | null | undefined>;
34
55
  /** The text that opens the menu (default `[[`). */
35
56
  char?: string;
36
57
  /**
@@ -42,11 +63,107 @@
42
63
  menu?: Snippet<[TriggerMenuController<PageLinkItem>]>;
43
64
  };
44
65
 
66
+ /** The atom for `page`, its href sanitized. */
67
+ const atomOf = (page: PageLinkItem, options: PageLinkPluginOptions) => {
68
+ const href = sanitizeLinkHref(page.href ?? options.href?.(page)) ?? undefined;
69
+ const data: PageLinkData = {
70
+ id: page.id,
71
+ title: page.title,
72
+ ...(href ? { href } : {}),
73
+ ...(page.icon ? { icon: page.icon } : {})
74
+ };
75
+ return { type: 'pageLink', data };
76
+ };
77
+
78
+ const isPromise = <T,>(value: unknown): value is Promise<T> =>
79
+ typeof (value as Promise<T> | null)?.then === 'function';
80
+
81
+ /**
82
+ * Swap the pasted `item`, where the paste put it (right after `start`,
83
+ * past leading spaces), for `page`'s atom: one `autolink` step, so undo
84
+ * gives the text back. Nothing when the view is readonly or the text is
85
+ * no longer there; the caret follows the atom when it was at the text's end.
86
+ */
87
+ const swapPasted = (
88
+ edytor: Edytor,
89
+ start: TextAnchor,
90
+ item: string,
91
+ page: PageLinkItem,
92
+ options: PageLinkPluginOptions
93
+ ) => {
94
+ if (edytor.readonly) return;
95
+ const at = edytor.selection.resolveTextAnchor(start);
96
+ if (!at) return;
97
+ const { text } = at;
98
+ const content = text.stringContent;
99
+ let from = at.offset;
100
+ while (from < content.length && /\s/.test(content[from])) from++;
101
+ if (content.slice(from, from + item.length) !== item) return;
102
+ const block = text.parent;
103
+ const offset = text.segStart + from;
104
+ const caret = edytor.selection.caret;
105
+ const follows = caret?.block === block && caret.offset === offset + item.length;
106
+ edytor.dispatcher.run('autolink', () => {
107
+ if (edytor.deleteText(block.id, offset, item.length).status !== 'applied') return;
108
+ edytor.insertInline(block.id, offset, atomOf(page, options));
109
+ if (follows) edytor.dispatcher.caret({ block, offset: offset + 1 });
110
+ });
111
+ };
112
+
113
+ /**
114
+ * The paste hook of `fromPaste` (it runs before the other plugins'
115
+ * `onPaste`, `pasteFirst`): a plain text paste at a caret in a text,
116
+ * outside islands and voids, that is one item. A page answered at once
117
+ * claims the paste (the text, then the swap); a promise claims nothing
118
+ * (the rich text link, the URL menu or the plain paste take it) and swaps
119
+ * what landed when it answers a page.
120
+ */
121
+ const pasteHook = (edytor: Edytor, options: PageLinkPluginOptions): PluginOperations['onPaste'] =>
122
+ options.fromPaste &&
123
+ (({ e, prevent, at }) => {
124
+ const { fromPaste } = options;
125
+ if (!fromPaste || at || edytor.readonly) return;
126
+ const plain = e.clipboardData?.getData('text/plain') ?? '';
127
+ const item = plain.trim();
128
+ if (!item || /[\r\n]/.test(item)) return;
129
+ const { value, projection } = edytor.selection;
130
+ if (value.kind !== 'text' || !projection.isCollapsed) return;
131
+ if (projection.islandRoot !== null || projection.voidRoot !== null) return;
132
+ const { startText: text, yStart } = edytor.selection.textRange();
133
+ if (!text) return;
134
+ const start = edytor.selection.createTextAnchor(text, yStart, 'left');
135
+ if (!start) return;
136
+ let answer: ReturnType<typeof fromPaste>;
137
+ try {
138
+ answer = fromPaste(item);
139
+ } catch {
140
+ return;
141
+ }
142
+ if (isPromise<PageLinkItem | null | undefined>(answer)) {
143
+ answer.then(
144
+ (page) => page && swapPasted(edytor, start, item, page, options),
145
+ () => {}
146
+ );
147
+ return;
148
+ }
149
+ const page = answer;
150
+ if (!page) return;
151
+ prevent(() => {
152
+ edytor.dispatcher.run('insertFromPaste', () => {
153
+ edytor.insertText(text.parent.id, text.segStart + yStart, item);
154
+ edytor.dispatcher.caretIn(text, yStart + item.length);
155
+ });
156
+ if (edytor.dispatcher.last?.status === 'applied')
157
+ swapPasted(edytor, start, item, page, options);
158
+ });
159
+ });
160
+
45
161
  /**
46
162
  * Links to your app's pages, as Notion's `[[`: it opens a menu of the pages
47
163
  * your app's `search(query)` answers; picking one replaces `[[query` with
48
164
  * a `pageLink` atom (`{ id, title, href, icon }`), an `<a>` to the page,
49
- * and puts the caret after it, in one undo step.
165
+ * and puts the caret after it, in one undo step. With `fromPaste`, a
166
+ * pasted text your app recognizes becomes a page link too.
50
167
  */
51
168
  export const createPageLinkPlugin =
52
169
  (options: PageLinkPluginOptions): Plugin =>
@@ -54,13 +171,13 @@
54
171
  withRulesAndTriggers(
55
172
  edytor,
56
173
  {
174
+ ...(edytor && options.fromPaste
175
+ ? { onPaste: pasteHook(edytor, options), pasteFirst: true }
176
+ : {}),
57
177
  inlineBlocks: {
58
178
  pageLink: {
59
- snippet: pageLink,
60
- plain: (data) => {
61
- const title = (data as Partial<PageLinkData> | undefined)?.title;
62
- return typeof title === 'string' ? title : '';
63
- }
179
+ ...pageLinkForms(),
180
+ snippet: pageLink
64
181
  }
65
182
  }
66
183
  },
@@ -72,19 +189,7 @@
72
189
  label: (page: PageLinkItem) => page.title,
73
190
  key: (page: PageLinkItem) => page.id,
74
191
  menu: options.menu,
75
- onPick: (page: PageLinkItem, { edytor, block, from, caret }) => {
76
- const href = sanitizeLinkHref(page.href ?? options.href?.(page)) ?? undefined;
77
- const data: PageLinkData = {
78
- id: page.id,
79
- title: page.title,
80
- ...(href ? { href } : {}),
81
- ...(page.icon ? { icon: page.icon } : {})
82
- };
83
- const atom = { type: 'pageLink', data };
84
- const written = edytor.insertInline(block.id, from, atom).status === 'applied';
85
- if (written) caret(from + 1);
86
- return written;
87
- }
192
+ onPick: (page: PageLinkItem, ctx) => placeAtom(ctx, atomOf(page, options))
88
193
  }
89
194
  ]
90
195
  }
@@ -104,10 +209,3 @@
104
209
  ></a
105
210
  >
106
211
  {/snippet}
107
-
108
- <style>
109
- /* One unit, on one line; its look is a theme's. */
110
- .edytor-page-link {
111
- white-space: nowrap;
112
- }
113
- </style>
@@ -1,3 +1,4 @@
1
+ import './pageLink.css';
1
2
  import type { Snippet } from 'svelte';
2
3
  import type { Plugin, TriggerContext } from '../../plugins.js';
3
4
  import type { TriggerMenuController } from '../triggers/TriggerController.svelte.js';
@@ -24,6 +25,16 @@ export type PageLinkPluginOptions = {
24
25
  search: (query: string, ctx: TriggerContext) => readonly PageLinkItem[] | Promise<readonly PageLinkItem[]>;
25
26
  /** A page's URL when the search answers none (`/pages/<id>`, say). */
26
27
  href?: (page: PageLinkItem) => string | undefined;
28
+ /**
29
+ * The page a pasted text names (your app's URL for it, `page:roadmap`…),
30
+ * at once or as a promise; `null` when it names none. Asked for a plain
31
+ * text paste at a caret that is one item (trimmed, one line): a page
32
+ * turns the pasted text into a `pageLink` atom, an undo step of its own
33
+ * after the paste, so undo gives the text back. A promise lets the paste
34
+ * land as it would without this option and swaps it when it answers, if
35
+ * the text is still there.
36
+ */
37
+ fromPaste?: (text: string) => PageLinkItem | null | undefined | Promise<PageLinkItem | null | undefined>;
27
38
  /** The text that opens the menu (default `[[`). */
28
39
  char?: string;
29
40
  /**
@@ -38,7 +49,8 @@ export type PageLinkPluginOptions = {
38
49
  * Links to your app's pages, as Notion's `[[`: it opens a menu of the pages
39
50
  * your app's `search(query)` answers; picking one replaces `[[query` with
40
51
  * a `pageLink` atom (`{ id, title, href, icon }`), an `<a>` to the page,
41
- * and puts the caret after it, in one undo step.
52
+ * and puts the caret after it, in one undo step. With `fromPaste`, a
53
+ * pasted text your app recognizes becomes a page link too.
42
54
  */
43
55
  export declare const createPageLinkPlugin: (options: PageLinkPluginOptions) => Plugin;
44
56
  declare const PageLinkPlugin: import("svelte").Component<Record<string, never>, {}, "">;
@@ -0,0 +1,6 @@
1
+ /* Loaded by the plugin's module (a component's own <style> is dropped when only
2
+ its module is used: the plugin factory, never the component). */
3
+ /* One unit, on one line; its look is a theme's. */
4
+ .edytor-page-link {
5
+ white-space: nowrap;
6
+ }