edytor 2.0.0 → 4.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.
- package/README.md +19 -4
- package/dist/block/block.svelte.js +2 -4
- package/dist/clipboard/htmlFlow.d.ts +20 -7
- package/dist/clipboard/htmlFlow.js +23 -15
- package/dist/clipboard/serializeClipboardFragment.d.ts +37 -4
- package/dist/clipboard/serializeClipboardFragment.js +6 -4
- package/dist/clipboard/textBlocks.d.ts +8 -0
- package/dist/clipboard/textBlocks.js +6 -2
- package/dist/cloudflare/DocumentRoom.d.ts +13 -9
- package/dist/cloudflare/DocumentRoom.js +10 -6
- package/dist/cloudflare/room/admission.js +3 -3
- package/dist/cloudflare/room/context.d.ts +28 -27
- package/dist/cloudflare/room/context.js +2 -2
- package/dist/cloudflare/room/purge.d.ts +6 -3
- package/dist/cloudflare/room/purge.js +44 -19
- package/dist/cloudflare/room/updates.d.ts +3 -4
- package/dist/cloudflare/room/updates.js +6 -8
- package/dist/cloudflare/routeDocumentSocket.d.ts +17 -0
- package/dist/cloudflare/routeDocumentSocket.js +53 -0
- package/dist/collaboration/comments/client.d.ts +1 -1
- package/dist/collaboration/comments/client.js +1 -1
- package/dist/collaboration/remoteSelection.js +3 -3
- package/dist/components/Edytor.svelte +2 -10
- package/dist/components/Edytor.svelte.d.ts +1 -3
- package/dist/crdt/admission.d.ts +5 -10
- package/dist/crdt/admission.js +5 -11
- package/dist/crdt/attribution/attribution.d.ts +31 -12
- package/dist/crdt/attribution/attribution.js +56 -18
- package/dist/crdt/attribution/block.d.ts +3 -3
- package/dist/crdt/attribution/block.js +7 -8
- package/dist/crdt/attribution/index.d.ts +1 -1
- package/dist/crdt/attribution/index.js +1 -1
- package/dist/crdt/data.d.ts +62 -31
- package/dist/crdt/data.js +211 -103
- package/dist/crdt/doc/delete.d.ts +1 -1
- package/dist/crdt/doc/delete.js +13 -30
- package/dist/crdt/doc/funnel.d.ts +7 -6
- package/dist/crdt/doc/funnel.js +39 -33
- package/dist/crdt/doc/gate.d.ts +3 -20
- package/dist/crdt/doc/gate.js +2 -41
- package/dist/crdt/doc/history.d.ts +25 -9
- package/dist/crdt/doc/history.js +152 -42
- package/dist/crdt/doc/layout.js +11 -22
- package/dist/crdt/doc/meta.js +12 -17
- package/dist/crdt/doc/moves.d.ts +1 -1
- package/dist/crdt/doc/moves.js +52 -32
- package/dist/crdt/doc/reads.js +2 -2
- package/dist/crdt/doc/seed.js +2 -2
- package/dist/crdt/doc/split-merge.js +12 -15
- package/dist/crdt/doc/steps.d.ts +25 -13
- package/dist/crdt/doc/steps.js +52 -122
- package/dist/crdt/doc/types.d.ts +21 -9
- package/dist/crdt/document.d.ts +21 -19
- package/dist/crdt/document.js +26 -23
- package/dist/crdt/edytor-doc.d.ts +66 -68
- package/dist/crdt/edytor-doc.js +21 -20
- package/dist/crdt/engine-api.d.ts +33 -1
- package/dist/crdt/engine.d.ts +0 -1
- package/dist/crdt/engine.js +1 -4
- package/dist/crdt/flow.d.ts +1 -1
- package/dist/crdt/flow.js +25 -27
- package/dist/crdt/index.d.ts +29 -28
- package/dist/crdt/index.js +3 -3
- package/dist/crdt/placement/display.d.ts +5 -30
- package/dist/crdt/placement/display.js +39 -144
- package/dist/crdt/placement/keys.d.ts +58 -0
- package/dist/crdt/placement/keys.js +90 -0
- package/dist/crdt/placement/model.d.ts +119 -118
- package/dist/crdt/placement/model.js +242 -142
- package/dist/crdt/placement/resolve.d.ts +123 -35
- package/dist/crdt/placement/resolve.js +187 -153
- package/dist/crdt/placement/slots.d.ts +134 -0
- package/dist/crdt/placement/slots.js +152 -0
- package/dist/crdt/protocol.d.ts +2 -2
- package/dist/crdt/protocol.js +2 -2
- package/dist/crdt/providers/index.d.ts +1 -1
- package/dist/crdt/providers/room.d.ts +26 -11
- package/dist/crdt/providers/room.js +77 -27
- package/dist/crdt/providers/websocket.d.ts +1 -1
- package/dist/crdt/providers/websocket.js +33 -32
- package/dist/crdt/purge.d.ts +17 -8
- package/dist/crdt/purge.js +236 -126
- package/dist/crdt/restore.d.ts +2 -2
- package/dist/crdt/restore.js +18 -34
- package/dist/crdt/schema.d.ts +22 -21
- package/dist/crdt/schema.js +29 -23
- package/dist/crdt/semantics.js +3 -1
- package/dist/crdt/storage.d.ts +1 -1
- package/dist/crdt/storage.js +1 -1
- package/dist/crdt/structs.d.ts +15 -15
- package/dist/crdt/structs.js +8 -22
- package/dist/crdt/text/index/cache.js +4 -0
- package/dist/crdt/text/index/claims.js +12 -0
- package/dist/crdt/text/index/fold.d.ts +2 -3
- package/dist/crdt/text/index/fold.js +194 -44
- package/dist/crdt/text/index/layout.js +5 -8
- package/dist/crdt/text/index/placement.d.ts +26 -18
- package/dist/crdt/text/index/placement.js +404 -138
- package/dist/crdt/text/index/records.d.ts +3 -3
- package/dist/crdt/text/index/records.js +24 -31
- package/dist/crdt/text/index/shared.d.ts +11 -3
- package/dist/crdt/text/index/shared.js +12 -3
- package/dist/crdt/text/index/state.d.ts +21 -17
- package/dist/crdt/text/index/state.js +15 -17
- package/dist/crdt/text/index/streams.d.ts +10 -5
- package/dist/crdt/text/index/streams.js +78 -27
- package/dist/crdt/text/items.d.ts +10 -3
- package/dist/crdt/text/items.js +7 -0
- package/dist/crdt/text/model.d.ts +77 -62
- package/dist/crdt/text/model.js +84 -169
- package/dist/crdt/text/ranges.d.ts +1 -1
- package/dist/crdt/text/ranges.js +3 -3
- package/dist/crdt/text/rows.d.ts +13 -7
- package/dist/crdt/text/rows.js +24 -7
- package/dist/crdt/text/runs.d.ts +17 -12
- package/dist/crdt/text/runs.js +24 -7
- package/dist/crdt/text/streams.d.ts +26 -13
- package/dist/crdt/text/streams.js +41 -14
- package/dist/crdt/vendor/yjs/UPSTREAM.md +568 -21
- package/dist/crdt/vendor/yjs/dts/global.d.ts +2 -0
- package/dist/crdt/vendor/yjs/dts/index.d.ts +1 -1
- package/dist/crdt/vendor/yjs/dts/structs/AbstractStruct.d.ts +7 -0
- package/dist/crdt/vendor/yjs/dts/structs/Item.d.ts +194 -10
- package/dist/crdt/vendor/yjs/dts/utils/Doc.d.ts +6 -3
- package/dist/crdt/vendor/yjs/dts/utils/StructStore.d.ts +5 -18
- package/dist/crdt/vendor/yjs/dts/utils/Transaction.d.ts +15 -1
- package/dist/crdt/vendor/yjs/dts/utils/UpdateDecoder.d.ts +14 -6
- package/dist/crdt/vendor/yjs/dts/utils/UpdateEncoder.d.ts +33 -6
- package/dist/crdt/vendor/yjs/dts/utils/ids.d.ts +1 -65
- package/dist/crdt/vendor/yjs/dts/utils/marks.d.ts +6 -1
- package/dist/crdt/vendor/yjs/dts/ynode.d.ts +43 -7
- package/dist/crdt/vendor/yjs/global.d.ts +2 -0
- package/dist/crdt/vendor/yjs/src/index.js +1 -1
- package/dist/crdt/vendor/yjs/src/structs/AbstractStruct.js +10 -0
- package/dist/crdt/vendor/yjs/src/structs/Item.js +610 -51
- package/dist/crdt/vendor/yjs/src/utils/BlockSet.js +18 -9
- package/dist/crdt/vendor/yjs/src/utils/Doc.js +5 -3
- package/dist/crdt/vendor/yjs/src/utils/StructStore.js +5 -0
- package/dist/crdt/vendor/yjs/src/utils/Transaction.js +36 -13
- package/dist/crdt/vendor/yjs/src/utils/UpdateDecoder.js +88 -18
- package/dist/crdt/vendor/yjs/src/utils/UpdateEncoder.js +119 -32
- package/dist/crdt/vendor/yjs/src/utils/encoding.js +13 -1
- package/dist/crdt/vendor/yjs/src/utils/ids.js +7 -7
- package/dist/crdt/vendor/yjs/src/utils/marks.js +193 -8
- package/dist/crdt/vendor/yjs/src/utils/redo.js +1 -1
- package/dist/crdt/vendor/yjs/src/utils/transaction-helpers.js +1 -1
- package/dist/crdt/vendor/yjs/src/utils/updates.js +3 -3
- package/dist/crdt/vendor/yjs/src/ynode.js +177 -12
- package/dist/dnd/autoScroll.d.ts +15 -7
- package/dist/dnd/autoScroll.js +10 -15
- package/dist/dnd/dropIndicator.js +2 -5
- package/dist/dnd/external.d.ts +6 -0
- package/dist/dnd/external.js +17 -0
- package/dist/dnd/geometry.d.ts +2 -0
- package/dist/dnd/geometry.js +3 -4
- package/dist/dnd/placement.d.ts +1 -1
- package/dist/dnd/pragmatic.d.ts +3 -1
- package/dist/dnd/pragmatic.js +12 -2
- package/dist/dnd/vendor/pragmatic/UPSTREAM.md +51 -18
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/drop-target-for-external.d.ts +4 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/drop-target-for-external.js +6 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/element-adapter.d.ts +0 -2
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/element-adapter.js +4 -22
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/external-adapter-types.d.ts +26 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/external-adapter.d.ts +30 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/external-adapter.js +203 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/get-available-items.js +13 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/get-available-types.js +9 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/is-an-available-type.js +19 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/monitor-for-external.d.ts +4 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/monitor-for-external.js +6 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/honey-pot-fix/make-honey-pot-fix.js +4 -16
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/internal-types.d.ts +1 -8
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/make-adapter/make-drop-target.js +5 -6
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/make-adapter/make-monitor.js +2 -3
- 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
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/element/custom-native-drag-preview/set-custom-native-drag-preview.js +6 -24
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/external/contains-files.d.ts +2 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/external/contains-files.js +5 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/external/get-files.d.ts +3 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/external/get-files.js +12 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/external/native-types.d.ts +12 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/changing-window/is-entering-window-in-safari.js +10 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/changing-window/is-entering-window.js +55 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/entry-point/external.d.ts +4 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/entry-point/external.js +8 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/internal-types.d.ts +0 -4
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/over-element/get-scroll-by.js +16 -72
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/over-element/try-scroll.js +1 -9
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/configuration.js +4 -29
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/get-scroll-change.js +2 -3
- package/dist/edytor.svelte.d.ts +2 -5
- package/dist/edytor.svelte.js +21 -32
- package/dist/events/beforeInputCommands.js +2 -2
- package/dist/events/onBeforeInput.js +1 -1
- package/dist/events/onDrop.d.ts +0 -6
- package/dist/events/onDrop.js +10 -23
- package/dist/events/onInput.d.ts +0 -12
- package/dist/events/onInput.js +1 -67
- package/dist/events/onPageHide.d.ts +3 -0
- package/dist/events/onPageHide.js +16 -0
- package/dist/export/forms.d.ts +627 -0
- package/dist/export/forms.js +749 -0
- package/dist/export/index.d.ts +170 -0
- package/dist/export/index.js +161 -0
- package/dist/export/markdown.d.ts +41 -0
- package/dist/export/markdown.js +585 -0
- package/dist/export/markdownWriter.d.ts +9 -0
- package/dist/export/markdownWriter.js +133 -0
- package/dist/export/mdx.d.ts +15 -0
- package/dist/export/mdx.js +33 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/kinds.js +1 -1
- package/dist/plugins/anchored.svelte.d.ts +21 -25
- package/dist/plugins/anchored.svelte.js +23 -39
- package/dist/plugins/blockHandles/BlockHandleController.svelte.js +24 -32
- package/dist/plugins/blockHandles/dragPreview.d.ts +26 -7
- package/dist/plugins/blockHandles/dragPreview.js +33 -0
- package/dist/plugins/blockMenu/BlockMenuController.svelte.d.ts +5 -0
- package/dist/plugins/blockMenu/BlockMenuController.svelte.js +7 -2
- package/dist/plugins/blockMenu/blockMenuPlugin.js +5 -50
- package/dist/plugins/chrome.d.ts +27 -5
- package/dist/plugins/chrome.js +24 -9
- package/dist/plugins/code/CodePlugin.svelte +12 -9
- package/dist/plugins/code/header.svelte.js +7 -11
- package/dist/plugins/code/languageMenu.svelte.d.ts +5 -2
- package/dist/plugins/code/languageMenu.svelte.js +13 -20
- package/dist/plugins/columns/ColumnsPlugin.svelte +7 -20
- package/dist/plugins/columns/resize.svelte.d.ts +2 -3
- package/dist/plugins/columns/resize.svelte.js +2 -5
- package/dist/plugins/comments/CommentsController.svelte.d.ts +6 -0
- package/dist/plugins/comments/CommentsController.svelte.js +20 -0
- package/dist/plugins/equation/EquationPlugin.svelte +12 -22
- package/dist/plugins/equation/equation.svelte.d.ts +7 -15
- package/dist/plugins/equation/equation.svelte.js +13 -50
- package/dist/plugins/equation/tex.d.ts +17 -0
- package/dist/plugins/equation/tex.js +42 -0
- package/dist/plugins/fileDrop.d.ts +38 -23
- package/dist/plugins/fileDrop.js +82 -76
- package/dist/plugins/find/FindController.svelte.d.ts +5 -0
- package/dist/plugins/find/FindController.svelte.js +14 -14
- package/dist/plugins/find/findPlugin.js +1 -15
- package/dist/plugins/image/ImageChrome.svelte +8 -2
- package/dist/plugins/image/ImagePlugin.svelte +15 -52
- package/dist/plugins/image/controls.svelte.js +16 -2
- package/dist/plugins/image/toolbar.svelte.d.ts +5 -4
- package/dist/plugins/image/toolbar.svelte.js +21 -31
- package/dist/plugins/index.d.ts +1 -1
- package/dist/plugins/list.svelte.d.ts +9 -0
- package/dist/plugins/list.svelte.js +18 -0
- package/dist/plugins/marquee/marqueePlugin.js +0 -4
- package/dist/plugins/media/AudioPlugin.svelte +1 -1
- package/dist/plugins/media/BookmarkPlugin.svelte +6 -97
- package/dist/plugins/media/EmbedPlugin.svelte +5 -90
- package/dist/plugins/media/EmptySlot.svelte +36 -20
- package/dist/plugins/media/EmptySlot.svelte.d.ts +6 -15
- package/dist/plugins/media/FilePlugin.svelte +1 -19
- package/dist/plugins/media/MediaEmpty.svelte +2 -55
- package/dist/plugins/media/MediaEmpty.svelte.d.ts +18 -14
- package/dist/plugins/media/SourceBlock.svelte +142 -52
- package/dist/plugins/media/SourceBlock.svelte.d.ts +10 -4
- package/dist/plugins/media/VideoPlugin.svelte +1 -1
- package/dist/plugins/media/empty.svelte.js +2 -7
- package/dist/plugins/media/media.d.ts +2 -63
- package/dist/plugins/media/media.js +1 -194
- package/dist/plugins/media/sources.d.ts +60 -0
- package/dist/plugins/media/sources.js +165 -0
- package/dist/plugins/media/urlPaste.svelte.d.ts +2 -1
- package/dist/plugins/media/urlPaste.svelte.js +12 -21
- package/dist/plugins/mention/MentionPlugin.svelte +8 -35
- package/dist/plugins/page/PagePlugin.svelte +2 -9
- package/dist/plugins/page/page.js +1 -1
- package/dist/plugins/pageLink/PageLinkPlugin.svelte +6 -13
- package/dist/plugins/richtext/RichTextPlugin.svelte +85 -197
- package/dist/plugins/richtext/body.js +1 -1
- package/dist/plugins/richtext/calloutIcons.svelte.d.ts +2 -2
- package/dist/plugins/richtext/calloutIcons.svelte.js +5 -7
- package/dist/plugins/richtext/richTextOperations.d.ts +1 -19
- package/dist/plugins/richtext/richTextOperations.js +3 -49
- package/dist/plugins/slashMenu/SlashMenuController.svelte.d.ts +2 -0
- package/dist/plugins/slashMenu/SlashMenuController.svelte.js +7 -0
- package/dist/plugins/slashMenu/slashMenuPlugin.js +5 -14
- package/dist/plugins/table/TableChrome.svelte +2 -3
- package/dist/plugins/table/TablePlugin.svelte +20 -54
- package/dist/plugins/table/chrome.svelte.d.ts +5 -2
- package/dist/plugins/table/chrome.svelte.js +30 -54
- package/dist/plugins/table/table.js +3 -3
- package/dist/plugins/toc/TocPlugin.svelte +2 -4
- package/dist/plugins/toolbar/ToolbarController.svelte.d.ts +7 -0
- package/dist/plugins/toolbar/ToolbarController.svelte.js +20 -13
- package/dist/plugins/toolbar/toolbarPlugin.js +19 -53
- package/dist/plugins/triggers/TriggerController.svelte.d.ts +7 -2
- package/dist/plugins/triggers/TriggerController.svelte.js +11 -4
- package/dist/plugins/triggers/place.js +1 -2
- package/dist/plugins/triggers/triggers.d.ts +6 -1
- package/dist/plugins/triggers/triggers.js +12 -13
- package/dist/plugins/uploads.svelte.d.ts +5 -2
- package/dist/plugins/uploads.svelte.js +25 -15
- package/dist/plugins.d.ts +26 -0
- package/dist/plugins.js +0 -24
- package/dist/selection/replaceSelection.js +2 -2
- package/dist/selection/selection.svelte.d.ts +4 -10
- package/dist/selection/selection.svelte.js +7 -11
- package/dist/session/bindings.js +1 -1
- package/dist/session/commands.d.ts +7 -11
- package/dist/session/commands.js +13 -19
- package/dist/session/inputRules.js +1 -1
- package/dist/session/navigation.js +1 -1
- package/dist/session/suggestions.svelte.d.ts +0 -11
- package/dist/session/suggestions.svelte.js +15 -3
- package/dist/session/uploads.d.ts +8 -8
- package/dist/surface/lines.js +1 -3
- package/dist/surface/observer.svelte.d.ts +17 -1
- package/dist/surface/observer.svelte.js +74 -19
- package/dist/surface/overlay.d.ts +0 -14
- package/dist/surface/overlay.js +0 -25
- package/dist/surface/projector.svelte.d.ts +15 -3
- package/dist/surface/projector.svelte.js +60 -9
- package/dist/text/text.svelte.js +3 -13
- package/dist/utils/sanitize.d.ts +38 -0
- package/dist/utils/sanitize.js +82 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +5 -0
- package/package.json +19 -2
- package/dist/crdt/placement/rank.d.ts +0 -87
- package/dist/crdt/placement/rank.js +0 -332
- package/dist/crdt/placement/source.d.ts +0 -43
- package/dist/crdt/placement/source.js +0 -66
- package/dist/crdt/text/delete-index.d.ts +0 -129
- package/dist/crdt/text/delete-index.js +0 -365
- package/dist/crdt/text/deletes.d.ts +0 -76
- package/dist/crdt/text/deletes.js +0 -362
- package/dist/crdt/text/index/anchored.d.ts +0 -17
- package/dist/crdt/text/index/anchored.js +0 -197
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/max-z-index.js +0 -3
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/supports-popover.js +0 -16
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/is-axis-allowed.js +0 -3
- package/dist/plugins/code/LanguageMenu.svelte +0 -25
- package/dist/plugins/code/LanguageMenu.svelte.d.ts +0 -9
- package/dist/plugins/equation/EquationEditor.svelte +0 -27
- package/dist/plugins/equation/EquationEditor.svelte.d.ts +0 -9
- package/dist/plugins/image/ImageToolbarHost.svelte +0 -37
- package/dist/plugins/image/ImageToolbarHost.svelte.d.ts +0 -9
- package/dist/plugins/richtext/CalloutIconPicker.svelte +0 -26
- package/dist/plugins/richtext/CalloutIconPicker.svelte.d.ts +0 -9
|
@@ -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'));
|
|
@@ -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
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { placeNear } from '../../surface/overlay.js';
|
|
2
|
-
import { onPress } from '../../events/onFocus.js';
|
|
3
1
|
import { convertToKind, holdsNothing } from '../../kinds.js';
|
|
4
2
|
import { pastedLink } from '../richtext/richTextOperations.js';
|
|
5
3
|
import { safeWebUrl } from './media.js';
|
|
6
|
-
import { mountChrome } from '../chrome.js';
|
|
4
|
+
import { mountChrome, onPressOutside, virtualAnchor } from '../chrome.js';
|
|
7
5
|
import { ChromeList, inPage, listHotkeys } from '../list.svelte.js';
|
|
8
6
|
/**
|
|
9
7
|
* Pasting a bare URL on an empty line (Notion): the URL lands as a link at
|
|
@@ -27,9 +25,6 @@ export class UrlPasteController extends ChromeList {
|
|
|
27
25
|
get count() {
|
|
28
26
|
return this.open?.options.length ?? 0;
|
|
29
27
|
}
|
|
30
|
-
get readonly() {
|
|
31
|
-
return this.edytor.readonly;
|
|
32
|
-
}
|
|
33
28
|
/** A custom menu (a media plugin's `menu`): the first one a plugin making an offer passed. */
|
|
34
29
|
#menu;
|
|
35
30
|
/** Take `menu` as the view's menu, unless a plugin listed before passed one. @internal */
|
|
@@ -70,7 +65,7 @@ export class UrlPasteController extends ChromeList {
|
|
|
70
65
|
: {};
|
|
71
66
|
edytor.dispatcher.run('insertFromPaste', () => {
|
|
72
67
|
edytor.insertText(block.id, text.segStart, url, marks);
|
|
73
|
-
edytor.dispatcher.
|
|
68
|
+
edytor.dispatcher.caretIn(edytor.idToBlock.get(block.id)?.firstText, url.length);
|
|
74
69
|
});
|
|
75
70
|
if (edytor.dispatcher.last?.status !== 'applied')
|
|
76
71
|
return;
|
|
@@ -122,12 +117,7 @@ export class UrlPasteController extends ChromeList {
|
|
|
122
117
|
this.close();
|
|
123
118
|
},
|
|
124
119
|
onEdytorAttached: ({ node }) => {
|
|
125
|
-
const
|
|
126
|
-
const target = event.target;
|
|
127
|
-
if (!target?.closest?.('[data-edytor-url-paste-host]'))
|
|
128
|
-
this.close();
|
|
129
|
-
};
|
|
130
|
-
const offPress = onPress(this.edytor, node.ownerDocument, outside, true);
|
|
120
|
+
const offPress = onPressOutside(this.edytor, node.ownerDocument, '[data-edytor-url-paste-host]', this.close);
|
|
131
121
|
const unmount = mountChrome(this.edytor, this.#menu, this, () => this.open, { name: 'edytor-url-paste-host', zIndex: 50, measure: this.measure }, this.close);
|
|
132
122
|
return () => {
|
|
133
123
|
offPress();
|
|
@@ -136,16 +126,17 @@ export class UrlPasteController extends ChromeList {
|
|
|
136
126
|
};
|
|
137
127
|
}
|
|
138
128
|
});
|
|
139
|
-
/**
|
|
140
|
-
measure = (
|
|
129
|
+
/** A gone line closes the menu (measured in the overlay's frame). @internal */
|
|
130
|
+
measure = () => {
|
|
141
131
|
const { open, edytor } = this;
|
|
142
|
-
if (!open)
|
|
143
|
-
return;
|
|
144
|
-
const block = edytor.idToBlock.get(open.block);
|
|
145
|
-
if (!block?.isInTree)
|
|
132
|
+
if (open && !edytor.idToBlock.get(open.block)?.isInTree)
|
|
146
133
|
return () => this.close();
|
|
147
|
-
|
|
148
|
-
|
|
134
|
+
};
|
|
135
|
+
/** Where the menu stands (`ChromeAnchor`): the pasted line, which your `menu` places itself under. */
|
|
136
|
+
anchor = virtualAnchor(() => this.#line()?.getBoundingClientRect(), () => this.#line());
|
|
137
|
+
#line = () => {
|
|
138
|
+
const block = this.open && this.edytor.idToBlock.get(this.open.block);
|
|
139
|
+
return block ? (block.firstText?.node ?? block.node) : undefined;
|
|
149
140
|
};
|
|
150
141
|
}
|
|
151
142
|
const controllers = new WeakMap();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<svelte:options runes />
|
|
2
2
|
|
|
3
3
|
<script module lang="ts">
|
|
4
|
+
import { mentionForms } from '../../export/forms.js';
|
|
4
5
|
import './mention.css';
|
|
5
6
|
import type { Snippet } from 'svelte';
|
|
6
7
|
import type {
|
|
@@ -11,7 +12,7 @@
|
|
|
11
12
|
TriggerContext
|
|
12
13
|
} from '../../plugins.js';
|
|
13
14
|
import type { TriggerMenuController } from '../triggers/TriggerController.svelte.js';
|
|
14
|
-
import { withRulesAndTriggers } from '../triggers/triggers.js';
|
|
15
|
+
import { placeAtom, withRulesAndTriggers } from '../triggers/triggers.js';
|
|
15
16
|
|
|
16
17
|
/** A person the mention menu offers: what your app's directory answers. */
|
|
17
18
|
export type MentionItem = {
|
|
@@ -60,12 +61,6 @@
|
|
|
60
61
|
(atom.handle?.edytor.inlineBlocks.get(atom.type) as Partial<MentionKind> | undefined)?.char ??
|
|
61
62
|
'@';
|
|
62
63
|
|
|
63
|
-
const escape = (value: string) =>
|
|
64
|
-
value.replace(
|
|
65
|
-
/[&<>"]/g,
|
|
66
|
-
(c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' })[c]!
|
|
67
|
-
);
|
|
68
|
-
|
|
69
64
|
/**
|
|
70
65
|
* People mentions, as in Notion: `@` opens a menu of the people your app's
|
|
71
66
|
* `items(query)` answers; picking one replaces `@query` with a `mention`
|
|
@@ -75,30 +70,10 @@
|
|
|
75
70
|
export const createMentionPlugin = (options: MentionPluginOptions): Plugin => {
|
|
76
71
|
const char = options.char ?? '@';
|
|
77
72
|
const kind = options.kind ?? 'mention';
|
|
78
|
-
const labelOf = (data: unknown) => {
|
|
79
|
-
const label = (data as Partial<MentionData> | undefined)?.label;
|
|
80
|
-
return typeof label === 'string' ? label : '';
|
|
81
|
-
};
|
|
82
73
|
const atom: MentionKind = {
|
|
83
74
|
snippet: mention,
|
|
84
75
|
char,
|
|
85
|
-
|
|
86
|
-
html: (data) => {
|
|
87
|
-
const id = (data as Partial<MentionData> | undefined)?.id;
|
|
88
|
-
return (
|
|
89
|
-
`<span data-edytor-mention="${escape(kind)}"` +
|
|
90
|
-
(typeof id === 'string' ? ` data-mention-id="${escape(id)}"` : '') +
|
|
91
|
-
`>${escape(char + labelOf(data))}</span>`
|
|
92
|
-
);
|
|
93
|
-
},
|
|
94
|
-
parse: (element) => {
|
|
95
|
-
// A bare `data-edytor-mention` (an older export) is a `mention`.
|
|
96
|
-
const own = element.dataset.edytorMention;
|
|
97
|
-
if (own === undefined || (own || 'mention') !== kind) return undefined;
|
|
98
|
-
const text = element.textContent ?? '';
|
|
99
|
-
const label = text.startsWith(char) ? text.slice(char.length) : text;
|
|
100
|
-
return { id: element.dataset.mentionId ?? '', label };
|
|
101
|
-
}
|
|
76
|
+
...mentionForms(kind, char)
|
|
102
77
|
};
|
|
103
78
|
return (edytor) =>
|
|
104
79
|
withRulesAndTriggers(
|
|
@@ -113,13 +88,11 @@
|
|
|
113
88
|
label: (item: MentionItem) => item.label,
|
|
114
89
|
key: (item: MentionItem) => item.id,
|
|
115
90
|
menu: options.menu,
|
|
116
|
-
onPick: (item: MentionItem,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return written;
|
|
122
|
-
}
|
|
91
|
+
onPick: (item: MentionItem, ctx) =>
|
|
92
|
+
placeAtom(ctx, {
|
|
93
|
+
type: kind,
|
|
94
|
+
data: { id: item.id, label: item.label } satisfies MentionData
|
|
95
|
+
})
|
|
123
96
|
}
|
|
124
97
|
]
|
|
125
98
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script module lang="ts">
|
|
2
|
+
import { pageForms } from '../../export/forms.js';
|
|
2
3
|
import type { Plugin, BlockSnippetPayload } from '../../plugins.js';
|
|
3
4
|
import { pageKinds } from '../../crdt/semantics.js';
|
|
4
5
|
import { convertToKind, type KindRow } from '../../kinds.js';
|
|
@@ -6,7 +7,6 @@
|
|
|
6
7
|
import { getSelectedBlocksInDocumentOrder } from '../../selection/replaceSelection.js';
|
|
7
8
|
import PageLink from './PageLink.svelte';
|
|
8
9
|
import {
|
|
9
|
-
pageHtml,
|
|
10
10
|
pageIdOf,
|
|
11
11
|
pageTitleOf,
|
|
12
12
|
pageOptions as optionsOf,
|
|
@@ -63,15 +63,8 @@
|
|
|
63
63
|
...pageKinds.page,
|
|
64
64
|
snippet: page,
|
|
65
65
|
empty: { content: [], children: [] },
|
|
66
|
-
html: (block) => pageHtml(block.data, own, labels.untitled),
|
|
67
|
-
plain: (block) => pageTitleOf(block.data?.title) ?? labels.untitled,
|
|
68
66
|
// Its own export: a `p[data-edytor-page]` naming the page.
|
|
69
|
-
|
|
70
|
-
const pageId = pageIdOf(el.getAttribute('data-edytor-page'));
|
|
71
|
-
if (el.localName !== 'p' || !pageId) return;
|
|
72
|
-
const title = pageTitleOf(el.textContent);
|
|
73
|
-
return { pageId, ...(title && { title }) };
|
|
74
|
-
}
|
|
67
|
+
...pageForms(own, labels.untitled).page
|
|
75
68
|
}
|
|
76
69
|
},
|
|
77
70
|
commands: create
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { sanitizeLinkHref } from '
|
|
1
|
+
import { sanitizeLinkHref } from '../../utils/sanitize.js';
|
|
2
2
|
import { escapeHtml } from '../../clipboard/serializeClipboardFragment.js';
|
|
3
3
|
/** Each view's page options (the first page plugin listed sets them). */
|
|
4
4
|
export const pageOptions = new WeakMap();
|