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,4 +1,5 @@
1
1
  <script module lang="ts">
2
+ import './richtext.css';
2
3
  import type { Plugin, BlockSnippetPayload, KindPreset, PlaceholderView } from '../../plugins.js';
3
4
  import { branded } from '../brand.js';
4
5
  import type { Edytor } from '../../edytor.svelte.js';
@@ -10,28 +11,47 @@
10
11
  openLink,
11
12
  pastedLink,
12
13
  richTextOperations,
13
- sanitizeLinkHref,
14
- sanitizeCssColorValue,
15
14
  typedLink,
16
15
  type RichTextMark
17
16
  } from './richTextOperations.js';
18
17
  import { marksForInsertion } from '../../session/editing/text.js';
19
18
  import { firstUriListEntry } from '../../events/dataTransferPayload.js';
20
19
  import { flipToggles, shownSelectionBlocks } from '../../selection/replaceSelection.js';
21
- import { mediaKinds, richTextKinds, richTextMarks } from '../../crdt/semantics.js';
20
+ import { mediaKinds, richTextKinds } from '../../crdt/semantics.js';
22
21
  import { richTextEnglish, keywordsOf, labelsOf, type RichTextLabels } from '../../labels.js';
23
22
  import TodoCheckbox from './TodoCheckbox.svelte';
24
23
  import EmptyBody from './EmptyBody.svelte';
25
24
  import CalloutIcon from './CalloutIcon.svelte';
26
25
  import RichTextControl from './RichTextControl.svelte';
27
- import {
28
- DEFAULT_CALLOUT_ICON,
29
- calloutHtml,
30
- calloutIcons,
31
- calloutPickers,
32
- iconOf
33
- } from './callouts.js';
26
+ import { DEFAULT_CALLOUT_ICON, calloutIcons, calloutPickers, iconOf } from './callouts.js';
34
27
  import { listen } from '../../utils/listen.js';
28
+ import {
29
+ HEADING_LEVELS,
30
+ calloutRead,
31
+ calloutWrite,
32
+ dividerRead,
33
+ dividerWrite,
34
+ headingLevel,
35
+ headingRead,
36
+ headingWrite,
37
+ itemRead,
38
+ itemWrite,
39
+ numberedRead,
40
+ orderedRead,
41
+ orderedWrite,
42
+ quoteRead,
43
+ quoteWrite,
44
+ richTextMarkForms,
45
+ todoWrite,
46
+ toggleHeadingRead,
47
+ toggleHeadingWrite,
48
+ toggleRead,
49
+ unorderedRead,
50
+ unorderedWrite
51
+ } from '../../export/forms.js';
52
+ import { onLastLine } from '../../session/navigation.js';
53
+ import { bodyShows } from '../../selection/visibility.js';
54
+ import { openBody } from './body.js';
35
55
  import type { Snippet } from 'svelte';
36
56
  import {
37
57
  richTextControls,
@@ -54,62 +74,10 @@
54
74
  formatSubscript: 'subscript'
55
75
  } as const;
56
76
 
57
- const isNativeFormatInputType = (
58
- inputType: string
59
- ): inputType is keyof typeof nativeFormatMarks => inputType in nativeFormatMarks;
60
-
61
- /** A thematic break's clipboard forms. */
62
- const rule = { html: () => '<hr>', plain: () => '---' };
63
77
  /** A native disclosure: the browser owns `open` (declared view state). */
64
- const disclosure = { element: 'details', viewState: ['open'] };
78
+ const disclosure = { viewState: ['open'] };
65
79
  /** The kinds drawn as a disclosure: a toggle this view creates opens (`body.open`). */
66
80
  const DISCLOSURES = new Set(['toggle', 'toggle-heading']);
67
- /**
68
- * A list's elements without list semantics (`role="none"`): a list's items
69
- * are sibling blocks, never the only children of one `ul` or `ol`, so an
70
- * `li` would be an orphan list item to assistive technology (WCAG 1.3.1);
71
- * the tags stay for styles and the HTML export.
72
- */
73
- const unlisted = (tag: 'li' | 'ul' | 'ol') => ({ tag, attributes: { role: 'none' } });
74
- /**
75
- * Notion's three heading levels: a missing level is h1, any other (a
76
- * stored `h4`–`h6`, as HTML import reads them) h3.
77
- */
78
- const headingLevel = (level: unknown) =>
79
- level === 'h1' || level === 'h2' || level === 'h3' ? level : level === undefined ? 'h1' : 'h3';
80
- /** An unsafe scheme drops the href (and its target): an inert anchor still carries the text. */
81
- const linkAttributes = (mark: { href?: unknown; target?: string }) => {
82
- const href = sanitizeLinkHref(mark?.href) ?? undefined;
83
- return { href, target: href && mark.target };
84
- };
85
- /** A hostile color value (a `;` payload would inject declarations) drops the style. */
86
- const styled = (property: string) => (value: unknown) => {
87
- const safe = sanitizeCssColorValue(value);
88
- return { style: safe ? `${property}: ${safe};` : undefined };
89
- };
90
- /**
91
- * HTML import: a pasted color, sanitized, unless it is the page's
92
- * default (Google Docs writes black text and transparent backgrounds on
93
- * every span: they are no mark, and a black mark is unreadable in dark mode).
94
- */
95
- const colorOf = (value: string, none: RegExp) =>
96
- none.test(value.replace(/\s+/g, '').toLowerCase())
97
- ? undefined
98
- : (sanitizeCssColorValue(value) ?? undefined);
99
- const INHERITED = 'initial|inherit|unset|revert|currentcolor';
100
- const NO_COLOR = new RegExp(
101
- `^(${INHERITED}|black|windowtext|#000|#000000|rgba?\\(0,0,0(,1)?\\))$`
102
- );
103
- const NO_HIGHLIGHT = new RegExp(
104
- `^(${INHERITED}|transparent|rgba\\(\\d+,\\d+,\\d+,0\\)|white|#fff|#ffffff|rgba?\\(255,255,255(,1)?\\))$`
105
- );
106
- /** HTML import: a tag alias (unless its own style says otherwise) or a style (Google Docs). */
107
- const alias =
108
- (tags: RegExp, property: 'fontWeight' | 'fontStyle' | 'textDecoration', value: RegExp) =>
109
- (el: HTMLElement) =>
110
- (tags.test(el.localName) && !el.style[property]) ||
111
- value.test(el.style[property]) ||
112
- undefined;
113
81
 
114
82
  /**
115
83
  * The kinds whose empty text names them, by placeholder (a callout's
@@ -216,6 +184,7 @@
216
184
  { callout: calloutOptions, checkbox, calloutIcon, emptyBody }: RichTextPluginOptions
217
185
  ): ReturnType<Plugin> => {
218
186
  const { kinds, marks: words } = labels;
187
+ const marks = richTextMarkForms;
219
188
  /** A new callout's icon. */
220
189
  const icon = calloutOptions?.icon ?? DEFAULT_CALLOUT_ICON;
221
190
  /** The block this view is retyping into a disclosure (`body.open`), until it applied. */
@@ -262,6 +231,16 @@
262
231
  'mod+shift+s': setMarkAndSelect('strike'),
263
232
  'mod+shift+x': setMarkAndSelect('strike'),
264
233
  'mod+shift+h': setMarkAndSelect('color', 'red'),
234
+ // ArrowDown on the last line of a header whose body shows and is empty
235
+ // (an open toggle, a callout) goes into that body, as its hint's click
236
+ // does (`body.down`): one command, one undo step. Elsewhere the key
237
+ // moves as usual.
238
+ arrowdown: ({ prevent }) => {
239
+ const header = onLastLine(edytor);
240
+ if (!header || header.hasChildren || !bodyShows(header)) return;
241
+ if (!edytor.dispatcher.permits()) return;
242
+ prevent(() => void openBody(header));
243
+ },
265
244
  // Mod+Enter checks or unchecks each shown to-do it is in (Notion): the
266
245
  // selected blocks', else the caret's; the toggles among them open or
267
246
  // close as with the built-in binding, in the same key.
@@ -303,7 +282,7 @@
303
282
  const end = yStart + href.length;
304
283
  edytor.dispatcher.run('insertFromPaste', () => {
305
284
  edytor.insertText(text.parent.id, text.segStart + yStart, href);
306
- edytor.dispatcher.caret(text, end);
285
+ edytor.dispatcher.caretIn(text, end);
307
286
  });
308
287
  if (text.stringContent.slice(yStart, end) === href)
309
288
  operations.linkText(text, yStart, end, href);
@@ -346,7 +325,7 @@
346
325
  } finally {
347
326
  autolinking = false;
348
327
  }
349
- edytor.dispatcher.caret(text, yStart + 1);
328
+ edytor.dispatcher.caretIn(text, yStart + 1);
350
329
  operations.linkText(text, found.start, end, found.href);
351
330
  });
352
331
  },
@@ -378,8 +357,8 @@
378
357
  },
379
358
  onBeforeInput: ({ e, prevent }) => {
380
359
  const { inputType, data } = e;
381
- if (isNativeFormatInputType(inputType)) {
382
- const mark = nativeFormatMarks[inputType];
360
+ if (inputType in nativeFormatMarks) {
361
+ const mark = nativeFormatMarks[inputType as keyof typeof nativeFormatMarks];
383
362
  prevent(() => operations.setMarkAtRange(mark));
384
363
  } else if (inputType === 'formatRemove') {
385
364
  prevent(() => operations.removeAllMarksAtRange());
@@ -404,54 +383,17 @@
404
383
  },
405
384
  // Toolbar buttons and export wrapping follow this order (first innermost).
406
385
  marks: {
407
- bold: {
408
- tag: 'strong',
409
- toolbar: { label: words.bold, icon: 'B' },
410
- parse: alias(/^b$/, 'fontWeight', /^(bold|[6-9]00)$/)
411
- },
412
- italic: {
413
- tag: 'em',
414
- toolbar: { label: words.italic, icon: 'I' },
415
- parse: alias(/^i$/, 'fontStyle', /italic/)
416
- },
417
- underline: {
418
- tag: 'u',
419
- toolbar: { label: words.underline, icon: 'U' },
420
- parse: alias(/^u$/, 'textDecoration', /underline/)
421
- },
422
- strike: {
423
- tag: 's',
424
- toolbar: { label: words.strike, icon: 'S' },
425
- parse: alias(/^(strike|del)$/, 'textDecoration', /line-through/)
426
- },
427
- code: { tag: 'code', toolbar: { label: words.code, icon: '</>' } },
428
- // Typing at a link's trailing edge extends it only from inside the anchor.
429
- link: {
430
- tag: 'a',
431
- attributes: linkAttributes,
432
- ...richTextMarks.link,
433
- parse: (el) => {
434
- const href = el.localName === 'a' && sanitizeLinkHref(el.getAttribute('href'));
435
- const target = el.getAttribute('target');
436
- return href ? { href, ...(target ? { target } : {}) } : undefined;
437
- }
438
- },
439
- superscript: { tag: 'sup' },
440
- subscript: { tag: 'sub' },
441
- color: {
442
- tag: 'span',
443
- attributes: styled('color'),
444
- parse: (el) => colorOf(el.style.color, NO_COLOR)
445
- },
446
- highlight: {
447
- tag: 'span',
448
- attributes: styled('background-color'),
449
- parse: (el) => {
450
- const background = el.style.backgroundColor;
451
- if (background) return colorOf(background, NO_HIGHLIGHT);
452
- return el.localName === 'mark' ? 'yellow' : undefined;
453
- }
454
- }
386
+ ...marks,
387
+ // The toolbar's marks, by their icons.
388
+ ...Object.fromEntries(
389
+ (['bold', 'italic', 'underline', 'strike', 'code'] as const).map((name, i) => [
390
+ name,
391
+ {
392
+ ...marks[name],
393
+ toolbar: { label: words[name], icon: ['B', 'I', 'U', 'S', '</>'][i]! }
394
+ }
395
+ ])
396
+ )
455
397
  },
456
398
  blocks: {
457
399
  paragraph: {
@@ -461,42 +403,25 @@
461
403
  ])
462
404
  },
463
405
  heading: {
406
+ ...headingRead,
407
+ ...headingWrite,
464
408
  snippet: textThenChildren,
465
- contentElement: (data) => headingLevel(data.level),
466
- presets: presets('heading', [
467
- {
468
- label: kinds.heading1,
469
- icon: 'H₁',
470
- keywords: ['h1', 'title'],
471
- data: { level: 'h1' },
472
- markdown: ['# ']
473
- },
474
- {
475
- label: kinds.heading2,
476
- icon: 'H₂',
477
- keywords: ['h2', 'subtitle'],
478
- data: { level: 'h2' },
479
- markdown: ['## ']
480
- },
481
- {
482
- label: kinds.heading3,
483
- icon: 'H₃',
484
- keywords: ['h3'],
485
- data: { level: 'h3' },
486
- markdown: ['### ']
487
- }
488
- ]),
489
- // HTML import: h1–h3 come from the presets; h4–h6 read as h3.
490
- parse: (el) => (/^h[4-6]$/.test(el.localName) ? { level: 'h3' } : undefined),
491
- html: (block, content, children) => {
492
- const tag = headingLevel(block.data?.level);
493
- return `<${tag}>${content}</${tag}>${children}`;
494
- }
409
+ presets: presets(
410
+ 'heading',
411
+ HEADING_LEVELS.map((level, index) => ({
412
+ label: [kinds.heading1, kinds.heading2, kinds.heading3][index]!,
413
+ icon: `H${['₁', '₂', '₃'][index]}`,
414
+ keywords: [level, ...[['title'], ['subtitle'], []][index]!],
415
+ data: { level },
416
+ markdown: [`${'#'.repeat(index + 1)} `]
417
+ }))
418
+ )
495
419
  },
496
420
  'bulleted-list-item': {
421
+ ...itemRead,
422
+ ...itemWrite,
497
423
  continues: true,
498
424
  snippet: listItem,
499
- element: unlisted('li'),
500
425
  presets: presets('bulleted-list-item', [
501
426
  {
502
427
  label: kinds.bulletedList,
@@ -504,13 +429,13 @@
504
429
  keywords: ['bullet', 'ul'],
505
430
  markdown: ['- ', '* ', '+ ']
506
431
  }
507
- ]),
508
- html: 'li'
432
+ ])
509
433
  },
510
434
  'numbered-list-item': {
435
+ ...numberedRead,
436
+ ...itemWrite,
511
437
  continues: true,
512
438
  snippet: listItem,
513
- element: unlisted('li'),
514
439
  presets: presets('numbered-list-item', [
515
440
  {
516
441
  label: kinds.numberedList,
@@ -518,13 +443,10 @@
518
443
  keywords: ['number', 'ol'],
519
444
  markdown: ['1. ', 'a. ', 'i. ']
520
445
  }
521
- ]),
522
- html: 'li',
523
- // HTML import: an `li` is a bulleted item (the first `li` kind) unless its list is ordered.
524
- parse: (el) =>
525
- el.localName === 'li' && el.parentElement?.localName === 'ol' ? {} : undefined
446
+ ])
526
447
  },
527
448
  'todo-item': {
449
+ ...todoWrite,
528
450
  continues: true,
529
451
  // The app's control markup, if any, through its own snippet: the default costs no branch.
530
452
  snippet: checkbox ? todoItemOwn : todoItem,
@@ -536,15 +458,10 @@
536
458
  data: { checked: false },
537
459
  markdown: ['[ ] ', '[] ']
538
460
  }
539
- ]),
540
- html: (block, content, children) =>
541
- `<li data-edytor-todo-item="true"><input type="checkbox"${block.data?.checked === true ? ' checked' : ''}>${content}${children}</li>`,
542
- plain: (block, content, children) =>
543
- [`${block.data?.checked === true ? '[x]' : '[ ]'} ${content}`.trim(), children]
544
- .filter(Boolean)
545
- .join('\n')
461
+ ])
546
462
  },
547
463
  toggle: {
464
+ ...toggleRead,
548
465
  continues: true,
549
466
  container: true,
550
467
  snippet: emptyBody ? detailsOwn : details,
@@ -563,13 +480,14 @@
563
480
  // kept); from a toggle, `#`, `##` or `###` + space; so `> # ` typed on a new
564
481
  // line is a toggle heading 1.
565
482
  'toggle-heading': {
483
+ ...toggleHeadingRead,
484
+ ...toggleHeadingWrite,
566
485
  container: true,
567
486
  snippet: emptyBody ? detailsOwn : details,
568
487
  ...disclosure,
569
- contentElement: (data) => headingLevel(data.level),
570
488
  presets: presets(
571
489
  'toggle-heading',
572
- (['h1', 'h2', 'h3'] as const).map((level, index) => {
490
+ HEADING_LEVELS.map((level, index) => {
573
491
  const hashes = '#'.repeat(index + 1);
574
492
  return {
575
493
  label: [kinds.toggleHeading1, kinds.toggleHeading2, kinds.toggleHeading3][index]!,
@@ -583,28 +501,21 @@
583
501
  }
584
502
  };
585
503
  })
586
- ),
587
- // HTML import: a `details` whose summary holds an h1–h3 (its own export).
588
- parse: (el) => {
589
- if (el.localName !== 'details') return;
590
- const tag = el.querySelector(':scope > summary > :is(h1, h2, h3, h4, h5, h6)');
591
- return tag ? { level: headingLevel(tag.localName) } : undefined;
592
- },
593
- html: (block, content, children) => {
594
- const tag = headingLevel(block.data?.level);
595
- return `<details><summary><${tag}>${content}</${tag}></summary>${children}</details>`;
596
- }
504
+ )
597
505
  },
598
506
  // Notion's callout: an icon (`data.icon`, the picker's), a title (its own
599
507
  // text) and a content (its children, any blocks), which shows even empty
600
508
  // (`body`): Enter at the end of the title goes into it.
601
509
  callout: {
510
+ ...calloutRead,
602
511
  container: true,
603
512
  body: true,
604
513
  snippet: calloutIcon || emptyBody ? calloutOwn : callout,
605
514
  presets: presets('callout', [
606
515
  { label: kinds.callout, icon: '✦', keywords: ['note', 'tip'], data: { icon } }
607
516
  ]),
517
+ // A callout without its own icon exports the view's.
518
+ ...calloutWrite((data) => iconOf(data, edytor)),
608
519
  // The block menu's Change icon, while the view lists a picker: the keyboard's way to it.
609
520
  menu: (block) => {
610
521
  const picker = calloutPickers.get(edytor);
@@ -618,47 +529,38 @@
618
529
  }
619
530
  ]
620
531
  : [];
621
- },
622
- // HTML import: its own export, the icon in its attribute.
623
- parse: (el) => {
624
- const own = el.getAttribute('data-edytor-callout');
625
- return own === null ? undefined : { icon: own };
626
- },
627
- html: (block, content, children) =>
628
- calloutHtml(iconOf(block.data, edytor), content, children)
532
+ }
629
533
  },
630
534
  quote: {
535
+ ...quoteRead,
536
+ ...quoteWrite,
631
537
  container: true,
632
538
  snippet: textThenChildren,
633
- contentElement: 'blockquote',
634
539
  // Notion: `"` + space is a quote; `>` + space is a toggle.
635
- presets: presets('quote', [{ label: kinds.quote, icon: '❝', markdown: ['" '] }]),
636
- html: 'blockquote'
540
+ presets: presets('quote', [{ label: kinds.quote, icon: '❝', markdown: ['" '] }])
637
541
  },
638
542
  divider: {
639
543
  ...richTextKinds.divider,
640
- element: 'hr',
544
+ ...dividerRead,
545
+ ...dividerWrite,
641
546
  presets: presets('divider', [
642
547
  { label: kinds.divider, icon: '—', keywords: ['hr', 'separator'], markdown: ['---'] }
643
548
  ]),
644
- empty: { content: [], children: [] },
645
- ...rule
549
+ empty: { content: [], children: [] }
646
550
  },
647
551
  'ordered-list': {
648
552
  ...richTextKinds['ordered-list'],
649
- itemKind: 'numbered-list-item',
650
- snippet: list,
651
- element: unlisted('ol'),
652
- html: 'ol'
553
+ ...orderedRead,
554
+ ...orderedWrite,
555
+ snippet: list
653
556
  },
654
557
  'unordered-list': {
655
558
  ...richTextKinds['unordered-list'],
656
- itemKind: 'bulleted-list-item',
657
- snippet: list,
658
- element: unlisted('ul'),
659
- html: 'ul'
559
+ ...unorderedRead,
560
+ ...unorderedWrite,
561
+ snippet: list
660
562
  },
661
- 'list-item': { snippet: listItem, element: unlisted('li'), html: 'li' }
563
+ 'list-item': { ...itemRead, ...itemWrite, snippet: listItem }
662
564
  }
663
565
  };
664
566
  };
@@ -760,10 +662,3 @@
760
662
  {#snippet list({ children }: BlockSnippetPayload)}<!--
761
663
  -->{#if children}<div>{@render children()}</div>{/if}<!--
762
664
  -->{/snippet}
763
-
764
- <style>
765
- /* An empty body's hint: chrome, not text (`body.hint`); its look is a theme's. */
766
- :global([data-edytor-empty-body]) {
767
- user-select: none;
768
- }
769
- </style>
@@ -1,3 +1,4 @@
1
+ import './richtext.css';
1
2
  import type { Plugin, PlaceholderView } from '../../plugins.js';
2
3
  import type { Edytor } from '../../edytor.svelte.js';
3
4
  import { richTextOperations } from './richTextOperations.js';
@@ -10,7 +10,7 @@ export const openBody = (block) => {
10
10
  return edytor.dispatcher.run('openBody', () => {
11
11
  const added = () => edytor.insertBlocks({ parent: block.id, index: 0 }, [{ type: edytor.defaultChild(block) }]);
12
12
  const child = block.children[0] ?? edytor.idToBlock.get(added().ids[0] ?? '');
13
- edytor.dispatcher.caret(child?.firstText, 0);
13
+ edytor.dispatcher.caretIn(child?.firstText, 0);
14
14
  return child ?? undefined;
15
15
  });
16
16
  };
@@ -40,6 +40,8 @@ export declare class CalloutIconPicker extends ChromeList {
40
40
  constructor(edytor: Edytor,
41
41
  /** The picker's choices. */
42
42
  icons?: readonly string[]);
43
+ /** Where the picker stands: the open callout's icon (`ChromeAnchor`), which your `picker` places itself under. */
44
+ get anchor(): import("../chrome.js").ChromeAnchor;
43
45
  /** Whether the picker is open (reactive). */
44
46
  get isOpen(): boolean;
45
47
  /** The callout it is open on (reactive). */
@@ -48,8 +50,6 @@ export declare class CalloutIconPicker extends ChromeList {
48
50
  get current(): string | undefined;
49
51
  /** The keyboard's rows: the icons, then Remove icon. */
50
52
  protected get count(): number;
51
- /** The view is readonly (reactive): the picker closes. */
52
- get readonly(): boolean;
53
53
  /** Open the picker on `block`, or close it when it is open there. */
54
54
  toggle: (block: Block) => void;
55
55
  /** Open the picker on the callout `block`, its icon the keyboard's row (refused in a view that may not write). */
@@ -14,7 +14,6 @@ import { takeKeys } from '../../events/onFocus.js';
14
14
  import { ChromeList, holdKeys, menuKey, optionAttachment } from '../list.svelte.js';
15
15
  import { Anchored } from '../anchored.svelte.js';
16
16
  import { calloutPickers, iconOf } from './callouts.js';
17
- import CalloutIconHost from './CalloutIconPicker.svelte';
18
17
  import { all, listen } from '../../utils/listen.js';
19
18
  /** The picker's choices unless `createCalloutIconsPlugin({ icons })` names others. */
20
19
  export const CALLOUT_ICONS = [
@@ -89,6 +88,10 @@ export class CalloutIconPicker extends ChromeList {
89
88
  return icon ?? node ?? null;
90
89
  }, () => this.close(false));
91
90
  }
91
+ /** Where the picker stands: the open callout's icon (`ChromeAnchor`), which your `picker` places itself under. */
92
+ get anchor() {
93
+ return this.panel.anchor;
94
+ }
92
95
  /** Whether the picker is open (reactive). */
93
96
  get isOpen() {
94
97
  return this.target !== null;
@@ -107,10 +110,6 @@ export class CalloutIconPicker extends ChromeList {
107
110
  get count() {
108
111
  return this.icons.length + 1;
109
112
  }
110
- /** The view is readonly (reactive): the picker closes. */
111
- get readonly() {
112
- return this.edytor.readonly;
113
- }
114
113
  /** Open the picker on `block`, or close it when it is open there. */
115
114
  toggle = (block) => (this.target === block.id ? this.close() : this.open(block));
116
115
  /** Open the picker on the callout `block`, its icon the keyboard's row (refused in a view that may not write). */
@@ -129,7 +128,6 @@ export class CalloutIconPicker extends ChromeList {
129
128
  return;
130
129
  const before = this.#before;
131
130
  this.target = null;
132
- this.panel.box = null;
133
131
  this.#before = null;
134
132
  this.edytor.overlay.invalidate();
135
133
  if (!keys)
@@ -209,6 +207,6 @@ export const createCalloutIconsPlugin = ({ picker: menu, icons } = {}) => (edyto
209
207
  calloutPickers.set(edytor, picker);
210
208
  return {
211
209
  // With the menus: above the handles and the image chrome.
212
- onEdytorAttached: ({ node }) => menu && picker.panel.mount(node, CalloutIconHost, { picker, menu }, 70)
210
+ onEdytorAttached: ({ node }) => menu && picker.panel.mount(node, menu, picker, () => picker.isOpen, 70)
213
211
  };
214
212
  };
@@ -6,25 +6,7 @@ export type RichTextLink = {
6
6
  href: string;
7
7
  target?: string;
8
8
  };
9
- /**
10
- * CSS-color sanitizer for `color`/`highlight` marks. The value lands in
11
- * `style="color: {value}"` — a `;`/`{`/`}` payload escapes the property
12
- * and injects arbitrary declarations (`url()` exfil, `position:fixed`
13
- * overlays). Metacharacters, escape/obfuscation sequences, and
14
- * resource-loading functions are rejected; real `<color>` values
15
- * (`#hex`, `rgb()/hsl()/oklch()/color-mix()/var()`, named colors) pass.
16
- */
17
- export declare const sanitizeCssColorValue: (value: unknown) => string | null;
18
- /**
19
- * Scheme allowlist for link hrefs. `javascript:`/`data:`/`vbscript:`
20
- * payloads can arrive through native `insertLink`, pasted HTML, or a
21
- * malicious collaborator — and Svelte renders `href` verbatim. The href is
22
- * read as the browser's URL parser reads it: it strips C0 controls and
23
- * spaces at both ends and tab/newline/CR anywhere (`\u0001javascript:` is
24
- * `javascript:` to a browser), then resolves it against a base; only an
25
- * allowed scheme, or a relative href that stays relative, passes.
26
- */
27
- export declare const sanitizeLinkHref: (href: unknown) => string | null;
9
+ export { sanitizeCssColorValue, sanitizeLinkHref } from '../../utils/sanitize.js';
28
10
  /**
29
11
  * The URL a paste carries when its plain text is one link and nothing else
30
12
  * (surrounding whitespace aside): `http:`/`https:` with a host, or
@@ -3,54 +3,8 @@ import { holdsNothing, placing } from '../../kinds.js';
3
3
  import { id } from '../../utils.js';
4
4
  import { marksForInsertion } from '../../session/editing/text.js';
5
5
  import { selectedTextSpans, viewOf } from '../../selection/visibility.js';
6
- const SAFE_LINK_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'tel:']);
7
- /**
8
- * CSS-color sanitizer for `color`/`highlight` marks. The value lands in
9
- * `style="color: {value}"` — a `;`/`{`/`}` payload escapes the property
10
- * and injects arbitrary declarations (`url()` exfil, `position:fixed`
11
- * overlays). Metacharacters, escape/obfuscation sequences, and
12
- * resource-loading functions are rejected; real `<color>` values
13
- * (`#hex`, `rgb()/hsl()/oklch()/color-mix()/var()`, named colors) pass.
14
- */
15
- export const sanitizeCssColorValue = (value) => {
16
- if (typeof value !== 'string')
17
- return null;
18
- const trimmed = value.trim();
19
- if (!trimmed || trimmed.length > 128)
20
- return null;
21
- if (/[;{}<>\\'"`]/.test(trimmed) || /url\s*\(|expression\s*\(|\/\*|\*\//i.test(trimmed)) {
22
- return null;
23
- }
24
- return trimmed;
25
- };
26
- /**
27
- * Scheme allowlist for link hrefs. `javascript:`/`data:`/`vbscript:`
28
- * payloads can arrive through native `insertLink`, pasted HTML, or a
29
- * malicious collaborator — and Svelte renders `href` verbatim. The href is
30
- * read as the browser's URL parser reads it: it strips C0 controls and
31
- * spaces at both ends and tab/newline/CR anywhere (`\u0001javascript:` is
32
- * `javascript:` to a browser), then resolves it against a base; only an
33
- * allowed scheme, or a relative href that stays relative, passes.
34
- */
35
- export const sanitizeLinkHref = (href) => {
36
- // Marks arrive from untrusted sources too (synced peers, paste) —
37
- // reject non-strings outright instead of throwing on `.replace`.
38
- if (typeof href !== 'string')
39
- return null;
40
- // eslint-disable-next-line no-control-regex
41
- const trimmed = href.replace(/[\t\n\r]/g, '').replace(/^[\u0000-\u0020]+|[\u0000-\u0020]+$/g, '');
42
- if (!trimmed)
43
- return null;
44
- try {
45
- // A relative href resolves to the base's `https:`: it names no scheme of its own.
46
- return SAFE_LINK_PROTOCOLS.has(new URL(trimmed, 'https://relative.invalid/').protocol)
47
- ? trimmed
48
- : null;
49
- }
50
- catch {
51
- return null;
52
- }
53
- };
6
+ export { sanitizeCssColorValue, sanitizeLinkHref } from '../../utils/sanitize.js';
7
+ import { sanitizeCssColorValue, sanitizeLinkHref } from '../../utils/sanitize.js';
54
8
  /**
55
9
  * The URL a paste carries when its plain text is one link and nothing else
56
10
  * (surrounding whitespace aside): `http:`/`https:` with a host, or
@@ -194,7 +148,7 @@ export const richTextOperations = (edytor) => ({
194
148
  if (dispatcher.plan('insertDivider', {}, prepare, block).status !== 'applied')
195
149
  return null;
196
150
  const text = caret ? edytor.idToBlock.get(caret)?.firstText : startText;
197
- dispatcher.caret(text, caret ? 0 : yStart);
151
+ dispatcher.caretIn(text, caret ? 0 : yStart);
198
152
  return block;
199
153
  },
200
154
  removeAllMarksAtRange: () => {
@@ -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
+ /* An empty body's hint: chrome, not text (`body.hint`); its look is a theme's. */
4
+ [data-edytor-empty-body] {
5
+ user-select: none;
6
+ }
@@ -19,6 +19,8 @@ export declare class SlashMenuController extends TextTriggerController {
19
19
  /** The selection when a `+` opened the menu, given back when it closes unpicked. */
20
20
  private held;
21
21
  constructor(edytor: Edytor);
22
+ /** A `+`'s menu stands under the `+` (its block while the `+` is gone); a `/` menu at the caret. */
23
+ protected anchorRect(): DOMRect | undefined;
22
24
  /** A `+`'s menu names its highlighted row from its own field, and tells the `+` it opened it. */
23
25
  protected popupOf(active: string | undefined): Popup;
24
26
  /**