edytor 2.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 +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/room/admission.js +3 -3
- package/dist/cloudflare/room/context.d.ts +1 -1
- 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/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/document.d.ts +5 -14
- package/dist/crdt/document.js +5 -19
- package/dist/crdt/edytor-doc.d.ts +4 -4
- package/dist/crdt/edytor-doc.js +3 -7
- package/dist/crdt/index.d.ts +3 -3
- package/dist/crdt/index.js +2 -2
- 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/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 +17 -31
- 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/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 +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/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
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
<script module lang="ts">
|
|
2
|
+
import { imageForms } from '../../export/forms.js';
|
|
2
3
|
import './image.css';
|
|
3
|
-
|
|
4
|
+
|
|
4
5
|
import type { Plugin, BlockSnippetPayload } from '../../plugins.js';
|
|
5
|
-
import type { Edytor } from '../../edytor.svelte.js';
|
|
6
6
|
import EmptySlot from '../media/EmptySlot.svelte';
|
|
7
7
|
import ImageChrome from './ImageChrome.svelte';
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
import {
|
|
10
10
|
imageAlignOf,
|
|
11
11
|
imageAltOf,
|
|
12
12
|
imageWidthOf,
|
|
13
|
-
imgData,
|
|
14
13
|
isImageFile,
|
|
15
14
|
MAX_INLINE_IMAGE_BYTES,
|
|
16
15
|
MIN_IMAGE_WIDTH,
|
|
@@ -20,7 +19,7 @@
|
|
|
20
19
|
type ImageAlign
|
|
21
20
|
} from './image.js';
|
|
22
21
|
import { ImageControls, imageControls } from './controls.svelte.js';
|
|
23
|
-
import { MEDIA_ICONS
|
|
22
|
+
import { MEDIA_ICONS } from '../media/empty.svelte.js';
|
|
24
23
|
import { imageEnglish, keywordsOf, labelsOf } from '../../labels.js';
|
|
25
24
|
import { listen } from '../../utils/listen.js';
|
|
26
25
|
import { fileUploads, uploadHooks, type Uploader } from '../uploads.svelte.js';
|
|
@@ -41,11 +40,6 @@
|
|
|
41
40
|
|
|
42
41
|
const px = (width: number | undefined) => (width === undefined ? undefined : `${width}px`);
|
|
43
42
|
|
|
44
|
-
/** One view's image state: its source rule, the chrome. */
|
|
45
|
-
type View = EmptyView & { controls: ImageControls };
|
|
46
|
-
/** By view; the first image plugin of a view owns it, as its kind's definition (first wins). */
|
|
47
|
-
const views = new WeakMap<Edytor, View>();
|
|
48
|
-
|
|
49
43
|
export type ImagePluginOptions = {
|
|
50
44
|
/**
|
|
51
45
|
* Upload an image file and answer its URL: an empty image's Upload,
|
|
@@ -67,23 +61,12 @@
|
|
|
67
61
|
const plugin: Plugin = (edytor) => {
|
|
68
62
|
const labels = labelsOf(edytor, 'image', imageEnglish);
|
|
69
63
|
const uploads = uploadHooks(edytor, upload);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (oversizedInlineImage(src)) return 'inline';
|
|
77
|
-
block.edytor.patchData(block.id, [{ path: ['src'], value: src }]);
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
// The records read with no view (`plugin(undefined)`) keep no state.
|
|
82
|
-
if (edytor && !views.has(edytor)) {
|
|
83
|
-
views.set(edytor, own);
|
|
84
|
-
imageControls.set(edytor, own.controls);
|
|
85
|
-
}
|
|
86
|
-
const owns = () => views.get(edytor) === own;
|
|
64
|
+
// The view's image state (its source rule, the chrome): the first image
|
|
65
|
+
// plugin of a view owns it, as its kind's definition (first wins). The
|
|
66
|
+
// records read with no view (`plugin(undefined)`) keep no state.
|
|
67
|
+
const controls = new ImageControls(edytor);
|
|
68
|
+
if (edytor && !imageControls.has(edytor)) imageControls.set(edytor, controls);
|
|
69
|
+
const owns = () => imageControls.get(edytor) === controls;
|
|
87
70
|
return {
|
|
88
71
|
onPaste: uploads.onPaste,
|
|
89
72
|
// The chrome: in the overlay, for the image under the pointer.
|
|
@@ -91,7 +74,6 @@
|
|
|
91
74
|
const release = uploads.onEdytorAttached?.(view);
|
|
92
75
|
if (!owns()) return release;
|
|
93
76
|
const { node } = view;
|
|
94
|
-
const { controls } = own;
|
|
95
77
|
const over = (event: PointerEvent) => controls.hover(event.target);
|
|
96
78
|
const leave = (event: PointerEvent) =>
|
|
97
79
|
!controls.drag && controls.leave(event.relatedTarget);
|
|
@@ -117,7 +99,7 @@
|
|
|
117
99
|
},
|
|
118
100
|
blocks: {
|
|
119
101
|
image: {
|
|
120
|
-
...
|
|
102
|
+
...imageForms().image,
|
|
121
103
|
snippet: image,
|
|
122
104
|
element: 'figure',
|
|
123
105
|
// Image files, with an `upload`: an Upload and pasted or dropped files.
|
|
@@ -139,26 +121,7 @@
|
|
|
139
121
|
keywords: keywordsOf(edytor, 'block.image', ['picture', 'photo', 'img']),
|
|
140
122
|
group: 'Media'
|
|
141
123
|
}
|
|
142
|
-
]
|
|
143
|
-
html: (block, caption) => {
|
|
144
|
-
const src = safeImageSrc(block.data?.src);
|
|
145
|
-
const width = imageWidthOf(block.data);
|
|
146
|
-
const align = imageAlignOf(block.data);
|
|
147
|
-
const img = src
|
|
148
|
-
? `<img src="${escapeHtml(src)}" alt="${escapeHtml(imageAltOf(block.data))}"${width ? ` width="${Math.round(width)}"` : ''}>`
|
|
149
|
-
: '';
|
|
150
|
-
const at = align === 'center' ? '' : ` data-align="${align}"`;
|
|
151
|
-
return `<figure${at}>${img}<figcaption>${caption}</figcaption></figure>`;
|
|
152
|
-
},
|
|
153
|
-
// A `figure` holding an `img`, or a bare `img`, with an accepted
|
|
154
|
-
// source; a pasted inline image over the cap is not imported.
|
|
155
|
-
parse: (el) => {
|
|
156
|
-
if (el.localName === 'img') return imgData(el) ?? undefined;
|
|
157
|
-
const data = el.localName === 'figure' && imgData(el.querySelector('img'));
|
|
158
|
-
if (!data) return undefined;
|
|
159
|
-
const align = el.getAttribute('data-align');
|
|
160
|
-
return align === 'left' || align === 'right' ? { ...data, align } : data;
|
|
161
|
-
}
|
|
124
|
+
]
|
|
162
125
|
}
|
|
163
126
|
}
|
|
164
127
|
};
|
|
@@ -172,7 +135,7 @@
|
|
|
172
135
|
<!-- prettier-ignore -->
|
|
173
136
|
{#snippet image({ block, content }: BlockSnippetPayload<ImageData>)}
|
|
174
137
|
<!-- A suggestion's preview (no handle) reads only its data. -->
|
|
175
|
-
{@const
|
|
138
|
+
{@const controls = block.handle ? imageControls.get(block.handle.edytor) : undefined}
|
|
176
139
|
{@const src = safeImageSrc(block.data.src)}
|
|
177
140
|
{@const pending = block.handle && !src ? fileUploads(block.handle.edytor)?.of(block.id) : undefined}
|
|
178
141
|
{#if src}
|
|
@@ -182,7 +145,7 @@
|
|
|
182
145
|
alt={imageAltOf(block.data)}
|
|
183
146
|
draggable="false"
|
|
184
147
|
style:width={px(
|
|
185
|
-
|
|
148
|
+
controls ? controls.width(block.id, block.data) : imageWidthOf(block.data)
|
|
186
149
|
)}
|
|
187
150
|
/>
|
|
188
151
|
</div>
|
|
@@ -196,7 +159,7 @@
|
|
|
196
159
|
<EmptySlot
|
|
197
160
|
block={block.handle}
|
|
198
161
|
kind="image"
|
|
199
|
-
viewOf={(edytor) => (edytor ?
|
|
162
|
+
viewOf={(edytor) => (edytor ? imageControls.get(edytor) : undefined)}
|
|
200
163
|
/>
|
|
201
164
|
</div>
|
|
202
165
|
{/if}<!--
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BandDrag } from '../bands.svelte.js';
|
|
2
2
|
import { layerBox, pin, sameValues } from '../../surface/overlay.js';
|
|
3
|
-
import { imageAlignOf, imageWidthOf, MIN_IMAGE_WIDTH } from './image.js';
|
|
3
|
+
import { imageAlignOf, imageWidthOf, MIN_IMAGE_WIDTH, oversizedInlineImage, safeImageSrc } from './image.js';
|
|
4
4
|
/** The image element of a shown image block (none while it uploads); an image holds no block. */
|
|
5
5
|
const imageOf = (node) => node?.querySelector('[data-edytor-image] > img') ?? null;
|
|
6
6
|
/**
|
|
@@ -29,13 +29,27 @@ export class ImageControls {
|
|
|
29
29
|
/** The handles' gesture (a resize band's: press, view-only preview, one write). @internal */
|
|
30
30
|
band;
|
|
31
31
|
/** The view's image toolbar, when its plugin is listed. @internal */
|
|
32
|
-
toolbar;
|
|
32
|
+
toolbar = $state();
|
|
33
33
|
constructor(
|
|
34
34
|
/** @internal */
|
|
35
35
|
edytor) {
|
|
36
36
|
this.edytor = edytor;
|
|
37
37
|
this.band = new BandDrag(edytor);
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Store a link or an upload's answer on `block` (an empty image's): an
|
|
41
|
+
* accepted source; an inline image over the cap is never stored.
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
store = (block, value) => {
|
|
45
|
+
const src = safeImageSrc(value);
|
|
46
|
+
if (!src)
|
|
47
|
+
return 'invalid';
|
|
48
|
+
if (oversizedInlineImage(src))
|
|
49
|
+
return 'inline';
|
|
50
|
+
block.edytor.patchData(block.id, [{ path: ['src'], value: src }]);
|
|
51
|
+
return null;
|
|
52
|
+
};
|
|
39
53
|
/** The handle being dragged. */
|
|
40
54
|
get drag() {
|
|
41
55
|
return this.band.drag;
|
|
@@ -11,7 +11,6 @@ import type { Attachment } from 'svelte/attachments';
|
|
|
11
11
|
import type { Snippet } from 'svelte';
|
|
12
12
|
import type { Plugin } from '../../plugins.js';
|
|
13
13
|
import { type ImageAlign } from './image.js';
|
|
14
|
-
import { type ImageControls } from './controls.svelte.js';
|
|
15
14
|
/**
|
|
16
15
|
* What a `toolbar` snippet receives: the image the chrome is on (`box`),
|
|
17
16
|
* its alignment (`align`) and alt text (`alt`, `setAlt`), the alt field's
|
|
@@ -20,9 +19,11 @@ import { type ImageControls } from './controls.svelte.js';
|
|
|
20
19
|
*/
|
|
21
20
|
export declare class ImageToolbar {
|
|
22
21
|
#private;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Where the toolbar stands (`ChromeAnchor`): the image the chrome is
|
|
24
|
+
* on, whose top right your `toolbar` places itself in.
|
|
25
|
+
*/
|
|
26
|
+
readonly anchor: import("../chrome.js").ChromeAnchor;
|
|
26
27
|
/** The measured image the toolbar is on (`null`: none). */
|
|
27
28
|
get box(): import("./controls.svelte.js").ImageBox | null;
|
|
28
29
|
/** The view is readonly (reactive). */
|
|
@@ -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,7 +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
3
|
export { createArrowMovePlugin } from './arrowMove/arrowMove.js';
|
|
4
|
-
export type { MenuItemPayload } from './chrome.js';
|
|
4
|
+
export type { ChromeAnchor, MenuItemPayload } from './chrome.js';
|
|
5
5
|
export { BLOCK_ACTIVATE_EVENT, BLOCK_ADD_EVENT, type BlockActivation, type BlockAddition } from './blockHandles/BlockHandleController.svelte.js';
|
|
6
6
|
export { createBlockHandlesPlugin, type BlockHandlesOptions, type BlockHandleSnippetPayload } from './blockHandles/blockHandlesPlugin.js';
|
|
7
7
|
export { createCodePlugin, CODE_LANGUAGES, loadCodeLanguage, type CodeLanguage, type CodePluginOptions } from './code/CodePlugin.svelte';
|
|
@@ -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);
|
|
@@ -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;
|
|
@@ -19,5 +19,5 @@
|
|
|
19
19
|
* Void: its only text is the caption.
|
|
20
20
|
*/
|
|
21
21
|
export const createAudioPlugin = ({ upload }: AudioPluginOptions = {}): Plugin =>
|
|
22
|
-
sourcePlugin('audio', ['sound', 'music', 'mp3'],
|
|
22
|
+
sourcePlugin('audio', ['sound', 'music', 'mp3'], {}, upload);
|
|
23
23
|
</script>
|
|
@@ -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}
|