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,4 +1,5 @@
|
|
|
1
1
|
<script module lang="ts">
|
|
2
|
+
import './richtext.css';
|
|
2
3
|
import type { Plugin, BlockSnippetPayload, KindPreset, PlaceholderView } from '../../plugins.js';
|
|
3
4
|
import { branded } from '../brand.js';
|
|
4
5
|
import type { Edytor } from '../../edytor.svelte.js';
|
|
@@ -10,28 +11,47 @@
|
|
|
10
11
|
openLink,
|
|
11
12
|
pastedLink,
|
|
12
13
|
richTextOperations,
|
|
13
|
-
sanitizeLinkHref,
|
|
14
|
-
sanitizeCssColorValue,
|
|
15
14
|
typedLink,
|
|
16
15
|
type RichTextMark
|
|
17
16
|
} from './richTextOperations.js';
|
|
18
17
|
import { marksForInsertion } from '../../session/editing/text.js';
|
|
19
18
|
import { firstUriListEntry } from '../../events/dataTransferPayload.js';
|
|
20
19
|
import { flipToggles, shownSelectionBlocks } from '../../selection/replaceSelection.js';
|
|
21
|
-
import { mediaKinds, richTextKinds
|
|
20
|
+
import { mediaKinds, richTextKinds } from '../../crdt/semantics.js';
|
|
22
21
|
import { richTextEnglish, keywordsOf, labelsOf, type RichTextLabels } from '../../labels.js';
|
|
23
22
|
import TodoCheckbox from './TodoCheckbox.svelte';
|
|
24
23
|
import EmptyBody from './EmptyBody.svelte';
|
|
25
24
|
import CalloutIcon from './CalloutIcon.svelte';
|
|
26
25
|
import RichTextControl from './RichTextControl.svelte';
|
|
27
|
-
import {
|
|
28
|
-
DEFAULT_CALLOUT_ICON,
|
|
29
|
-
calloutHtml,
|
|
30
|
-
calloutIcons,
|
|
31
|
-
calloutPickers,
|
|
32
|
-
iconOf
|
|
33
|
-
} from './callouts.js';
|
|
26
|
+
import { DEFAULT_CALLOUT_ICON, calloutIcons, calloutPickers, iconOf } from './callouts.js';
|
|
34
27
|
import { listen } from '../../utils/listen.js';
|
|
28
|
+
import {
|
|
29
|
+
HEADING_LEVELS,
|
|
30
|
+
calloutRead,
|
|
31
|
+
calloutWrite,
|
|
32
|
+
dividerRead,
|
|
33
|
+
dividerWrite,
|
|
34
|
+
headingLevel,
|
|
35
|
+
headingRead,
|
|
36
|
+
headingWrite,
|
|
37
|
+
itemRead,
|
|
38
|
+
itemWrite,
|
|
39
|
+
numberedRead,
|
|
40
|
+
orderedRead,
|
|
41
|
+
orderedWrite,
|
|
42
|
+
quoteRead,
|
|
43
|
+
quoteWrite,
|
|
44
|
+
richTextMarkForms,
|
|
45
|
+
todoWrite,
|
|
46
|
+
toggleHeadingRead,
|
|
47
|
+
toggleHeadingWrite,
|
|
48
|
+
toggleRead,
|
|
49
|
+
unorderedRead,
|
|
50
|
+
unorderedWrite
|
|
51
|
+
} from '../../export/forms.js';
|
|
52
|
+
import { onLastLine } from '../../session/navigation.js';
|
|
53
|
+
import { bodyShows } from '../../selection/visibility.js';
|
|
54
|
+
import { openBody } from './body.js';
|
|
35
55
|
import type { Snippet } from 'svelte';
|
|
36
56
|
import {
|
|
37
57
|
richTextControls,
|
|
@@ -54,62 +74,10 @@
|
|
|
54
74
|
formatSubscript: 'subscript'
|
|
55
75
|
} as const;
|
|
56
76
|
|
|
57
|
-
const isNativeFormatInputType = (
|
|
58
|
-
inputType: string
|
|
59
|
-
): inputType is keyof typeof nativeFormatMarks => inputType in nativeFormatMarks;
|
|
60
|
-
|
|
61
|
-
/** A thematic break's clipboard forms. */
|
|
62
|
-
const rule = { html: () => '<hr>', plain: () => '---' };
|
|
63
77
|
/** A native disclosure: the browser owns `open` (declared view state). */
|
|
64
|
-
const disclosure = {
|
|
78
|
+
const disclosure = { viewState: ['open'] };
|
|
65
79
|
/** The kinds drawn as a disclosure: a toggle this view creates opens (`body.open`). */
|
|
66
80
|
const DISCLOSURES = new Set(['toggle', 'toggle-heading']);
|
|
67
|
-
/**
|
|
68
|
-
* A list's elements without list semantics (`role="none"`): a list's items
|
|
69
|
-
* are sibling blocks, never the only children of one `ul` or `ol`, so an
|
|
70
|
-
* `li` would be an orphan list item to assistive technology (WCAG 1.3.1);
|
|
71
|
-
* the tags stay for styles and the HTML export.
|
|
72
|
-
*/
|
|
73
|
-
const unlisted = (tag: 'li' | 'ul' | 'ol') => ({ tag, attributes: { role: 'none' } });
|
|
74
|
-
/**
|
|
75
|
-
* Notion's three heading levels: a missing level is h1, any other (a
|
|
76
|
-
* stored `h4`–`h6`, as HTML import reads them) h3.
|
|
77
|
-
*/
|
|
78
|
-
const headingLevel = (level: unknown) =>
|
|
79
|
-
level === 'h1' || level === 'h2' || level === 'h3' ? level : level === undefined ? 'h1' : 'h3';
|
|
80
|
-
/** An unsafe scheme drops the href (and its target): an inert anchor still carries the text. */
|
|
81
|
-
const linkAttributes = (mark: { href?: unknown; target?: string }) => {
|
|
82
|
-
const href = sanitizeLinkHref(mark?.href) ?? undefined;
|
|
83
|
-
return { href, target: href && mark.target };
|
|
84
|
-
};
|
|
85
|
-
/** A hostile color value (a `;` payload would inject declarations) drops the style. */
|
|
86
|
-
const styled = (property: string) => (value: unknown) => {
|
|
87
|
-
const safe = sanitizeCssColorValue(value);
|
|
88
|
-
return { style: safe ? `${property}: ${safe};` : undefined };
|
|
89
|
-
};
|
|
90
|
-
/**
|
|
91
|
-
* HTML import: a pasted color, sanitized, unless it is the page's
|
|
92
|
-
* default (Google Docs writes black text and transparent backgrounds on
|
|
93
|
-
* every span: they are no mark, and a black mark is unreadable in dark mode).
|
|
94
|
-
*/
|
|
95
|
-
const colorOf = (value: string, none: RegExp) =>
|
|
96
|
-
none.test(value.replace(/\s+/g, '').toLowerCase())
|
|
97
|
-
? undefined
|
|
98
|
-
: (sanitizeCssColorValue(value) ?? undefined);
|
|
99
|
-
const INHERITED = 'initial|inherit|unset|revert|currentcolor';
|
|
100
|
-
const NO_COLOR = new RegExp(
|
|
101
|
-
`^(${INHERITED}|black|windowtext|#000|#000000|rgba?\\(0,0,0(,1)?\\))$`
|
|
102
|
-
);
|
|
103
|
-
const NO_HIGHLIGHT = new RegExp(
|
|
104
|
-
`^(${INHERITED}|transparent|rgba\\(\\d+,\\d+,\\d+,0\\)|white|#fff|#ffffff|rgba?\\(255,255,255(,1)?\\))$`
|
|
105
|
-
);
|
|
106
|
-
/** HTML import: a tag alias (unless its own style says otherwise) or a style (Google Docs). */
|
|
107
|
-
const alias =
|
|
108
|
-
(tags: RegExp, property: 'fontWeight' | 'fontStyle' | 'textDecoration', value: RegExp) =>
|
|
109
|
-
(el: HTMLElement) =>
|
|
110
|
-
(tags.test(el.localName) && !el.style[property]) ||
|
|
111
|
-
value.test(el.style[property]) ||
|
|
112
|
-
undefined;
|
|
113
81
|
|
|
114
82
|
/**
|
|
115
83
|
* The kinds whose empty text names them, by placeholder (a callout's
|
|
@@ -216,6 +184,7 @@
|
|
|
216
184
|
{ callout: calloutOptions, checkbox, calloutIcon, emptyBody }: RichTextPluginOptions
|
|
217
185
|
): ReturnType<Plugin> => {
|
|
218
186
|
const { kinds, marks: words } = labels;
|
|
187
|
+
const marks = richTextMarkForms;
|
|
219
188
|
/** A new callout's icon. */
|
|
220
189
|
const icon = calloutOptions?.icon ?? DEFAULT_CALLOUT_ICON;
|
|
221
190
|
/** The block this view is retyping into a disclosure (`body.open`), until it applied. */
|
|
@@ -262,6 +231,16 @@
|
|
|
262
231
|
'mod+shift+s': setMarkAndSelect('strike'),
|
|
263
232
|
'mod+shift+x': setMarkAndSelect('strike'),
|
|
264
233
|
'mod+shift+h': setMarkAndSelect('color', 'red'),
|
|
234
|
+
// ArrowDown on the last line of a header whose body shows and is empty
|
|
235
|
+
// (an open toggle, a callout) goes into that body, as its hint's click
|
|
236
|
+
// does (`body.down`): one command, one undo step. Elsewhere the key
|
|
237
|
+
// moves as usual.
|
|
238
|
+
arrowdown: ({ prevent }) => {
|
|
239
|
+
const header = onLastLine(edytor);
|
|
240
|
+
if (!header || header.hasChildren || !bodyShows(header)) return;
|
|
241
|
+
if (!edytor.dispatcher.permits()) return;
|
|
242
|
+
prevent(() => void openBody(header));
|
|
243
|
+
},
|
|
265
244
|
// Mod+Enter checks or unchecks each shown to-do it is in (Notion): the
|
|
266
245
|
// selected blocks', else the caret's; the toggles among them open or
|
|
267
246
|
// close as with the built-in binding, in the same key.
|
|
@@ -303,7 +282,7 @@
|
|
|
303
282
|
const end = yStart + href.length;
|
|
304
283
|
edytor.dispatcher.run('insertFromPaste', () => {
|
|
305
284
|
edytor.insertText(text.parent.id, text.segStart + yStart, href);
|
|
306
|
-
edytor.dispatcher.
|
|
285
|
+
edytor.dispatcher.caretIn(text, end);
|
|
307
286
|
});
|
|
308
287
|
if (text.stringContent.slice(yStart, end) === href)
|
|
309
288
|
operations.linkText(text, yStart, end, href);
|
|
@@ -346,7 +325,7 @@
|
|
|
346
325
|
} finally {
|
|
347
326
|
autolinking = false;
|
|
348
327
|
}
|
|
349
|
-
edytor.dispatcher.
|
|
328
|
+
edytor.dispatcher.caretIn(text, yStart + 1);
|
|
350
329
|
operations.linkText(text, found.start, end, found.href);
|
|
351
330
|
});
|
|
352
331
|
},
|
|
@@ -378,8 +357,8 @@
|
|
|
378
357
|
},
|
|
379
358
|
onBeforeInput: ({ e, prevent }) => {
|
|
380
359
|
const { inputType, data } = e;
|
|
381
|
-
if (
|
|
382
|
-
const mark = nativeFormatMarks[inputType];
|
|
360
|
+
if (inputType in nativeFormatMarks) {
|
|
361
|
+
const mark = nativeFormatMarks[inputType as keyof typeof nativeFormatMarks];
|
|
383
362
|
prevent(() => operations.setMarkAtRange(mark));
|
|
384
363
|
} else if (inputType === 'formatRemove') {
|
|
385
364
|
prevent(() => operations.removeAllMarksAtRange());
|
|
@@ -404,54 +383,17 @@
|
|
|
404
383
|
},
|
|
405
384
|
// Toolbar buttons and export wrapping follow this order (first innermost).
|
|
406
385
|
marks: {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
tag: 'u',
|
|
419
|
-
toolbar: { label: words.underline, icon: 'U' },
|
|
420
|
-
parse: alias(/^u$/, 'textDecoration', /underline/)
|
|
421
|
-
},
|
|
422
|
-
strike: {
|
|
423
|
-
tag: 's',
|
|
424
|
-
toolbar: { label: words.strike, icon: 'S' },
|
|
425
|
-
parse: alias(/^(strike|del)$/, 'textDecoration', /line-through/)
|
|
426
|
-
},
|
|
427
|
-
code: { tag: 'code', toolbar: { label: words.code, icon: '</>' } },
|
|
428
|
-
// Typing at a link's trailing edge extends it only from inside the anchor.
|
|
429
|
-
link: {
|
|
430
|
-
tag: 'a',
|
|
431
|
-
attributes: linkAttributes,
|
|
432
|
-
...richTextMarks.link,
|
|
433
|
-
parse: (el) => {
|
|
434
|
-
const href = el.localName === 'a' && sanitizeLinkHref(el.getAttribute('href'));
|
|
435
|
-
const target = el.getAttribute('target');
|
|
436
|
-
return href ? { href, ...(target ? { target } : {}) } : undefined;
|
|
437
|
-
}
|
|
438
|
-
},
|
|
439
|
-
superscript: { tag: 'sup' },
|
|
440
|
-
subscript: { tag: 'sub' },
|
|
441
|
-
color: {
|
|
442
|
-
tag: 'span',
|
|
443
|
-
attributes: styled('color'),
|
|
444
|
-
parse: (el) => colorOf(el.style.color, NO_COLOR)
|
|
445
|
-
},
|
|
446
|
-
highlight: {
|
|
447
|
-
tag: 'span',
|
|
448
|
-
attributes: styled('background-color'),
|
|
449
|
-
parse: (el) => {
|
|
450
|
-
const background = el.style.backgroundColor;
|
|
451
|
-
if (background) return colorOf(background, NO_HIGHLIGHT);
|
|
452
|
-
return el.localName === 'mark' ? 'yellow' : undefined;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
386
|
+
...marks,
|
|
387
|
+
// The toolbar's marks, by their icons.
|
|
388
|
+
...Object.fromEntries(
|
|
389
|
+
(['bold', 'italic', 'underline', 'strike', 'code'] as const).map((name, i) => [
|
|
390
|
+
name,
|
|
391
|
+
{
|
|
392
|
+
...marks[name],
|
|
393
|
+
toolbar: { label: words[name], icon: ['B', 'I', 'U', 'S', '</>'][i]! }
|
|
394
|
+
}
|
|
395
|
+
])
|
|
396
|
+
)
|
|
455
397
|
},
|
|
456
398
|
blocks: {
|
|
457
399
|
paragraph: {
|
|
@@ -461,42 +403,25 @@
|
|
|
461
403
|
])
|
|
462
404
|
},
|
|
463
405
|
heading: {
|
|
406
|
+
...headingRead,
|
|
407
|
+
...headingWrite,
|
|
464
408
|
snippet: textThenChildren,
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
{
|
|
468
|
-
label: kinds.heading1,
|
|
469
|
-
icon: '
|
|
470
|
-
keywords: ['
|
|
471
|
-
data: { level
|
|
472
|
-
markdown: ['#
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
label: kinds.heading2,
|
|
476
|
-
icon: 'H₂',
|
|
477
|
-
keywords: ['h2', 'subtitle'],
|
|
478
|
-
data: { level: 'h2' },
|
|
479
|
-
markdown: ['## ']
|
|
480
|
-
},
|
|
481
|
-
{
|
|
482
|
-
label: kinds.heading3,
|
|
483
|
-
icon: 'H₃',
|
|
484
|
-
keywords: ['h3'],
|
|
485
|
-
data: { level: 'h3' },
|
|
486
|
-
markdown: ['### ']
|
|
487
|
-
}
|
|
488
|
-
]),
|
|
489
|
-
// HTML import: h1–h3 come from the presets; h4–h6 read as h3.
|
|
490
|
-
parse: (el) => (/^h[4-6]$/.test(el.localName) ? { level: 'h3' } : undefined),
|
|
491
|
-
html: (block, content, children) => {
|
|
492
|
-
const tag = headingLevel(block.data?.level);
|
|
493
|
-
return `<${tag}>${content}</${tag}>${children}`;
|
|
494
|
-
}
|
|
409
|
+
presets: presets(
|
|
410
|
+
'heading',
|
|
411
|
+
HEADING_LEVELS.map((level, index) => ({
|
|
412
|
+
label: [kinds.heading1, kinds.heading2, kinds.heading3][index]!,
|
|
413
|
+
icon: `H${['₁', '₂', '₃'][index]}`,
|
|
414
|
+
keywords: [level, ...[['title'], ['subtitle'], []][index]!],
|
|
415
|
+
data: { level },
|
|
416
|
+
markdown: [`${'#'.repeat(index + 1)} `]
|
|
417
|
+
}))
|
|
418
|
+
)
|
|
495
419
|
},
|
|
496
420
|
'bulleted-list-item': {
|
|
421
|
+
...itemRead,
|
|
422
|
+
...itemWrite,
|
|
497
423
|
continues: true,
|
|
498
424
|
snippet: listItem,
|
|
499
|
-
element: unlisted('li'),
|
|
500
425
|
presets: presets('bulleted-list-item', [
|
|
501
426
|
{
|
|
502
427
|
label: kinds.bulletedList,
|
|
@@ -504,13 +429,13 @@
|
|
|
504
429
|
keywords: ['bullet', 'ul'],
|
|
505
430
|
markdown: ['- ', '* ', '+ ']
|
|
506
431
|
}
|
|
507
|
-
])
|
|
508
|
-
html: 'li'
|
|
432
|
+
])
|
|
509
433
|
},
|
|
510
434
|
'numbered-list-item': {
|
|
435
|
+
...numberedRead,
|
|
436
|
+
...itemWrite,
|
|
511
437
|
continues: true,
|
|
512
438
|
snippet: listItem,
|
|
513
|
-
element: unlisted('li'),
|
|
514
439
|
presets: presets('numbered-list-item', [
|
|
515
440
|
{
|
|
516
441
|
label: kinds.numberedList,
|
|
@@ -518,13 +443,10 @@
|
|
|
518
443
|
keywords: ['number', 'ol'],
|
|
519
444
|
markdown: ['1. ', 'a. ', 'i. ']
|
|
520
445
|
}
|
|
521
|
-
])
|
|
522
|
-
html: 'li',
|
|
523
|
-
// HTML import: an `li` is a bulleted item (the first `li` kind) unless its list is ordered.
|
|
524
|
-
parse: (el) =>
|
|
525
|
-
el.localName === 'li' && el.parentElement?.localName === 'ol' ? {} : undefined
|
|
446
|
+
])
|
|
526
447
|
},
|
|
527
448
|
'todo-item': {
|
|
449
|
+
...todoWrite,
|
|
528
450
|
continues: true,
|
|
529
451
|
// The app's control markup, if any, through its own snippet: the default costs no branch.
|
|
530
452
|
snippet: checkbox ? todoItemOwn : todoItem,
|
|
@@ -536,15 +458,10 @@
|
|
|
536
458
|
data: { checked: false },
|
|
537
459
|
markdown: ['[ ] ', '[] ']
|
|
538
460
|
}
|
|
539
|
-
])
|
|
540
|
-
html: (block, content, children) =>
|
|
541
|
-
`<li data-edytor-todo-item="true"><input type="checkbox"${block.data?.checked === true ? ' checked' : ''}>${content}${children}</li>`,
|
|
542
|
-
plain: (block, content, children) =>
|
|
543
|
-
[`${block.data?.checked === true ? '[x]' : '[ ]'} ${content}`.trim(), children]
|
|
544
|
-
.filter(Boolean)
|
|
545
|
-
.join('\n')
|
|
461
|
+
])
|
|
546
462
|
},
|
|
547
463
|
toggle: {
|
|
464
|
+
...toggleRead,
|
|
548
465
|
continues: true,
|
|
549
466
|
container: true,
|
|
550
467
|
snippet: emptyBody ? detailsOwn : details,
|
|
@@ -563,13 +480,14 @@
|
|
|
563
480
|
// kept); from a toggle, `#`, `##` or `###` + space; so `> # ` typed on a new
|
|
564
481
|
// line is a toggle heading 1.
|
|
565
482
|
'toggle-heading': {
|
|
483
|
+
...toggleHeadingRead,
|
|
484
|
+
...toggleHeadingWrite,
|
|
566
485
|
container: true,
|
|
567
486
|
snippet: emptyBody ? detailsOwn : details,
|
|
568
487
|
...disclosure,
|
|
569
|
-
contentElement: (data) => headingLevel(data.level),
|
|
570
488
|
presets: presets(
|
|
571
489
|
'toggle-heading',
|
|
572
|
-
|
|
490
|
+
HEADING_LEVELS.map((level, index) => {
|
|
573
491
|
const hashes = '#'.repeat(index + 1);
|
|
574
492
|
return {
|
|
575
493
|
label: [kinds.toggleHeading1, kinds.toggleHeading2, kinds.toggleHeading3][index]!,
|
|
@@ -583,28 +501,21 @@
|
|
|
583
501
|
}
|
|
584
502
|
};
|
|
585
503
|
})
|
|
586
|
-
)
|
|
587
|
-
// HTML import: a `details` whose summary holds an h1–h3 (its own export).
|
|
588
|
-
parse: (el) => {
|
|
589
|
-
if (el.localName !== 'details') return;
|
|
590
|
-
const tag = el.querySelector(':scope > summary > :is(h1, h2, h3, h4, h5, h6)');
|
|
591
|
-
return tag ? { level: headingLevel(tag.localName) } : undefined;
|
|
592
|
-
},
|
|
593
|
-
html: (block, content, children) => {
|
|
594
|
-
const tag = headingLevel(block.data?.level);
|
|
595
|
-
return `<details><summary><${tag}>${content}</${tag}></summary>${children}</details>`;
|
|
596
|
-
}
|
|
504
|
+
)
|
|
597
505
|
},
|
|
598
506
|
// Notion's callout: an icon (`data.icon`, the picker's), a title (its own
|
|
599
507
|
// text) and a content (its children, any blocks), which shows even empty
|
|
600
508
|
// (`body`): Enter at the end of the title goes into it.
|
|
601
509
|
callout: {
|
|
510
|
+
...calloutRead,
|
|
602
511
|
container: true,
|
|
603
512
|
body: true,
|
|
604
513
|
snippet: calloutIcon || emptyBody ? calloutOwn : callout,
|
|
605
514
|
presets: presets('callout', [
|
|
606
515
|
{ label: kinds.callout, icon: '✦', keywords: ['note', 'tip'], data: { icon } }
|
|
607
516
|
]),
|
|
517
|
+
// A callout without its own icon exports the view's.
|
|
518
|
+
...calloutWrite((data) => iconOf(data, edytor)),
|
|
608
519
|
// The block menu's Change icon, while the view lists a picker: the keyboard's way to it.
|
|
609
520
|
menu: (block) => {
|
|
610
521
|
const picker = calloutPickers.get(edytor);
|
|
@@ -618,47 +529,38 @@
|
|
|
618
529
|
}
|
|
619
530
|
]
|
|
620
531
|
: [];
|
|
621
|
-
}
|
|
622
|
-
// HTML import: its own export, the icon in its attribute.
|
|
623
|
-
parse: (el) => {
|
|
624
|
-
const own = el.getAttribute('data-edytor-callout');
|
|
625
|
-
return own === null ? undefined : { icon: own };
|
|
626
|
-
},
|
|
627
|
-
html: (block, content, children) =>
|
|
628
|
-
calloutHtml(iconOf(block.data, edytor), content, children)
|
|
532
|
+
}
|
|
629
533
|
},
|
|
630
534
|
quote: {
|
|
535
|
+
...quoteRead,
|
|
536
|
+
...quoteWrite,
|
|
631
537
|
container: true,
|
|
632
538
|
snippet: textThenChildren,
|
|
633
|
-
contentElement: 'blockquote',
|
|
634
539
|
// Notion: `"` + space is a quote; `>` + space is a toggle.
|
|
635
|
-
presets: presets('quote', [{ label: kinds.quote, icon: '❝', markdown: ['" '] }])
|
|
636
|
-
html: 'blockquote'
|
|
540
|
+
presets: presets('quote', [{ label: kinds.quote, icon: '❝', markdown: ['" '] }])
|
|
637
541
|
},
|
|
638
542
|
divider: {
|
|
639
543
|
...richTextKinds.divider,
|
|
640
|
-
|
|
544
|
+
...dividerRead,
|
|
545
|
+
...dividerWrite,
|
|
641
546
|
presets: presets('divider', [
|
|
642
547
|
{ label: kinds.divider, icon: '—', keywords: ['hr', 'separator'], markdown: ['---'] }
|
|
643
548
|
]),
|
|
644
|
-
empty: { content: [], children: [] }
|
|
645
|
-
...rule
|
|
549
|
+
empty: { content: [], children: [] }
|
|
646
550
|
},
|
|
647
551
|
'ordered-list': {
|
|
648
552
|
...richTextKinds['ordered-list'],
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
html: 'ol'
|
|
553
|
+
...orderedRead,
|
|
554
|
+
...orderedWrite,
|
|
555
|
+
snippet: list
|
|
653
556
|
},
|
|
654
557
|
'unordered-list': {
|
|
655
558
|
...richTextKinds['unordered-list'],
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
html: 'ul'
|
|
559
|
+
...unorderedRead,
|
|
560
|
+
...unorderedWrite,
|
|
561
|
+
snippet: list
|
|
660
562
|
},
|
|
661
|
-
'list-item': {
|
|
563
|
+
'list-item': { ...itemRead, ...itemWrite, snippet: listItem }
|
|
662
564
|
}
|
|
663
565
|
};
|
|
664
566
|
};
|
|
@@ -760,10 +662,3 @@
|
|
|
760
662
|
{#snippet list({ children }: BlockSnippetPayload)}<!--
|
|
761
663
|
-->{#if children}<div>{@render children()}</div>{/if}<!--
|
|
762
664
|
-->{/snippet}
|
|
763
|
-
|
|
764
|
-
<style>
|
|
765
|
-
/* An empty body's hint: chrome, not text (`body.hint`); its look is a theme's. */
|
|
766
|
-
:global([data-edytor-empty-body]) {
|
|
767
|
-
user-select: none;
|
|
768
|
-
}
|
|
769
|
-
</style>
|
|
@@ -10,7 +10,7 @@ export const openBody = (block) => {
|
|
|
10
10
|
return edytor.dispatcher.run('openBody', () => {
|
|
11
11
|
const added = () => edytor.insertBlocks({ parent: block.id, index: 0 }, [{ type: edytor.defaultChild(block) }]);
|
|
12
12
|
const child = block.children[0] ?? edytor.idToBlock.get(added().ids[0] ?? '');
|
|
13
|
-
edytor.dispatcher.
|
|
13
|
+
edytor.dispatcher.caretIn(child?.firstText, 0);
|
|
14
14
|
return child ?? undefined;
|
|
15
15
|
});
|
|
16
16
|
};
|
|
@@ -40,6 +40,8 @@ export declare class CalloutIconPicker extends ChromeList {
|
|
|
40
40
|
constructor(edytor: Edytor,
|
|
41
41
|
/** The picker's choices. */
|
|
42
42
|
icons?: readonly string[]);
|
|
43
|
+
/** Where the picker stands: the open callout's icon (`ChromeAnchor`), which your `picker` places itself under. */
|
|
44
|
+
get anchor(): import("../chrome.js").ChromeAnchor;
|
|
43
45
|
/** Whether the picker is open (reactive). */
|
|
44
46
|
get isOpen(): boolean;
|
|
45
47
|
/** The callout it is open on (reactive). */
|
|
@@ -48,8 +50,6 @@ export declare class CalloutIconPicker extends ChromeList {
|
|
|
48
50
|
get current(): string | undefined;
|
|
49
51
|
/** The keyboard's rows: the icons, then Remove icon. */
|
|
50
52
|
protected get count(): number;
|
|
51
|
-
/** The view is readonly (reactive): the picker closes. */
|
|
52
|
-
get readonly(): boolean;
|
|
53
53
|
/** Open the picker on `block`, or close it when it is open there. */
|
|
54
54
|
toggle: (block: Block) => void;
|
|
55
55
|
/** Open the picker on the callout `block`, its icon the keyboard's row (refused in a view that may not write). */
|
|
@@ -14,7 +14,6 @@ import { takeKeys } from '../../events/onFocus.js';
|
|
|
14
14
|
import { ChromeList, holdKeys, menuKey, optionAttachment } from '../list.svelte.js';
|
|
15
15
|
import { Anchored } from '../anchored.svelte.js';
|
|
16
16
|
import { calloutPickers, iconOf } from './callouts.js';
|
|
17
|
-
import CalloutIconHost from './CalloutIconPicker.svelte';
|
|
18
17
|
import { all, listen } from '../../utils/listen.js';
|
|
19
18
|
/** The picker's choices unless `createCalloutIconsPlugin({ icons })` names others. */
|
|
20
19
|
export const CALLOUT_ICONS = [
|
|
@@ -89,6 +88,10 @@ export class CalloutIconPicker extends ChromeList {
|
|
|
89
88
|
return icon ?? node ?? null;
|
|
90
89
|
}, () => this.close(false));
|
|
91
90
|
}
|
|
91
|
+
/** Where the picker stands: the open callout's icon (`ChromeAnchor`), which your `picker` places itself under. */
|
|
92
|
+
get anchor() {
|
|
93
|
+
return this.panel.anchor;
|
|
94
|
+
}
|
|
92
95
|
/** Whether the picker is open (reactive). */
|
|
93
96
|
get isOpen() {
|
|
94
97
|
return this.target !== null;
|
|
@@ -107,10 +110,6 @@ export class CalloutIconPicker extends ChromeList {
|
|
|
107
110
|
get count() {
|
|
108
111
|
return this.icons.length + 1;
|
|
109
112
|
}
|
|
110
|
-
/** The view is readonly (reactive): the picker closes. */
|
|
111
|
-
get readonly() {
|
|
112
|
-
return this.edytor.readonly;
|
|
113
|
-
}
|
|
114
113
|
/** Open the picker on `block`, or close it when it is open there. */
|
|
115
114
|
toggle = (block) => (this.target === block.id ? this.close() : this.open(block));
|
|
116
115
|
/** Open the picker on the callout `block`, its icon the keyboard's row (refused in a view that may not write). */
|
|
@@ -129,7 +128,6 @@ export class CalloutIconPicker extends ChromeList {
|
|
|
129
128
|
return;
|
|
130
129
|
const before = this.#before;
|
|
131
130
|
this.target = null;
|
|
132
|
-
this.panel.box = null;
|
|
133
131
|
this.#before = null;
|
|
134
132
|
this.edytor.overlay.invalidate();
|
|
135
133
|
if (!keys)
|
|
@@ -209,6 +207,6 @@ export const createCalloutIconsPlugin = ({ picker: menu, icons } = {}) => (edyto
|
|
|
209
207
|
calloutPickers.set(edytor, picker);
|
|
210
208
|
return {
|
|
211
209
|
// With the menus: above the handles and the image chrome.
|
|
212
|
-
onEdytorAttached: ({ node }) => menu && picker.panel.mount(node,
|
|
210
|
+
onEdytorAttached: ({ node }) => menu && picker.panel.mount(node, menu, picker, () => picker.isOpen, 70)
|
|
213
211
|
};
|
|
214
212
|
};
|
|
@@ -6,25 +6,7 @@ export type RichTextLink = {
|
|
|
6
6
|
href: string;
|
|
7
7
|
target?: string;
|
|
8
8
|
};
|
|
9
|
-
|
|
10
|
-
* CSS-color sanitizer for `color`/`highlight` marks. The value lands in
|
|
11
|
-
* `style="color: {value}"` — a `;`/`{`/`}` payload escapes the property
|
|
12
|
-
* and injects arbitrary declarations (`url()` exfil, `position:fixed`
|
|
13
|
-
* overlays). Metacharacters, escape/obfuscation sequences, and
|
|
14
|
-
* resource-loading functions are rejected; real `<color>` values
|
|
15
|
-
* (`#hex`, `rgb()/hsl()/oklch()/color-mix()/var()`, named colors) pass.
|
|
16
|
-
*/
|
|
17
|
-
export declare const sanitizeCssColorValue: (value: unknown) => string | null;
|
|
18
|
-
/**
|
|
19
|
-
* Scheme allowlist for link hrefs. `javascript:`/`data:`/`vbscript:`
|
|
20
|
-
* payloads can arrive through native `insertLink`, pasted HTML, or a
|
|
21
|
-
* malicious collaborator — and Svelte renders `href` verbatim. The href is
|
|
22
|
-
* read as the browser's URL parser reads it: it strips C0 controls and
|
|
23
|
-
* spaces at both ends and tab/newline/CR anywhere (`\u0001javascript:` is
|
|
24
|
-
* `javascript:` to a browser), then resolves it against a base; only an
|
|
25
|
-
* allowed scheme, or a relative href that stays relative, passes.
|
|
26
|
-
*/
|
|
27
|
-
export declare const sanitizeLinkHref: (href: unknown) => string | null;
|
|
9
|
+
export { sanitizeCssColorValue, sanitizeLinkHref } from '../../utils/sanitize.js';
|
|
28
10
|
/**
|
|
29
11
|
* The URL a paste carries when its plain text is one link and nothing else
|
|
30
12
|
* (surrounding whitespace aside): `http:`/`https:` with a host, or
|
|
@@ -3,54 +3,8 @@ import { holdsNothing, placing } from '../../kinds.js';
|
|
|
3
3
|
import { id } from '../../utils.js';
|
|
4
4
|
import { marksForInsertion } from '../../session/editing/text.js';
|
|
5
5
|
import { selectedTextSpans, viewOf } from '../../selection/visibility.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* CSS-color sanitizer for `color`/`highlight` marks. The value lands in
|
|
9
|
-
* `style="color: {value}"` — a `;`/`{`/`}` payload escapes the property
|
|
10
|
-
* and injects arbitrary declarations (`url()` exfil, `position:fixed`
|
|
11
|
-
* overlays). Metacharacters, escape/obfuscation sequences, and
|
|
12
|
-
* resource-loading functions are rejected; real `<color>` values
|
|
13
|
-
* (`#hex`, `rgb()/hsl()/oklch()/color-mix()/var()`, named colors) pass.
|
|
14
|
-
*/
|
|
15
|
-
export const sanitizeCssColorValue = (value) => {
|
|
16
|
-
if (typeof value !== 'string')
|
|
17
|
-
return null;
|
|
18
|
-
const trimmed = value.trim();
|
|
19
|
-
if (!trimmed || trimmed.length > 128)
|
|
20
|
-
return null;
|
|
21
|
-
if (/[;{}<>\\'"`]/.test(trimmed) || /url\s*\(|expression\s*\(|\/\*|\*\//i.test(trimmed)) {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
return trimmed;
|
|
25
|
-
};
|
|
26
|
-
/**
|
|
27
|
-
* Scheme allowlist for link hrefs. `javascript:`/`data:`/`vbscript:`
|
|
28
|
-
* payloads can arrive through native `insertLink`, pasted HTML, or a
|
|
29
|
-
* malicious collaborator — and Svelte renders `href` verbatim. The href is
|
|
30
|
-
* read as the browser's URL parser reads it: it strips C0 controls and
|
|
31
|
-
* spaces at both ends and tab/newline/CR anywhere (`\u0001javascript:` is
|
|
32
|
-
* `javascript:` to a browser), then resolves it against a base; only an
|
|
33
|
-
* allowed scheme, or a relative href that stays relative, passes.
|
|
34
|
-
*/
|
|
35
|
-
export const sanitizeLinkHref = (href) => {
|
|
36
|
-
// Marks arrive from untrusted sources too (synced peers, paste) —
|
|
37
|
-
// reject non-strings outright instead of throwing on `.replace`.
|
|
38
|
-
if (typeof href !== 'string')
|
|
39
|
-
return null;
|
|
40
|
-
// eslint-disable-next-line no-control-regex
|
|
41
|
-
const trimmed = href.replace(/[\t\n\r]/g, '').replace(/^[\u0000-\u0020]+|[\u0000-\u0020]+$/g, '');
|
|
42
|
-
if (!trimmed)
|
|
43
|
-
return null;
|
|
44
|
-
try {
|
|
45
|
-
// A relative href resolves to the base's `https:`: it names no scheme of its own.
|
|
46
|
-
return SAFE_LINK_PROTOCOLS.has(new URL(trimmed, 'https://relative.invalid/').protocol)
|
|
47
|
-
? trimmed
|
|
48
|
-
: null;
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
};
|
|
6
|
+
export { sanitizeCssColorValue, sanitizeLinkHref } from '../../utils/sanitize.js';
|
|
7
|
+
import { sanitizeCssColorValue, sanitizeLinkHref } from '../../utils/sanitize.js';
|
|
54
8
|
/**
|
|
55
9
|
* The URL a paste carries when its plain text is one link and nothing else
|
|
56
10
|
* (surrounding whitespace aside): `http:`/`https:` with a host, or
|
|
@@ -194,7 +148,7 @@ export const richTextOperations = (edytor) => ({
|
|
|
194
148
|
if (dispatcher.plan('insertDivider', {}, prepare, block).status !== 'applied')
|
|
195
149
|
return null;
|
|
196
150
|
const text = caret ? edytor.idToBlock.get(caret)?.firstText : startText;
|
|
197
|
-
dispatcher.
|
|
151
|
+
dispatcher.caretIn(text, caret ? 0 : yStart);
|
|
198
152
|
return block;
|
|
199
153
|
},
|
|
200
154
|
removeAllMarksAtRange: () => {
|
|
@@ -0,0 +1,6 @@
|
|
|
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
|
+
/* An empty body's hint: chrome, not text (`body.hint`); its look is a theme's. */
|
|
4
|
+
[data-edytor-empty-body] {
|
|
5
|
+
user-select: none;
|
|
6
|
+
}
|
|
@@ -19,6 +19,8 @@ export declare class SlashMenuController extends TextTriggerController {
|
|
|
19
19
|
/** The selection when a `+` opened the menu, given back when it closes unpicked. */
|
|
20
20
|
private held;
|
|
21
21
|
constructor(edytor: Edytor);
|
|
22
|
+
/** A `+`'s menu stands under the `+` (its block while the `+` is gone); a `/` menu at the caret. */
|
|
23
|
+
protected anchorRect(): DOMRect | undefined;
|
|
22
24
|
/** A `+`'s menu names its highlighted row from its own field, and tells the `+` it opened it. */
|
|
23
25
|
protected popupOf(active: string | undefined): Popup;
|
|
24
26
|
/**
|