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.
- package/README.md +26 -69
- package/dist/block/block.svelte.d.ts +6 -4
- package/dist/block/block.svelte.js +12 -8
- package/dist/block/inlineBlock.svelte.d.ts +6 -2
- package/dist/block/inlineBlock.svelte.js +7 -2
- 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/room/admission.js +3 -3
- package/dist/cloudflare/room/context.d.ts +7 -1
- package/dist/cloudflare/routeDocumentSocket.d.ts +17 -0
- package/dist/cloudflare/routeDocumentSocket.js +53 -0
- package/dist/collaboration/awarenessSelection.d.ts +22 -1
- package/dist/collaboration/awarenessSelection.js +42 -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/Block.svelte +3 -1
- package/dist/components/Edytor.svelte +38 -20
- package/dist/components/Edytor.svelte.d.ts +9 -4
- package/dist/components/InlineBlock.svelte +1 -1
- package/dist/crdt/admission.d.ts +5 -10
- package/dist/crdt/admission.js +5 -11
- package/dist/crdt/data.d.ts +10 -0
- package/dist/crdt/data.js +20 -2
- package/dist/crdt/doc/funnel.d.ts +7 -6
- package/dist/crdt/doc/funnel.js +10 -9
- package/dist/crdt/doc/gate.d.ts +0 -17
- package/dist/crdt/doc/gate.js +2 -41
- package/dist/crdt/doc/history.js +2 -2
- package/dist/crdt/doc/reads.d.ts +7 -0
- package/dist/crdt/doc/reads.js +11 -1
- package/dist/crdt/document.d.ts +13 -14
- package/dist/crdt/document.js +17 -19
- package/dist/crdt/edytor-doc.d.ts +22 -4
- package/dist/crdt/edytor-doc.js +5 -8
- package/dist/crdt/index.d.ts +9 -3
- package/dist/crdt/index.js +2 -2
- package/dist/crdt/operations.d.ts +1 -1
- package/dist/crdt/operations.js +1 -0
- package/dist/crdt/props.d.ts +32 -0
- package/dist/crdt/props.js +172 -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 +10 -9
- package/dist/crdt/providers/room.js +13 -10
- package/dist/crdt/providers/websocket.d.ts +1 -1
- package/dist/crdt/providers/websocket.js +28 -29
- package/dist/crdt/semantics.js +3 -1
- package/dist/dnd/autoScroll.d.ts +15 -7
- package/dist/dnd/autoScroll.js +10 -15
- package/dist/{plugins/blockHandles → dnd}/dropIndicator.d.ts +2 -2
- package/dist/{plugins/blockHandles → dnd}/dropIndicator.js +2 -5
- package/dist/dnd/external.d.ts +6 -0
- package/dist/dnd/external.js +17 -0
- package/dist/{plugins/blockHandles → dnd}/geometry.d.ts +4 -2
- package/dist/{plugins/blockHandles → dnd}/geometry.js +3 -4
- package/dist/dnd/placement.d.ts +200 -0
- package/dist/dnd/placement.js +426 -0
- 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 +19 -9
- package/dist/edytor.svelte.js +28 -33
- package/dist/events/beforeInputCommands.d.ts +2 -2
- package/dist/events/beforeInputCommands.js +8 -4
- package/dist/events/onBeforeInput.js +1 -1
- package/dist/events/onDrop.js +25 -10
- package/dist/events/onInput.d.ts +0 -12
- package/dist/events/onInput.js +1 -67
- package/dist/events/onPaste.d.ts +2 -1
- package/dist/events/onPaste.js +8 -3
- 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 +4 -4
- package/dist/index.js +2 -2
- package/dist/kinds.js +1 -1
- package/dist/labels.d.ts +2 -0
- package/dist/labels.js +2 -1
- package/dist/plugins/anchored.svelte.d.ts +21 -25
- package/dist/plugins/anchored.svelte.js +23 -39
- package/dist/plugins/arrowMove/arrowMove.d.ts +1 -1
- package/dist/plugins/arrowMove/arrowMove.js +1 -1
- package/dist/plugins/blockHandles/BlockHandleController.svelte.d.ts +2 -122
- package/dist/plugins/blockHandles/BlockHandleController.svelte.js +48 -414
- package/dist/plugins/blockHandles/BlockHandles.svelte +14 -32
- package/dist/plugins/blockHandles/blockHandlesPlugin.js +2 -1
- 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/CodeHeader.svelte +5 -3
- package/dist/plugins/code/CodePlugin.svelte +15 -12
- 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/code/languages.d.ts +0 -3
- package/dist/plugins/code/languages.js +0 -2
- package/dist/plugins/columns/ColumnResize.svelte +3 -2
- package/dist/plugins/columns/ColumnsPlugin.svelte +8 -21
- package/dist/plugins/columns/gaps.d.ts +7 -5
- package/dist/plugins/columns/gaps.js +11 -6
- package/dist/plugins/columns/resize.svelte.d.ts +5 -6
- package/dist/plugins/columns/resize.svelte.js +12 -14
- 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 +13 -30
- package/dist/plugins/equation/EquationPlugin.svelte.d.ts +1 -0
- package/dist/plugins/equation/equation.css +7 -0
- 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 +43 -0
- package/dist/plugins/fileDrop.js +122 -0
- 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 +56 -84
- package/dist/plugins/image/ImagePlugin.svelte.d.ts +13 -1
- package/dist/plugins/image/controls.svelte.js +16 -2
- package/dist/plugins/image/image.css +17 -0
- 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 +4 -5
- package/dist/plugins/index.js +0 -1
- package/dist/plugins/list.svelte.d.ts +9 -0
- package/dist/plugins/list.svelte.js +18 -0
- package/dist/plugins/markdownShortcuts.js +5 -5
- package/dist/plugins/marquee/marqueePlugin.js +0 -4
- package/dist/plugins/media/AudioPlugin.svelte +13 -1
- package/dist/plugins/media/AudioPlugin.svelte.d.ts +11 -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 +12 -20
- package/dist/plugins/media/FilePlugin.svelte.d.ts +9 -0
- package/dist/plugins/media/MediaEmpty.svelte +2 -55
- package/dist/plugins/media/MediaEmpty.svelte.d.ts +18 -14
- package/dist/plugins/media/SourceBlock.svelte +178 -52
- package/dist/plugins/media/SourceBlock.svelte.d.ts +13 -6
- package/dist/plugins/media/UploadPreview.svelte +32 -0
- package/dist/plugins/media/UploadPreview.svelte.d.ts +11 -0
- package/dist/plugins/media/VideoPlugin.svelte +13 -1
- package/dist/plugins/media/VideoPlugin.svelte.d.ts +11 -1
- package/dist/plugins/media/empty.svelte.d.ts +13 -4
- package/dist/plugins/media/empty.svelte.js +32 -26
- package/dist/plugins/media/media.d.ts +9 -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/upload.css +23 -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 +50 -38
- package/dist/plugins/mention/MentionPlugin.svelte.d.ts +11 -2
- package/dist/plugins/mention/mention.css +6 -0
- package/dist/plugins/page/PagePlugin.svelte +2 -9
- package/dist/plugins/page/page.js +1 -1
- package/dist/plugins/pageLink/PageLinkPlugin.svelte +126 -28
- package/dist/plugins/pageLink/PageLinkPlugin.svelte.d.ts +13 -1
- package/dist/plugins/pageLink/pageLink.css +6 -0
- package/dist/plugins/richtext/RichTextPlugin.svelte +99 -204
- package/dist/plugins/richtext/RichTextPlugin.svelte.d.ts +1 -0
- 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/richtext/richtext.css +6 -0
- 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 +33 -56
- 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 +48 -32
- package/dist/plugins/uploads.svelte.js +119 -43
- package/dist/plugins.d.ts +66 -14
- 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/attempt.d.ts +16 -6
- package/dist/session/attempt.js +1 -0
- package/dist/session/bindings.js +35 -32
- package/dist/session/commands.d.ts +7 -11
- package/dist/session/commands.js +15 -19
- package/dist/session/history.js +1 -0
- package/dist/session/inputRules.d.ts +6 -3
- package/dist/session/inputRules.js +10 -6
- package/dist/session/moves.d.ts +33 -0
- package/dist/session/moves.js +71 -1
- package/dist/session/navigation.d.ts +7 -0
- package/dist/session/navigation.js +20 -11
- package/dist/session/props.d.ts +28 -0
- package/dist/session/props.js +65 -0
- package/dist/session/suggestions.svelte.d.ts +0 -11
- package/dist/session/suggestions.svelte.js +15 -3
- package/dist/session/uploads.d.ts +29 -0
- package/dist/session/uploads.js +10 -0
- package/dist/surface/lines.js +1 -3
- package/dist/surface/observer.svelte.d.ts +17 -1
- package/dist/surface/observer.svelte.js +72 -17
- package/dist/surface/overlay.d.ts +0 -14
- package/dist/surface/overlay.js +0 -25
- package/dist/surface/projector.svelte.js +1 -1
- package/dist/text/text.svelte.js +3 -13
- package/dist/themes/notion.css +9 -0
- 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 +18 -1
- 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
- /package/dist/{plugins/columns → dnd}/stacking.d.ts +0 -0
- /package/dist/{plugins/columns → dnd}/stacking.js +0 -0
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { pin } from '../../surface/overlay.js';
|
|
1
|
+
import { takeKeys } from '../../events/onFocus.js';
|
|
3
2
|
import { imageAlignOf, imageAltOf } from './image.js';
|
|
4
3
|
import { imageControls } from './controls.svelte.js';
|
|
5
|
-
import { identify, keepFocus } from '../chrome.js';
|
|
4
|
+
import { identify, keepFocus, onPressOutside, virtualAnchor } from '../chrome.js';
|
|
6
5
|
import { all, listen } from '../../utils/listen.js';
|
|
7
|
-
import
|
|
6
|
+
import { keysOf } from '../list.svelte.js';
|
|
8
7
|
/** Where a press keeps the alt field open: the toolbar (its Alt button toggles it) and the field's own. */
|
|
9
8
|
const ALT_CHROME = '[data-edytor-image-alt-panel], [data-edytor-image-toolbar], [data-edytor-image-alt]';
|
|
10
9
|
/**
|
|
@@ -15,11 +14,22 @@ const ALT_CHROME = '[data-edytor-image-alt-panel], [data-edytor-image-toolbar],
|
|
|
15
14
|
*/
|
|
16
15
|
export class ImageToolbar {
|
|
17
16
|
controls;
|
|
17
|
+
snippet;
|
|
18
|
+
/** @internal */
|
|
18
19
|
constructor(
|
|
19
20
|
/** @internal */
|
|
20
|
-
controls
|
|
21
|
+
controls,
|
|
22
|
+
/** The app's markup. @internal */
|
|
23
|
+
snippet) {
|
|
21
24
|
this.controls = controls;
|
|
25
|
+
this.snippet = snippet;
|
|
22
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Where the toolbar stands (`ChromeAnchor`): the image the chrome is
|
|
29
|
+
* on, whose top right your `toolbar` places itself in.
|
|
30
|
+
*/
|
|
31
|
+
anchor = virtualAnchor(() => this.#image()?.getBoundingClientRect(), () => this.#image());
|
|
32
|
+
#image = () => this.#block()?.node?.querySelector('[data-edytor-image] img');
|
|
23
33
|
/** The measured image the toolbar is on (`null`: none). */
|
|
24
34
|
get box() {
|
|
25
35
|
return this.controls.box;
|
|
@@ -59,20 +69,6 @@ export class ImageToolbar {
|
|
|
59
69
|
this.editing = this.editing ? null : (this.box?.id ?? null);
|
|
60
70
|
this.#edytor.overlay.invalidate();
|
|
61
71
|
};
|
|
62
|
-
/**
|
|
63
|
-
* A press anywhere (`onPress`, capture): one outside the alt panel and the
|
|
64
|
-
* toolbar (whose Alt button toggles it) closes the field, so the chrome
|
|
65
|
-
* follows the pointer again.
|
|
66
|
-
* @internal
|
|
67
|
-
*/
|
|
68
|
-
pressed = (event) => {
|
|
69
|
-
const target = event.target;
|
|
70
|
-
if (this.editing === null)
|
|
71
|
-
return;
|
|
72
|
-
if (target?.closest?.(ALT_CHROME))
|
|
73
|
-
return;
|
|
74
|
-
this.closeAlt();
|
|
75
|
-
};
|
|
76
72
|
/** Focus left the alt field: for somewhere outside its panel, the field closes. @internal */
|
|
77
73
|
blurred = (event) => {
|
|
78
74
|
const to = event.relatedTarget;
|
|
@@ -106,13 +102,8 @@ export class ImageToolbar {
|
|
|
106
102
|
identify(node, this.#edytor.popups.idOf('image-alt'));
|
|
107
103
|
node.setAttribute('data-edytor-image-alt', '');
|
|
108
104
|
node.focus({ preventScroll: true });
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
return;
|
|
112
|
-
event.preventDefault();
|
|
113
|
-
this.closeAlt(true);
|
|
114
|
-
};
|
|
115
|
-
return all(listen(node, 'keydown', keydown), listen(node, 'focusout', this.blurred));
|
|
105
|
+
const close = () => this.closeAlt(true);
|
|
106
|
+
return all(listen(node, 'keydown', keysOf({ Enter: close, Escape: close })), listen(node, 'focusout', this.blurred));
|
|
116
107
|
};
|
|
117
108
|
/** Close the alt field; with `keys`, the editor takes the keys back. */
|
|
118
109
|
closeAlt = (keys = false) => {
|
|
@@ -135,11 +126,10 @@ export const createImageToolbarPlugin = ({ toolbar }) => (edytor) => ({
|
|
|
135
126
|
const controls = imageControls.get(edytor);
|
|
136
127
|
if (!controls || controls.toolbar)
|
|
137
128
|
return;
|
|
138
|
-
const bar = (controls.toolbar = new ImageToolbar(controls));
|
|
129
|
+
const bar = (controls.toolbar = new ImageToolbar(controls, toolbar));
|
|
139
130
|
return all(
|
|
140
|
-
// A press outside the alt panel
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
7, (host) => () => pin(host)), () => (controls.toolbar = undefined));
|
|
131
|
+
// A press outside the alt panel and the toolbar (whose Alt button toggles it)
|
|
132
|
+
// closes it (WebKit's lone `mousedown` too).
|
|
133
|
+
onPressOutside(edytor, node.ownerDocument, ALT_CHROME, () => bar.closeAlt()), () => (controls.toolbar = undefined));
|
|
144
134
|
}
|
|
145
135
|
});
|
package/dist/plugins/index.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
export { createRichTextPlugin, richTextOperations, richTextPlaceholder, type RichTextPluginOptions, type TodoCheckboxPayload, type CalloutIconPayload, type EmptyBodyPayload } from './richtext/RichTextPlugin.svelte';
|
|
2
2
|
export { CALLOUT_ICONS, createCalloutIconsPlugin, type CalloutIconPicker, type CalloutIconsOptions } from './richtext/calloutIcons.svelte.js';
|
|
3
|
-
export { createUploadsPlugin, type UploadsOptions } from './uploads.svelte.js';
|
|
4
3
|
export { createArrowMovePlugin } from './arrowMove/arrowMove.js';
|
|
5
|
-
export type { MenuItemPayload } from './chrome.js';
|
|
4
|
+
export type { ChromeAnchor, MenuItemPayload } from './chrome.js';
|
|
6
5
|
export { BLOCK_ACTIVATE_EVENT, BLOCK_ADD_EVENT, type BlockActivation, type BlockAddition } from './blockHandles/BlockHandleController.svelte.js';
|
|
7
6
|
export { createBlockHandlesPlugin, type BlockHandlesOptions, type BlockHandleSnippetPayload } from './blockHandles/blockHandlesPlugin.js';
|
|
8
7
|
export { createCodePlugin, CODE_LANGUAGES, loadCodeLanguage, type CodeLanguage, type CodePluginOptions } from './code/CodePlugin.svelte';
|
|
@@ -21,12 +20,12 @@ export { createBlockMenuPlugin } from './blockMenu/blockMenuPlugin.js';
|
|
|
21
20
|
export { createSuggestionsPlugin, type SuggestionBarOptions, type SuggestionBarPayload } from './suggestions/suggestionsPlugin.js';
|
|
22
21
|
export { createSuggestionBarPlugin } from './suggestions/SuggestionBars.svelte';
|
|
23
22
|
export type { BlockMenuOptions, BlockMenuController, BlockMenuAction, BlockMenuColor } from './blockMenu/BlockMenuController.svelte.js';
|
|
24
|
-
export { createImagePlugin, safeImageSrc, storableImageSrc, oversizedInlineImage, MAX_INLINE_IMAGE_BYTES, MIN_IMAGE_WIDTH, type ImageData, type ImageAlign } from './image/ImagePlugin.svelte';
|
|
23
|
+
export { createImagePlugin, type ImagePluginOptions, safeImageSrc, storableImageSrc, oversizedInlineImage, MAX_INLINE_IMAGE_BYTES, MIN_IMAGE_WIDTH, type ImageData, type ImageAlign } from './image/ImagePlugin.svelte';
|
|
25
24
|
export { createEmbedPlugin, EMBED_PROVIDERS, EMBED_SANDBOX, EMBED_ALLOW, embedSourceOf, type EmbedProvider, type EmbedPluginOptions, type EmbedFallbackPayload } from './media/EmbedPlugin.svelte';
|
|
26
25
|
export { createBookmarkPlugin, type BookmarkPluginOptions, type BookmarkPreview, type BookmarkCardPayload } from './media/BookmarkPlugin.svelte';
|
|
27
26
|
export { createFilePlugin, type FilePluginOptions, type FileRowPayload } from './media/FilePlugin.svelte';
|
|
28
|
-
export { createVideoPlugin } from './media/VideoPlugin.svelte';
|
|
29
|
-
export { createAudioPlugin } from './media/AudioPlugin.svelte';
|
|
27
|
+
export { createVideoPlugin, type VideoPluginOptions } from './media/VideoPlugin.svelte';
|
|
28
|
+
export { createAudioPlugin, type AudioPluginOptions } from './media/AudioPlugin.svelte';
|
|
30
29
|
export { safeWebUrl, safeMediaSrc, type MediaKind } from './media/media.js';
|
|
31
30
|
export type { MediaEmptyController, MediaEmptyFailure } from './media/empty.svelte.js';
|
|
32
31
|
export { createMediaEmptyPlugin, type MediaEmptyOptions } from './media/MediaEmpty.svelte';
|
package/dist/plugins/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export { createRichTextPlugin, richTextOperations, richTextPlaceholder } from './richtext/RichTextPlugin.svelte';
|
|
2
2
|
export { CALLOUT_ICONS, createCalloutIconsPlugin } from './richtext/calloutIcons.svelte.js';
|
|
3
|
-
export { createUploadsPlugin } from './uploads.svelte.js';
|
|
4
3
|
export { createArrowMovePlugin } from './arrowMove/arrowMove.js';
|
|
5
4
|
export { BLOCK_ACTIVATE_EVENT, BLOCK_ADD_EVENT } from './blockHandles/BlockHandleController.svelte.js';
|
|
6
5
|
export { createBlockHandlesPlugin } from './blockHandles/blockHandlesPlugin.js';
|
|
@@ -36,6 +36,13 @@ type Steps = {
|
|
|
36
36
|
ends?: boolean;
|
|
37
37
|
usable?: (index: number) => boolean;
|
|
38
38
|
};
|
|
39
|
+
/**
|
|
40
|
+
* A field's or a control's keys (`keydown`): `keys[event.key]` runs and the
|
|
41
|
+
* key's default is prevented (with `stop`, it goes no further), unless the
|
|
42
|
+
* handler answers `false`; a key an IME composes, or one already prevented,
|
|
43
|
+
* is left alone.
|
|
44
|
+
*/
|
|
45
|
+
export declare const keysOf: (keys: Partial<Record<string, (event: KeyboardEvent) => unknown>>, stop?: boolean) => ((event: KeyboardEvent) => void);
|
|
39
46
|
/**
|
|
40
47
|
* A key on a menu that holds the keyboard itself, not a field (WAI-ARIA's
|
|
41
48
|
* menu keys): the arrows, Home and End move its row (`stepRow`), Enter and
|
|
@@ -112,6 +119,8 @@ export declare abstract class ChromeList {
|
|
|
112
119
|
role: 'listbox' | 'menu', open?: (node: HTMLElement) => Omit<Popup, 'id'> | false | null);
|
|
113
120
|
/** How many rows the keyboard walks. */
|
|
114
121
|
protected abstract get count(): number;
|
|
122
|
+
/** The view is readonly (reactive): the list closes. */
|
|
123
|
+
get readonly(): boolean;
|
|
115
124
|
/** The list's element id (page-unique), for `aria-controls`. */
|
|
116
125
|
get listId(): string;
|
|
117
126
|
/** Row `index`'s element id (page-unique), for `aria-activedescendant`. */
|
|
@@ -70,6 +70,20 @@ export const stepRow = (key, at, count, { columns = 0, ends = false, usable = ()
|
|
|
70
70
|
if (ends && length && (key === 'Home' || key === 'End'))
|
|
71
71
|
return rows[key === 'Home' ? 0 : length - 1];
|
|
72
72
|
};
|
|
73
|
+
/**
|
|
74
|
+
* A field's or a control's keys (`keydown`): `keys[event.key]` runs and the
|
|
75
|
+
* key's default is prevented (with `stop`, it goes no further), unless the
|
|
76
|
+
* handler answers `false`; a key an IME composes, or one already prevented,
|
|
77
|
+
* is left alone.
|
|
78
|
+
*/
|
|
79
|
+
export const keysOf = (keys, stop = false) => (event) => {
|
|
80
|
+
const run = keys[event.key];
|
|
81
|
+
if (event.defaultPrevented || event.isComposing || !run || run(event) === false)
|
|
82
|
+
return;
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
if (stop)
|
|
85
|
+
event.stopPropagation();
|
|
86
|
+
};
|
|
73
87
|
/**
|
|
74
88
|
* A key on a menu that holds the keyboard itself, not a field (WAI-ARIA's
|
|
75
89
|
* menu keys): the arrows, Home and End move its row (`stepRow`), Enter and
|
|
@@ -192,6 +206,10 @@ export class ChromeList {
|
|
|
192
206
|
this.role = role;
|
|
193
207
|
this.popup = listPopup(edytor, owner, role, open);
|
|
194
208
|
}
|
|
209
|
+
/** The view is readonly (reactive): the list closes. */
|
|
210
|
+
get readonly() {
|
|
211
|
+
return this.edytor.readonly;
|
|
212
|
+
}
|
|
195
213
|
/** The list's element id (page-unique), for `aria-controls`. */
|
|
196
214
|
get listId() {
|
|
197
215
|
return this.edytor.popups.idOf(this.owner);
|
|
@@ -91,12 +91,12 @@ export const createMarkdownShortcutsPlugin = () => (edytor) => {
|
|
|
91
91
|
if (!text)
|
|
92
92
|
return false;
|
|
93
93
|
const { id } = block;
|
|
94
|
-
// The segment's start, in block offsets: the caret, less the text before it.
|
|
95
|
-
const at = to -
|
|
94
|
+
// The segment's start, in block offsets: the caret (after the typed text), less the text before it.
|
|
95
|
+
const at = to - match.input.length + found.start;
|
|
96
96
|
const { facade, dispatcher } = edytor;
|
|
97
|
-
const plan = () => facade.compose(facade.prepare.formatRange(id, at + found.open, found.content, { [found.mark]: true }),
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
const plan = () => facade.compose(facade.prepare.formatRange(id, at + found.open, found.content, { [found.mark]: true }),
|
|
98
|
+
// The closing marker, the typed character with it.
|
|
99
|
+
facade.prepare.deleteText(id, at + found.open + found.content, found.closing + typed.length), facade.prepare.deleteText(id, at, found.open));
|
|
100
100
|
const payload = { value: typed, start: before.length, end: before.length };
|
|
101
101
|
const result = dispatcher.plan('inlineMarkdown', payload, plan, block, text);
|
|
102
102
|
if (result.status !== 'applied')
|
|
@@ -3,9 +3,6 @@ import MarqueeBox from './MarqueeBox.svelte';
|
|
|
3
3
|
import { MarqueeController } from './MarqueeController.svelte.js';
|
|
4
4
|
/** The handle gutter: a block handle's own box, around its buttons (never a button). */
|
|
5
5
|
const HANDLE_HOST = '[data-edytor-block-handle-host]';
|
|
6
|
-
const controllers = new WeakMap();
|
|
7
|
-
/** The marquee's controller of `edytor`; `undefined` when the plugin is not listed. @internal */
|
|
8
|
-
export const marqueeController = (edytor) => controllers.get(edytor);
|
|
9
6
|
/**
|
|
10
7
|
* Block selection with a rectangle (Notion's rubber band), opt-in: a press
|
|
11
8
|
* in the editor's empty area — the margins beside the blocks (the handle
|
|
@@ -18,7 +15,6 @@ export const marqueeController = (edytor) => controllers.get(edytor);
|
|
|
18
15
|
*/
|
|
19
16
|
export const createMarqueePlugin = (options = {}) => (edytor) => {
|
|
20
17
|
const controller = new MarqueeController(edytor, options.threshold ?? 4);
|
|
21
|
-
controllers.set(edytor, controller);
|
|
22
18
|
return {
|
|
23
19
|
onEdytorAttached: ({ node }) => {
|
|
24
20
|
const layer = edytor.overlay.layer;
|
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
<script module lang="ts">
|
|
2
2
|
import type { Plugin } from '../../plugins.js';
|
|
3
3
|
import { sourcePlugin } from './SourceBlock.svelte';
|
|
4
|
+
import type { Uploader } from '../uploads.svelte.js';
|
|
5
|
+
|
|
6
|
+
export type AudioPluginOptions = {
|
|
7
|
+
/**
|
|
8
|
+
* Upload a audio file and answer its URL: an empty block's Upload, and
|
|
9
|
+
* the pasted and dropped audio files, each placed as a block at once
|
|
10
|
+
* (one undo step), showing its name and progress, then filled with the
|
|
11
|
+
* URL this answers. Without it, audios are only linked.
|
|
12
|
+
*/
|
|
13
|
+
upload?: Uploader;
|
|
14
|
+
};
|
|
4
15
|
|
|
5
16
|
/**
|
|
6
17
|
* Notion's audio block: an audio file (an `http(s)` link, or an upload
|
|
7
18
|
* through `upload`) in the browser's player, with an editable caption.
|
|
8
19
|
* Void: its only text is the caption.
|
|
9
20
|
*/
|
|
10
|
-
export const createAudioPlugin = (): Plugin =>
|
|
21
|
+
export const createAudioPlugin = ({ upload }: AudioPluginOptions = {}): Plugin =>
|
|
22
|
+
sourcePlugin('audio', ['sound', 'music', 'mp3'], {}, upload);
|
|
11
23
|
</script>
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import type { Plugin } from '../../plugins.js';
|
|
2
|
+
import type { Uploader } from '../uploads.svelte.js';
|
|
3
|
+
export type AudioPluginOptions = {
|
|
4
|
+
/**
|
|
5
|
+
* Upload a audio file and answer its URL: an empty block's Upload, and
|
|
6
|
+
* the pasted and dropped audio files, each placed as a block at once
|
|
7
|
+
* (one undo step), showing its name and progress, then filled with the
|
|
8
|
+
* URL this answers. Without it, audios are only linked.
|
|
9
|
+
*/
|
|
10
|
+
upload?: Uploader;
|
|
11
|
+
};
|
|
2
12
|
/**
|
|
3
13
|
* Notion's audio block: an audio file (an `http(s)` link, or an upload
|
|
4
14
|
* through `upload`) in the browser's player, with an editable caption.
|
|
5
15
|
* Void: its only text is the caption.
|
|
6
16
|
*/
|
|
7
|
-
export declare const createAudioPlugin: () => Plugin;
|
|
17
|
+
export declare const createAudioPlugin: ({ upload }?: AudioPluginOptions) => Plugin;
|
|
8
18
|
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
9
19
|
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
10
20
|
$$bindings?: Bindings;
|
|
@@ -1,21 +1,11 @@
|
|
|
1
1
|
<script module lang="ts">
|
|
2
|
-
import
|
|
2
|
+
import { mediaForms } from '../../export/forms.js';
|
|
3
|
+
import type { Plugin } from '../../plugins.js';
|
|
3
4
|
import type { Block } from '../../block/block.svelte.js';
|
|
4
|
-
import
|
|
5
|
-
import Control from '../Control.svelte';
|
|
5
|
+
import { previewData, source } from './SourceBlock.svelte';
|
|
6
6
|
import type { Snippet } from 'svelte';
|
|
7
7
|
import { urlPaste, type UrlPasteController } from './urlPaste.svelte.js';
|
|
8
|
-
import {
|
|
9
|
-
MEDIA_ICONS,
|
|
10
|
-
escapeHtml,
|
|
11
|
-
mediaControl,
|
|
12
|
-
mediaLink,
|
|
13
|
-
mediaPlugin,
|
|
14
|
-
mediaView,
|
|
15
|
-
safeWebUrl,
|
|
16
|
-
LINK_REL,
|
|
17
|
-
type BookmarkCardPayload
|
|
18
|
-
} from './media.js';
|
|
8
|
+
import { MEDIA_ICONS, mediaPlugin, safeWebUrl, type BookmarkCardPayload } from './media.js';
|
|
19
9
|
|
|
20
10
|
export type { BookmarkCardPayload };
|
|
21
11
|
|
|
@@ -54,21 +44,6 @@
|
|
|
54
44
|
card?: Snippet<[BookmarkCardPayload]>;
|
|
55
45
|
};
|
|
56
46
|
|
|
57
|
-
/** A preview's text field, trimmed and capped: it is stored in the document. */
|
|
58
|
-
const text = (value: unknown, max: number) =>
|
|
59
|
-
typeof value === 'string' && value.trim() ? value.trim().slice(0, max) : undefined;
|
|
60
|
-
|
|
61
|
-
/** The fields of `preview` a bookmark stores: text capped, images {@link safeWebUrl} only. */
|
|
62
|
-
const previewData = (preview: BookmarkPreview | null | undefined) => {
|
|
63
|
-
const fields = {
|
|
64
|
-
title: text(preview?.title, 300),
|
|
65
|
-
description: text(preview?.description, 1000),
|
|
66
|
-
image: safeWebUrl(preview?.image) ?? undefined,
|
|
67
|
-
icon: safeWebUrl(preview?.icon) ?? undefined
|
|
68
|
-
};
|
|
69
|
-
return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined));
|
|
70
|
-
};
|
|
71
|
-
|
|
72
47
|
/**
|
|
73
48
|
* The unfurl's writes: background bookkeeping, not a gesture, so no
|
|
74
49
|
* view's history tracks this origin (`crdt/document.ts`, History). The
|
|
@@ -77,9 +52,6 @@
|
|
|
77
52
|
*/
|
|
78
53
|
const UNFURL_ORIGIN = Symbol('edytor.bookmark.unfurl');
|
|
79
54
|
|
|
80
|
-
/** The view's card markup, if the app gave one. */
|
|
81
|
-
const cardOf = mediaControl('bookmark', 'card');
|
|
82
|
-
|
|
83
55
|
/**
|
|
84
56
|
* Notion's web bookmark: a card linking to a page, with its title,
|
|
85
57
|
* description and images when your `unfurl` answers them, and an
|
|
@@ -135,72 +107,9 @@
|
|
|
135
107
|
options.menu
|
|
136
108
|
),
|
|
137
109
|
record: {
|
|
138
|
-
snippet:
|
|
139
|
-
|
|
140
|
-
const url = safeWebUrl(block.data?.url);
|
|
141
|
-
const title = text(block.data?.title, 300) ?? url;
|
|
142
|
-
const link = url ? `<a href="${escapeHtml(url)}">${escapeHtml(title!)}</a>` : '';
|
|
143
|
-
return `<figure data-edytor-bookmark>${link}<figcaption>${caption}</figcaption></figure>`;
|
|
144
|
-
},
|
|
145
|
-
plain: (block, caption) =>
|
|
146
|
-
[safeWebUrl(block.data?.url), caption].filter(Boolean).join('\n'),
|
|
147
|
-
// Its own export: a `figure[data-edytor-bookmark]` holding the link.
|
|
148
|
-
parse: (el) => {
|
|
149
|
-
if (el.localName !== 'figure' || !el.hasAttribute('data-edytor-bookmark')) return;
|
|
150
|
-
const anchor = el.querySelector('a[href]');
|
|
151
|
-
const url = safeWebUrl(anchor?.getAttribute('href'));
|
|
152
|
-
const title = text(anchor?.textContent, 300);
|
|
153
|
-
return url ? { url, ...(title && title !== url ? { title } : {}) } : undefined;
|
|
154
|
-
}
|
|
110
|
+
snippet: source,
|
|
111
|
+
...mediaForms().bookmark
|
|
155
112
|
}
|
|
156
113
|
};
|
|
157
114
|
});
|
|
158
115
|
</script>
|
|
159
|
-
|
|
160
|
-
{#snippet bookmark({ block, content }: BlockSnippetPayload<Record<string, unknown>>)}
|
|
161
|
-
{@const url = safeWebUrl(block.data.url)}
|
|
162
|
-
{#if url}
|
|
163
|
-
<div use:block.void data-edytor-bookmark>
|
|
164
|
-
<Control
|
|
165
|
-
own={cardOf}
|
|
166
|
-
{initial}
|
|
167
|
-
payload={{
|
|
168
|
-
block: block.handle,
|
|
169
|
-
url,
|
|
170
|
-
title: text(block.data.title, 300),
|
|
171
|
-
description: text(block.data.description, 1000),
|
|
172
|
-
image: safeWebUrl(block.data.image) ?? undefined,
|
|
173
|
-
icon: safeWebUrl(block.data.icon) ?? undefined,
|
|
174
|
-
link: mediaLink({ draggable: 'false' })
|
|
175
|
-
}}
|
|
176
|
-
/>
|
|
177
|
-
</div>
|
|
178
|
-
{:else}
|
|
179
|
-
<div use:block.void data-edytor-media-empty>
|
|
180
|
-
<EmptySlot
|
|
181
|
-
block={block.handle}
|
|
182
|
-
kind="bookmark"
|
|
183
|
-
viewOf={(edytor) => mediaView(edytor, 'bookmark')}
|
|
184
|
-
/>
|
|
185
|
-
</div>
|
|
186
|
-
{/if}
|
|
187
|
-
<!-- The core renders the kind's <figure> around this markup. -->
|
|
188
|
-
<!-- svelte-ignore a11y_figcaption_parent -->
|
|
189
|
-
<figcaption>{@render content()}</figcaption>
|
|
190
|
-
{/snippet}
|
|
191
|
-
|
|
192
|
-
<!-- Notion's card: the page's title, description and URL (with its icon) beside its cover. -->
|
|
193
|
-
{#snippet initial({ url, title, description, image, icon, link }: BookmarkCardPayload)}
|
|
194
|
-
<a href={url} target="_blank" rel={LINK_REL} draggable="false" {@attach link}>
|
|
195
|
-
<span data-edytor-bookmark-text>
|
|
196
|
-
<span data-edytor-bookmark-title>{title ?? url}</span>
|
|
197
|
-
{#if description}<span data-edytor-bookmark-description>{description}</span>{/if}
|
|
198
|
-
<span data-edytor-bookmark-url>
|
|
199
|
-
{#if icon}<img src={icon} alt="" draggable="false" />{/if}<span>{url}</span>
|
|
200
|
-
</span>
|
|
201
|
-
</span>
|
|
202
|
-
{#if image}
|
|
203
|
-
<span data-edytor-bookmark-cover><img src={image} alt="" draggable="false" /></span>
|
|
204
|
-
{/if}
|
|
205
|
-
</a>
|
|
206
|
-
{/snippet}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
<script module lang="ts">
|
|
2
|
-
import
|
|
3
|
-
import type {
|
|
4
|
-
import
|
|
5
|
-
import Control from '../Control.svelte';
|
|
2
|
+
import { mediaForms } from '../../export/forms.js';
|
|
3
|
+
import type { Plugin } from '../../plugins.js';
|
|
4
|
+
import { source } from './SourceBlock.svelte';
|
|
6
5
|
import type { Snippet } from 'svelte';
|
|
7
6
|
import { urlPaste, type UrlPasteController } from './urlPaste.svelte.js';
|
|
8
7
|
import {
|
|
@@ -10,14 +9,8 @@
|
|
|
10
9
|
EMBED_PROVIDERS,
|
|
11
10
|
EMBED_SANDBOX,
|
|
12
11
|
MEDIA_ICONS,
|
|
13
|
-
claimed,
|
|
14
12
|
embedSourceOf,
|
|
15
|
-
escapeHtml,
|
|
16
|
-
mediaControl,
|
|
17
|
-
mediaLink,
|
|
18
|
-
LINK_REL,
|
|
19
13
|
mediaPlugin,
|
|
20
|
-
mediaView,
|
|
21
14
|
safeWebUrl,
|
|
22
15
|
type EmbedFallbackPayload,
|
|
23
16
|
type EmbedProvider
|
|
@@ -54,16 +47,6 @@
|
|
|
54
47
|
fallback?: Snippet<[EmbedFallbackPayload]>;
|
|
55
48
|
};
|
|
56
49
|
|
|
57
|
-
/** The view's fallback markup, if the app gave one. */
|
|
58
|
-
const fallbackOf = mediaControl('embed', 'fallback');
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* The view's allowlist: the first embed plugin listed in it (first wins,
|
|
62
|
-
* as for the kind). Never a broader list: no entry plays nothing.
|
|
63
|
-
*/
|
|
64
|
-
const providers = (block: Block | undefined) =>
|
|
65
|
-
mediaView(block?.edytor, 'embed')?.providers ?? [];
|
|
66
|
-
|
|
67
50
|
/**
|
|
68
51
|
* Notion's embed block: an "Embed a link" panel until it has a link, then
|
|
69
52
|
* the provider's player in a sandboxed frame, with an editable caption.
|
|
@@ -98,78 +81,10 @@
|
|
|
98
81
|
options.menu
|
|
99
82
|
),
|
|
100
83
|
record: {
|
|
101
|
-
snippet:
|
|
102
|
-
|
|
103
|
-
const player = embedSourceOf(block.data?.url, allowed);
|
|
104
|
-
const url = safeWebUrl(block.data?.url);
|
|
105
|
-
const media = player
|
|
106
|
-
? `<iframe src="${escapeHtml(player.src)}" title="${escapeHtml(player.provider.name)}" sandbox="${EMBED_SANDBOX}" allowfullscreen></iframe>`
|
|
107
|
-
: url
|
|
108
|
-
? `<a href="${escapeHtml(url)}">${escapeHtml(url)}</a>`
|
|
109
|
-
: '';
|
|
110
|
-
return `<figure>${media}<figcaption>${caption}</figcaption></figure>`;
|
|
111
|
-
},
|
|
112
|
-
plain: (block, caption) =>
|
|
113
|
-
[safeWebUrl(block.data?.url), caption].filter(Boolean).join('\n'),
|
|
114
|
-
// An allowlisted `iframe` (bare, or in a `figure`): its `src` is the link.
|
|
115
|
-
parse: (el) => {
|
|
116
|
-
const src = claimed(el, 'iframe')?.getAttribute('src');
|
|
117
|
-
const url = embedSourceOf(src, allowed) && safeWebUrl(src);
|
|
118
|
-
return url ? { url } : undefined;
|
|
119
|
-
}
|
|
84
|
+
snippet: source,
|
|
85
|
+
...mediaForms(allowed).embed
|
|
120
86
|
}
|
|
121
87
|
})
|
|
122
88
|
);
|
|
123
89
|
};
|
|
124
90
|
</script>
|
|
125
|
-
|
|
126
|
-
{#snippet embed({ block, content }: BlockSnippetPayload<{ url?: string }>)}
|
|
127
|
-
{@const player = block.handle ? embedSourceOf(block.data.url, providers(block.handle)) : null}
|
|
128
|
-
{@const url = safeWebUrl(block.data.url)}
|
|
129
|
-
{#if player}
|
|
130
|
-
{@const { height, aspectRatio = '16 / 9' } = player.provider}
|
|
131
|
-
<div
|
|
132
|
-
use:block.void
|
|
133
|
-
data-edytor-embed
|
|
134
|
-
style:aspect-ratio={height ? undefined : aspectRatio}
|
|
135
|
-
style:height={height ? `${height}px` : undefined}
|
|
136
|
-
>
|
|
137
|
-
<iframe
|
|
138
|
-
src={player.src}
|
|
139
|
-
title={player.provider.name}
|
|
140
|
-
sandbox={EMBED_SANDBOX}
|
|
141
|
-
allow={EMBED_ALLOW}
|
|
142
|
-
referrerpolicy="strict-origin-when-cross-origin"
|
|
143
|
-
loading="lazy"
|
|
144
|
-
allowfullscreen
|
|
145
|
-
style="display: block; width: 100%; height: 100%; border: 0"
|
|
146
|
-
></iframe>
|
|
147
|
-
</div>
|
|
148
|
-
{:else if url}
|
|
149
|
-
<!-- A link no provider plays (a peer may store any), or a suggestion's preview (no handle, so no
|
|
150
|
-
view's allowlist): a plain link, never a frame. -->
|
|
151
|
-
<div use:block.void data-edytor-embed-unsupported>
|
|
152
|
-
<Control
|
|
153
|
-
own={fallbackOf}
|
|
154
|
-
{initial}
|
|
155
|
-
payload={{ block: block.handle, url, link: mediaLink() }}
|
|
156
|
-
/>
|
|
157
|
-
</div>
|
|
158
|
-
{:else}
|
|
159
|
-
<div use:block.void data-edytor-media-empty>
|
|
160
|
-
<EmptySlot
|
|
161
|
-
block={block.handle}
|
|
162
|
-
kind="embed"
|
|
163
|
-
viewOf={(edytor) => mediaView(edytor, 'embed')}
|
|
164
|
-
/>
|
|
165
|
-
</div>
|
|
166
|
-
{/if}
|
|
167
|
-
<!-- The core renders the kind's <figure> around this markup. -->
|
|
168
|
-
<!-- svelte-ignore a11y_figcaption_parent -->
|
|
169
|
-
<figcaption>{@render content()}</figcaption>
|
|
170
|
-
{/snippet}
|
|
171
|
-
|
|
172
|
-
<!-- The link itself. -->
|
|
173
|
-
{#snippet initial({ url, link }: EmbedFallbackPayload)}
|
|
174
|
-
<a href={url} target="_blank" rel={LINK_REL} {@attach link}>{url}</a>
|
|
175
|
-
{/snippet}
|
|
@@ -1,32 +1,29 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
|
-
import type {
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { Block } from '../../block/block.svelte.js';
|
|
4
4
|
import type { Edytor } from '../../edytor.svelte.js';
|
|
5
5
|
import type { MediaKind } from './media.js';
|
|
6
|
-
import
|
|
6
|
+
import { MediaEmptyController, type EmptyView } from './empty.svelte.js';
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
Host: Component<{
|
|
11
|
-
block: Block | undefined;
|
|
12
|
-
kind: MediaKind | 'image';
|
|
13
|
-
viewOf: (edytor: Edytor | undefined) => EmptyView | undefined;
|
|
14
|
-
empty: Snippet<[MediaEmptyController]>;
|
|
15
|
-
}>;
|
|
16
|
-
empty: Snippet<[MediaEmptyController]>;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
/** Each view's empty-block chrome (the first media empty plugin listed claims it). */
|
|
20
|
-
export const emptyHosts = new WeakMap<object, EmptyHost>();
|
|
8
|
+
/** Each view's empty-block markup (the first media empty plugin listed claims it). */
|
|
9
|
+
export const emptyHosts = new WeakMap<object, Snippet<[MediaEmptyController]>>();
|
|
21
10
|
</script>
|
|
22
11
|
|
|
23
12
|
<script lang="ts">
|
|
24
13
|
import { getContext, untrack } from 'svelte';
|
|
14
|
+
import { fileUploads } from '../uploads.svelte.js';
|
|
25
15
|
|
|
26
16
|
/**
|
|
27
|
-
* Where an empty image or media block shows its empty state:
|
|
28
|
-
*
|
|
29
|
-
*
|
|
17
|
+
* Where an empty image or media block shows its empty state: a
|
|
18
|
+
* `MediaEmptyController`, which the view's `empty` snippet (the media
|
|
19
|
+
* empty plugin's) draws, when one is listed (else nothing). The view
|
|
20
|
+
* rendering it is the block's, or a suggestion preview's. The kind in the
|
|
21
|
+
* block's view (`viewOf`) stores the link, or an uploaded file's URL
|
|
22
|
+
* (only on a block still empty when it lands). A readonly view, or a
|
|
23
|
+
* suggestion's preview (no `block`), may not write: no control may run
|
|
24
|
+
* `upload`. A pasted or dropped file shows its upload (`pending`); a
|
|
25
|
+
* failed one opens the panel with its error; data answered while the
|
|
26
|
+
* block was not shown fills it as soon as it shows.
|
|
30
27
|
*/
|
|
31
28
|
let {
|
|
32
29
|
block,
|
|
@@ -35,10 +32,29 @@
|
|
|
35
32
|
}: {
|
|
36
33
|
block: Block | undefined;
|
|
37
34
|
kind: MediaKind | 'image';
|
|
35
|
+
/** The kind in a view: how it stores a source. */
|
|
38
36
|
viewOf: (edytor: Edytor | undefined) => EmptyView | undefined;
|
|
39
37
|
} = $props();
|
|
40
38
|
const view = getContext<Edytor>('edytor');
|
|
41
|
-
const
|
|
39
|
+
const empty = untrack(() => emptyHosts.get(block?.edytor ?? view));
|
|
40
|
+
const panel = untrack(() => new MediaEmptyController(block, kind, viewOf(block?.edytor)));
|
|
41
|
+
/** The upload of a pasted or dropped file into this block, if any. */
|
|
42
|
+
const pending = $derived(empty && panel.pending);
|
|
43
|
+
// After the flush that shows the block: never inside the transaction that showed it.
|
|
44
|
+
$effect(() => {
|
|
45
|
+
if (pending?.status === 'landed' && block && !block.edytor.readonly) {
|
|
46
|
+
const id = block.id;
|
|
47
|
+
untrack(() => fileUploads(block.edytor)?.fill(id));
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
// The panel opens on a failed upload, with its error; the user closes it.
|
|
51
|
+
$effect(() => {
|
|
52
|
+
if (pending?.status === 'failed')
|
|
53
|
+
untrack(() => {
|
|
54
|
+
panel.open = true;
|
|
55
|
+
panel.failed = 'upload';
|
|
56
|
+
});
|
|
57
|
+
});
|
|
42
58
|
</script>
|
|
43
59
|
|
|
44
|
-
{#if
|
|
60
|
+
{#if empty}{@render empty(panel)}{/if}
|
|
@@ -1,25 +1,16 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
2
|
import type { Block } from '../../block/block.svelte.js';
|
|
3
3
|
import type { Edytor } from '../../edytor.svelte.js';
|
|
4
4
|
import type { MediaKind } from './media.js';
|
|
5
|
-
import
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
Host: Component<{
|
|
9
|
-
block: Block | undefined;
|
|
10
|
-
kind: MediaKind | 'image';
|
|
11
|
-
viewOf: (edytor: Edytor | undefined) => EmptyView | undefined;
|
|
12
|
-
empty: Snippet<[MediaEmptyController]>;
|
|
13
|
-
}>;
|
|
14
|
-
empty: Snippet<[MediaEmptyController]>;
|
|
15
|
-
};
|
|
16
|
-
/** Each view's empty-block chrome (the first media empty plugin listed claims it). */
|
|
17
|
-
export declare const emptyHosts: WeakMap<object, EmptyHost>;
|
|
5
|
+
import { MediaEmptyController, type EmptyView } from './empty.svelte.js';
|
|
6
|
+
/** Each view's empty-block markup (the first media empty plugin listed claims it). */
|
|
7
|
+
export declare const emptyHosts: WeakMap<object, Snippet<[MediaEmptyController]>>;
|
|
18
8
|
type $$ComponentProps = {
|
|
19
9
|
block: Block | undefined;
|
|
20
10
|
kind: MediaKind | 'image';
|
|
11
|
+
/** The kind in a view: how it stores a source. */
|
|
21
12
|
viewOf: (edytor: Edytor | undefined) => EmptyView | undefined;
|
|
22
13
|
};
|
|
23
|
-
declare const EmptySlot: Component<$$ComponentProps, {}, "">;
|
|
14
|
+
declare const EmptySlot: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
24
15
|
type EmptySlot = ReturnType<typeof EmptySlot>;
|
|
25
16
|
export default EmptySlot;
|