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,5 +1,6 @@
1
1
  import { fileUploads } from '../uploads.svelte.js';
2
2
  import { listen } from '../../utils/listen.js';
3
+ import { keysOf } from '../list.svelte.js';
3
4
  /** Each media kind's icon (the image's too): its menu row, its empty block, its paste offer. */
4
5
  export const MEDIA_ICONS = {
5
6
  image: '🖼',
@@ -54,7 +55,7 @@ export class MediaEmptyController {
54
55
  get readonly() {
55
56
  return !this.block || this.block.edytor.readonly;
56
57
  }
57
- /** The view lists the uploads plugin and the kind takes files: offer Upload. */
58
+ /** The kind's plugin was given an `upload`: offer Upload. */
58
59
  get canUpload() {
59
60
  const { block } = this;
60
61
  return Boolean(block && fileUploads(block.edytor) && block.definition.upload);
@@ -64,6 +65,19 @@ export class MediaEmptyController {
64
65
  const { block } = this;
65
66
  return block ? fileUploads(block.edytor)?.of(block.id) : undefined;
66
67
  }
68
+ /**
69
+ * The file uploading into this block, shown at once: its object URL
70
+ * (this view's only, never stored; revoked once the upload ends), else `null`.
71
+ */
72
+ get preview() {
73
+ const { pending } = this;
74
+ return pending?.status === 'uploading' ? pending.preview : null;
75
+ }
76
+ /** The share of the upload sent, `0` to `1`; `null` before a report, or with no upload. */
77
+ get progress() {
78
+ const { pending } = this;
79
+ return pending?.status === 'uploading' ? pending.progress : null;
80
+ }
67
81
  /** Store `value` on the block: why it did not take it, or `null`. */
68
82
  #store = (value, file) => this.block && this.view ? this.view.store(this.block, value, file) : 'invalid';
69
83
  /** Embed `value` (default the `draft`): the block takes the link, or `failed` says why not. */
@@ -73,39 +87,31 @@ export class MediaEmptyController {
73
87
  fileUploads(this.block.edytor)?.clear(this.block.id);
74
88
  };
75
89
  /**
76
- * Upload `file` with the view's uploader (`createUploadsPlugin`) and store the URL it answers,
77
- * only on the block as it was picked from: still in the tree, of its kind
78
- * and empty (a peer may have set a source, or deleted it, meanwhile).
90
+ * Upload `file` with its kind's uploader (its plugin's `upload`), the
91
+ * block showing it meanwhile (`pending`, `preview`, `progress`), and
92
+ * store the URL it answers, only on the block as it was picked from:
93
+ * still in the tree, of its kind and empty (a peer may have set a
94
+ * source, or deleted it, meanwhile).
79
95
  */
80
96
  upload = async (picked) => {
81
97
  const { block } = this;
82
- const upload = this.canUpload && fileUploads(block?.edytor)?.upload;
83
- if (!upload || !block || block.edytor.readonly)
98
+ const uploads = this.canUpload && fileUploads(block?.edytor);
99
+ if (!uploads || !block || block.edytor.readonly)
84
100
  return;
85
101
  const { edytor, id, type } = block;
86
- let src;
87
- try {
88
- src = await upload(picked, { progress: () => { } });
89
- }
90
- catch {
102
+ const stored = await uploads.pick(id, picked, (src) => {
103
+ const live = edytor.idToBlock.get(id);
104
+ if (!live?.isInTree || live.type !== type || edytor.readonly)
105
+ return;
106
+ if (edytor.facade.blockDataOf(id)?.src !== undefined)
107
+ return;
108
+ this.failed = this.#store(src, picked);
109
+ });
110
+ if (!stored)
91
111
  this.failed = 'upload';
92
- return;
93
- }
94
- const live = edytor.idToBlock.get(id);
95
- if (!live?.isInTree || live.type !== type || edytor.readonly)
96
- return;
97
- if (edytor.facade.blockDataOf(id)?.src !== undefined)
98
- return;
99
- this.failed = this.#store(src, picked);
100
112
  };
101
113
  /** The link field (`{@attach empty.field}`): Enter embeds its `draft`. */
102
114
  field = (node) => {
103
- const keydown = (event) => {
104
- if (event.key !== 'Enter' || event.isComposing)
105
- return;
106
- event.preventDefault();
107
- this.embed();
108
- };
109
- return listen(node, 'keydown', keydown);
115
+ return listen(node, 'keydown', keysOf({ Enter: () => this.embed() }));
110
116
  };
111
117
  }
@@ -49,6 +49,13 @@ export type FileRowPayload = {
49
49
  size: number | undefined;
50
50
  /** That size in the view's words (`media.fileSize`), `''` without one. */
51
51
  sizeLabel: string;
52
+ /**
53
+ * The file is uploading (then `src` is its object URL, this view's only,
54
+ * never stored): the row shows it at once, with `progress`.
55
+ */
56
+ uploading: boolean;
57
+ /** The share sent while it uploads, `0` to `1`; `null` before a report or once stored. */
58
+ progress: number | null;
52
59
  /**
53
60
  * The row's link (`<a href={row.src} {@attach row.link}>`): it downloads
54
61
  * the file under its name, in a new tab without the opener, never dragged
@@ -94,67 +101,6 @@ export declare const mediaPlugin: (kind: MediaKind, keywords: string[], own: (ed
94
101
  hooks?: PluginOperations & PluginDefinitions;
95
102
  record: Partial<BlockDefinition>;
96
103
  }) => Plugin;
97
- /**
98
- * A web page's URL a media block may store or render: the link sanitizer's
99
- * reading of it (`sanitizeLinkHref`: ASCII control characters and spaces stripped,
100
- * tab/newline anywhere), then only an absolute `http:`/`https:` URL with a
101
- * host. `null` for anything else: a script or `data:` URL, a `mailto:`, a
102
- * relative path, a bare domain.
103
- */
104
- export declare const safeWebUrl: (value: unknown) => string | null;
105
- /**
106
- * A media file's source (a video, an audio track, an attachment): a
107
- * {@link safeWebUrl}, or a `blob:` URL an upload answered. Never `data:`:
108
- * an inline file would be stored in the document and sent in every update.
109
- */
110
- export declare const safeMediaSrc: (value: unknown) => string | null;
111
- /** One allowlisted embed provider: which links it plays, and its player. */
112
- export type EmbedProvider = {
113
- /** The provider's name: the frame's `title`. */
114
- name: string;
115
- /**
116
- * The player's URL for a link of this provider, else `null`. It must be
117
- * `https:` on another origin than the page's: any other answer is
118
- * refused (the frame has `allow-scripts` and `allow-same-origin`, so a
119
- * player on the page's origin, or on one that can script it, could lift
120
- * its own sandbox). Match the host exactly (never a
121
- * suffix or a substring) and check every id you copy into the player URL.
122
- */
123
- embed: (url: URL) => string | null;
124
- /** The frame's CSS `aspect-ratio` (default `16 / 9`); ignored with `height`. */
125
- aspectRatio?: string;
126
- /** A fixed frame height in pixels (a music player). */
127
- height?: number;
128
- };
129
- /** The frame's `sandbox`: scripts and the provider's own origin, popups that leave it; no top navigation, modals, downloads or forms. */
130
- export declare const EMBED_SANDBOX = "allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-presentation";
131
- /** The frame's permissions (`allow`): what a player needs, nothing that reads the viewer. */
132
- export declare const EMBED_ALLOW = "fullscreen; picture-in-picture; encrypted-media; clipboard-write";
133
- /**
134
- * The default allowlist: YouTube (through `youtube-nocookie.com`), Vimeo,
135
- * Loom, Figma, CodePen and Spotify. A link no provider plays renders no
136
- * frame. Extend it with `createEmbedPlugin({ providers: [...EMBED_PROVIDERS, mine] })`.
137
- */
138
- export declare const EMBED_PROVIDERS: readonly EmbedProvider[];
139
- /**
140
- * The player for `value` (a stored or pasted link): the first provider of
141
- * `providers` that plays it, and its `https:` player URL. `null` when the
142
- * link is not a {@link safeWebUrl}, no provider plays it, or the player URL
143
- * a provider answers is not `https:` or is on the page's own origin. The frame's `src` is always this
144
- * answer, never the stored value.
145
- */
146
- export declare const embedSourceOf: (value: unknown, providers?: readonly EmbedProvider[]) => {
147
- src: string;
148
- provider: EmbedProvider;
149
- } | null;
150
- /** A file's display name from its URL's last path segment, decoded. */
151
- export declare const fileNameOf: (src: string) => string;
152
- /**
153
- * The element a media kind's HTML import reads: `element` itself when it
154
- * is a `tag` (not inside a `figure`, which claims it), or a `figure`'s own
155
- * `tag` child.
156
- */
157
- export declare const claimed: (element: HTMLElement, tag: string) => HTMLElement | undefined;
158
- /** An imported `video`'s or `audio`'s source: its `src`, else its first `source`'s. */
159
- export declare const mediaSourceOf: (element: HTMLElement | undefined) => string | null;
104
+ export * from './sources.js';
105
+ import type { EmbedProvider } from './sources.js';
160
106
  export { escapeHtml };
@@ -1,6 +1,5 @@
1
1
  import { attach } from '../chrome.js';
2
2
  import { mediaKinds } from '../../crdt/semantics.js';
3
- import { sanitizeLinkHref } from '../richtext/richTextOperations.js';
4
3
  import { escapeHtml } from '../../clipboard/serializeClipboardFragment.js';
5
4
  import { keywordsOf, labelsOf, mediaEnglish } from '../../labels.js';
6
5
  import { MEDIA_ICONS } from './empty.svelte.js';
@@ -56,197 +55,5 @@ export const mediaPlugin = (kind, keywords, own) => (edytor) => {
56
55
  }
57
56
  };
58
57
  };
59
- /**
60
- * A web page's URL a media block may store or render: the link sanitizer's
61
- * reading of it (`sanitizeLinkHref`: ASCII control characters and spaces stripped,
62
- * tab/newline anywhere), then only an absolute `http:`/`https:` URL with a
63
- * host. `null` for anything else: a script or `data:` URL, a `mailto:`, a
64
- * relative path, a bare domain.
65
- */
66
- export const safeWebUrl = (value) => {
67
- const href = sanitizeLinkHref(value);
68
- if (href === null)
69
- return null;
70
- try {
71
- const url = new URL(href);
72
- return (url.protocol === 'http:' || url.protocol === 'https:') && url.host ? href : null;
73
- }
74
- catch {
75
- return null;
76
- }
77
- };
78
- /**
79
- * A media file's source (a video, an audio track, an attachment): a
80
- * {@link safeWebUrl}, or a `blob:` URL an upload answered. Never `data:`:
81
- * an inline file would be stored in the document and sent in every update.
82
- */
83
- export const safeMediaSrc = (value) => {
84
- const blob = typeof value === 'string' ? /^blob:(.*)$/is.exec(value.trim()) : null;
85
- const origin = blob && safeWebUrl(blob[1]);
86
- return origin ? `blob:${origin}` : safeWebUrl(value);
87
- };
88
- /** The frame's `sandbox`: scripts and the provider's own origin, popups that leave it; no top navigation, modals, downloads or forms. */
89
- export const EMBED_SANDBOX = 'allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-presentation';
90
- /** The frame's permissions (`allow`): what a player needs, nothing that reads the viewer. */
91
- export const EMBED_ALLOW = 'fullscreen; picture-in-picture; encrypted-media; clipboard-write';
92
- const ID = /^[\w-]{1,128}$/;
93
- const HASH = /^[\da-f]{1,32}$/i;
94
- /** `url`'s path segments (empty ones dropped) when its host is one of `hosts`, else none. */
95
- const on = (url, ...hosts) => hosts.includes(url.hostname.toLowerCase()) ? url.pathname.split('/').filter(Boolean) : [];
96
- /** A YouTube start time (`t=42`, `t=42s`, `start=42`) in seconds. */
97
- const youtubeStart = (url) => {
98
- const t = url.searchParams.get('t') ?? url.searchParams.get('start');
99
- return t && /^\d{1,6}s?$/.test(t) ? `?start=${parseInt(t, 10)}` : '';
100
- };
101
- const youtube = {
102
- name: 'YouTube',
103
- embed: (url) => {
104
- const [first, second] = on(url, 'youtu.be', 'youtube.com', 'www.youtube.com', 'm.youtube.com', 'music.youtube.com', 'www.youtube-nocookie.com');
105
- const id = url.hostname.toLowerCase() === 'youtu.be'
106
- ? first
107
- : first === 'watch'
108
- ? url.searchParams.get('v')
109
- : first && ['shorts', 'embed', 'live'].includes(first)
110
- ? second
111
- : undefined;
112
- return id && /^[\w-]{11}$/.test(id)
113
- ? `https://www.youtube-nocookie.com/embed/${id}${youtubeStart(url)}`
114
- : null;
115
- }
116
- };
117
- const vimeo = {
118
- name: 'Vimeo',
119
- embed: (url) => {
120
- const [player, ...video] = on(url, 'player.vimeo.com');
121
- const [id, hash] = player === 'video' ? video : on(url, 'vimeo.com', 'www.vimeo.com');
122
- if (!id || !/^\d{1,12}$/.test(id))
123
- return null;
124
- const h = hash && HASH.test(hash) ? hash : url.searchParams.get('h');
125
- return `https://player.vimeo.com/video/${id}${h && HASH.test(h) ? `?h=${h}` : ''}`;
126
- }
127
- };
128
- const loom = {
129
- name: 'Loom',
130
- embed: (url) => {
131
- const [kind, id] = on(url, 'loom.com', 'www.loom.com');
132
- return (kind === 'share' || kind === 'embed') && id && /^[\da-f]{32}$/i.test(id)
133
- ? `https://www.loom.com/embed/${id}`
134
- : null;
135
- }
136
- };
137
- const figma = {
138
- name: 'Figma',
139
- embed: (url) => {
140
- const [kind, key] = on(url, 'figma.com', 'www.figma.com');
141
- return key &&
142
- ID.test(key) &&
143
- ['file', 'design', 'proto', 'board', 'slides', 'deck'].includes(kind)
144
- ? `https://www.figma.com/embed?embed_host=edytor&url=${encodeURIComponent(url.href)}`
145
- : null;
146
- }
147
- };
148
- const codepen = {
149
- name: 'CodePen',
150
- embed: (url) => {
151
- const [user, kind, id] = on(url, 'codepen.io');
152
- return user && ID.test(user) && (kind === 'pen' || kind === 'embed') && id && ID.test(id)
153
- ? `https://codepen.io/${user}/embed/${id}?default-tab=result`
154
- : null;
155
- },
156
- height: 400
157
- };
158
- const spotify = {
159
- name: 'Spotify',
160
- embed: (url) => {
161
- let parts = on(url, 'open.spotify.com');
162
- if (parts[0] === 'embed' || /^intl-[a-z-]+$/i.test(parts[0] ?? ''))
163
- parts = parts.slice(1);
164
- const [kind, id] = parts;
165
- return ['track', 'album', 'playlist', 'episode', 'show', 'artist'].includes(kind) &&
166
- id &&
167
- /^[A-Za-z0-9]{1,64}$/.test(id)
168
- ? `https://open.spotify.com/embed/${kind}/${id}`
169
- : null;
170
- },
171
- height: 352
172
- };
173
- /**
174
- * The default allowlist: YouTube (through `youtube-nocookie.com`), Vimeo,
175
- * Loom, Figma, CodePen and Spotify. A link no provider plays renders no
176
- * frame. Extend it with `createEmbedPlugin({ providers: [...EMBED_PROVIDERS, mine] })`.
177
- */
178
- export const EMBED_PROVIDERS = Object.freeze([
179
- youtube,
180
- vimeo,
181
- loom,
182
- figma,
183
- codepen,
184
- spotify
185
- ]);
186
- /**
187
- * `url` is on the page's own origin (in a browser): a frame there with
188
- * `allow-scripts` and `allow-same-origin` could remove its own sandbox.
189
- */
190
- const sameOrigin = (url) => {
191
- const origin = globalThis.location?.origin;
192
- return typeof origin === 'string' && url.origin === origin;
193
- };
194
- /**
195
- * The player for `value` (a stored or pasted link): the first provider of
196
- * `providers` that plays it, and its `https:` player URL. `null` when the
197
- * link is not a {@link safeWebUrl}, no provider plays it, or the player URL
198
- * a provider answers is not `https:` or is on the page's own origin. The frame's `src` is always this
199
- * answer, never the stored value.
200
- */
201
- export const embedSourceOf = (value, providers = EMBED_PROVIDERS) => {
202
- const href = safeWebUrl(value);
203
- if (!href)
204
- return null;
205
- const url = new URL(href);
206
- for (const provider of providers) {
207
- let src;
208
- try {
209
- src = provider.embed(new URL(url.href));
210
- }
211
- catch {
212
- continue;
213
- }
214
- if (src === null || src === undefined)
215
- continue;
216
- try {
217
- const player = new URL(src);
218
- if (player.protocol === 'https:' && safeWebUrl(src) === src && !sameOrigin(player))
219
- return { src, provider };
220
- }
221
- catch {
222
- // An unparsable player URL is refused like any other non-https one.
223
- }
224
- return null;
225
- }
226
- return null;
227
- };
228
- /** A file's display name from its URL's last path segment, decoded. */
229
- export const fileNameOf = (src) => {
230
- try {
231
- const last = new URL(src).pathname.split('/').filter(Boolean).at(-1);
232
- return last ? decodeURIComponent(last) : src;
233
- }
234
- catch {
235
- return src;
236
- }
237
- };
238
- /**
239
- * The element a media kind's HTML import reads: `element` itself when it
240
- * is a `tag` (not inside a `figure`, which claims it), or a `figure`'s own
241
- * `tag` child.
242
- */
243
- export const claimed = (element, tag) => {
244
- if (element.localName === 'figure')
245
- return [...element.children].find((child) => child.localName === tag);
246
- return element.localName === tag && element.parentElement?.localName !== 'figure'
247
- ? element
248
- : undefined;
249
- };
250
- /** An imported `video`'s or `audio`'s source: its `src`, else its first `source`'s. */
251
- export const mediaSourceOf = (element) => safeMediaSrc(element?.getAttribute('src') ?? element?.querySelector('source[src]')?.getAttribute('src'));
58
+ export * from './sources.js';
252
59
  export { escapeHtml };
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The media kinds' pure source rules: which URL a media block may store or
3
+ * render, the embed providers and their players, a file's name, the element
4
+ * an import reads. No DOM state, no Svelte: the export entry reads them too.
5
+ */
6
+ import { safeMediaSrc, safeWebUrl } from '../../utils/sanitize.js';
7
+ export { safeMediaSrc, safeWebUrl };
8
+ /** One allowlisted embed provider: which links it plays, and its player. */
9
+ export type EmbedProvider = {
10
+ /** The provider's name: the frame's `title`. */
11
+ name: string;
12
+ /**
13
+ * The player's URL for a link of this provider, else `null`. It must be
14
+ * `https:` on another origin than the page's: any other answer is
15
+ * refused (the frame has `allow-scripts` and `allow-same-origin`, so a
16
+ * player on the page's origin, or on one that can script it, could lift
17
+ * its own sandbox). Match the host exactly (never a
18
+ * suffix or a substring) and check every id you copy into the player URL.
19
+ */
20
+ embed: (url: URL) => string | null;
21
+ /** The frame's CSS `aspect-ratio` (default `16 / 9`); ignored with `height`. */
22
+ aspectRatio?: string;
23
+ /** A fixed frame height in pixels (a music player). */
24
+ height?: number;
25
+ };
26
+ /** The frame's `sandbox`: scripts and the provider's own origin, popups that leave it; no top navigation, modals, downloads or forms. */
27
+ export declare const EMBED_SANDBOX = "allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-presentation";
28
+ /** The frame's permissions (`allow`): what a player needs, nothing that reads the viewer. */
29
+ export declare const EMBED_ALLOW = "fullscreen; picture-in-picture; encrypted-media; clipboard-write";
30
+ /**
31
+ * The default allowlist: YouTube (through `youtube-nocookie.com`), Vimeo,
32
+ * Loom, Figma, CodePen and Spotify. A link no provider plays renders no
33
+ * frame. Extend it with `createEmbedPlugin({ providers: [...EMBED_PROVIDERS, mine] })`.
34
+ */
35
+ export declare const EMBED_PROVIDERS: readonly EmbedProvider[];
36
+ /**
37
+ * The player for `value` (a stored or pasted link): the first provider of
38
+ * `providers` that plays it, and its `https:` player URL. `null` when the
39
+ * link is not a {@link safeWebUrl}, no provider plays it, or the player URL
40
+ * a provider answers is not `https:` or is on the page's own origin. The frame's `src` is always this
41
+ * answer, never the stored value.
42
+ */
43
+ export declare const embedSourceOf: (value: unknown, providers?: readonly EmbedProvider[]) => {
44
+ src: string;
45
+ provider: EmbedProvider;
46
+ } | null;
47
+ /** A file's display name from its URL's last path segment, decoded. */
48
+ export declare const fileNameOf: (src: string) => string;
49
+ /** A file block's name: its stored `name`, else its source's last segment (`''` without one). */
50
+ export declare const fileNameFor: (data: {
51
+ name?: unknown;
52
+ } | undefined, src: string | null) => string;
53
+ /**
54
+ * The element a media kind's HTML import reads: `element` itself when it
55
+ * is a `tag` (not inside a `figure`, which claims it), or a `figure`'s own
56
+ * `tag` child.
57
+ */
58
+ export declare const claimed: (element: HTMLElement, tag: string) => HTMLElement | undefined;
59
+ /** An imported `video`'s or `audio`'s source: its `src`, else its first `source`'s. */
60
+ export declare const mediaSourceOf: (element: HTMLElement | undefined) => string | null;
@@ -0,0 +1,165 @@
1
+ /**
2
+ * The media kinds' pure source rules: which URL a media block may store or
3
+ * render, the embed providers and their players, a file's name, the element
4
+ * an import reads. No DOM state, no Svelte: the export entry reads them too.
5
+ */
6
+ import { safeMediaSrc, safeWebUrl } from '../../utils/sanitize.js';
7
+ export { safeMediaSrc, safeWebUrl };
8
+ /** The frame's `sandbox`: scripts and the provider's own origin, popups that leave it; no top navigation, modals, downloads or forms. */
9
+ export const EMBED_SANDBOX = 'allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-presentation';
10
+ /** The frame's permissions (`allow`): what a player needs, nothing that reads the viewer. */
11
+ export const EMBED_ALLOW = 'fullscreen; picture-in-picture; encrypted-media; clipboard-write';
12
+ const ID = /^[\w-]{1,128}$/;
13
+ const HASH = /^[\da-f]{1,32}$/i;
14
+ /** `url`'s path segments (empty ones dropped) when its host is exactly one `hosts` matches, else none. */
15
+ const on = (url, hosts) => hosts.test(url.hostname) ? url.pathname.split('/').filter(Boolean) : [];
16
+ const youtube = {
17
+ name: 'YouTube',
18
+ embed: (url) => {
19
+ const [first = '', second] = on(url, /^((www\.|m\.|music\.)?youtube\.com|youtu\.be|www\.youtube-nocookie\.com)$/);
20
+ const id = url.hostname === 'youtu.be'
21
+ ? first
22
+ : first === 'watch'
23
+ ? url.searchParams.get('v')
24
+ : /^(shorts|embed|live)$/.test(first) && second;
25
+ // A start time (`t=42`, `t=42s`, `start=42`) in seconds.
26
+ const t = url.searchParams.get('t') ?? url.searchParams.get('start');
27
+ return id && /^[\w-]{11}$/.test(id)
28
+ ? `https://www.youtube-nocookie.com/embed/${id}${t && /^\d{1,6}s?$/.test(t) ? `?start=${parseInt(t, 10)}` : ''}`
29
+ : null;
30
+ }
31
+ };
32
+ const vimeo = {
33
+ name: 'Vimeo',
34
+ embed: (url) => {
35
+ const [player, ...video] = on(url, /^player\.vimeo\.com$/);
36
+ const [id, hash] = player === 'video' ? video : on(url, /^(www\.)?vimeo\.com$/);
37
+ const h = hash && HASH.test(hash) ? hash : url.searchParams.get('h');
38
+ return id && /^\d{1,12}$/.test(id)
39
+ ? `https://player.vimeo.com/video/${id}${h && HASH.test(h) ? `?h=${h}` : ''}`
40
+ : null;
41
+ }
42
+ };
43
+ const loom = {
44
+ name: 'Loom',
45
+ embed: (url) => {
46
+ const [kind = '', id = ''] = on(url, /^(www\.)?loom\.com$/);
47
+ return /^(share|embed)$/.test(kind) && /^[\da-f]{32}$/i.test(id)
48
+ ? `https://www.loom.com/embed/${id}`
49
+ : null;
50
+ }
51
+ };
52
+ const figma = {
53
+ name: 'Figma',
54
+ embed: (url) => {
55
+ const [kind = '', key = ''] = on(url, /^(www\.)?figma\.com$/);
56
+ return ID.test(key) && /^(file|design|proto|board|slides|deck)$/.test(kind)
57
+ ? `https://www.figma.com/embed?embed_host=edytor&url=${encodeURIComponent(url.href)}`
58
+ : null;
59
+ }
60
+ };
61
+ const codepen = {
62
+ name: 'CodePen',
63
+ embed: (url) => {
64
+ const [user = '', kind = '', id = ''] = on(url, /^codepen\.io$/);
65
+ return ID.test(user) && /^(pen|embed)$/.test(kind) && ID.test(id)
66
+ ? `https://codepen.io/${user}/embed/${id}?default-tab=result`
67
+ : null;
68
+ },
69
+ height: 400
70
+ };
71
+ const spotify = {
72
+ name: 'Spotify',
73
+ embed: (url) => {
74
+ let parts = on(url, /^open\.spotify\.com$/);
75
+ if (/^(embed|intl-[a-z-]+)$/i.test(parts[0] ?? ''))
76
+ parts = parts.slice(1);
77
+ const [kind = '', id = ''] = parts;
78
+ return /^(track|album|playlist|episode|show|artist)$/.test(kind) &&
79
+ /^[A-Za-z0-9]{1,64}$/.test(id)
80
+ ? `https://open.spotify.com/embed/${kind}/${id}`
81
+ : null;
82
+ },
83
+ height: 352
84
+ };
85
+ /**
86
+ * The default allowlist: YouTube (through `youtube-nocookie.com`), Vimeo,
87
+ * Loom, Figma, CodePen and Spotify. A link no provider plays renders no
88
+ * frame. Extend it with `createEmbedPlugin({ providers: [...EMBED_PROVIDERS, mine] })`.
89
+ */
90
+ export const EMBED_PROVIDERS = Object.freeze([
91
+ youtube,
92
+ vimeo,
93
+ loom,
94
+ figma,
95
+ codepen,
96
+ spotify
97
+ ]);
98
+ /**
99
+ * `url` is on the page's own origin (in a browser): a frame there with
100
+ * `allow-scripts` and `allow-same-origin` could remove its own sandbox.
101
+ */
102
+ const sameOrigin = (url) => {
103
+ const origin = globalThis.location?.origin;
104
+ return typeof origin === 'string' && url.origin === origin;
105
+ };
106
+ /**
107
+ * The player for `value` (a stored or pasted link): the first provider of
108
+ * `providers` that plays it, and its `https:` player URL. `null` when the
109
+ * link is not a {@link safeWebUrl}, no provider plays it, or the player URL
110
+ * a provider answers is not `https:` or is on the page's own origin. The frame's `src` is always this
111
+ * answer, never the stored value.
112
+ */
113
+ export const embedSourceOf = (value, providers = EMBED_PROVIDERS) => {
114
+ const href = safeWebUrl(value);
115
+ if (!href)
116
+ return null;
117
+ const url = new URL(href);
118
+ for (const provider of providers) {
119
+ let src;
120
+ try {
121
+ src = provider.embed(new URL(url.href));
122
+ }
123
+ catch {
124
+ continue;
125
+ }
126
+ if (src === null || src === undefined)
127
+ continue;
128
+ try {
129
+ const player = new URL(src);
130
+ if (player.protocol === 'https:' && safeWebUrl(src) === src && !sameOrigin(player))
131
+ return { src, provider };
132
+ }
133
+ catch {
134
+ // An unparsable player URL is refused like any other non-https one.
135
+ }
136
+ return null;
137
+ }
138
+ return null;
139
+ };
140
+ /** A file's display name from its URL's last path segment, decoded. */
141
+ export const fileNameOf = (src) => {
142
+ try {
143
+ const last = new URL(src).pathname.split('/').filter(Boolean).at(-1);
144
+ return last ? decodeURIComponent(last) : src;
145
+ }
146
+ catch {
147
+ return src;
148
+ }
149
+ };
150
+ /** A file block's name: its stored `name`, else its source's last segment (`''` without one). */
151
+ export const fileNameFor = (data, src) => String(data?.name || (src && fileNameOf(src)) || '');
152
+ /**
153
+ * The element a media kind's HTML import reads: `element` itself when it
154
+ * is a `tag` (not inside a `figure`, which claims it), or a `figure`'s own
155
+ * `tag` child.
156
+ */
157
+ export const claimed = (element, tag) => {
158
+ if (element.localName === 'figure')
159
+ return [...element.children].find((child) => child.localName === tag);
160
+ return element.localName === tag && element.parentElement?.localName !== 'figure'
161
+ ? element
162
+ : undefined;
163
+ };
164
+ /** An imported `video`'s or `audio`'s source: its `src`, else its first `source`'s. */
165
+ export const mediaSourceOf = (element) => safeMediaSrc(element?.getAttribute('src') ?? element?.querySelector('source[src]')?.getAttribute('src'));
@@ -0,0 +1,23 @@
1
+ /* Loaded by the preview's module: a file uploading, drawn by the plugin when
2
+ the view has no empty markup (`createMediaEmptyPlugin`). */
3
+ [data-edytor-upload] {
4
+ display: flex;
5
+ position: relative;
6
+ flex-direction: column;
7
+ gap: 6px;
8
+ }
9
+ [data-edytor-upload] img {
10
+ display: block;
11
+ max-width: 100%;
12
+ }
13
+ [data-edytor-upload] progress {
14
+ width: 100%;
15
+ height: 4px;
16
+ }
17
+ [data-edytor-upload-image] progress {
18
+ position: absolute;
19
+ right: 8px;
20
+ bottom: 8px;
21
+ left: 8px;
22
+ width: auto;
23
+ }
@@ -43,10 +43,11 @@ export declare class UrlPasteController extends ChromeList {
43
43
  } | null;
44
44
  constructor(edytor: Edytor);
45
45
  protected get count(): number;
46
- get readonly(): boolean;
47
46
  close: () => void;
48
47
  /** Pick a row: keep the link, or turn the line into the offer's kind. */
49
48
  pick: (option?: UrlPasteOption | undefined) => void;
49
+ /** Where the menu stands (`ChromeAnchor`): the pasted line, which your `menu` places itself under. */
50
+ readonly anchor: import("../chrome.js").ChromeAnchor;
50
51
  }
51
52
  /**
52
53
  * Register `offer` on `edytor`'s URL paste menu. The first plugin to offer