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
package/dist/edytor.svelte.js
CHANGED
|
@@ -29,7 +29,7 @@ import Announcer from './components/Announcer.svelte';
|
|
|
29
29
|
import RemoteSelections from './collaboration/RemoteSelections.svelte';
|
|
30
30
|
import Ranges from './components/Ranges.svelte';
|
|
31
31
|
import { Handles } from './session/handles.js';
|
|
32
|
-
import { id } from './utils.js';
|
|
32
|
+
import { cleanups, id } from './utils.js';
|
|
33
33
|
import { Y } from './crdt/engine.js';
|
|
34
34
|
import { markName } from './crdt/text/marks.js';
|
|
35
35
|
import { callEach } from './crdt/protocols/observable.js';
|
|
@@ -43,6 +43,7 @@ import { Keymap } from './session/keymap.js';
|
|
|
43
43
|
import { TRANSACTION } from './constants.js';
|
|
44
44
|
import { Dispatcher } from './session/commands.js';
|
|
45
45
|
import { History } from './session/history.js';
|
|
46
|
+
import { DataWrites } from './session/props.js';
|
|
46
47
|
import { kindCatalogue, kindCommand, UNKNOWN_KIND } from './kinds.js';
|
|
47
48
|
import { clearDomSelection, getActiveElement, getDomSelection, getDomSelectionSnapshot } from './selection/domSelection.js';
|
|
48
49
|
/** A consumer that is set (a prop or a plugin hook left out is `undefined`). */
|
|
@@ -131,13 +132,18 @@ export class Edytor {
|
|
|
131
132
|
void this.#dataRevision;
|
|
132
133
|
return this.facade.docData();
|
|
133
134
|
};
|
|
135
|
+
/** @internal The view's property writes (`session/props`): the data proxies' one queue. */
|
|
136
|
+
dataWrites = new DataWrites(this);
|
|
137
|
+
#dataProxy;
|
|
134
138
|
/**
|
|
135
|
-
* The document's properties,
|
|
136
|
-
*
|
|
137
|
-
*
|
|
139
|
+
* The document's properties, as plain state: reactive at any depth, and
|
|
140
|
+
* writable (`edytor.data.title = 'Notes'`, `edytor.data.tags.push('x')`,
|
|
141
|
+
* `delete edytor.data.draft`). Each write is the `patchData` operation,
|
|
142
|
+
* dispatched; the writes of one task are one undo step. Type them by
|
|
143
|
+
* augmenting {@link EdytorDocData}.
|
|
138
144
|
*/
|
|
139
145
|
get data() {
|
|
140
|
-
return this.docData
|
|
146
|
+
return (this.#dataProxy ??= this.dataWrites.proxy(null, this.docData));
|
|
141
147
|
}
|
|
142
148
|
/** @internal The IME host pin (`surface/pin`): the composing cell's segment list and render, frozen. */
|
|
143
149
|
pin = new Pin();
|
|
@@ -149,6 +155,8 @@ export class Edytor {
|
|
|
149
155
|
announcer = new AnnouncerState(this);
|
|
150
156
|
/** The words the view was given (`<Edytor labels>`); its plugins read their section through `labelsOf`. */
|
|
151
157
|
labels = {};
|
|
158
|
+
/** Whether users may nest blocks (`allowNesting`, read once; the rule is `session/moves.ts` `nestsInto`). */
|
|
159
|
+
allowNesting = false;
|
|
152
160
|
off = [];
|
|
153
161
|
onChange;
|
|
154
162
|
onDocChange;
|
|
@@ -185,11 +193,10 @@ export class Edytor {
|
|
|
185
193
|
// CRDT (v14) — the document is the composition owner: it holds the ONE
|
|
186
194
|
// engine doc, the shared facade (only structural read/write surface),
|
|
187
195
|
// the default local history and the shared awareness. Injected
|
|
188
|
-
// documents are borrowed; a view with no `document` option
|
|
189
|
-
// owns one (built around `doc`/`awareness` when those are injected).
|
|
196
|
+
// documents are borrowed; a view with no `document` option owns a fresh one.
|
|
190
197
|
document;
|
|
191
|
-
/**
|
|
192
|
-
|
|
198
|
+
/** `true` when this view created its document — `destroy()` then releases it. */
|
|
199
|
+
#owned = false;
|
|
193
200
|
get doc() {
|
|
194
201
|
return this.document.doc;
|
|
195
202
|
}
|
|
@@ -289,29 +296,23 @@ export class Edytor {
|
|
|
289
296
|
this.dispatcher.record({ operation: command, status: this.history.replay(command) });
|
|
290
297
|
};
|
|
291
298
|
constructor(options) {
|
|
292
|
-
const { readonly, hotkeys, plugins, document,
|
|
299
|
+
const { readonly, hotkeys, plugins, document, actor, requireHydration, presence, sync, value, onSelectionChange, labels, onChange, onDocChange, allowNesting } = options;
|
|
293
300
|
for (const name of DOCUMENT_WRITES)
|
|
294
301
|
Object.defineProperty(this, name, {
|
|
295
302
|
value: (...args) => this.dispatcher.operate(name, args)
|
|
296
303
|
});
|
|
297
304
|
if (labels)
|
|
298
305
|
this.labels = labels;
|
|
306
|
+
this.allowNesting = allowNesting === true;
|
|
299
307
|
if (document !== undefined) {
|
|
300
|
-
if (DEV &&
|
|
301
|
-
(
|
|
302
|
-
awareness !== undefined ||
|
|
303
|
-
actor !== undefined ||
|
|
304
|
-
requireHydration !== undefined)) {
|
|
305
|
-
throw new Error('EdytorOptions: `document` cannot be combined with `doc`/`awareness`/`actor`/`requireHydration` — ' +
|
|
306
|
-
'the document owns them (compose them via attachDocument first).');
|
|
308
|
+
if (DEV && (actor !== undefined || requireHydration !== undefined)) {
|
|
309
|
+
throw new Error('EdytorOptions: `document` cannot be combined with `actor`/`requireHydration`: set them on the document.');
|
|
307
310
|
}
|
|
308
311
|
this.document = document;
|
|
309
312
|
}
|
|
310
313
|
else {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
this.document = attachDocument(doc ?? new Y.Doc(), { awareness, actor, requireHydration });
|
|
314
|
-
this.ownsDocument = true;
|
|
314
|
+
this.document = attachDocument(new Y.Doc(), { actor, requireHydration });
|
|
315
|
+
this.#owned = true;
|
|
315
316
|
}
|
|
316
317
|
this.readonly = readonly || false;
|
|
317
318
|
this.onChange = onChange;
|
|
@@ -379,7 +380,7 @@ export class Edytor {
|
|
|
379
380
|
// decision; every other view binds on the one readiness event
|
|
380
381
|
// (`<Edytor>` decides an injected document at mount, once the
|
|
381
382
|
// providers of its sibling views attached).
|
|
382
|
-
if (this
|
|
383
|
+
if (this.#owned && !sync) {
|
|
383
384
|
this.sync(value || { children: [] });
|
|
384
385
|
}
|
|
385
386
|
else {
|
|
@@ -400,7 +401,7 @@ export class Edytor {
|
|
|
400
401
|
// document-lifetime additive declarations, and an injected
|
|
401
402
|
// document's are left for the views that legitimately share it.
|
|
402
403
|
this.document.untrackOrigin(this.transaction);
|
|
403
|
-
if (this
|
|
404
|
+
if (this.#owned) {
|
|
404
405
|
this.document.destroy();
|
|
405
406
|
}
|
|
406
407
|
throw error;
|
|
@@ -602,9 +603,7 @@ export class Edytor {
|
|
|
602
603
|
// A failed async handler (a command's hook or write) surfaces
|
|
603
604
|
// through the platform's error reporting; the DOM ignores
|
|
604
605
|
// the listener's promise.
|
|
605
|
-
void Promise.resolve(result)
|
|
606
|
-
.finally(clear)
|
|
607
|
-
.catch((error) => typeof reportError === 'function' ? reportError(error) : console.error(error));
|
|
606
|
+
void Promise.resolve(result).finally(clear).catch(reportError);
|
|
608
607
|
}
|
|
609
608
|
else {
|
|
610
609
|
clear();
|
|
@@ -821,11 +820,7 @@ export class Edytor {
|
|
|
821
820
|
props: { announcer: this.announcer }
|
|
822
821
|
});
|
|
823
822
|
this.off.push(() => unmount(presence), () => unmount(ranges), () => unmount(live), detachOverlay);
|
|
824
|
-
this.
|
|
825
|
-
const action = plugin.onEdytorAttached?.({ node });
|
|
826
|
-
if (typeof action === 'function')
|
|
827
|
-
this.off.push(action);
|
|
828
|
-
});
|
|
823
|
+
this.off.push(...cleanups(this.plugins, (plugin) => plugin.onEdytorAttached?.({ node })));
|
|
829
824
|
return {
|
|
830
825
|
destroy: () => {
|
|
831
826
|
if (selectionIsInside(node))
|
|
@@ -855,7 +850,7 @@ export class Edytor {
|
|
|
855
850
|
* discarded.
|
|
856
851
|
*
|
|
857
852
|
* Releases every listener the editor holds on potentially-SHARED objects
|
|
858
|
-
* — an injected
|
|
853
|
+
* — an injected document outlives a single editor, so an
|
|
859
854
|
* undestroyed mount leaks a facade `update` listener and the undo
|
|
860
855
|
* manager's doc observers forever.
|
|
861
856
|
*
|
|
@@ -899,7 +894,7 @@ export class Edytor {
|
|
|
899
894
|
// awareness, doc) belong to the document — released exactly once,
|
|
900
895
|
// and only when THIS view created it. A borrowed document keeps
|
|
901
896
|
// serving its sibling views and headless consumers.
|
|
902
|
-
if (this
|
|
897
|
+
if (this.#owned) {
|
|
903
898
|
this.document.destroy();
|
|
904
899
|
}
|
|
905
900
|
};
|
|
@@ -9,6 +9,6 @@ import { type Attempt } from '../session/attempt.js';
|
|
|
9
9
|
*/
|
|
10
10
|
export declare const insertLineBreak: (edytor: Edytor, snapshot: Attempt, caret?: "after" | "before") => void;
|
|
11
11
|
/** The model command for a `beforeinput`, run as one user command (undo policy, prevention scope). */
|
|
12
|
-
export declare const runBeforeInputCommand: (edytor: Edytor, snapshot: Attempt) => boolean | void | Promise<undefined> |
|
|
12
|
+
export declare const runBeforeInputCommand: (edytor: Edytor, snapshot: Attempt) => boolean | void | Text | Promise<undefined> | null | undefined;
|
|
13
13
|
/** An editing intent at the current selection (a key binding's command): no event to fabricate. */
|
|
14
|
-
export declare const runIntent: (edytor: Edytor, inputType: string) => boolean | void | Promise<undefined> |
|
|
14
|
+
export declare const runIntent: (edytor: Edytor, inputType: string) => boolean | void | Text | Promise<undefined> | null | undefined;
|
|
@@ -97,10 +97,10 @@ export const insertLineBreak = (edytor, snapshot, caret = 'after') => {
|
|
|
97
97
|
: block.parent?.children[block.index + 1]?.firstText;
|
|
98
98
|
caretAt(edytor, line, before ? (line?.length ?? 0) : 0);
|
|
99
99
|
};
|
|
100
|
-
const runPastePlugins = (edytor, dataTransfer) => {
|
|
100
|
+
const runPastePlugins = (edytor, dataTransfer, at) => {
|
|
101
101
|
const event = { clipboardData: dataTransfer };
|
|
102
102
|
for (const plugin of edytor.plugins)
|
|
103
|
-
vetoable((prevent) => plugin.onPaste?.({ prevent, e: event }));
|
|
103
|
+
vetoable((prevent) => plugin.onPaste?.({ prevent, e: event, ...(at && { at }) }));
|
|
104
104
|
};
|
|
105
105
|
/** The drop point the browser reported: its text and offset (`null`: not in a text). */
|
|
106
106
|
const dropPoint = (edytor, { declared: range }) => {
|
|
@@ -150,9 +150,13 @@ const insertPayload = async (edytor, snapshot) => {
|
|
|
150
150
|
// A paste occurrence is the plain text its handler left to it (Shift-paste too).
|
|
151
151
|
const fragment = !paste && readEdytorClipboardFragment(dataTransfer);
|
|
152
152
|
if (!paste && !fragment && dataTransfer) {
|
|
153
|
-
// A plugin places the files at the
|
|
154
|
-
|
|
153
|
+
// A plugin places the files at the drop indicator's placement, else at the
|
|
154
|
+
// selection: the drop point, never over selected blocks.
|
|
155
|
+
if (dataTransfer.files?.length) {
|
|
156
|
+
if (snapshot.placement)
|
|
157
|
+
return void runPastePlugins(edytor, dataTransfer, snapshot.placement);
|
|
155
158
|
return void (land() !== null && runPastePlugins(edytor, dataTransfer));
|
|
159
|
+
}
|
|
156
160
|
if (dataTransfer.getData('text/html'))
|
|
157
161
|
runPastePlugins(edytor, dataTransfer);
|
|
158
162
|
}
|
|
@@ -7,7 +7,7 @@ import { isAndroidChromeBrowser } from './events.utils.js';
|
|
|
7
7
|
import { isNativeInteractiveControl, isNativeInteractiveEvent, ownsEvent } from './nativeInteractiveControl.js';
|
|
8
8
|
import { runBeforeInputCommand } from './beforeInputCommands.js';
|
|
9
9
|
import { deleteInText } from './beforeInputDeleteCommands.js';
|
|
10
|
-
import { readDomText } from '
|
|
10
|
+
import { readDomText } from '../surface/observer.svelte.js';
|
|
11
11
|
/** The Android no-op-Backspace and model-owned drift deadlines (named, counted browser rules). */
|
|
12
12
|
const NATIVE_INPUT_REPAIR_WINDOW_MS = 150;
|
|
13
13
|
const dirOf = (inputType) => INTENTS[inputType]?.dir;
|
package/dist/events/onDrop.js
CHANGED
|
@@ -3,15 +3,20 @@ import { runOccurrence } from './onBeforeInput.js';
|
|
|
3
3
|
import { createEdytorClipboardFragment, writeEdytorClipboardData } from '../clipboard/clipboard.js';
|
|
4
4
|
import { endTextDrag, startTextDrag, textDragOf } from '../clipboard/moveText.js';
|
|
5
5
|
import { caretAtPoint, getDomSelection } from '../selection/domSelection.js';
|
|
6
|
+
import { viewUploads } from '../session/uploads.js';
|
|
6
7
|
/**
|
|
7
8
|
* Payloads the editor can consume on drop. Preventing `dragover` is what
|
|
8
9
|
* enables a drop, so it is prevented only for these types — internal and
|
|
9
10
|
* unsupported drags keep the browser's not-allowed affordance and can never
|
|
10
11
|
* produce a native DOM mutation.
|
|
11
12
|
*
|
|
12
|
-
* `Files` is intentionally accepted
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* `Files` is intentionally accepted (so the browser never opens a dropped
|
|
14
|
+
* file in place of the page): with the view's uploads (a media plugin given
|
|
15
|
+
* an `upload`), file drops route through the plugin `onPaste` hook with the
|
|
16
|
+
* placement the uploads' drop target resolved (`ViewUploads.drop`, Pragmatic's
|
|
17
|
+
* external adapter: a block per file the kinds take there; unclaimed files
|
|
18
|
+
* insert nothing); without them a drop carrying
|
|
19
|
+
* files is ignored, its effect `none`.
|
|
15
20
|
*/
|
|
16
21
|
const ACCEPTED_FOREIGN_DROP_TYPES = ['Files', 'text/html', 'text/plain', 'text/uri-list'];
|
|
17
22
|
/**
|
|
@@ -121,8 +126,8 @@ const isAcceptedForeignDrop = (root, dataTransfer) => {
|
|
|
121
126
|
// Preventing the drop suppresses the browser's own `insertFromDrop`
|
|
122
127
|
// beforeinput, so the accepted payload is the occurrence itself — the same
|
|
123
128
|
// routing as `insertFromPaste`, with the drop point as its declared target
|
|
124
|
-
// range (`caretAtPoint`).
|
|
125
|
-
const insertFromDrop = (edytor, root, event) => {
|
|
129
|
+
// range (`caretAtPoint`); files carry the indicator's placement (`at`).
|
|
130
|
+
const insertFromDrop = (edytor, root, event, placement = null) => {
|
|
126
131
|
const at = caretAtPoint(root.ownerDocument, event.clientX, event.clientY);
|
|
127
132
|
const point = at &&
|
|
128
133
|
new StaticRange({
|
|
@@ -135,6 +140,7 @@ const insertFromDrop = (edytor, root, event) => {
|
|
|
135
140
|
inputType: 'insertFromDrop',
|
|
136
141
|
dataTransfer: event.dataTransfer,
|
|
137
142
|
declared: point && root.contains(point.startContainer) ? point : null,
|
|
143
|
+
placement,
|
|
138
144
|
cancelable: true
|
|
139
145
|
});
|
|
140
146
|
};
|
|
@@ -176,22 +182,31 @@ export const preventUnsupportedDrop = (event, edytor) => {
|
|
|
176
182
|
drag.dropping = true;
|
|
177
183
|
return insertFromDrop(edytor, root, event);
|
|
178
184
|
}
|
|
185
|
+
const files = typesOf(event.dataTransfer).includes('Files');
|
|
186
|
+
// The view's uploads take dropped files (a media plugin given an `upload`); none: ignored.
|
|
187
|
+
const drop = files ? viewUploads(edytor)?.drop : undefined;
|
|
179
188
|
if (event.type === 'dragover') {
|
|
180
189
|
if (isAcceptedForeignDrop(root, event.dataTransfer)) {
|
|
181
190
|
event.preventDefault();
|
|
182
191
|
event.stopPropagation();
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
192
|
+
// Where files land shows from the uploads' drop target (Pragmatic's
|
|
193
|
+
// external adapter); a read-only view or one without uploads takes no
|
|
194
|
+
// file drop, and the browser must not open the file.
|
|
195
|
+
if (event.dataTransfer)
|
|
196
|
+
event.dataTransfer.dropEffect = files && (edytor.readonly || !drop) ? 'none' : 'copy';
|
|
186
197
|
}
|
|
187
198
|
return;
|
|
188
199
|
}
|
|
189
200
|
const accepted = isAcceptedForeignDrop(root, event.dataTransfer);
|
|
201
|
+
// Where the files land: the placement the uploads' drop target resolved at
|
|
202
|
+
// the drop point (its window listener captures, so it ran first).
|
|
203
|
+
const placed = drop?.take() ?? null;
|
|
204
|
+
const at = accepted && !ownsEvent(root, event) ? placed : null;
|
|
190
205
|
if (accepted && isControlDrop(root, event))
|
|
191
206
|
return;
|
|
192
207
|
event.preventDefault();
|
|
193
208
|
event.stopPropagation();
|
|
194
|
-
if (!accepted) {
|
|
209
|
+
if (!accepted || (files && (edytor.readonly || !drop))) {
|
|
195
210
|
return;
|
|
196
211
|
}
|
|
197
212
|
if (ownsEvent(root, event) || isNativeInteractiveControl(event.target)) {
|
|
@@ -201,5 +216,5 @@ export const preventUnsupportedDrop = (event, edytor) => {
|
|
|
201
216
|
}
|
|
202
217
|
// Mid-composition drops are swallowed: the preview owns the write path.
|
|
203
218
|
if (!edytor.isComposing)
|
|
204
|
-
return insertFromDrop(edytor, root, event);
|
|
219
|
+
return insertFromDrop(edytor, root, event, at);
|
|
205
220
|
};
|
package/dist/events/onInput.d.ts
CHANGED
|
@@ -1,16 +1,4 @@
|
|
|
1
1
|
import type { Edytor } from '../edytor.svelte.js';
|
|
2
|
-
import type { Text } from '../text/text.svelte.js';
|
|
3
|
-
/**
|
|
4
|
-
* What the browser shows for `text`: its element's text nodes in order (WebKit's
|
|
5
|
-
* converted-space wrappers read as plain spaces), the empty filler and the
|
|
6
|
-
* trailing-newline marker stripped.
|
|
7
|
-
*/
|
|
8
|
-
export declare const readDomText: (text: Text) => string;
|
|
9
|
-
export declare const getCollapsedDomTextSelection: (edytor: Edytor) => {
|
|
10
|
-
text: Text;
|
|
11
|
-
offset: number;
|
|
12
|
-
} | null;
|
|
13
|
-
export declare const handleNativeLineBreakTextValue: (edytor: Edytor, text: Text, value: string) => Promise<boolean>;
|
|
14
2
|
/**
|
|
15
3
|
* An `input` event. Browser-made text is never adopted here: the mutation
|
|
16
4
|
* queue is the only adopter (a native line break there runs as its
|
package/dist/events/onInput.js
CHANGED
|
@@ -1,76 +1,10 @@
|
|
|
1
|
-
import { diffText } from '../utils/diffText.js';
|
|
2
|
-
import { getDomSelectionSnapshot } from '../selection/domSelection.js';
|
|
3
|
-
import { domOffsetIn } from '../selection/selection.utils.js';
|
|
4
1
|
import { isControlEvent } from './nativeInteractiveControl.js';
|
|
5
|
-
import { runOccurrence } from './onBeforeInput.js';
|
|
6
2
|
import { attemptOf, kindOf } from '../session/attempt.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* What the browser shows for `text`: its element's text nodes in order (WebKit's
|
|
10
|
-
* converted-space wrappers read as plain spaces), the empty filler and the
|
|
11
|
-
* trailing-newline marker stripped.
|
|
12
|
-
*/
|
|
13
|
-
export const readDomText = (text) => {
|
|
14
|
-
let value = '';
|
|
15
|
-
const node = text.node;
|
|
16
|
-
if (node) {
|
|
17
|
-
const walker = (node.ownerDocument ?? document).createTreeWalker(node, NodeFilter.SHOW_TEXT);
|
|
18
|
-
while (walker.nextNode()) {
|
|
19
|
-
const leaf = walker.currentNode;
|
|
20
|
-
value += leaf.parentElement?.closest('span.Apple-converted-space')
|
|
21
|
-
? leaf.data.replaceAll('\u00A0', ' ')
|
|
22
|
-
: leaf.data;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
// An empty text's filler shares its node with what the browser typed, on either side.
|
|
26
|
-
if (text.isEmpty)
|
|
27
|
-
return value.replaceAll(ZERO_WIDTH_SPACE, '');
|
|
28
|
-
if (text.endsWithNewline && value.endsWith(ZERO_WIDTH_SPACE)) {
|
|
29
|
-
value = value.slice(0, -ZERO_WIDTH_SPACE.length);
|
|
30
|
-
}
|
|
31
|
-
return value;
|
|
32
|
-
};
|
|
33
|
-
export const getCollapsedDomTextSelection = (edytor) => {
|
|
34
|
-
const selection = getDomSelectionSnapshot(edytor.node);
|
|
35
|
-
if (!selection?.anchorNode || !selection.isCollapsed) {
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
if (!edytor.node?.contains(selection.anchorNode)) {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
const text = edytor.selection.getTextOfNode(selection.anchorNode);
|
|
42
|
-
if (!text?.node) {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
// No model rule here: the DOM caret is read BEFORE the reconcile writes
|
|
46
|
-
// the model, so a native insertion legitimately sits past `text.length`
|
|
47
|
-
// (`selectText` clamps the final write).
|
|
48
|
-
return { text, offset: domOffsetIn(text, selection.anchorNode, selection.anchorOffset) };
|
|
49
|
-
};
|
|
3
|
+
import { getCollapsedDomTextSelection } from '../surface/observer.svelte.js';
|
|
50
4
|
const getInputType = (event) => {
|
|
51
5
|
const inputType = event.inputType;
|
|
52
6
|
return typeof inputType === 'string' ? inputType : '';
|
|
53
7
|
};
|
|
54
|
-
const getNativeLineBreakInsertionIndexFromValue = (text, value) => {
|
|
55
|
-
const change = diffText(text.stringContent, value);
|
|
56
|
-
return change && !change.remove && /^[\r\n]+$/.test(change.insert) ? change.at : null;
|
|
57
|
-
};
|
|
58
|
-
export const handleNativeLineBreakTextValue = async (edytor, text, value) => {
|
|
59
|
-
if (!text.isInDocument)
|
|
60
|
-
return false;
|
|
61
|
-
const insertionIndex = getNativeLineBreakInsertionIndexFromValue(text, value);
|
|
62
|
-
if (insertionIndex === null) {
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
// The native line break is the key's occurrence: its intent, run by the model.
|
|
66
|
-
const inputType = edytor.attempts.confirm()?.inputType === 'insertLineBreak' || value.includes('\n')
|
|
67
|
-
? 'insertLineBreak'
|
|
68
|
-
: 'insertParagraph';
|
|
69
|
-
text.refreshFromModel();
|
|
70
|
-
edytor.selection.selectText(text, insertionIndex);
|
|
71
|
-
await runOccurrence(edytor, { inputType, cancelable: false });
|
|
72
|
-
return true;
|
|
73
|
-
};
|
|
74
8
|
/**
|
|
75
9
|
* An `input` no attempt expected (its engine sent no `beforeinput`): the
|
|
76
10
|
* browser changed the text under its caret — a browser-owned attempt of its own.
|
package/dist/events/onPaste.d.ts
CHANGED
|
@@ -6,7 +6,8 @@ import type { Edytor } from '../edytor.svelte.js';
|
|
|
6
6
|
* the document AND cancel its paste) or under a live composition (its
|
|
7
7
|
* preview owns the write path: the browser pastes, the observer reconciles
|
|
8
8
|
* it after the end, as PM input.ts:656-660). Else, unless Shift asks for
|
|
9
|
-
* plain text: an Edytor fragment; what an extension's `onPaste` claims
|
|
9
|
+
* plain text: an Edytor fragment; what an extension's `onPaste` claims (the
|
|
10
|
+
* `pasteFirst` ones first, then list order); a
|
|
10
11
|
* file nothing claimed inserts nothing (the Files → image seam), never its
|
|
11
12
|
* name; external HTML (HTML that carries nothing falls through); markdown
|
|
12
13
|
* text as its blocks (`paste.markdown`), except in a code block's lines.
|
package/dist/events/onPaste.js
CHANGED
|
@@ -54,7 +54,8 @@ const syncCollapsedDomCaretForPaste = (edytor) => {
|
|
|
54
54
|
* the document AND cancel its paste) or under a live composition (its
|
|
55
55
|
* preview owns the write path: the browser pastes, the observer reconciles
|
|
56
56
|
* it after the end, as PM input.ts:656-660). Else, unless Shift asks for
|
|
57
|
-
* plain text: an Edytor fragment; what an extension's `onPaste` claims
|
|
57
|
+
* plain text: an Edytor fragment; what an extension's `onPaste` claims (the
|
|
58
|
+
* `pasteFirst` ones first, then list order); a
|
|
58
59
|
* file nothing claimed inserts nothing (the Files → image seam), never its
|
|
59
60
|
* name; external HTML (HTML that carries nothing falls through); markdown
|
|
60
61
|
* text as its blocks (`paste.markdown`), except in a code block's lines.
|
|
@@ -70,8 +71,12 @@ export async function onPaste(e) {
|
|
|
70
71
|
if (!(shiftPasteModifierHeld && plain)) {
|
|
71
72
|
const fragment = readEdytorClipboardFragment(data);
|
|
72
73
|
if (!fragment) {
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
// The `pasteFirst` hooks (a pasted reference: page link's `fromPaste`), then the
|
|
75
|
+
// others in list order; the first that prevents claims the paste.
|
|
76
|
+
const claimed = (first) => (plugin) => !!plugin.pasteFirst === first && vetoable((prevent) => plugin.onPaste?.({ prevent, e }));
|
|
77
|
+
const stop = () => e.preventDefault();
|
|
78
|
+
if (this.dispatcher.intercept(claimed(true), stop) ||
|
|
79
|
+
this.dispatcher.intercept(claimed(false), stop))
|
|
75
80
|
return;
|
|
76
81
|
if (data?.files?.length)
|
|
77
82
|
return e.preventDefault();
|