@threadlabs/looma 0.28.0 → 0.28.2
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/editor/code-highlighting.d.ts +25 -0
- package/editor/content.d.ts +10 -0
- package/{vue/editor → editor}/image-delivery.d.ts +1 -1
- package/editor/selection-geometry.d.ts +8 -0
- package/editor/session.d.ts +39 -0
- package/editor/shortcuts.d.ts +6 -0
- package/package.json +1 -1
- package/vue/editor/LoomaEditor.d.ts +3 -3
- package/vue/editor/compact/index.d.ts +2 -2
- package/vue/editor/compact/index.js +2 -2
- package/vue/editor/index.d.ts +1 -1
- package/vue/editor/index.js +288 -208
- /package/{vue/editor → editor}/code-language-catalog.d.ts +0 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Editor } from "@tiptap/core";
|
|
2
|
+
import { createLowlight } from "lowlight";
|
|
3
|
+
import type { LoomaCodeLanguages } from "./extensions/preset.js";
|
|
4
|
+
/** Code-block highlighting for one editor: its lowlight registry, picker languages, and grammar loading. */
|
|
5
|
+
export interface LoomaCodeHighlighting {
|
|
6
|
+
/** The registry the code-block extension highlights with. */
|
|
7
|
+
readonly lowlight: ReturnType<typeof createLowlight>;
|
|
8
|
+
/** Languages the picker offers, in display order. */
|
|
9
|
+
readonly languageNames: readonly string[];
|
|
10
|
+
/**
|
|
11
|
+
* Loads the grammar a code block needs, then re-highlights the document.
|
|
12
|
+
*
|
|
13
|
+
* @contract Does nothing when the host supplied its own languages; each grammar loads once.
|
|
14
|
+
* @failure A failed import is forgotten so a later call retries it.
|
|
15
|
+
*/
|
|
16
|
+
loadForCodeBlock(editor: Editor, language: unknown, value: string): void;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Highlighting from the host's `codeLanguages`, or, when that is null, from the built-in catalog,
|
|
20
|
+
* whose grammars load on demand and whose automatic detection uses a common subset.
|
|
21
|
+
*
|
|
22
|
+
* @lifecycle `onGrammarsLoaded` runs after each lazy load, before the document is re-highlighted,
|
|
23
|
+
* because the lowlight registry is mutable rather than reactive.
|
|
24
|
+
*/
|
|
25
|
+
export declare function createCodeHighlighting(codeLanguages: LoomaCodeLanguages | null, onGrammarsLoaded: () => void): LoomaCodeHighlighting;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { JSONContent } from "@tiptap/core";
|
|
2
|
+
/** Whether a controlled document already matches the editor, so applying it would only reset the selection. */
|
|
3
|
+
export declare function sameDocument(left: JSONContent, right: JSONContent): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* A link destination the editor may store: a site-relative path, fragment, or query, or an
|
|
6
|
+
* absolute http, https, mailto, or tel URL.
|
|
7
|
+
*
|
|
8
|
+
* @failure Returns null for empty input, control characters, other schemes, and unparseable URLs.
|
|
9
|
+
*/
|
|
10
|
+
export declare function validLinkHref(value: string): string | null;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Extension, type Editor, type JSONContent } from "@tiptap/core";
|
|
2
|
-
export { normalizeImageDimension } from "
|
|
2
|
+
export { normalizeImageDimension } from "./extensions/image.js";
|
|
3
3
|
/** What caused an overlay to open or close. */
|
|
4
4
|
type OverlayTrigger = "keyboard" | "pointer" | "programmatic";
|
|
5
5
|
/**
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Editor } from "@tiptap/core";
|
|
2
|
+
import type { SlashMenuAnchorRect } from "./slash-menu.js";
|
|
3
|
+
/** A plain copy of a client rectangle, the anchor shape the slash and mention menus accept as a prop. */
|
|
4
|
+
export declare function menuAnchorRect(rect: DOMRect | null): SlashMenuAnchorRect | null;
|
|
5
|
+
/** The rendered table that holds the selection's start. */
|
|
6
|
+
export declare function selectedTableElement(editor: Editor): HTMLTableElement | null;
|
|
7
|
+
/** The rendered cell that holds the selection's start. */
|
|
8
|
+
export declare function selectedTableCellElement(editor: Editor): HTMLTableCellElement | null;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Editor, type EditorOptions, type JSONContent } from "@tiptap/core";
|
|
2
|
+
import { type Selection, type SelectionBookmark } from "@tiptap/pm/state";
|
|
3
|
+
/** A selection remembered so a controlled document replacement can put it back. */
|
|
4
|
+
export interface LoomaSelectionSnapshot {
|
|
5
|
+
bookmark: SelectionBookmark;
|
|
6
|
+
from: number;
|
|
7
|
+
to: number;
|
|
8
|
+
text: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function selectionSnapshot(selection: Selection): LoomaSelectionSnapshot;
|
|
11
|
+
/**
|
|
12
|
+
* One editor's Tiptap instance, independent of any component framework.
|
|
13
|
+
*
|
|
14
|
+
* @ownership The session owns the `Editor` from creation until `destroy()`. A host renders an
|
|
15
|
+
* element for the document and hands it to `attach()`; it never creates or destroys the editor.
|
|
16
|
+
* @lifecycle The editor, its document, and its undo history survive `attach()` to a new element,
|
|
17
|
+
* so a host can remount its view without losing state.
|
|
18
|
+
*/
|
|
19
|
+
export interface LoomaEditorSession<E extends Editor = Editor> {
|
|
20
|
+
readonly editor: E;
|
|
21
|
+
/** Moves the editor's view into `element` (idempotent). */
|
|
22
|
+
attach(element: HTMLElement): void;
|
|
23
|
+
/**
|
|
24
|
+
* Applies a controlled document.
|
|
25
|
+
*
|
|
26
|
+
* @contract Returns false, and leaves the editor untouched, when the document already matches.
|
|
27
|
+
* A focused editor keeps `selection` (or its current selection) in the same transaction.
|
|
28
|
+
* @failure A selection the new document cannot hold becomes the closest valid text range.
|
|
29
|
+
*/
|
|
30
|
+
setContent(content: JSONContent, selection?: LoomaSelectionSnapshot | null): boolean;
|
|
31
|
+
/** Calls `listener` after every transaction; returns the unsubscribe function. */
|
|
32
|
+
subscribe(listener: () => void): () => void;
|
|
33
|
+
destroy(): void;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Creates the session's editor from `options`. A framework layer may pass its own `Editor`
|
|
37
|
+
* subclass, such as `@tiptap/vue-3`'s reactive one, so the editor it exposes keeps that behavior.
|
|
38
|
+
*/
|
|
39
|
+
export declare function createEditorSession<E extends Editor = Editor>(options: Partial<EditorOptions>, EditorClass?: new (options: Partial<EditorOptions>) => E): LoomaEditorSession<E>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shows a key binding in ProseMirror notation ("Mod-Shift-8") the way the reader's platform writes
|
|
3
|
+
* it: modifier symbols in Apple's order on Apple devices (⇧⌘8), and Ctrl+Shift+8 elsewhere.
|
|
4
|
+
*/
|
|
5
|
+
export declare function formatEditorShortcut(keys: string, apple: boolean): string;
|
|
6
|
+
export declare const isApplePlatform: () => boolean;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@ import "./looma-editor.css";
|
|
|
3
3
|
import { type AnyExtension, type Editor, type JSONContent } from "@tiptap/core";
|
|
4
4
|
import { type PropType, type VNode } from "vue";
|
|
5
5
|
import { type LoomaSlashCommand, type LoomaMentionItem, type LoomaMentionProvider, type LoomaCodeLanguages } from "@threadlabs/looma/editor";
|
|
6
|
-
import { type LoomaImageActivationDetail, type LoomaImageAttributeResolver, type LoomaImageDescriptor, type LoomaImageRenditionErrorDetail } from "
|
|
6
|
+
import { type LoomaImageActivationDetail, type LoomaImageAttributeResolver, type LoomaImageDescriptor, type LoomaImageRenditionErrorDetail } from "../../editor/image-delivery.js";
|
|
7
7
|
/**
|
|
8
8
|
* Host upload result normalized into the editor's durable image descriptor.
|
|
9
9
|
* `url` becomes the stored `src`; optional dimensions preserve layout before
|
|
@@ -368,10 +368,10 @@ export declare const LoomaEditor: import("vue").DefineComponent<import("vue").Ex
|
|
|
368
368
|
onImageActivate?: ((_detail: LoomaImageActivationDetail) => any) | undefined;
|
|
369
369
|
onImageRenditionError?: ((_detail: LoomaImageRenditionErrorDetail) => any) | undefined;
|
|
370
370
|
}>, {
|
|
371
|
-
label: string;
|
|
372
|
-
placeholder: string;
|
|
373
371
|
extensions: AnyExtension[];
|
|
374
372
|
editable: boolean;
|
|
373
|
+
label: string;
|
|
374
|
+
placeholder: string;
|
|
375
375
|
linkBaseUrl: string;
|
|
376
376
|
disableHighlight: boolean;
|
|
377
377
|
codeLanguages: Readonly<Record<string, import("highlight.js").LanguageFn>> | null;
|
|
@@ -62,10 +62,10 @@ export declare const LoomaCompactEditor: import("vue").DefineComponent<import("v
|
|
|
62
62
|
"onUpdate:modelValue"?: ((_value: JSONContent) => any) | undefined;
|
|
63
63
|
onReady?: ((_editor: Editor) => any) | undefined;
|
|
64
64
|
}>, {
|
|
65
|
-
label: string;
|
|
66
|
-
id: string;
|
|
67
65
|
extensions: AnyExtension[];
|
|
68
66
|
editable: boolean;
|
|
67
|
+
label: string;
|
|
68
|
+
id: string;
|
|
69
69
|
modelValue: JSONContent;
|
|
70
70
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
71
71
|
/** Renders basic compact JSON as escaped Vue nodes, including during SSR. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineComponent, h, onBeforeUnmount, onMounted, shallowRef, watch } from "vue";
|
|
2
2
|
import { Extension, Mark, Node, markInputRule, markPasteRule, mergeAttributes } from "@tiptap/core";
|
|
3
|
-
import { Editor, EditorContent } from "@tiptap/vue-3";
|
|
3
|
+
import { Editor as Editor$1, EditorContent } from "@tiptap/vue-3";
|
|
4
4
|
import { closeHistory, history, redo, undo } from "@tiptap/pm/history";
|
|
5
5
|
import { Plugin } from "@tiptap/pm/state";
|
|
6
6
|
//#region ../../node_modules/.pnpm/@tiptap+extension-document@2.27.2_@tiptap+core@2.27.2_@tiptap+pm@2.27.2_/node_modules/@tiptap/extension-document/dist/index.js
|
|
@@ -498,7 +498,7 @@ var LoomaCompactEditor = defineComponent({
|
|
|
498
498
|
setup(props, { attrs, emit, expose }) {
|
|
499
499
|
const editor = shallowRef();
|
|
500
500
|
onMounted(() => {
|
|
501
|
-
editor.value = new Editor({
|
|
501
|
+
editor.value = new Editor$1({
|
|
502
502
|
extensions: [...getCompactEditorExtensions(), ...props.extensions],
|
|
503
503
|
content: props.modelValue,
|
|
504
504
|
editable: props.editable,
|
package/vue/editor/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export * from "@threadlabs/looma/editor";
|
|
2
2
|
export * from "./primitives.js";
|
|
3
3
|
export { LoomaEditor, type LoomaEditorToolbarMode, type LoomaEditorSelectionAction, type LoomaEditorSelectionActionDetail, type LoomaEditorRangeAction, type LoomaEditorRangeActionDetail, type LoomaEditorSlashCommands, type LoomaLinkTarget, type LoomaLinkSearch, type LoomaLinkResolve, type LoomaImageUploader, type LoomaImageUploadResult, } from "./LoomaEditor.js";
|
|
4
|
-
export type { LoomaImageActivationDetail, LoomaImageActivationTrigger, LoomaImageAttributeResolver, LoomaImageDescriptor, LoomaImageRenditionErrorDetail, LoomaImageRenditionAttributes, } from "
|
|
4
|
+
export type { LoomaImageActivationDetail, LoomaImageActivationTrigger, LoomaImageAttributeResolver, LoomaImageDescriptor, LoomaImageRenditionErrorDetail, LoomaImageRenditionAttributes, } from "../../editor/image-delivery.js";
|
package/vue/editor/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { computed, createApp, defineComponent, h, nextTick, onBeforeUnmount, onMounted, reactive, ref, shallowRef, useId, watch } from "vue";
|
|
2
2
|
import { DEFAULT_MENTION_RESULT_LIMIT, LOOMA_CHIP_COLORS, LOOMA_ICONS, createLoomaMentionExtension, createLoomaSlashCommandExtension, getActiveTableUiState, getDefaultEditorExtensions, getDefaultSlashCommands, getTableOfContentsEntries, getVisualViewportRect, handleTableAction, handleTableOverlayAction, measureTableOverlayGeometry, normalizeActiveTableColumnWidths, normalizeLoomaChipColor, normalizeTableOfContentsConfig, resolveTableCellAt, siteRelativeHref } from "@threadlabs/looma/editor";
|
|
3
3
|
import { Button, Callout, Card, Checkbox, Cluster, Combobox, Disclosure, EditorInsertTableGrid, EditorMentionMenu, EditorMentionMenuItem, EditorSlashMenu, EditorSlashMenuGroup, EditorSlashMenuItem, EditorSurface, EditorTableContextMenu, EditorTableOverlay, EditorTableToolbar, EditorToolbar, EditorToolbar as EditorToolbar$1, FormField, Icon, IconButton, Image, Input, InputGroup, List, Menu, MenuItem, Popover, ScrollArea, SearchResultRow, Separator, Stack, Text, Tooltip } from "@threadlabs/looma/vue";
|
|
4
|
-
import { Extension, Node as Node$1, getText, getTextSerializersFromSchema, mergeAttributes, nodeInputRule, posToDOMRect } from "@tiptap/core";
|
|
5
|
-
import { BubbleMenu,
|
|
4
|
+
import { Editor, Extension, Node as Node$1, getText, getTextSerializersFromSchema, mergeAttributes, nodeInputRule, posToDOMRect } from "@tiptap/core";
|
|
5
|
+
import { BubbleMenu, Editor as Editor$1 } from "@tiptap/vue-3";
|
|
6
6
|
import { closeHistory } from "@tiptap/pm/history";
|
|
7
|
-
import { createLowlight } from "lowlight";
|
|
8
7
|
import { AllSelection, NodeSelection, Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
|
|
9
8
|
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
9
|
+
import { createLowlight } from "lowlight";
|
|
10
10
|
import { DOMParser, DOMSerializer, Fragment as Fragment$1 } from "@tiptap/pm/model";
|
|
11
11
|
export * from "@threadlabs/looma/editor";
|
|
12
12
|
//#region src/vue/editor/primitives.ts
|
|
@@ -272,7 +272,7 @@ Image$1.extend({ addAttributes() {
|
|
|
272
272
|
};
|
|
273
273
|
} });
|
|
274
274
|
//#endregion
|
|
275
|
-
//#region src/
|
|
275
|
+
//#region src/editor/image-delivery.ts
|
|
276
276
|
function optionalString(value) {
|
|
277
277
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
278
278
|
}
|
|
@@ -409,7 +409,7 @@ function imageUploadContent(image) {
|
|
|
409
409
|
};
|
|
410
410
|
}
|
|
411
411
|
//#endregion
|
|
412
|
-
//#region src/
|
|
412
|
+
//#region src/editor/code-language-catalog.ts
|
|
413
413
|
var codeLanguageCatalog = {
|
|
414
414
|
"1c": () => import("../chunks/1c.js").then((module) => module.default),
|
|
415
415
|
"abnf": () => import("../chunks/abnf.js").then((module) => module.default),
|
|
@@ -628,6 +628,191 @@ var autoCodeLanguageNames = [
|
|
|
628
628
|
"yaml"
|
|
629
629
|
];
|
|
630
630
|
//#endregion
|
|
631
|
+
//#region src/editor/code-highlighting.ts
|
|
632
|
+
/**
|
|
633
|
+
* Highlighting from the host's `codeLanguages`, or, when that is null, from the built-in catalog,
|
|
634
|
+
* whose grammars load on demand and whose automatic detection uses a common subset.
|
|
635
|
+
*
|
|
636
|
+
* @lifecycle `onGrammarsLoaded` runs after each lazy load, before the document is re-highlighted,
|
|
637
|
+
* because the lowlight registry is mutable rather than reactive.
|
|
638
|
+
*/
|
|
639
|
+
function createCodeHighlighting(codeLanguages, onGrammarsLoaded) {
|
|
640
|
+
const registry = createLowlight(codeLanguages ?? void 0);
|
|
641
|
+
const lowlight = codeLanguages === null ? {
|
|
642
|
+
...registry,
|
|
643
|
+
highlightAuto: (value) => registry.highlightAuto(value, { subset: [...autoCodeLanguageNames] })
|
|
644
|
+
} : registry;
|
|
645
|
+
const languageNames = codeLanguages === null ? [...autoCodeLanguageNames, ...Object.keys(codeLanguageCatalog).filter((name) => !autoCodeLanguageNames.includes(name)).sort()] : Object.keys(codeLanguages).sort();
|
|
646
|
+
const grammarLoads = /* @__PURE__ */ new Map();
|
|
647
|
+
let catalogLoad;
|
|
648
|
+
const refresh = (editor) => {
|
|
649
|
+
if (editor.isDestroyed) return;
|
|
650
|
+
onGrammarsLoaded();
|
|
651
|
+
const tr = editor.state.tr;
|
|
652
|
+
editor.state.doc.descendants((node, pos) => {
|
|
653
|
+
if (node.type.name === "codeBlock") tr.setNodeMarkup(pos, void 0, node.attrs);
|
|
654
|
+
});
|
|
655
|
+
if (tr.docChanged) editor.view.dispatch(tr.setMeta("addToHistory", false));
|
|
656
|
+
};
|
|
657
|
+
const loadGrammar = (name) => {
|
|
658
|
+
const existing = grammarLoads.get(name);
|
|
659
|
+
if (existing) return existing;
|
|
660
|
+
const pending = codeLanguageCatalog[name]().then((grammar) => {
|
|
661
|
+
registry.register({ [name]: grammar });
|
|
662
|
+
}).catch((error) => {
|
|
663
|
+
grammarLoads.delete(name);
|
|
664
|
+
throw error;
|
|
665
|
+
});
|
|
666
|
+
grammarLoads.set(name, pending);
|
|
667
|
+
return pending;
|
|
668
|
+
};
|
|
669
|
+
return {
|
|
670
|
+
lowlight,
|
|
671
|
+
languageNames,
|
|
672
|
+
loadForCodeBlock(editor, language, value) {
|
|
673
|
+
if (codeLanguages !== null || editor.isDestroyed) return;
|
|
674
|
+
if (typeof language === "string" && Object.hasOwn(codeLanguageCatalog, language)) {
|
|
675
|
+
if (registry.registered(language) || grammarLoads.has(language)) return;
|
|
676
|
+
loadGrammar(language).then(() => refresh(editor)).catch(() => {});
|
|
677
|
+
} else if (!language && value.trim()) catalogLoad ??= Promise.all(autoCodeLanguageNames.map((name) => loadGrammar(name))).then(() => refresh(editor)).catch(() => {
|
|
678
|
+
catalogLoad = void 0;
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
//#endregion
|
|
684
|
+
//#region src/editor/content.ts
|
|
685
|
+
/** Whether a controlled document already matches the editor, so applying it would only reset the selection. */
|
|
686
|
+
function sameDocument(left, right) {
|
|
687
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* A link destination the editor may store: a site-relative path, fragment, or query, or an
|
|
691
|
+
* absolute http, https, mailto, or tel URL.
|
|
692
|
+
*
|
|
693
|
+
* @failure Returns null for empty input, control characters, other schemes, and unparseable URLs.
|
|
694
|
+
*/
|
|
695
|
+
function validLinkHref(value) {
|
|
696
|
+
const href = value.trim();
|
|
697
|
+
if (!href || /[\u0000-\u001f\u007f]/.test(href)) return null;
|
|
698
|
+
if (/^(\/|#|\?)/.test(href)) return href;
|
|
699
|
+
try {
|
|
700
|
+
const url = new URL(href);
|
|
701
|
+
return [
|
|
702
|
+
"http:",
|
|
703
|
+
"https:",
|
|
704
|
+
"mailto:",
|
|
705
|
+
"tel:"
|
|
706
|
+
].includes(url.protocol) ? href : null;
|
|
707
|
+
} catch {
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
//#endregion
|
|
712
|
+
//#region src/editor/selection-geometry.ts
|
|
713
|
+
/** A plain copy of a client rectangle, the anchor shape the slash and mention menus accept as a prop. */
|
|
714
|
+
function menuAnchorRect(rect) {
|
|
715
|
+
if (!rect) return null;
|
|
716
|
+
return {
|
|
717
|
+
left: rect.left,
|
|
718
|
+
top: rect.top,
|
|
719
|
+
right: rect.right,
|
|
720
|
+
bottom: rect.bottom,
|
|
721
|
+
x: rect.x,
|
|
722
|
+
y: rect.y,
|
|
723
|
+
width: rect.width,
|
|
724
|
+
height: rect.height
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
function selectionElement(editor) {
|
|
728
|
+
const { node } = editor.view.domAtPos(editor.state.selection.from);
|
|
729
|
+
return node instanceof HTMLElement ? node : node.parentElement;
|
|
730
|
+
}
|
|
731
|
+
/** The rendered table that holds the selection's start. */
|
|
732
|
+
function selectedTableElement(editor) {
|
|
733
|
+
return selectionElement(editor)?.closest("table");
|
|
734
|
+
}
|
|
735
|
+
/** The rendered cell that holds the selection's start. */
|
|
736
|
+
function selectedTableCellElement(editor) {
|
|
737
|
+
return selectionElement(editor)?.closest("td, th");
|
|
738
|
+
}
|
|
739
|
+
//#endregion
|
|
740
|
+
//#region src/editor/session.ts
|
|
741
|
+
function selectionSnapshot(selection) {
|
|
742
|
+
return {
|
|
743
|
+
bookmark: selection.getBookmark(),
|
|
744
|
+
from: selection.from,
|
|
745
|
+
to: selection.to,
|
|
746
|
+
text: selection instanceof TextSelection
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Creates the session's editor from `options`. A framework layer may pass its own `Editor`
|
|
751
|
+
* subclass, such as `@tiptap/vue-3`'s reactive one, so the editor it exposes keeps that behavior.
|
|
752
|
+
*/
|
|
753
|
+
function createEditorSession(options, EditorClass = Editor) {
|
|
754
|
+
const editor = new EditorClass(options);
|
|
755
|
+
return {
|
|
756
|
+
editor,
|
|
757
|
+
attach(element) {
|
|
758
|
+
const current = editor.options.element;
|
|
759
|
+
if (editor.isDestroyed || current === element || !current.firstChild) return;
|
|
760
|
+
element.append(...current.childNodes);
|
|
761
|
+
editor.setOptions({ element });
|
|
762
|
+
editor.createNodeViews();
|
|
763
|
+
},
|
|
764
|
+
setContent(content, selection) {
|
|
765
|
+
if (editor.isDestroyed || sameDocument(editor.getJSON(), content)) return false;
|
|
766
|
+
const previous = selection ?? selectionSnapshot(editor.view.state.selection);
|
|
767
|
+
if (!editor.isFocused) {
|
|
768
|
+
editor.commands.setContent(content, false);
|
|
769
|
+
return true;
|
|
770
|
+
}
|
|
771
|
+
editor.chain().setContent(content, false).command(({ tr }) => {
|
|
772
|
+
try {
|
|
773
|
+
tr.setSelection(previous.text ? TextSelection.create(tr.doc, previous.from, previous.to) : previous.bookmark.resolve(tr.doc));
|
|
774
|
+
} catch {
|
|
775
|
+
const max = tr.doc.content.size;
|
|
776
|
+
const from = Math.max(1, Math.min(previous.from, max));
|
|
777
|
+
const to = Math.max(from, Math.min(previous.to, max));
|
|
778
|
+
tr.setSelection(TextSelection.create(tr.doc, from, to));
|
|
779
|
+
}
|
|
780
|
+
return true;
|
|
781
|
+
}).run();
|
|
782
|
+
editor.view.focus();
|
|
783
|
+
return true;
|
|
784
|
+
},
|
|
785
|
+
subscribe(listener) {
|
|
786
|
+
editor.on("transaction", listener);
|
|
787
|
+
return () => {
|
|
788
|
+
editor.off("transaction", listener);
|
|
789
|
+
};
|
|
790
|
+
},
|
|
791
|
+
destroy() {
|
|
792
|
+
editor.destroy();
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
//#endregion
|
|
797
|
+
//#region src/editor/shortcuts.ts
|
|
798
|
+
/**
|
|
799
|
+
* Shows a key binding in ProseMirror notation ("Mod-Shift-8") the way the reader's platform writes
|
|
800
|
+
* it: modifier symbols in Apple's order on Apple devices (⇧⌘8), and Ctrl+Shift+8 elsewhere.
|
|
801
|
+
*/
|
|
802
|
+
function formatEditorShortcut(keys, apple) {
|
|
803
|
+
const parts = keys.split("-");
|
|
804
|
+
const key = (parts.pop() ?? "").toUpperCase();
|
|
805
|
+
const has = (name) => parts.includes(name);
|
|
806
|
+
if (apple) return `${has("Ctrl") ? "⌃" : ""}${has("Alt") ? "⌥" : ""}${has("Shift") ? "⇧" : ""}${has("Mod") ? "⌘" : ""}${key}`;
|
|
807
|
+
return [
|
|
808
|
+
has("Mod") || has("Ctrl") ? "Ctrl" : "",
|
|
809
|
+
has("Alt") ? "Alt" : "",
|
|
810
|
+
has("Shift") ? "Shift" : "",
|
|
811
|
+
key
|
|
812
|
+
].filter(Boolean).join("+");
|
|
813
|
+
}
|
|
814
|
+
var isApplePlatform = () => typeof navigator !== "undefined" && /mac|iphone|ipad|ipod/i.test(navigator.userAgentData?.platform || navigator.platform || navigator.userAgent);
|
|
815
|
+
//#endregion
|
|
631
816
|
//#region src/editor/extensions/expand.ts
|
|
632
817
|
/**
|
|
633
818
|
* Reveals collapsed ancestors before a host navigates to a document position.
|
|
@@ -1669,23 +1854,6 @@ var imageNodeView = ({ editor, node, getPos, decorations }) => {
|
|
|
1669
1854
|
};
|
|
1670
1855
|
//#endregion
|
|
1671
1856
|
//#region src/vue/editor/LoomaEditor.ts
|
|
1672
|
-
/**
|
|
1673
|
-
* Shows a key binding in ProseMirror notation ("Mod-Shift-8") the way the reader's platform writes
|
|
1674
|
-
* it: modifier symbols in Apple's order on Apple devices (⇧⌘8), and Ctrl+Shift+8 elsewhere.
|
|
1675
|
-
*/
|
|
1676
|
-
function formatEditorShortcut(keys, apple) {
|
|
1677
|
-
const parts = keys.split("-");
|
|
1678
|
-
const key = (parts.pop() ?? "").toUpperCase();
|
|
1679
|
-
const has = (name) => parts.includes(name);
|
|
1680
|
-
if (apple) return `${has("Ctrl") ? "⌃" : ""}${has("Alt") ? "⌥" : ""}${has("Shift") ? "⇧" : ""}${has("Mod") ? "⌘" : ""}${key}`;
|
|
1681
|
-
return [
|
|
1682
|
-
has("Mod") || has("Ctrl") ? "Ctrl" : "",
|
|
1683
|
-
has("Alt") ? "Alt" : "",
|
|
1684
|
-
has("Shift") ? "Shift" : "",
|
|
1685
|
-
key
|
|
1686
|
-
].filter(Boolean).join("+");
|
|
1687
|
-
}
|
|
1688
|
-
var isApplePlatform = () => typeof navigator !== "undefined" && /mac|iphone|ipad|ipod/i.test(navigator.userAgentData?.platform || navigator.platform || navigator.userAgent);
|
|
1689
1857
|
var EMPTY_DOCUMENT = {
|
|
1690
1858
|
type: "doc",
|
|
1691
1859
|
content: []
|
|
@@ -1708,38 +1876,6 @@ var EMPTY_CAPABILITIES = {
|
|
|
1708
1876
|
canMergeCells: false,
|
|
1709
1877
|
canSplitCell: false
|
|
1710
1878
|
};
|
|
1711
|
-
function sameDocument(left, right) {
|
|
1712
|
-
return JSON.stringify(left) === JSON.stringify(right);
|
|
1713
|
-
}
|
|
1714
|
-
function validLinkHref(value) {
|
|
1715
|
-
const href = value.trim();
|
|
1716
|
-
if (!href || /[\u0000-\u001f\u007f]/.test(href)) return null;
|
|
1717
|
-
if (/^(\/|#|\?)/.test(href)) return href;
|
|
1718
|
-
try {
|
|
1719
|
-
const url = new URL(href);
|
|
1720
|
-
return [
|
|
1721
|
-
"http:",
|
|
1722
|
-
"https:",
|
|
1723
|
-
"mailto:",
|
|
1724
|
-
"tel:"
|
|
1725
|
-
].includes(url.protocol) ? href : null;
|
|
1726
|
-
} catch {
|
|
1727
|
-
return null;
|
|
1728
|
-
}
|
|
1729
|
-
}
|
|
1730
|
-
function managedMenuAnchorRect(rect) {
|
|
1731
|
-
if (!rect) return null;
|
|
1732
|
-
return {
|
|
1733
|
-
left: rect.left,
|
|
1734
|
-
top: rect.top,
|
|
1735
|
-
right: rect.right,
|
|
1736
|
-
bottom: rect.bottom,
|
|
1737
|
-
x: rect.x,
|
|
1738
|
-
y: rect.y,
|
|
1739
|
-
width: rect.width,
|
|
1740
|
-
height: rect.height
|
|
1741
|
-
};
|
|
1742
|
-
}
|
|
1743
1879
|
function slashMenuRows(items) {
|
|
1744
1880
|
const groups = [];
|
|
1745
1881
|
for (const [index, item] of items.entries()) {
|
|
@@ -1760,14 +1896,6 @@ function slashMenuRows(items) {
|
|
|
1760
1896
|
label
|
|
1761
1897
|
}, () => rows)] : rows);
|
|
1762
1898
|
}
|
|
1763
|
-
function selectedTableElement(editor) {
|
|
1764
|
-
const { node } = editor.view.domAtPos(editor.state.selection.from);
|
|
1765
|
-
return (node instanceof HTMLElement ? node : node.parentElement)?.closest("table");
|
|
1766
|
-
}
|
|
1767
|
-
function selectedTableCellElement(editor) {
|
|
1768
|
-
const { node } = editor.view.domAtPos(editor.state.selection.from);
|
|
1769
|
-
return (node instanceof HTMLElement ? node : node.parentElement)?.closest("td, th");
|
|
1770
|
-
}
|
|
1771
1899
|
function loomaIcon(name) {
|
|
1772
1900
|
return h("svg", {
|
|
1773
1901
|
class: "looma-icon",
|
|
@@ -2135,21 +2263,13 @@ var LoomaEditor = defineComponent({
|
|
|
2135
2263
|
let linkPressedSelection = null;
|
|
2136
2264
|
const rememberSelection = (instance) => {
|
|
2137
2265
|
if (!instance.isFocused) return;
|
|
2138
|
-
|
|
2139
|
-
lastFocusedSelection = {
|
|
2140
|
-
bookmark: selection.getBookmark(),
|
|
2141
|
-
from: selection.from,
|
|
2142
|
-
to: selection.to,
|
|
2143
|
-
text: selection instanceof TextSelection
|
|
2144
|
-
};
|
|
2266
|
+
lastFocusedSelection = selectionSnapshot(instance.view.state.selection);
|
|
2145
2267
|
};
|
|
2146
2268
|
const authorHighlight = !props.disableHighlight;
|
|
2147
|
-
const
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
} : codeLowlight;
|
|
2152
|
-
const codeLanguageNames = props.codeLanguages === null ? [...autoCodeLanguageNames, ...Object.keys(codeLanguageCatalog).filter((name) => !autoCodeLanguageNames.includes(name)).sort()] : Object.keys(props.codeLanguages).sort();
|
|
2269
|
+
const codeHighlighting = createCodeHighlighting(props.codeLanguages, () => {
|
|
2270
|
+
codeUi.grammarVersion += 1;
|
|
2271
|
+
});
|
|
2272
|
+
const codeLanguageNames = codeHighlighting.languageNames;
|
|
2153
2273
|
const codeUi = reactive({
|
|
2154
2274
|
open: false,
|
|
2155
2275
|
pos: 0,
|
|
@@ -2158,115 +2278,97 @@ var LoomaEditor = defineComponent({
|
|
|
2158
2278
|
grammarVersion: 0,
|
|
2159
2279
|
style: {}
|
|
2160
2280
|
});
|
|
2161
|
-
const
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
expandNodeView,
|
|
2204
|
-
dividerNodeView,
|
|
2205
|
-
calloutNodeView,
|
|
2206
|
-
imageNodeView
|
|
2207
|
-
}),
|
|
2208
|
-
imageDelivery.extension,
|
|
2209
|
-
slashExtension,
|
|
2210
|
-
...props.extensions
|
|
2211
|
-
],
|
|
2212
|
-
content: props.modelValue,
|
|
2213
|
-
editable: props.editable,
|
|
2214
|
-
editorProps: {
|
|
2215
|
-
attributes: {
|
|
2216
|
-
role: "textbox",
|
|
2217
|
-
"aria-multiline": "true",
|
|
2218
|
-
"aria-label": props.label
|
|
2219
|
-
},
|
|
2220
|
-
handleKeyDown: (_view, event) => onFormattingKeyDown(event),
|
|
2221
|
-
handleDOMEvents: { click: (view, event) => {
|
|
2222
|
-
if (!(event.target instanceof Element) || event.button !== 0) return false;
|
|
2223
|
-
const link = event.target.closest("a[href]");
|
|
2224
|
-
if (!link || !view.dom.contains(link)) return false;
|
|
2225
|
-
const href = validLinkHref(link.getAttribute("href") ?? "");
|
|
2226
|
-
if (!href) return false;
|
|
2227
|
-
if (isApplePlatform() ? event.metaKey : event.ctrlKey) {
|
|
2228
|
-
event.preventDefault();
|
|
2229
|
-
window.open(href, "_blank", "noopener,noreferrer");
|
|
2230
|
-
return true;
|
|
2231
|
-
}
|
|
2232
|
-
dismissedFormattingSelection = null;
|
|
2233
|
-
requestAnimationFrame(() => {
|
|
2234
|
-
if (view.isDestroyed) return;
|
|
2235
|
-
editorStateVersion.value += 1;
|
|
2236
|
-
const instance = editor.value;
|
|
2237
|
-
if (instance?.isFocused && instance.state.selection.empty && instance.isActive("link") && !linkOpen.value) {
|
|
2238
|
-
linkPopup?.setProps({ getReferenceClientRect: () => link.getBoundingClientRect() });
|
|
2239
|
-
linkPopup?.show();
|
|
2281
|
+
const loadForCodeBlock = codeHighlighting.loadForCodeBlock;
|
|
2282
|
+
const editor = shallowRef();
|
|
2283
|
+
let session;
|
|
2284
|
+
onMounted(() => {
|
|
2285
|
+
session = createEditorSession({
|
|
2286
|
+
extensions: [
|
|
2287
|
+
...getDefaultEditorExtensions({
|
|
2288
|
+
placeholder: props.placeholder,
|
|
2289
|
+
linkBaseUrl: props.linkBaseUrl,
|
|
2290
|
+
mention: mentionExtension ?? false,
|
|
2291
|
+
disableHighlight: props.disableHighlight,
|
|
2292
|
+
codeLanguages: props.codeLanguages ?? void 0,
|
|
2293
|
+
codeLowlight: codeHighlighting.lowlight,
|
|
2294
|
+
tableOfContentsNodeView,
|
|
2295
|
+
expandNodeView,
|
|
2296
|
+
dividerNodeView,
|
|
2297
|
+
calloutNodeView,
|
|
2298
|
+
imageNodeView
|
|
2299
|
+
}),
|
|
2300
|
+
imageDelivery.extension,
|
|
2301
|
+
slashExtension,
|
|
2302
|
+
...props.extensions
|
|
2303
|
+
],
|
|
2304
|
+
content: props.modelValue,
|
|
2305
|
+
editable: props.editable,
|
|
2306
|
+
editorProps: {
|
|
2307
|
+
attributes: {
|
|
2308
|
+
role: "textbox",
|
|
2309
|
+
"aria-multiline": "true",
|
|
2310
|
+
"aria-label": props.label
|
|
2311
|
+
},
|
|
2312
|
+
handleKeyDown: (_view, event) => onFormattingKeyDown(event),
|
|
2313
|
+
handleDOMEvents: { click: (view, event) => {
|
|
2314
|
+
if (!(event.target instanceof Element) || event.button !== 0) return false;
|
|
2315
|
+
const link = event.target.closest("a[href]");
|
|
2316
|
+
if (!link || !view.dom.contains(link)) return false;
|
|
2317
|
+
const href = validLinkHref(link.getAttribute("href") ?? "");
|
|
2318
|
+
if (!href) return false;
|
|
2319
|
+
if (isApplePlatform() ? event.metaKey : event.ctrlKey) {
|
|
2320
|
+
event.preventDefault();
|
|
2321
|
+
window.open(href, "_blank", "noopener,noreferrer");
|
|
2322
|
+
return true;
|
|
2240
2323
|
}
|
|
2324
|
+
dismissedFormattingSelection = null;
|
|
2325
|
+
requestAnimationFrame(() => {
|
|
2326
|
+
if (view.isDestroyed) return;
|
|
2327
|
+
editorStateVersion.value += 1;
|
|
2328
|
+
const instance = editor.value;
|
|
2329
|
+
if (instance?.isFocused && instance.state.selection.empty && instance.isActive("link") && !linkOpen.value) {
|
|
2330
|
+
linkPopup?.setProps({ getReferenceClientRect: () => link.getBoundingClientRect() });
|
|
2331
|
+
linkPopup?.show();
|
|
2332
|
+
}
|
|
2333
|
+
});
|
|
2334
|
+
return false;
|
|
2335
|
+
} }
|
|
2336
|
+
},
|
|
2337
|
+
onCreate: ({ editor: instance }) => {
|
|
2338
|
+
emit("ready", instance);
|
|
2339
|
+
instance.state.doc.descendants((node) => {
|
|
2340
|
+
if (node.type.name === "codeBlock") loadForCodeBlock(instance, node.attrs.language, node.textContent);
|
|
2241
2341
|
});
|
|
2242
|
-
|
|
2243
|
-
}
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
const
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
if (!formattingSelectionActive(instance)) formattingPopup?.hide();
|
|
2342
|
+
},
|
|
2343
|
+
onFocus: ({ editor: instance }) => rememberSelection(instance),
|
|
2344
|
+
onSelectionUpdate: ({ editor: instance }) => {
|
|
2345
|
+
rememberSelection(instance);
|
|
2346
|
+
if (floatingFormattingMode()) {
|
|
2347
|
+
const selection = instance.state.selection;
|
|
2348
|
+
if (!dismissedFormattingSelection?.eq(selection)) dismissedFormattingSelection = null;
|
|
2349
|
+
if (!heldFormattingSelection?.eq(selection)) heldFormattingSelection = null;
|
|
2350
|
+
if (!formattingSelectionActive(instance)) formattingPopup?.hide();
|
|
2351
|
+
}
|
|
2352
|
+
if (linkContextEditing.value && instance.state.selection.from !== linkSelection?.from) linkContextEditing.value = false;
|
|
2353
|
+
},
|
|
2354
|
+
onUpdate: ({ editor: instance }) => {
|
|
2355
|
+
const value = instance.getJSON();
|
|
2356
|
+
emit("update:modelValue", value);
|
|
2357
|
+
emit("update", value);
|
|
2259
2358
|
}
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
onTransaction: () => {
|
|
2359
|
+
}, Editor$1);
|
|
2360
|
+
session.subscribe(() => {
|
|
2263
2361
|
editorStateVersion.value += 1;
|
|
2264
|
-
}
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2362
|
+
});
|
|
2363
|
+
editor.value = session.editor;
|
|
2364
|
+
});
|
|
2365
|
+
const attachDocument = (element) => {
|
|
2366
|
+
if (element instanceof HTMLElement) session?.attach(element);
|
|
2367
|
+
};
|
|
2368
|
+
onBeforeUnmount(() => {
|
|
2369
|
+
const element = session?.editor.options.element;
|
|
2370
|
+
element?.parentNode?.replaceChild(element.cloneNode(true), element);
|
|
2371
|
+
session?.destroy();
|
|
2270
2372
|
});
|
|
2271
2373
|
watch(() => {
|
|
2272
2374
|
editorStateVersion.value;
|
|
@@ -2735,31 +2837,9 @@ var LoomaEditor = defineComponent({
|
|
|
2735
2837
|
});
|
|
2736
2838
|
watch(() => props.modelValue, (value) => {
|
|
2737
2839
|
const instance = editor.value;
|
|
2738
|
-
if (!instance ||
|
|
2840
|
+
if (!instance || !session) return;
|
|
2739
2841
|
const wasFocused = instance.isFocused;
|
|
2740
|
-
|
|
2741
|
-
bookmark: instance.view.state.selection.getBookmark(),
|
|
2742
|
-
from: instance.view.state.selection.from,
|
|
2743
|
-
to: instance.view.state.selection.to,
|
|
2744
|
-
text: instance.view.state.selection instanceof TextSelection
|
|
2745
|
-
};
|
|
2746
|
-
if (!wasFocused) {
|
|
2747
|
-
instance.commands.setContent(value, false);
|
|
2748
|
-
return;
|
|
2749
|
-
}
|
|
2750
|
-
instance.chain().setContent(value, false).command(({ tr }) => {
|
|
2751
|
-
try {
|
|
2752
|
-
tr.setSelection(previousSelection.text ? TextSelection.create(tr.doc, previousSelection.from, previousSelection.to) : previousSelection.bookmark.resolve(tr.doc));
|
|
2753
|
-
} catch {
|
|
2754
|
-
const max = tr.doc.content.size;
|
|
2755
|
-
const from = Math.max(1, Math.min(previousSelection.from, max));
|
|
2756
|
-
const to = Math.max(from, Math.min(previousSelection.to, max));
|
|
2757
|
-
tr.setSelection(TextSelection.create(tr.doc, from, to));
|
|
2758
|
-
}
|
|
2759
|
-
return true;
|
|
2760
|
-
}).run();
|
|
2761
|
-
instance.view.focus();
|
|
2762
|
-
rememberSelection(instance);
|
|
2842
|
+
if (session.setContent(value, lastFocusedSelection) && wasFocused) rememberSelection(instance);
|
|
2763
2843
|
}, { deep: true });
|
|
2764
2844
|
const closeTableUi = () => {
|
|
2765
2845
|
tableUi.toolbarOpen = false;
|
|
@@ -2823,7 +2903,7 @@ var LoomaEditor = defineComponent({
|
|
|
2823
2903
|
};
|
|
2824
2904
|
const codeLanguageOptions = computed(() => {
|
|
2825
2905
|
codeUi.grammarVersion;
|
|
2826
|
-
const detected = codeUi.open && codeUi.text.trim() ?
|
|
2906
|
+
const detected = codeUi.open && codeUi.text.trim() ? codeHighlighting.lowlight.highlightAuto(codeUi.text).data?.language : null;
|
|
2827
2907
|
return [
|
|
2828
2908
|
{
|
|
2829
2909
|
value: "auto",
|
|
@@ -3672,7 +3752,7 @@ var LoomaEditor = defineComponent({
|
|
|
3672
3752
|
}
|
|
3673
3753
|
}
|
|
3674
3754
|
}, { default: () => h(ImageControls, { editor: instance }) }) : null,
|
|
3675
|
-
instance ? h(EditorSurface, { density: props.editable ? "comfortable" : props.contentDensity }, () => h(
|
|
3755
|
+
instance ? h(EditorSurface, { density: props.editable ? "comfortable" : props.contentDensity }, () => h("div", { ref: attachDocument })) : null,
|
|
3676
3756
|
codeUi.open ? h("div", {
|
|
3677
3757
|
ref: codeLanguageRef,
|
|
3678
3758
|
class: "looma-editor__code-language",
|
|
@@ -3836,7 +3916,7 @@ var LoomaEditor = defineComponent({
|
|
|
3836
3916
|
open: true,
|
|
3837
3917
|
query: slash.query,
|
|
3838
3918
|
selectedIndex: slash.selectedIndex,
|
|
3839
|
-
anchorRect:
|
|
3919
|
+
anchorRect: menuAnchorRect(slash.rect),
|
|
3840
3920
|
onHighlight: (event) => {
|
|
3841
3921
|
slash.highlight?.(event.detail.index);
|
|
3842
3922
|
},
|
|
@@ -3857,7 +3937,7 @@ var LoomaEditor = defineComponent({
|
|
|
3857
3937
|
query: mention.query,
|
|
3858
3938
|
items: mention.items,
|
|
3859
3939
|
selectedIndex: mention.selectedIndex,
|
|
3860
|
-
anchorRect:
|
|
3940
|
+
anchorRect: menuAnchorRect(mention.rect),
|
|
3861
3941
|
loading: mention.loading,
|
|
3862
3942
|
onHighlight: (event) => {
|
|
3863
3943
|
mention.selectedIndex = event.detail.index;
|
|
File without changes
|