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
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Files dragged from outside over a view (Notion): the drop indicator a
|
|
3
|
+
* block drag shows, where the files land, and that placement for the drop.
|
|
4
|
+
* The drag is Pragmatic's external adapter's (`dnd/external.ts`), the same
|
|
5
|
+
* pipeline as the block and table drags: the host is its drop target, which
|
|
6
|
+
* takes a drag of files in an editable view. The placement is the block
|
|
7
|
+
* drag's own resolution (`DropResolver`) for a drag that moves no block,
|
|
8
|
+
* whose places are the ones the document takes new blocks of the files'
|
|
9
|
+
* kinds at (`canPlaceNew`). With or without the block handles.
|
|
10
|
+
*
|
|
11
|
+
* While they are dragged the browser shows each file's MIME type, not the
|
|
12
|
+
* file (the adapter's items carry it, a vendored change, `UPSTREAM.md`): the
|
|
13
|
+
* kinds are the ones the uploads would place those files as, so the line
|
|
14
|
+
* shows only where they land; none (a PDF with only an image uploader): no
|
|
15
|
+
* line, and the drop inserts nothing. Where the engine hides the types (no
|
|
16
|
+
* item, or an empty type), every kind the uploads take. The drop resolves
|
|
17
|
+
* again with the files themselves.
|
|
18
|
+
* Its listeners are the window's, capturing, so the drop target's `onDrop`
|
|
19
|
+
* runs before the host's own `drop` listener (`events/onDrop.ts`), which
|
|
20
|
+
* takes the placement (`ViewUploads.drop.take`) and runs the drop's
|
|
21
|
+
* occurrence (the uploads' paste hook places one block per file there, one
|
|
22
|
+
* command). Plugin-owned: the view's `FileUploads` (the first media plugin
|
|
23
|
+
* listed with an `upload`) installs it, so a view without one ships none of it.
|
|
24
|
+
*/
|
|
25
|
+
import type { Edytor } from '../edytor.svelte.js';
|
|
26
|
+
import type { DropAt } from '../session/attempt.js';
|
|
27
|
+
/** What the drag asks of the view's uploads. */
|
|
28
|
+
export type DropUploads = {
|
|
29
|
+
/** The kind that takes `file`, if any. */
|
|
30
|
+
kindOf(file: File): string | undefined;
|
|
31
|
+
/** Every kind that takes files. */
|
|
32
|
+
readonly types: readonly string[];
|
|
33
|
+
};
|
|
34
|
+
export declare class FileDrag {
|
|
35
|
+
#private;
|
|
36
|
+
private readonly edytor;
|
|
37
|
+
private readonly uploads;
|
|
38
|
+
constructor(edytor: Edytor, uploads: DropUploads);
|
|
39
|
+
/** The host takes drags of files from outside while it is editable; the answer releases it. */
|
|
40
|
+
attach(host: HTMLElement): () => void;
|
|
41
|
+
/** The placement the last drop of files landed at, once (the host's `drop` takes it). */
|
|
42
|
+
take(): DropAt | null;
|
|
43
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { canPlaceNew } from '../session/moves.js';
|
|
2
|
+
import { autoScrollFor } from '../dnd/autoScroll.js';
|
|
3
|
+
import { DropIndicator } from '../dnd/dropIndicator.js';
|
|
4
|
+
import { containsFiles, dropTargetForExternal, externalScrollers, getFiles } from '../dnd/external.js';
|
|
5
|
+
import { DropResolver, isDropTarget } from '../dnd/placement.js';
|
|
6
|
+
import { isKindControl } from '../events/nativeInteractiveControl.js';
|
|
7
|
+
export class FileDrag {
|
|
8
|
+
edytor;
|
|
9
|
+
uploads;
|
|
10
|
+
#indicator;
|
|
11
|
+
#resolver;
|
|
12
|
+
#scrolling = null;
|
|
13
|
+
#dropped = null;
|
|
14
|
+
#begun = false;
|
|
15
|
+
#kinds = [];
|
|
16
|
+
constructor(edytor, uploads) {
|
|
17
|
+
this.edytor = edytor;
|
|
18
|
+
this.uploads = uploads;
|
|
19
|
+
this.#indicator = new DropIndicator(edytor, {
|
|
20
|
+
group: () => [],
|
|
21
|
+
besideOf: (block) => this.#resolver.besideOf(block),
|
|
22
|
+
layoutOf: (outer) => this.#resolver.layoutOf(outer)
|
|
23
|
+
});
|
|
24
|
+
this.#resolver = new DropResolver(edytor, {
|
|
25
|
+
targetOf: (node) => {
|
|
26
|
+
const block = edytor.idToBlock.get(node.dataset.edytorId ?? '');
|
|
27
|
+
return block?.node === node && isDropTarget(edytor, block) ? block : undefined;
|
|
28
|
+
},
|
|
29
|
+
handle: () => null,
|
|
30
|
+
indicator: this.#indicator
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/** The host takes drags of files from outside while it is editable; the answer releases it. */
|
|
34
|
+
attach(host) {
|
|
35
|
+
const release = dropTargetForExternal({
|
|
36
|
+
element: host,
|
|
37
|
+
canDrop: ({ source }) => !this.edytor.readonly && containsFiles({ source }) && this.uploads.types.length > 0,
|
|
38
|
+
onDragEnter: ({ source, location }) => {
|
|
39
|
+
this.#kinds = this.#draggedKinds(source.items);
|
|
40
|
+
this.#dropped = null;
|
|
41
|
+
this.#scrolling ??= autoScrollFor(host, externalScrollers(() => !this.edytor.readonly));
|
|
42
|
+
this.#over(this.#kinds, location.current.input);
|
|
43
|
+
},
|
|
44
|
+
onDrag: ({ location }) => this.#over(this.#kinds, location.current.input),
|
|
45
|
+
onDragLeave: () => this.#end(),
|
|
46
|
+
onDrop: ({ source, location }) => {
|
|
47
|
+
const kinds = new Set(getFiles({ source }).flatMap((file) => this.uploads.kindOf(file) ?? []));
|
|
48
|
+
const placement = kinds.size ? this.#over([...kinds], location.current.input) : null;
|
|
49
|
+
this.#end();
|
|
50
|
+
this.#dropped = placement && { block: placement.target.id, position: placement.position };
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
return () => {
|
|
54
|
+
release();
|
|
55
|
+
this.#end();
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** The placement the last drop of files landed at, once (the host's `drop` takes it). */
|
|
59
|
+
take() {
|
|
60
|
+
const dropped = this.#dropped;
|
|
61
|
+
this.#dropped = null;
|
|
62
|
+
return dropped;
|
|
63
|
+
}
|
|
64
|
+
/** The kinds the dragged files would be, by their types; hidden types: every kind that takes files. */
|
|
65
|
+
#draggedKinds(items) {
|
|
66
|
+
const files = items.filter((item) => item.kind === 'file');
|
|
67
|
+
if (!files.length || files.some((item) => !item.type))
|
|
68
|
+
return this.uploads.types;
|
|
69
|
+
const kinds = new Set();
|
|
70
|
+
for (const { type } of files) {
|
|
71
|
+
const kind = this.uploads.kindOf(new File([], 'file', { type }));
|
|
72
|
+
if (kind)
|
|
73
|
+
kinds.add(kind);
|
|
74
|
+
}
|
|
75
|
+
return [...kinds];
|
|
76
|
+
}
|
|
77
|
+
/** The drag of files of `kinds`: no block moves; a place takes them all. */
|
|
78
|
+
#drag(kinds) {
|
|
79
|
+
return {
|
|
80
|
+
group: [],
|
|
81
|
+
fits: ({ target, position }) => canPlaceNew(this.edytor, kinds, target, position)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The placement at the pointer, shown (`null`: none, and nothing shows):
|
|
86
|
+
* over a drop target, its row's; elsewhere in the host, its margins'; on
|
|
87
|
+
* a kind's own control, none.
|
|
88
|
+
*/
|
|
89
|
+
#over(kinds, input) {
|
|
90
|
+
const host = this.edytor.node;
|
|
91
|
+
const document = host?.ownerDocument;
|
|
92
|
+
if (!document)
|
|
93
|
+
return null;
|
|
94
|
+
if (!this.#begun) {
|
|
95
|
+
this.#begun = true;
|
|
96
|
+
this.#indicator.begin(document);
|
|
97
|
+
}
|
|
98
|
+
const at = document.elementsFromPoint?.(input.clientX, input.clientY)[0] ?? null;
|
|
99
|
+
const control = at !== null && isKindControl(host, at);
|
|
100
|
+
const drag = this.#drag(kinds);
|
|
101
|
+
const node = control ? null : this.#resolver.hitAt(input);
|
|
102
|
+
const placement = control
|
|
103
|
+
? null
|
|
104
|
+
: node
|
|
105
|
+
? this.#resolver.over(drag, node, input)
|
|
106
|
+
: this.#resolver.margin(drag, input);
|
|
107
|
+
if (!placement) {
|
|
108
|
+
this.#indicator.clear();
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
// A margin's placement shows as the block drag's page target does (its reorder holds).
|
|
112
|
+
this.#indicator.show(node ?? document.body, placement);
|
|
113
|
+
return placement;
|
|
114
|
+
}
|
|
115
|
+
/** The drag left the view, or dropped: nothing shows, nothing scrolls. */
|
|
116
|
+
#end() {
|
|
117
|
+
this.#scrolling?.();
|
|
118
|
+
this.#scrolling = null;
|
|
119
|
+
this.#begun = false;
|
|
120
|
+
this.#indicator.end();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -14,6 +14,11 @@ export type ReplaceMatches = Commands['replaceMatches'];
|
|
|
14
14
|
export declare class FindController {
|
|
15
15
|
#private;
|
|
16
16
|
private edytor;
|
|
17
|
+
/**
|
|
18
|
+
* Where the bar stands (`ChromeAnchor`): the editor, whose top right
|
|
19
|
+
* your `bar` places itself in.
|
|
20
|
+
*/
|
|
21
|
+
readonly anchor: import("../chrome.js").ChromeAnchor;
|
|
17
22
|
isOpen: boolean;
|
|
18
23
|
/** The replacement text. */
|
|
19
24
|
replacement: string;
|
|
@@ -4,6 +4,8 @@ import { showText } from '../planned.js';
|
|
|
4
4
|
import { findMatches } from './search.js';
|
|
5
5
|
import { listen } from '../../utils/listen.js';
|
|
6
6
|
import { takeKeys } from '../../events/onFocus.js';
|
|
7
|
+
import { keysOf } from '../list.svelte.js';
|
|
8
|
+
import { virtualAnchor } from '../chrome.js';
|
|
7
9
|
/**
|
|
8
10
|
* The find bar's state and actions (`plugins/find`): the query, the matches
|
|
9
11
|
* in reading order, the current one and the replacements. It reads the
|
|
@@ -12,6 +14,11 @@ import { takeKeys } from '../../events/onFocus.js';
|
|
|
12
14
|
*/
|
|
13
15
|
export class FindController {
|
|
14
16
|
edytor;
|
|
17
|
+
/**
|
|
18
|
+
* Where the bar stands (`ChromeAnchor`): the editor, whose top right
|
|
19
|
+
* your `bar` places itself in.
|
|
20
|
+
*/
|
|
21
|
+
anchor = virtualAnchor(() => this.edytor.node?.getBoundingClientRect(), () => this.edytor.node);
|
|
15
22
|
isOpen = $state(false);
|
|
16
23
|
/** The replacement text. */
|
|
17
24
|
replacement = $state('');
|
|
@@ -151,20 +158,13 @@ export class FindController {
|
|
|
151
158
|
* closes, Mod+F selects the query.
|
|
152
159
|
*/
|
|
153
160
|
#keys = (node, query) => {
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
this.close();
|
|
162
|
-
else if (query && mod && event.key.toLowerCase() === 'f')
|
|
163
|
-
node.select();
|
|
164
|
-
else
|
|
165
|
-
return;
|
|
166
|
-
event.preventDefault();
|
|
167
|
-
};
|
|
161
|
+
const select = (event) => query && (this.edytor.keymap.isMac ? event.metaKey : event.ctrlKey) && node.select();
|
|
162
|
+
const keydown = keysOf({
|
|
163
|
+
Enter: (event) => (query ? (event.shiftKey ? this.previous : this.next) : this.replace)(),
|
|
164
|
+
Escape: () => this.close(),
|
|
165
|
+
f: select,
|
|
166
|
+
F: select
|
|
167
|
+
});
|
|
168
168
|
return listen(node, 'keydown', keydown);
|
|
169
169
|
};
|
|
170
170
|
/** Stop following the document (the editor is going away). */
|
|
@@ -8,19 +8,6 @@ const controllers = new WeakMap();
|
|
|
8
8
|
const MAX_HIGHLIGHTS = 1000;
|
|
9
9
|
/** The find plugin's controller of `edytor`; `undefined` when the plugin is not listed. */
|
|
10
10
|
export const findController = (edytor) => controllers.get(edytor);
|
|
11
|
-
/** At the editor's top right, inside the viewport; measured in the overlay's frame. */
|
|
12
|
-
const place = (edytor, find) => (host) => {
|
|
13
|
-
const editor = edytor.node;
|
|
14
|
-
const view = editor?.ownerDocument.defaultView;
|
|
15
|
-
if (!editor || !view || !find.isOpen)
|
|
16
|
-
return;
|
|
17
|
-
const rect = editor.getBoundingClientRect();
|
|
18
|
-
const width = host.firstElementChild?.getBoundingClientRect().width || 420;
|
|
19
|
-
const right = Math.min(rect.right, view.innerWidth - 8);
|
|
20
|
-
const left = `${Math.max(8, right - width)}px`;
|
|
21
|
-
const top = `${Math.max(8, rect.top + 8)}px`;
|
|
22
|
-
return () => Object.assign(host.style, { left, top });
|
|
23
|
-
};
|
|
24
11
|
/**
|
|
25
12
|
* Find and replace (Notion's find in page), opt-in: Mod+F opens a bar over
|
|
26
13
|
* the editor's top right. It searches the document's text in reading order,
|
|
@@ -63,8 +50,7 @@ export const createFindPlugin = (options = {}) => (edytor) => {
|
|
|
63
50
|
});
|
|
64
51
|
const unmountBar = mountChrome(edytor, options.bar, find, () => find.isOpen, {
|
|
65
52
|
name: 'edytor-find-host',
|
|
66
|
-
zIndex: 55
|
|
67
|
-
measure: place(edytor, find)
|
|
53
|
+
zIndex: 55
|
|
68
54
|
});
|
|
69
55
|
return () => {
|
|
70
56
|
find.destroy();
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* The image chrome (`ImageControls`), in the overlay: a resize handle on
|
|
6
|
-
* each side of the hovered image (Notion's pill, at mid-height)
|
|
7
|
-
* toolbar
|
|
6
|
+
* each side of the hovered image (Notion's pill, at mid-height), and the
|
|
7
|
+
* image toolbar plugin's toolbar (the app's markup, which places itself
|
|
8
|
+
* by the toolbar's `anchor`), while no handle drags.
|
|
8
9
|
*/
|
|
9
10
|
let { controls }: { controls: ImageControls } = $props();
|
|
10
11
|
|
|
@@ -38,6 +39,11 @@
|
|
|
38
39
|
{@attach controls.band.press((event) => controls.start(event, side))}
|
|
39
40
|
></div>
|
|
40
41
|
{/each}
|
|
42
|
+
{#if controls.toolbar && !controls.drag}
|
|
43
|
+
<div data-edytor-image-toolbar-slot data-selecting={controls.selecting ? 'true' : undefined}>
|
|
44
|
+
{@render controls.toolbar.snippet(controls.toolbar)}
|
|
45
|
+
</div>
|
|
46
|
+
{/if}
|
|
41
47
|
{/if}
|
|
42
48
|
</div>
|
|
43
49
|
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
<script module lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { imageForms } from '../../export/forms.js';
|
|
3
|
+
import './image.css';
|
|
4
|
+
|
|
3
5
|
import type { Plugin, BlockSnippetPayload } from '../../plugins.js';
|
|
4
|
-
import { branded } from '../brand.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,9 +19,12 @@
|
|
|
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';
|
|
25
|
+
import { fileUploads, uploadHooks, type Uploader } from '../uploads.svelte.js';
|
|
26
|
+
import { emptyHosts } from '../media/EmptySlot.svelte';
|
|
27
|
+
import UploadPreview from '../media/UploadPreview.svelte';
|
|
26
28
|
|
|
27
29
|
export {
|
|
28
30
|
MAX_INLINE_IMAGE_BYTES,
|
|
@@ -38,10 +40,16 @@
|
|
|
38
40
|
|
|
39
41
|
const px = (width: number | undefined) => (width === undefined ? undefined : `${width}px`);
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
export type ImagePluginOptions = {
|
|
44
|
+
/**
|
|
45
|
+
* Upload an image file and answer its URL: an empty image's Upload,
|
|
46
|
+
* and the pasted and dropped image files, each placed as an image at
|
|
47
|
+
* once (one undo step), showing the file while it uploads, then filled
|
|
48
|
+
* with the URL this answers. Without it, images are only linked. The
|
|
49
|
+
* first image plugin listed in a view gives it.
|
|
50
|
+
*/
|
|
51
|
+
upload?: Uploader;
|
|
52
|
+
};
|
|
45
53
|
|
|
46
54
|
/**
|
|
47
55
|
* Notion's image block: an "Add an image" panel until it has a source (a
|
|
@@ -49,31 +57,23 @@
|
|
|
49
57
|
* an editable caption, its alt text, width and alignment set from the
|
|
50
58
|
* chrome the plugin shows over it. Void: its only text is the caption.
|
|
51
59
|
*/
|
|
52
|
-
export const createImagePlugin = (): Plugin => {
|
|
60
|
+
export const createImagePlugin = ({ upload }: ImagePluginOptions = {}): Plugin => {
|
|
53
61
|
const plugin: Plugin = (edytor) => {
|
|
54
62
|
const labels = labelsOf(edytor, 'image', imageEnglish);
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
block.edytor.patchData(block.id, [{ path: ['src'], value: src }]);
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
// The records read with no view (`plugin(undefined)`) keep no state.
|
|
67
|
-
if (edytor && !views.has(edytor)) {
|
|
68
|
-
views.set(edytor, own);
|
|
69
|
-
imageControls.set(edytor, own.controls);
|
|
70
|
-
}
|
|
71
|
-
const owns = () => views.get(edytor) === own;
|
|
63
|
+
const uploads = uploadHooks(edytor, upload);
|
|
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;
|
|
72
70
|
return {
|
|
71
|
+
onPaste: uploads.onPaste,
|
|
73
72
|
// The chrome: in the overlay, for the image under the pointer.
|
|
74
|
-
onEdytorAttached: (
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
onEdytorAttached: (view) => {
|
|
74
|
+
const release = uploads.onEdytorAttached?.(view);
|
|
75
|
+
if (!owns()) return release;
|
|
76
|
+
const { node } = view;
|
|
77
77
|
const over = (event: PointerEvent) => controls.hover(event.target);
|
|
78
78
|
const leave = (event: PointerEvent) =>
|
|
79
79
|
!controls.drag && controls.leave(event.relatedTarget);
|
|
@@ -94,23 +94,26 @@
|
|
|
94
94
|
off2();
|
|
95
95
|
off3();
|
|
96
96
|
unmount();
|
|
97
|
+
if (typeof release === 'function') release();
|
|
97
98
|
};
|
|
98
99
|
},
|
|
99
100
|
blocks: {
|
|
100
101
|
image: {
|
|
101
|
-
...
|
|
102
|
+
...imageForms().image,
|
|
102
103
|
snippet: image,
|
|
103
104
|
element: 'figure',
|
|
104
|
-
// Image files
|
|
105
|
-
upload
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
105
|
+
// Image files, with an `upload`: an Upload and pasted or dropped files.
|
|
106
|
+
...(upload && {
|
|
107
|
+
upload: {
|
|
108
|
+
send: upload,
|
|
109
|
+
accepts: isImageFile,
|
|
110
|
+
data: (_file, answer) => {
|
|
111
|
+
const src = storableImageSrc(answer);
|
|
112
|
+
return src ? { src } : null;
|
|
113
|
+
},
|
|
114
|
+
filled: (data) => safeImageSrc(data.src) !== null
|
|
115
|
+
}
|
|
116
|
+
}),
|
|
114
117
|
presets: [
|
|
115
118
|
{
|
|
116
119
|
label: labels.image,
|
|
@@ -118,32 +121,13 @@
|
|
|
118
121
|
keywords: keywordsOf(edytor, 'block.image', ['picture', 'photo', 'img']),
|
|
119
122
|
group: 'Media'
|
|
120
123
|
}
|
|
121
|
-
]
|
|
122
|
-
html: (block, caption) => {
|
|
123
|
-
const src = safeImageSrc(block.data?.src);
|
|
124
|
-
const width = imageWidthOf(block.data);
|
|
125
|
-
const align = imageAlignOf(block.data);
|
|
126
|
-
const img = src
|
|
127
|
-
? `<img src="${escapeHtml(src)}" alt="${escapeHtml(imageAltOf(block.data))}"${width ? ` width="${Math.round(width)}"` : ''}>`
|
|
128
|
-
: '';
|
|
129
|
-
const at = align === 'center' ? '' : ` data-align="${align}"`;
|
|
130
|
-
return `<figure${at}>${img}<figcaption>${caption}</figcaption></figure>`;
|
|
131
|
-
},
|
|
132
|
-
// A `figure` holding an `img`, or a bare `img`, with an accepted
|
|
133
|
-
// source; a pasted inline image over the cap is not imported.
|
|
134
|
-
parse: (el) => {
|
|
135
|
-
if (el.localName === 'img') return imgData(el) ?? undefined;
|
|
136
|
-
const data = el.localName === 'figure' && imgData(el.querySelector('img'));
|
|
137
|
-
if (!data) return undefined;
|
|
138
|
-
const align = el.getAttribute('data-align');
|
|
139
|
-
return align === 'left' || align === 'right' ? { ...data, align } : data;
|
|
140
|
-
}
|
|
124
|
+
]
|
|
141
125
|
}
|
|
142
126
|
}
|
|
143
127
|
};
|
|
144
128
|
};
|
|
145
129
|
|
|
146
|
-
return
|
|
130
|
+
return plugin;
|
|
147
131
|
};
|
|
148
132
|
</script>
|
|
149
133
|
|
|
@@ -151,8 +135,9 @@
|
|
|
151
135
|
<!-- prettier-ignore -->
|
|
152
136
|
{#snippet image({ block, content }: BlockSnippetPayload<ImageData>)}
|
|
153
137
|
<!-- A suggestion's preview (no handle) reads only its data. -->
|
|
154
|
-
{@const
|
|
138
|
+
{@const controls = block.handle ? imageControls.get(block.handle.edytor) : undefined}
|
|
155
139
|
{@const src = safeImageSrc(block.data.src)}
|
|
140
|
+
{@const pending = block.handle && !src ? fileUploads(block.handle.edytor)?.of(block.id) : undefined}
|
|
156
141
|
{#if src}
|
|
157
142
|
<div use:block.void data-edytor-image data-align={imageAlignOf(block.data)}>
|
|
158
143
|
<img
|
|
@@ -160,37 +145,24 @@
|
|
|
160
145
|
alt={imageAltOf(block.data)}
|
|
161
146
|
draggable="false"
|
|
162
147
|
style:width={px(
|
|
163
|
-
|
|
148
|
+
controls ? controls.width(block.id, block.data) : imageWidthOf(block.data)
|
|
164
149
|
)}
|
|
165
150
|
/>
|
|
166
151
|
</div>
|
|
152
|
+
{:else if pending?.status === 'uploading' && !emptyHosts.has(block.handle!.edytor)}
|
|
153
|
+
<!-- An upload with no empty markup to draw it: the file at once, its progress over it. -->
|
|
154
|
+
<div use:block.void data-edytor-image-empty>
|
|
155
|
+
<UploadPreview upload={pending} image />
|
|
156
|
+
</div>
|
|
167
157
|
{:else}
|
|
168
158
|
<div use:block.void data-edytor-image-empty>
|
|
169
159
|
<EmptySlot
|
|
170
160
|
block={block.handle}
|
|
171
161
|
kind="image"
|
|
172
|
-
viewOf={(edytor) => (edytor ?
|
|
162
|
+
viewOf={(edytor) => (edytor ? imageControls.get(edytor) : undefined)}
|
|
173
163
|
/>
|
|
174
164
|
</div>
|
|
175
165
|
{/if}<!--
|
|
176
166
|
The core renders the kind's <figure> around this markup.
|
|
177
167
|
--><!-- svelte-ignore a11y_figcaption_parent --><figcaption>{@render content()}</figcaption>
|
|
178
168
|
{/snippet}
|
|
179
|
-
|
|
180
|
-
<style>
|
|
181
|
-
/* Where an image narrower than its block sits (Notion's alignment). */
|
|
182
|
-
[data-edytor-image] {
|
|
183
|
-
display: flex;
|
|
184
|
-
position: relative;
|
|
185
|
-
justify-content: center;
|
|
186
|
-
}
|
|
187
|
-
[data-edytor-image][data-align='left'] {
|
|
188
|
-
justify-content: flex-start;
|
|
189
|
-
}
|
|
190
|
-
[data-edytor-image][data-align='right'] {
|
|
191
|
-
justify-content: flex-end;
|
|
192
|
-
}
|
|
193
|
-
[data-edytor-image] img {
|
|
194
|
-
max-width: 100%;
|
|
195
|
-
}
|
|
196
|
-
</style>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import './image.css';
|
|
1
2
|
import type { Plugin } from '../../plugins.js';
|
|
2
3
|
import { MAX_INLINE_IMAGE_BYTES, MIN_IMAGE_WIDTH, oversizedInlineImage, safeImageSrc, storableImageSrc, type ImageAlign } from './image.js';
|
|
4
|
+
import { type Uploader } from '../uploads.svelte.js';
|
|
3
5
|
export { MAX_INLINE_IMAGE_BYTES, MIN_IMAGE_WIDTH, oversizedInlineImage, safeImageSrc, storableImageSrc, type ImageAlign };
|
|
4
6
|
/** The data an image block reads (all optional). */
|
|
5
7
|
export type ImageData = {
|
|
@@ -8,13 +10,23 @@ export type ImageData = {
|
|
|
8
10
|
width?: number;
|
|
9
11
|
align?: ImageAlign;
|
|
10
12
|
};
|
|
13
|
+
export type ImagePluginOptions = {
|
|
14
|
+
/**
|
|
15
|
+
* Upload an image file and answer its URL: an empty image's Upload,
|
|
16
|
+
* and the pasted and dropped image files, each placed as an image at
|
|
17
|
+
* once (one undo step), showing the file while it uploads, then filled
|
|
18
|
+
* with the URL this answers. Without it, images are only linked. The
|
|
19
|
+
* first image plugin listed in a view gives it.
|
|
20
|
+
*/
|
|
21
|
+
upload?: Uploader;
|
|
22
|
+
};
|
|
11
23
|
/**
|
|
12
24
|
* Notion's image block: an "Add an image" panel until it has a source (a
|
|
13
25
|
* pasted link, or an upload when `upload` is given), then the image with
|
|
14
26
|
* an editable caption, its alt text, width and alignment set from the
|
|
15
27
|
* chrome the plugin shows over it. Void: its only text is the caption.
|
|
16
28
|
*/
|
|
17
|
-
export declare const createImagePlugin: () => Plugin;
|
|
29
|
+
export declare const createImagePlugin: ({ upload }?: ImagePluginOptions) => Plugin;
|
|
18
30
|
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> {
|
|
19
31
|
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
20
32
|
$$bindings?: Bindings;
|
|
@@ -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;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/* Loaded by the plugin's module (a component's own <style> is dropped when only
|
|
2
|
+
its module is used: the plugin factory, never the component). */
|
|
3
|
+
/* Where an image narrower than its block sits (Notion's alignment). */
|
|
4
|
+
[data-edytor-image] {
|
|
5
|
+
display: flex;
|
|
6
|
+
position: relative;
|
|
7
|
+
justify-content: center;
|
|
8
|
+
}
|
|
9
|
+
[data-edytor-image][data-align='left'] {
|
|
10
|
+
justify-content: flex-start;
|
|
11
|
+
}
|
|
12
|
+
[data-edytor-image][data-align='right'] {
|
|
13
|
+
justify-content: flex-end;
|
|
14
|
+
}
|
|
15
|
+
[data-edytor-image] img {
|
|
16
|
+
max-width: 100%;
|
|
17
|
+
}
|
|
@@ -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). */
|