@meowdown/core 0.83.3 → 0.84.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 +1 -1
- package/dist/class-names.d.ts +189 -0
- package/dist/class-names.js +190 -0
- package/dist/index.d.ts +93 -3
- package/dist/index.js +269 -97
- package/dist/style.css +177 -3
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ATOM_SELECTED_CLASS, ATOM_VIEW_CLASS, ATOM_VIEW_CONTENT_CLASS, ATOM_VIEW_PREVIEW_CLASS, CONTENT_CLASS, EMBED_MENU_BUTTON_CLASS, EMBED_RESIZABLE_CLASS, FILE_MARK_CLASS, FILE_VIEW_CLASS, FILE_VIEW_CONTENT_CLASS, FILE_VIEW_ICON_CLASS, FILE_VIEW_NAME_CLASS, FILE_VIEW_PREVIEW_CLASS, FILE_VIEW_SIZE_CLASS, ICON_BUTTON_CLASS, ICON_CLASS, IMAGE_MARK_CLASS, IMAGE_RESIZABLE_CLASS, IMAGE_RESIZE_HANDLE_CLASS, IMAGE_VIEW_CLASS, IMAGE_VIEW_CONTENT_CLASS, IMAGE_VIEW_PREVIEW_CLASS, LINK_TEXT_MARK_CLASS, LINK_TITLE_MARK_CLASS, LINK_URI_MARK_CLASS, MATH_MARK_CLASS, MATH_VIEW_CLASS, MATH_VIEW_CONTENT_CLASS, MATH_VIEW_PREVIEW_CLASS, PACK_MARK_CLASS, PENDING_REPLACEMENT_CLASS, SHOW_CLASS, SYNTAX_MARK_CLASS, TAG_MARK_CLASS, VIRTUAL_CARET_CLASS, VIRTUAL_CARET_LAYER_CLASS, WIKILINK_MARK_CLASS, WIKILINK_VIEW_CLASS, WIKILINK_VIEW_CONTENT_CLASS, WIKILINK_VIEW_LABEL_CLASS, WIKILINK_VIEW_PREVIEW_CLASS, getIconClass } from "./class-names.js";
|
|
2
|
+
import { Priority, Priority as Priority$1, canUseRegexLookbehind, createMarkBuilders, createNodeBuilders, defaultBlockAt, defineBaseCommands, defineBaseKeymap, defineClipboardSerializer, defineCommands, defineDOMEventHandler, defineHistory, defineKeymap, defineMarkSpec, defineMarkView, defineNodeAttr, defineNodeSpec, definePlugin, findMarkRange, getMarkRange, getMarkType, getNodeType, isAllSelection, isApple, isAtBlockStart, isNodeSelection, isTextSelection, setBlockType, toggleNode, union, unsetBlockType, withPriority, withPriority as withPriority$1, withSkipCodeBlock } from "@prosekit/core";
|
|
2
3
|
import { codeBlockEnterRule, codeBlockInputRule, defineCodeBlockCommands, defineCodeBlockHighlight, defineCodeBlockKeymap, defineCodeBlockPreviewPlugin, defineCodeBlockSpec, isCodeBlockPreviewHiddenDecoration } from "@prosekit/extensions/code-block";
|
|
3
4
|
import { definePlaceholder, definePlaceholder as definePlaceholder$1 } from "@prosekit/extensions/placeholder";
|
|
4
5
|
import { defineSearchCommands, defineSearchQuery, defineSearchStatusHandler, getSearchStatus, getSearchStatus as getSearchStatus$1 } from "@prosekit/extensions/search";
|
|
@@ -611,7 +612,6 @@ function createCaretSnapPlugin(marks) {
|
|
|
611
612
|
})
|
|
612
613
|
});
|
|
613
614
|
}
|
|
614
|
-
const SELECTED_CLASS = "md-atom-selected";
|
|
615
615
|
function createSelectionPlugin(marks) {
|
|
616
616
|
return new Plugin({
|
|
617
617
|
key: new PluginKey("atom-mark-selection"),
|
|
@@ -620,12 +620,12 @@ function createSelectionPlugin(marks) {
|
|
|
620
620
|
if (markNames.length === 0) return;
|
|
621
621
|
if (isNodeSelection(state.selection)) return;
|
|
622
622
|
const range = getSelectedRange(state, markNames);
|
|
623
|
-
if (range) return DecorationSet.create(state.doc, [Decoration.inline(range.from, range.to, { class:
|
|
623
|
+
if (range) return DecorationSet.create(state.doc, [Decoration.inline(range.from, range.to, { class: ATOM_SELECTED_CLASS })]);
|
|
624
624
|
const { from, to, empty } = state.selection;
|
|
625
625
|
if (empty) return null;
|
|
626
626
|
const decorations = [];
|
|
627
627
|
state.doc.nodesBetween(from, to, (node, pos) => {
|
|
628
|
-
if (node.marks.some((mark) => markNames.includes(mark.type.name))) decorations.push(Decoration.inline(pos, pos + node.nodeSize, { class:
|
|
628
|
+
if (node.marks.some((mark) => markNames.includes(mark.type.name))) decorations.push(Decoration.inline(pos, pos + node.nodeSize, { class: ATOM_SELECTED_CLASS }));
|
|
629
629
|
});
|
|
630
630
|
return DecorationSet.create(state.doc, decorations);
|
|
631
631
|
}) }
|
|
@@ -1990,6 +1990,7 @@ function defineCodeBlock() {
|
|
|
1990
1990
|
}
|
|
1991
1991
|
|
|
1992
1992
|
// src/extensions/commands.ts
|
|
1993
|
+
/** @internal */
|
|
1993
1994
|
function selectText(anchor, head) {
|
|
1994
1995
|
return (state, dispatch) => {
|
|
1995
1996
|
if (dispatch) {
|
|
@@ -1999,6 +2000,7 @@ function selectText(anchor, head) {
|
|
|
1999
2000
|
return true;
|
|
2000
2001
|
};
|
|
2001
2002
|
}
|
|
2003
|
+
/** @internal */
|
|
2002
2004
|
function selectTextBetween($anchor, $head, bias) {
|
|
2003
2005
|
return (state, dispatch) => {
|
|
2004
2006
|
if (dispatch) {
|
|
@@ -2008,6 +2010,7 @@ function selectTextBetween($anchor, $head, bias) {
|
|
|
2008
2010
|
return true;
|
|
2009
2011
|
};
|
|
2010
2012
|
}
|
|
2013
|
+
/** @internal */
|
|
2011
2014
|
function insertMarkdown(markdown) {
|
|
2012
2015
|
return (state, dispatch) => {
|
|
2013
2016
|
if (!markdown.trim()) return false;
|
|
@@ -2032,6 +2035,8 @@ function insertMarkdown(markdown) {
|
|
|
2032
2035
|
* `insertText`. When a non-space character sits right before the caret, a
|
|
2033
2036
|
* space is inserted first so the trigger can match, like a user would type
|
|
2034
2037
|
* it. In a code block, where no menu can open, the command does nothing.
|
|
2038
|
+
*
|
|
2039
|
+
* @internal
|
|
2035
2040
|
*/
|
|
2036
2041
|
function insertTrigger(text) {
|
|
2037
2042
|
return (state, dispatch) => {
|
|
@@ -2053,16 +2058,40 @@ function insertTrigger(text) {
|
|
|
2053
2058
|
* Turns the current block into plain text, peeling one layer per call: a
|
|
2054
2059
|
* non-paragraph textblock becomes a paragraph, then a paragraph in a list
|
|
2055
2060
|
* loses its marker, then a paragraph in a blockquote lifts out of it.
|
|
2061
|
+
*
|
|
2062
|
+
* @internal
|
|
2056
2063
|
*/
|
|
2057
2064
|
function turnIntoText() {
|
|
2058
2065
|
return chainCommands(setBlockType({ type: "paragraph" }), unwrapList(), lift);
|
|
2059
2066
|
}
|
|
2067
|
+
/** @internal */
|
|
2060
2068
|
function scrollIntoView() {
|
|
2061
2069
|
return (state, dispatch) => {
|
|
2062
2070
|
if (dispatch) dispatch(state.tr.scrollIntoView());
|
|
2063
2071
|
return true;
|
|
2064
2072
|
};
|
|
2065
2073
|
}
|
|
2074
|
+
/**
|
|
2075
|
+
* Deletes the block at `pos` and puts the caret where it was. A block that is
|
|
2076
|
+
* the only child of its parent gives way to an empty default block (usually a
|
|
2077
|
+
* paragraph), since the parent cannot be left empty.
|
|
2078
|
+
*
|
|
2079
|
+
* @internal
|
|
2080
|
+
*/
|
|
2081
|
+
function deleteBlock(pos) {
|
|
2082
|
+
return (state, dispatch) => {
|
|
2083
|
+
const $pos = state.doc.resolve(pos);
|
|
2084
|
+
const node = $pos.nodeAfter;
|
|
2085
|
+
if (!node) return false;
|
|
2086
|
+
if (dispatch) {
|
|
2087
|
+
const end = pos + node.nodeSize;
|
|
2088
|
+
const tr = $pos.parent.childCount === 1 ? state.tr.replaceWith(pos, end, $pos.parent.type.contentMatch.defaultType?.createAndFill() ?? []) : state.tr.delete(pos, end);
|
|
2089
|
+
tr.setSelection(TextSelection.near(tr.doc.resolve(Math.min(pos, tr.doc.content.size)), 1));
|
|
2090
|
+
dispatch(tr.scrollIntoView());
|
|
2091
|
+
}
|
|
2092
|
+
return true;
|
|
2093
|
+
};
|
|
2094
|
+
}
|
|
2066
2095
|
function defineEditorCommands() {
|
|
2067
2096
|
return defineCommands({
|
|
2068
2097
|
insertMarkdown,
|
|
@@ -2282,6 +2311,32 @@ function defineExitBoundaryHandler(getExitBoundaryHandler) {
|
|
|
2282
2311
|
return withPriority$1(definePlugin(createExitBoundaryPlugin(getExitBoundaryHandler)), Priority$1.low);
|
|
2283
2312
|
}
|
|
2284
2313
|
|
|
2314
|
+
// src/utils/atom-view.ts
|
|
2315
|
+
/**
|
|
2316
|
+
* The atom mark view (an image, file, or wiki link) whose source holds
|
|
2317
|
+
* `insidePos`, a position inside the unit rather than on its edges.
|
|
2318
|
+
*/
|
|
2319
|
+
function findAtomViewAt(view, insidePos) {
|
|
2320
|
+
const { node } = view.domAtPos(insidePos, 0);
|
|
2321
|
+
return (isElementLike(node) ? node : node.parentElement)?.closest(`.${ATOM_VIEW_CLASS}`);
|
|
2322
|
+
}
|
|
2323
|
+
/**
|
|
2324
|
+
* The rendered preview of the atom mark view whose source holds `insidePos`.
|
|
2325
|
+
*/
|
|
2326
|
+
function findAtomPreviewElement(view, insidePos) {
|
|
2327
|
+
return findAtomViewAt(view, insidePos)?.querySelector(`.${ATOM_VIEW_PREVIEW_CLASS}`);
|
|
2328
|
+
}
|
|
2329
|
+
/**
|
|
2330
|
+
* The document position of the source of the atom mark view that holds
|
|
2331
|
+
* `element`. Read it from the hidden content holder, not from an event's
|
|
2332
|
+
* position: an event on the non-editable preview lands on the unit boundary,
|
|
2333
|
+
* where a mark lookup would pick the next adjacent unit.
|
|
2334
|
+
*/
|
|
2335
|
+
function getAtomViewSourcePos(view, element) {
|
|
2336
|
+
const content = element.closest(`.${ATOM_VIEW_CLASS}`)?.querySelector(`.${ATOM_VIEW_CONTENT_CLASS}`);
|
|
2337
|
+
return content ? view.posAtDOM(content, 0) : void 0;
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2285
2340
|
// src/utils/is-mod-event.ts
|
|
2286
2341
|
/**
|
|
2287
2342
|
* Whether the platform's mod key is held on `event`: `Command` on Apple, `Ctrl` elsewhere.
|
|
@@ -2314,11 +2369,11 @@ function defineFileClickHandler(getOnClick) {
|
|
|
2314
2369
|
props: { handleClick: (view, _pos, event) => {
|
|
2315
2370
|
const handler = getOnClick?.(view.state);
|
|
2316
2371
|
if (!handler) return false;
|
|
2317
|
-
const preview = event.target?.closest?.(
|
|
2372
|
+
const preview = event.target?.closest?.(`.${FILE_VIEW_PREVIEW_CLASS}`);
|
|
2318
2373
|
if (!preview) return false;
|
|
2319
|
-
const
|
|
2320
|
-
if (
|
|
2321
|
-
const hit = findFileAt(view.state,
|
|
2374
|
+
const pos = getAtomViewSourcePos(view, preview);
|
|
2375
|
+
if (pos == null) return false;
|
|
2376
|
+
const hit = findFileAt(view.state, pos);
|
|
2322
2377
|
if (!hit) return false;
|
|
2323
2378
|
handler({
|
|
2324
2379
|
href: hit.href,
|
|
@@ -2546,7 +2601,7 @@ const SVG_NS = "http://www.w3.org/2000/svg";
|
|
|
2546
2601
|
*/
|
|
2547
2602
|
function buildFileIcon() {
|
|
2548
2603
|
const svg = document.createElementNS(SVG_NS, "svg");
|
|
2549
|
-
svg.setAttribute("class",
|
|
2604
|
+
svg.setAttribute("class", FILE_VIEW_ICON_CLASS);
|
|
2550
2605
|
svg.setAttribute("viewBox", "0 0 24 24");
|
|
2551
2606
|
svg.setAttribute("aria-hidden", "true");
|
|
2552
2607
|
for (const shape of ["M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z", "M14 2v4a2 2 0 0 0 2 2h4"]) {
|
|
@@ -2572,9 +2627,9 @@ var FileMarkView = class {
|
|
|
2572
2627
|
constructor(mark, options) {
|
|
2573
2628
|
this.#attrs = mark.attrs;
|
|
2574
2629
|
this.#dom = document.createElement("span");
|
|
2575
|
-
this.#dom.className =
|
|
2630
|
+
this.#dom.className = `${FILE_VIEW_CLASS} ${ATOM_VIEW_CLASS}`;
|
|
2576
2631
|
this.#preview = document.createElement("span");
|
|
2577
|
-
this.#preview.className =
|
|
2632
|
+
this.#preview.className = `${FILE_VIEW_PREVIEW_CLASS} ${ATOM_VIEW_PREVIEW_CLASS}`;
|
|
2578
2633
|
this.#preview.contentEditable = "false";
|
|
2579
2634
|
this.#preview.dataset.testid = "file-pill";
|
|
2580
2635
|
this.#preview.dataset.fileKind = getFileKind(this.#attrs.href);
|
|
@@ -2582,15 +2637,15 @@ var FileMarkView = class {
|
|
|
2582
2637
|
this.#dom.appendChild(this.#preview);
|
|
2583
2638
|
this.#preview.appendChild(buildFileIcon());
|
|
2584
2639
|
this.#nameElement = document.createElement("span");
|
|
2585
|
-
this.#nameElement.className =
|
|
2640
|
+
this.#nameElement.className = FILE_VIEW_NAME_CLASS;
|
|
2586
2641
|
this.#nameElement.textContent = this.#attrs.name;
|
|
2587
2642
|
this.#preview.appendChild(this.#nameElement);
|
|
2588
2643
|
this.#sizeElement = document.createElement("span");
|
|
2589
|
-
this.#sizeElement.className =
|
|
2644
|
+
this.#sizeElement.className = FILE_VIEW_SIZE_CLASS;
|
|
2590
2645
|
this.#sizeElement.dataset.testid = "file-pill-size";
|
|
2591
2646
|
this.#preview.appendChild(this.#sizeElement);
|
|
2592
2647
|
this.#contentDOM = document.createElement("span");
|
|
2593
|
-
this.#contentDOM.className =
|
|
2648
|
+
this.#contentDOM.className = `${FILE_VIEW_CONTENT_CLASS} ${ATOM_VIEW_CONTENT_CLASS}`;
|
|
2594
2649
|
this.#dom.appendChild(this.#contentDOM);
|
|
2595
2650
|
this.#loadFileInfo(options?.resolveFileInfo);
|
|
2596
2651
|
}
|
|
@@ -2654,7 +2709,7 @@ function defineFileView(getOptions) {
|
|
|
2654
2709
|
const revealActiveMatch = new Plugin({ props: { decorations: guardDecorations((state) => {
|
|
2655
2710
|
const { from, to, empty } = state.selection;
|
|
2656
2711
|
if (empty || getSearchStatus$1(state).active === 0) return;
|
|
2657
|
-
return DecorationSet.create(state.doc, [Decoration.inline(from, to, { class:
|
|
2712
|
+
return DecorationSet.create(state.doc, [Decoration.inline(from, to, { class: SHOW_CLASS })]);
|
|
2658
2713
|
}) } });
|
|
2659
2714
|
/**
|
|
2660
2715
|
* Find over the editor's text. `setSearchQuery` highlights every match and
|
|
@@ -2764,37 +2819,39 @@ function getLinkUnitAt(state, pos) {
|
|
|
2764
2819
|
const imageClickKey = new PluginKey("meowdown-image-click");
|
|
2765
2820
|
/**
|
|
2766
2821
|
* The image preview wrapper around `target`, or nothing when the wrapper holds
|
|
2767
|
-
* a post-embed card
|
|
2822
|
+
* a post-embed card (its links and controls have their own click behavior) or
|
|
2823
|
+
* `target` is the preview's menu button.
|
|
2768
2824
|
*/
|
|
2769
2825
|
function getClosestImagePreview(target) {
|
|
2770
2826
|
if (!(target instanceof HTMLElement)) return;
|
|
2771
|
-
|
|
2827
|
+
if (target.closest(`.${"md-embed-menu-button"}`)) return;
|
|
2828
|
+
const preview = target.closest(`.${IMAGE_VIEW_PREVIEW_CLASS}`);
|
|
2772
2829
|
if (!preview || preview.dataset.postEmbed != null) return;
|
|
2773
2830
|
return preview;
|
|
2774
2831
|
}
|
|
2775
2832
|
function getPreviewImage(preview) {
|
|
2776
|
-
return preview.querySelector("img")
|
|
2833
|
+
return preview.querySelector("img");
|
|
2777
2834
|
}
|
|
2778
2835
|
function findImageAt(state, pos) {
|
|
2779
2836
|
const range = getMarkRangeAt(state, pos, "mdImage");
|
|
2780
2837
|
if (!range) return;
|
|
2781
|
-
const {
|
|
2838
|
+
const { from, to, mark } = range;
|
|
2839
|
+
const { src, alt, syntax } = mark.attrs;
|
|
2782
2840
|
return {
|
|
2783
|
-
from
|
|
2784
|
-
to
|
|
2841
|
+
from,
|
|
2842
|
+
to,
|
|
2785
2843
|
src,
|
|
2786
|
-
alt
|
|
2844
|
+
alt,
|
|
2845
|
+
syntax,
|
|
2846
|
+
kind: matchEmbed(src) ?? "image"
|
|
2787
2847
|
};
|
|
2788
2848
|
}
|
|
2789
2849
|
/**
|
|
2790
|
-
*
|
|
2791
|
-
* event's document position: an event on the non-editable preview lands on the
|
|
2792
|
-
* run boundary, where `getMarkRange` would pick the next adjacent image.
|
|
2850
|
+
* The image whose rendered preview, menu button, or other element is `element`.
|
|
2793
2851
|
*/
|
|
2794
|
-
function
|
|
2795
|
-
const
|
|
2796
|
-
|
|
2797
|
-
return findImageAt(view.state, view.posAtDOM(content, 0));
|
|
2852
|
+
function findImageForElement(view, element) {
|
|
2853
|
+
const pos = getAtomViewSourcePos(view, element);
|
|
2854
|
+
return pos == null ? void 0 : findImageAt(view.state, pos);
|
|
2798
2855
|
}
|
|
2799
2856
|
/**
|
|
2800
2857
|
* Fingers wander a little during a tap; past this it is a scroll or a drag.
|
|
@@ -2830,12 +2887,12 @@ function defineImageClickHandler(getOnClick) {
|
|
|
2830
2887
|
const preview = getClosestImagePreview(event.target);
|
|
2831
2888
|
if (!preview) return false;
|
|
2832
2889
|
event.preventDefault();
|
|
2833
|
-
const hit =
|
|
2890
|
+
const hit = findImageForElement(view, preview);
|
|
2834
2891
|
if (hit) handler({
|
|
2835
2892
|
src: hit.src,
|
|
2836
2893
|
alt: hit.alt,
|
|
2837
2894
|
event,
|
|
2838
|
-
element: getPreviewImage(preview),
|
|
2895
|
+
element: getPreviewImage(preview) ?? void 0,
|
|
2839
2896
|
mod: isModEvent(event)
|
|
2840
2897
|
});
|
|
2841
2898
|
return true;
|
|
@@ -2854,7 +2911,7 @@ function defineImageClickHandler(getOnClick) {
|
|
|
2854
2911
|
pendingTaps.delete(view);
|
|
2855
2912
|
if (event.touches.length !== 1) return false;
|
|
2856
2913
|
if (!getClosestImagePreview(event.target)) return false;
|
|
2857
|
-
if (event.target instanceof HTMLElement && event.target.closest("
|
|
2914
|
+
if (event.target instanceof HTMLElement && event.target.closest(`.${"md-image-resize-handle"}`)) return false;
|
|
2858
2915
|
const touch = event.changedTouches[0];
|
|
2859
2916
|
if (!touch) return false;
|
|
2860
2917
|
pendingTaps.set(view, {
|
|
@@ -2882,13 +2939,13 @@ function defineImageClickHandler(getOnClick) {
|
|
|
2882
2939
|
if (!handler) return false;
|
|
2883
2940
|
const preview = getClosestImagePreview(event.target);
|
|
2884
2941
|
if (!preview) return false;
|
|
2885
|
-
const hit =
|
|
2942
|
+
const hit = findImageForElement(view, preview);
|
|
2886
2943
|
if (!hit) return false;
|
|
2887
2944
|
handler({
|
|
2888
2945
|
src: hit.src,
|
|
2889
2946
|
alt: hit.alt,
|
|
2890
2947
|
event,
|
|
2891
|
-
element: getPreviewImage(preview),
|
|
2948
|
+
element: getPreviewImage(preview) ?? void 0,
|
|
2892
2949
|
mod: isModEvent(event)
|
|
2893
2950
|
});
|
|
2894
2951
|
return true;
|
|
@@ -2945,7 +3002,7 @@ function defineTagClickHandler(getOnClick) {
|
|
|
2945
3002
|
return defineMarkClickHandler({
|
|
2946
3003
|
key: tagClickKey,
|
|
2947
3004
|
enabled: (state) => !!getOnClick?.(state),
|
|
2948
|
-
selector:
|
|
3005
|
+
selector: `.${TAG_MARK_CLASS}`,
|
|
2949
3006
|
preventDefault: false,
|
|
2950
3007
|
findPayloadAt: (state, pos) => findTagAt(state, pos)?.tag,
|
|
2951
3008
|
onClick: (tag, event, state) => {
|
|
@@ -2982,9 +3039,8 @@ function findWikilinkAt(state, pos) {
|
|
|
2982
3039
|
* next adjacent mark; the hidden content holder has the exact source position.
|
|
2983
3040
|
*/
|
|
2984
3041
|
function findWikilinkForElement(view, element) {
|
|
2985
|
-
const
|
|
2986
|
-
|
|
2987
|
-
return findWikilinkAt(view.state, view.posAtDOM(content, 0));
|
|
3042
|
+
const pos = getAtomViewSourcePos(view, element);
|
|
3043
|
+
return pos == null ? void 0 : findWikilinkAt(view.state, pos);
|
|
2988
3044
|
}
|
|
2989
3045
|
/**
|
|
2990
3046
|
* Call `onClick` when the user clicks a rendered wikilink label, or presses
|
|
@@ -2995,7 +3051,7 @@ function defineWikilinkClickHandler(getOnClick) {
|
|
|
2995
3051
|
return defineMarkClickHandler({
|
|
2996
3052
|
key: wikilinkClickKey,
|
|
2997
3053
|
enabled: (state) => !!getOnClick?.(state),
|
|
2998
|
-
selector:
|
|
3054
|
+
selector: `.${WIKILINK_VIEW_PREVIEW_CLASS}`,
|
|
2999
3055
|
preventDefault: false,
|
|
3000
3056
|
findPayloadAt: (state, pos) => findWikilinkAt(state, pos)?.target,
|
|
3001
3057
|
findPayloadForElement: (view, element) => findWikilinkForElement(view, element)?.target,
|
|
@@ -3442,6 +3498,32 @@ function wikiEmbedBasename(target) {
|
|
|
3442
3498
|
|
|
3443
3499
|
// src/extensions/image.ts
|
|
3444
3500
|
/**
|
|
3501
|
+
* The menu button of an image or card, handled by `defineEmbedMenuHandler`.
|
|
3502
|
+
* It stays out of the tab order: `Mod-/` opens the menu from the keyboard.
|
|
3503
|
+
*/
|
|
3504
|
+
function createEmbedMenuButton() {
|
|
3505
|
+
const button = document.createElement("button");
|
|
3506
|
+
button.type = "button";
|
|
3507
|
+
button.className = `${EMBED_MENU_BUTTON_CLASS} ${ICON_BUTTON_CLASS}`;
|
|
3508
|
+
button.tabIndex = -1;
|
|
3509
|
+
button.setAttribute("aria-label", "More");
|
|
3510
|
+
button.setAttribute("aria-haspopup", "menu");
|
|
3511
|
+
button.title = `More (${isApple ? "⌘/" : "Ctrl+/"})`;
|
|
3512
|
+
button.dataset.testid = "embed-menu";
|
|
3513
|
+
const icon = document.createElement("span");
|
|
3514
|
+
icon.className = `${ICON_CLASS} ${getIconClass("ellipsis")}`;
|
|
3515
|
+
icon.setAttribute("aria-hidden", "true");
|
|
3516
|
+
button.appendChild(icon);
|
|
3517
|
+
return button;
|
|
3518
|
+
}
|
|
3519
|
+
/**
|
|
3520
|
+
* The YouTube video saved in a mark's `snapshot` attr, or `null` to fetch it.
|
|
3521
|
+
*/
|
|
3522
|
+
function getSavedYouTubeVideo(snapshot) {
|
|
3523
|
+
const saved = snapshot == null ? void 0 : parsePostEmbedSnapshot(snapshot);
|
|
3524
|
+
return saved?.kind === "youtube-video" ? saved.data : null;
|
|
3525
|
+
}
|
|
3526
|
+
/**
|
|
3445
3527
|
* Show an `src` as-is when it is an http(s) URL, otherwise skip rendering it.
|
|
3446
3528
|
*/
|
|
3447
3529
|
function defaultResolveImageUrl(src) {
|
|
@@ -3566,6 +3648,7 @@ var ImageMarkView = class {
|
|
|
3566
3648
|
#attrs;
|
|
3567
3649
|
#resizableRoot;
|
|
3568
3650
|
#image;
|
|
3651
|
+
#youtube;
|
|
3569
3652
|
#destroyed = false;
|
|
3570
3653
|
constructor(mark, view, options) {
|
|
3571
3654
|
this.#attrs = mark.attrs;
|
|
@@ -3575,9 +3658,9 @@ var ImageMarkView = class {
|
|
|
3575
3658
|
this.#mediaUrlProtocols = options.mediaUrlProtocols;
|
|
3576
3659
|
this.#resolveYouTubeVideo = options.resolveYouTubeVideo ?? defaultResolveYouTubeVideo;
|
|
3577
3660
|
this.#dom = document.createElement("span");
|
|
3578
|
-
this.#dom.className =
|
|
3661
|
+
this.#dom.className = `${IMAGE_VIEW_CLASS} ${ATOM_VIEW_CLASS}`;
|
|
3579
3662
|
this.#contentDOM = document.createElement("span");
|
|
3580
|
-
this.#contentDOM.className =
|
|
3663
|
+
this.#contentDOM.className = `${IMAGE_VIEW_CONTENT_CLASS} ${ATOM_VIEW_CONTENT_CLASS}`;
|
|
3581
3664
|
this.#contentDOM.dataset.testid = "image-source";
|
|
3582
3665
|
for (const [name, value] of Object.entries(NON_PROSE_ATTRS)) this.#contentDOM.setAttribute(name, value);
|
|
3583
3666
|
this.#dom.appendChild(this.#contentDOM);
|
|
@@ -3592,8 +3675,9 @@ var ImageMarkView = class {
|
|
|
3592
3675
|
update(mark) {
|
|
3593
3676
|
const next = mark.attrs;
|
|
3594
3677
|
const previous = this.#attrs;
|
|
3595
|
-
if (next.src !== previous.src
|
|
3678
|
+
if (next.src !== previous.src) return false;
|
|
3596
3679
|
this.#attrs = next;
|
|
3680
|
+
if (this.#youtube && next.snapshot !== previous.snapshot) this.#youtube.data = getSavedYouTubeVideo(next.snapshot);
|
|
3597
3681
|
if (this.#image && next.alt !== previous.alt) this.#image.alt = next.alt;
|
|
3598
3682
|
if (this.#resizableRoot && (next.width !== previous.width || next.height !== previous.height)) {
|
|
3599
3683
|
if (this.#image) applyImageDisplaySize(this.#resizableRoot, this.#image, next.width, next.height);
|
|
@@ -3620,6 +3704,7 @@ var ImageMarkView = class {
|
|
|
3620
3704
|
wrapper.dataset.testid = `${kind}-embed`;
|
|
3621
3705
|
wrapper.dataset.postEmbed = kind;
|
|
3622
3706
|
wrapper.appendChild(this.#buildPostEmbed(kind, src));
|
|
3707
|
+
wrapper.appendChild(createEmbedMenuButton());
|
|
3623
3708
|
this.#insertPreview(wrapper);
|
|
3624
3709
|
return;
|
|
3625
3710
|
}
|
|
@@ -3653,7 +3738,7 @@ var ImageMarkView = class {
|
|
|
3653
3738
|
}
|
|
3654
3739
|
#createWrapper() {
|
|
3655
3740
|
const wrapper = document.createElement("span");
|
|
3656
|
-
wrapper.className =
|
|
3741
|
+
wrapper.className = `${IMAGE_VIEW_PREVIEW_CLASS} ${ATOM_VIEW_PREVIEW_CLASS}`;
|
|
3657
3742
|
return wrapper;
|
|
3658
3743
|
}
|
|
3659
3744
|
/**
|
|
@@ -3671,13 +3756,13 @@ var ImageMarkView = class {
|
|
|
3671
3756
|
const wrapper = this.#createWrapper();
|
|
3672
3757
|
wrapper.dataset.testid = "image-preview";
|
|
3673
3758
|
wrapper.appendChild(this.#buildResizableImage(url));
|
|
3759
|
+
wrapper.appendChild(createEmbedMenuButton());
|
|
3674
3760
|
return wrapper;
|
|
3675
3761
|
}
|
|
3676
3762
|
/**
|
|
3677
3763
|
* Resolve X cards from their URL; YouTube cards may reuse a saved snapshot.
|
|
3678
3764
|
*/
|
|
3679
3765
|
#buildPostEmbed(kind, src) {
|
|
3680
|
-
const saved = this.#attrs.snapshot == null ? void 0 : parsePostEmbedSnapshot(this.#attrs.snapshot);
|
|
3681
3766
|
if (kind === "x-post") {
|
|
3682
3767
|
registerXPost();
|
|
3683
3768
|
const element = document.createElement("meowdown-embed-x");
|
|
@@ -3686,13 +3771,17 @@ var ImageMarkView = class {
|
|
|
3686
3771
|
element.url = src;
|
|
3687
3772
|
return element;
|
|
3688
3773
|
}
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3774
|
+
if (kind === "youtube-video") {
|
|
3775
|
+
registerYouTubeVideo();
|
|
3776
|
+
const element = document.createElement("meowdown-embed-youtube");
|
|
3777
|
+
element.playback = "inline";
|
|
3778
|
+
element.data = getSavedYouTubeVideo(this.#attrs.snapshot);
|
|
3779
|
+
element.resolver = this.#persisting(kind, this.#resolveYouTubeVideo);
|
|
3780
|
+
element.url = src;
|
|
3781
|
+
this.#youtube = element;
|
|
3782
|
+
return this.#buildResizableVideo(element);
|
|
3783
|
+
}
|
|
3784
|
+
throw new Error(`Unexpected embed kind ${kind}`);
|
|
3696
3785
|
}
|
|
3697
3786
|
/**
|
|
3698
3787
|
* Wrap a resolver so its first valid answer is persisted under `kind`.
|
|
@@ -3722,12 +3811,12 @@ var ImageMarkView = class {
|
|
|
3722
3811
|
registerResizableRootElement();
|
|
3723
3812
|
registerResizableHandleElement();
|
|
3724
3813
|
const root = document.createElement("prosekit-resizable-root");
|
|
3725
|
-
root.className =
|
|
3814
|
+
root.className = EMBED_RESIZABLE_CLASS;
|
|
3726
3815
|
root.dataset.testid = "embed-resizable";
|
|
3727
3816
|
applySize(root, this.#attrs.width, null);
|
|
3728
3817
|
root.appendChild(element);
|
|
3729
3818
|
const handle = document.createElement("prosekit-resizable-handle");
|
|
3730
|
-
handle.className =
|
|
3819
|
+
handle.className = IMAGE_RESIZE_HANDLE_CLASS;
|
|
3731
3820
|
handle.setAttribute("position", "bottom-right");
|
|
3732
3821
|
handle.addEventListener("click", (event) => event.stopPropagation());
|
|
3733
3822
|
root.appendChild(handle);
|
|
@@ -3748,12 +3837,12 @@ var ImageMarkView = class {
|
|
|
3748
3837
|
registerResizableRootElement();
|
|
3749
3838
|
registerResizableHandleElement();
|
|
3750
3839
|
const root = document.createElement("prosekit-resizable-root");
|
|
3751
|
-
root.className =
|
|
3840
|
+
root.className = IMAGE_RESIZABLE_CLASS;
|
|
3752
3841
|
root.dataset.testid = "image-resizable";
|
|
3753
3842
|
root.setAttribute("data-loading", "");
|
|
3754
3843
|
applySize(root, this.#attrs.width, this.#attrs.height);
|
|
3755
3844
|
const handle = document.createElement("prosekit-resizable-handle");
|
|
3756
|
-
handle.className =
|
|
3845
|
+
handle.className = IMAGE_RESIZE_HANDLE_CLASS;
|
|
3757
3846
|
handle.setAttribute("position", "bottom-right");
|
|
3758
3847
|
handle.addEventListener("click", (event) => event.stopPropagation());
|
|
3759
3848
|
root.appendChild(handle);
|
|
@@ -4929,10 +5018,10 @@ function defineMdImage() {
|
|
|
4929
5018
|
},
|
|
4930
5019
|
toDOM: () => [
|
|
4931
5020
|
"span",
|
|
4932
|
-
{ class:
|
|
5021
|
+
{ class: IMAGE_MARK_CLASS },
|
|
4933
5022
|
0
|
|
4934
5023
|
],
|
|
4935
|
-
parseDOM: [{ tag:
|
|
5024
|
+
parseDOM: [{ tag: `span.${IMAGE_MARK_CLASS}` }]
|
|
4936
5025
|
});
|
|
4937
5026
|
}
|
|
4938
5027
|
/**
|
|
@@ -4944,10 +5033,10 @@ function defineMdMark() {
|
|
|
4944
5033
|
inclusive: false,
|
|
4945
5034
|
toDOM: () => [
|
|
4946
5035
|
"span",
|
|
4947
|
-
{ class:
|
|
5036
|
+
{ class: SYNTAX_MARK_CLASS },
|
|
4948
5037
|
0
|
|
4949
5038
|
],
|
|
4950
|
-
parseDOM: [{ tag:
|
|
5039
|
+
parseDOM: [{ tag: `span.${SYNTAX_MARK_CLASS}` }]
|
|
4951
5040
|
});
|
|
4952
5041
|
}
|
|
4953
5042
|
function defineMdEm() {
|
|
@@ -4983,7 +5072,7 @@ function defineMdLinkText() {
|
|
|
4983
5072
|
toDOM: (mark) => [
|
|
4984
5073
|
"a",
|
|
4985
5074
|
{
|
|
4986
|
-
class:
|
|
5075
|
+
class: LINK_TEXT_MARK_CLASS,
|
|
4987
5076
|
href: mark.attrs.href
|
|
4988
5077
|
},
|
|
4989
5078
|
0
|
|
@@ -5003,12 +5092,12 @@ function defineMdLinkUri() {
|
|
|
5003
5092
|
toDOM: () => [
|
|
5004
5093
|
"span",
|
|
5005
5094
|
{
|
|
5006
|
-
class:
|
|
5095
|
+
class: LINK_URI_MARK_CLASS,
|
|
5007
5096
|
...NON_PROSE_ATTRS
|
|
5008
5097
|
},
|
|
5009
5098
|
0
|
|
5010
5099
|
],
|
|
5011
|
-
parseDOM: [{ tag:
|
|
5100
|
+
parseDOM: [{ tag: `span.${LINK_URI_MARK_CLASS}` }]
|
|
5012
5101
|
});
|
|
5013
5102
|
}
|
|
5014
5103
|
function defineMdLinkTitle() {
|
|
@@ -5017,10 +5106,10 @@ function defineMdLinkTitle() {
|
|
|
5017
5106
|
inclusive: false,
|
|
5018
5107
|
toDOM: () => [
|
|
5019
5108
|
"span",
|
|
5020
|
-
{ class:
|
|
5109
|
+
{ class: LINK_TITLE_MARK_CLASS },
|
|
5021
5110
|
0
|
|
5022
5111
|
],
|
|
5023
|
-
parseDOM: [{ tag:
|
|
5112
|
+
parseDOM: [{ tag: `span.${LINK_TITLE_MARK_CLASS}` }]
|
|
5024
5113
|
});
|
|
5025
5114
|
}
|
|
5026
5115
|
function defineMdDel() {
|
|
@@ -5046,10 +5135,10 @@ function defineMdTag() {
|
|
|
5046
5135
|
name: "mdTag",
|
|
5047
5136
|
toDOM: () => [
|
|
5048
5137
|
"span",
|
|
5049
|
-
{ class:
|
|
5138
|
+
{ class: TAG_MARK_CLASS },
|
|
5050
5139
|
0
|
|
5051
5140
|
],
|
|
5052
|
-
parseDOM: [{ tag:
|
|
5141
|
+
parseDOM: [{ tag: `span.${TAG_MARK_CLASS}` }]
|
|
5053
5142
|
});
|
|
5054
5143
|
}
|
|
5055
5144
|
/**
|
|
@@ -5065,10 +5154,10 @@ function defineMdWikilink() {
|
|
|
5065
5154
|
},
|
|
5066
5155
|
toDOM: () => [
|
|
5067
5156
|
"span",
|
|
5068
|
-
{ class:
|
|
5157
|
+
{ class: WIKILINK_MARK_CLASS },
|
|
5069
5158
|
0
|
|
5070
5159
|
],
|
|
5071
|
-
parseDOM: [{ tag:
|
|
5160
|
+
parseDOM: [{ tag: `span.${WIKILINK_MARK_CLASS}` }]
|
|
5072
5161
|
});
|
|
5073
5162
|
}
|
|
5074
5163
|
function defineMdFile() {
|
|
@@ -5082,10 +5171,10 @@ function defineMdFile() {
|
|
|
5082
5171
|
},
|
|
5083
5172
|
toDOM: () => [
|
|
5084
5173
|
"span",
|
|
5085
|
-
{ class:
|
|
5174
|
+
{ class: FILE_MARK_CLASS },
|
|
5086
5175
|
0
|
|
5087
5176
|
],
|
|
5088
|
-
parseDOM: [{ tag:
|
|
5177
|
+
parseDOM: [{ tag: `span.${FILE_MARK_CLASS}` }]
|
|
5089
5178
|
});
|
|
5090
5179
|
}
|
|
5091
5180
|
/**
|
|
@@ -5098,10 +5187,10 @@ function defineMdMath() {
|
|
|
5098
5187
|
attrs: { formula: { default: "" } },
|
|
5099
5188
|
toDOM: () => [
|
|
5100
5189
|
"span",
|
|
5101
|
-
{ class:
|
|
5190
|
+
{ class: MATH_MARK_CLASS },
|
|
5102
5191
|
0
|
|
5103
5192
|
],
|
|
5104
|
-
parseDOM: [{ tag:
|
|
5193
|
+
parseDOM: [{ tag: `span.${MATH_MARK_CLASS}` }]
|
|
5105
5194
|
});
|
|
5106
5195
|
}
|
|
5107
5196
|
/**
|
|
@@ -5125,16 +5214,17 @@ function defineMdPack() {
|
|
|
5125
5214
|
revealInHide: { default: null }
|
|
5126
5215
|
},
|
|
5127
5216
|
toDOM: (mark) => {
|
|
5217
|
+
const attrs = mark.attrs;
|
|
5128
5218
|
return [
|
|
5129
5219
|
"span",
|
|
5130
5220
|
{
|
|
5131
|
-
class:
|
|
5132
|
-
"data-key":
|
|
5221
|
+
class: PACK_MARK_CLASS,
|
|
5222
|
+
"data-key": attrs.key
|
|
5133
5223
|
},
|
|
5134
5224
|
0
|
|
5135
5225
|
];
|
|
5136
5226
|
},
|
|
5137
|
-
parseDOM: [{ tag:
|
|
5227
|
+
parseDOM: [{ tag: `span.${PACK_MARK_CLASS}` }]
|
|
5138
5228
|
});
|
|
5139
5229
|
}
|
|
5140
5230
|
function defineInlineMarks() {
|
|
@@ -5508,7 +5598,7 @@ function defineLinkClickHandler(getOnClick) {
|
|
|
5508
5598
|
return defineMarkClickHandler({
|
|
5509
5599
|
key: linkClickKey,
|
|
5510
5600
|
enabled: (state) => !!getOnClick?.(state),
|
|
5511
|
-
selector:
|
|
5601
|
+
selector: `.${LINK_TEXT_MARK_CLASS}`,
|
|
5512
5602
|
preventDefault: true,
|
|
5513
5603
|
findPayloadAt: (state, pos) => getLinkUnitAt(state, pos)?.href,
|
|
5514
5604
|
onClick: (href, event, state) => {
|
|
@@ -6168,7 +6258,7 @@ function computeRevealDecorations(state, mode) {
|
|
|
6168
6258
|
}
|
|
6169
6259
|
if (ranges.length === 0) return;
|
|
6170
6260
|
const decorations = ranges.map((range) => {
|
|
6171
|
-
return Decoration.inline(range.from, range.to, { class:
|
|
6261
|
+
return Decoration.inline(range.from, range.to, { class: SHOW_CLASS });
|
|
6172
6262
|
});
|
|
6173
6263
|
return DecorationSet.create(state.doc, decorations);
|
|
6174
6264
|
}
|
|
@@ -6220,9 +6310,9 @@ var MathMarkView = class {
|
|
|
6220
6310
|
constructor(mark, view) {
|
|
6221
6311
|
this.#formula = mark.attrs.formula;
|
|
6222
6312
|
this.#dom = document.createElement("span");
|
|
6223
|
-
this.#dom.className =
|
|
6313
|
+
this.#dom.className = MATH_VIEW_CLASS;
|
|
6224
6314
|
this.#preview = document.createElement("span");
|
|
6225
|
-
this.#preview.className =
|
|
6315
|
+
this.#preview.className = MATH_VIEW_PREVIEW_CLASS;
|
|
6226
6316
|
this.#preview.dataset.testid = "math-preview";
|
|
6227
6317
|
this.#preview.contentEditable = "false";
|
|
6228
6318
|
this.#preview.addEventListener("mousedown", (event) => {
|
|
@@ -6234,7 +6324,7 @@ var MathMarkView = class {
|
|
|
6234
6324
|
view.focus();
|
|
6235
6325
|
});
|
|
6236
6326
|
this.#contentDOM = document.createElement("span");
|
|
6237
|
-
this.#contentDOM.className =
|
|
6327
|
+
this.#contentDOM.className = MATH_VIEW_CONTENT_CLASS;
|
|
6238
6328
|
for (const [name, value] of Object.entries(NON_PROSE_ATTRS)) this.#contentDOM.setAttribute(name, value);
|
|
6239
6329
|
this.#dom.appendChild(this.#preview);
|
|
6240
6330
|
this.#dom.appendChild(this.#contentDOM);
|
|
@@ -6643,7 +6733,7 @@ const pendingReplacementPlugin = new Plugin({
|
|
|
6643
6733
|
props: { decorations: guardDecorations((state) => {
|
|
6644
6734
|
const pending = getPendingReplacement(state);
|
|
6645
6735
|
if (!pending || pending.from >= pending.to) return null;
|
|
6646
|
-
return DecorationSet.create(state.doc, [Decoration.inline(pending.from, pending.to, { class:
|
|
6736
|
+
return DecorationSet.create(state.doc, [Decoration.inline(pending.from, pending.to, { class: PENDING_REPLACEMENT_CLASS })]);
|
|
6647
6737
|
}) }
|
|
6648
6738
|
});
|
|
6649
6739
|
function startPendingReplacement(options) {
|
|
@@ -6896,10 +6986,6 @@ function findAtomEdgeRect(view, pos, side) {
|
|
|
6896
6986
|
return side === 1 ? fragments[0] : fragments[fragments.length - 1];
|
|
6897
6987
|
}
|
|
6898
6988
|
}
|
|
6899
|
-
function findAtomPreviewElement(view, insidePos) {
|
|
6900
|
-
const { node } = view.domAtPos(insidePos, 0);
|
|
6901
|
-
return (node instanceof Element ? node : node.parentElement)?.closest(".md-atom-view")?.querySelector(".md-atom-view-preview") ?? void 0;
|
|
6902
|
-
}
|
|
6903
6989
|
/**
|
|
6904
6990
|
* The caret rect for scroll targeting: the head-anchored subset of the
|
|
6905
6991
|
* geometry the virtual caret draws. Skips the native-selection measurement
|
|
@@ -7288,19 +7374,19 @@ function createWikilinkMarkView() {
|
|
|
7288
7374
|
return (mark) => {
|
|
7289
7375
|
const attrs = mark.attrs;
|
|
7290
7376
|
const dom = document.createElement("span");
|
|
7291
|
-
dom.className =
|
|
7377
|
+
dom.className = `${WIKILINK_VIEW_CLASS} ${ATOM_VIEW_CLASS}`;
|
|
7292
7378
|
const preview = document.createElement("span");
|
|
7293
|
-
preview.className =
|
|
7379
|
+
preview.className = `${WIKILINK_VIEW_PREVIEW_CLASS} ${ATOM_VIEW_PREVIEW_CLASS}`;
|
|
7294
7380
|
preview.contentEditable = "false";
|
|
7295
7381
|
preview.dataset.testid = "wikilink";
|
|
7296
7382
|
dom.appendChild(preview);
|
|
7297
7383
|
const label = document.createElement("span");
|
|
7298
|
-
label.className =
|
|
7384
|
+
label.className = WIKILINK_VIEW_LABEL_CLASS;
|
|
7299
7385
|
label.contentEditable = "false";
|
|
7300
7386
|
label.textContent = attrs.display || attrs.target;
|
|
7301
7387
|
preview.appendChild(label);
|
|
7302
7388
|
const contentDOM = document.createElement("span");
|
|
7303
|
-
contentDOM.className =
|
|
7389
|
+
contentDOM.className = `${WIKILINK_VIEW_CONTENT_CLASS} ${ATOM_VIEW_CONTENT_CLASS}`;
|
|
7304
7390
|
dom.appendChild(contentDOM);
|
|
7305
7391
|
return {
|
|
7306
7392
|
dom,
|
|
@@ -7367,7 +7453,7 @@ function defineEditorExtensionImpl(options) {
|
|
|
7367
7453
|
strategy: "doc"
|
|
7368
7454
|
}), defineReadonly((state) => !!getEditorConfig(state).readOnly), defineViewAttributes((state) => {
|
|
7369
7455
|
const config = getEditorConfig(state);
|
|
7370
|
-
const attributes = { class:
|
|
7456
|
+
const attributes = { class: CONTENT_CLASS };
|
|
7371
7457
|
if (config.editorClassName) attributes.class += ` ${config.editorClassName}`;
|
|
7372
7458
|
if (config.spellCheck != null) attributes.spellcheck = String(config.spellCheck);
|
|
7373
7459
|
if (config.writingSuggestions != null) attributes.writingsuggestions = String(config.writingSuggestions);
|
|
@@ -7539,6 +7625,92 @@ const codeBlockLanguages = languages.map((language) => ({
|
|
|
7539
7625
|
value: language.name.toLowerCase()
|
|
7540
7626
|
})).filter((language) => !excludeLanguages.has(language.label)).concat(extraLanguages).sort((a, b) => a.label.localeCompare(b.label));
|
|
7541
7627
|
|
|
7628
|
+
// src/extensions/embed-menu.ts
|
|
7629
|
+
/**
|
|
7630
|
+
* The image or card that the selection covers exactly, as arrow keys select
|
|
7631
|
+
* it.
|
|
7632
|
+
*/
|
|
7633
|
+
function getSelectedEmbed(state) {
|
|
7634
|
+
const { from, to, empty } = state.selection;
|
|
7635
|
+
if (empty) return;
|
|
7636
|
+
const embed = findImageAt(state, from);
|
|
7637
|
+
return embed && embed.from === from && embed.to === to ? embed : void 0;
|
|
7638
|
+
}
|
|
7639
|
+
/**
|
|
7640
|
+
* Replace the X post or YouTube card at `pos` with an ordinary link and put
|
|
7641
|
+
* the caret after it. A card without alt text becomes its bare URL, which
|
|
7642
|
+
* renders as an autolink; otherwise the `` source loses its
|
|
7643
|
+
* `!`. The trailing magic comment (size and saved snapshot) is dropped. Images
|
|
7644
|
+
* and `![[target]]` embeds are not converted.
|
|
7645
|
+
*/
|
|
7646
|
+
function convertEmbedToLink(pos) {
|
|
7647
|
+
return (state, dispatch) => {
|
|
7648
|
+
const embed = findImageAt(state, pos);
|
|
7649
|
+
if (!embed || embed.kind === "image" || embed.syntax != null) return false;
|
|
7650
|
+
if (dispatch) {
|
|
7651
|
+
const source = stripMagicComment(state.doc.textBetween(embed.from, embed.to));
|
|
7652
|
+
dispatch(replaceEmbed(state, embed, embed.alt ? source.slice(1) : embed.src));
|
|
7653
|
+
}
|
|
7654
|
+
return true;
|
|
7655
|
+
};
|
|
7656
|
+
}
|
|
7657
|
+
/**
|
|
7658
|
+
* Delete the image or card at `pos`, with its source and magic comment, and
|
|
7659
|
+
* put the caret where it was.
|
|
7660
|
+
*/
|
|
7661
|
+
function deleteEmbed(pos) {
|
|
7662
|
+
return (state, dispatch) => {
|
|
7663
|
+
const embed = findImageAt(state, pos);
|
|
7664
|
+
if (!embed) return false;
|
|
7665
|
+
if (dispatch) dispatch(replaceEmbed(state, embed, ""));
|
|
7666
|
+
return true;
|
|
7667
|
+
};
|
|
7668
|
+
}
|
|
7669
|
+
/**
|
|
7670
|
+
* Replace the source of `embed` with `text`, which may be empty, and put the
|
|
7671
|
+
* caret after the text.
|
|
7672
|
+
*/
|
|
7673
|
+
function replaceEmbed(state, embed, text) {
|
|
7674
|
+
const { from, to } = embed;
|
|
7675
|
+
const tr = text ? state.tr.insertText(text, from, to) : state.tr.delete(from, to);
|
|
7676
|
+
return tr.setSelection(TextSelection.create(tr.doc, from + text.length)).scrollIntoView();
|
|
7677
|
+
}
|
|
7678
|
+
/**
|
|
7679
|
+
* The menu button of the card at `pos`, if the card is rendered.
|
|
7680
|
+
*/
|
|
7681
|
+
function getEmbedMenuButton(view, pos) {
|
|
7682
|
+
return findAtomViewAt(view, pos + 1)?.querySelector(`.${EMBED_MENU_BUTTON_CLASS}`);
|
|
7683
|
+
}
|
|
7684
|
+
function getButtonTarget(target) {
|
|
7685
|
+
if (!isElementLike(target)) return;
|
|
7686
|
+
return target.closest(`.${EMBED_MENU_BUTTON_CLASS}`);
|
|
7687
|
+
}
|
|
7688
|
+
/**
|
|
7689
|
+
* Call `onOpen` when the user clicks the menu button of an image, X post card,
|
|
7690
|
+
* or YouTube card, or presses `Mod-/` with one selected.
|
|
7691
|
+
*/
|
|
7692
|
+
function defineEmbedMenuHandler(onOpen) {
|
|
7693
|
+
const openSelectedEmbed = (state, dispatch, view) => {
|
|
7694
|
+
const embed = getSelectedEmbed(state);
|
|
7695
|
+
const button = embed && view && getEmbedMenuButton(view, embed.from);
|
|
7696
|
+
if (!embed || !button) return false;
|
|
7697
|
+
if (dispatch) onOpen(embed, button);
|
|
7698
|
+
return true;
|
|
7699
|
+
};
|
|
7700
|
+
return union(defineDOMEventHandler("mousedown", (_view, event) => {
|
|
7701
|
+
if (!getButtonTarget(event.target)) return false;
|
|
7702
|
+
event.preventDefault();
|
|
7703
|
+
return true;
|
|
7704
|
+
}), defineDOMEventHandler("click", (view, event) => {
|
|
7705
|
+
const button = getButtonTarget(event.target);
|
|
7706
|
+
if (!button) return false;
|
|
7707
|
+
event.preventDefault();
|
|
7708
|
+
const embed = findImageForElement(view, button);
|
|
7709
|
+
if (embed) onOpen(embed, button);
|
|
7710
|
+
return true;
|
|
7711
|
+
}), defineKeymap({ "Mod-/": openSelectedEmbed }));
|
|
7712
|
+
}
|
|
7713
|
+
|
|
7542
7714
|
// src/extensions/key-bindings.ts
|
|
7543
7715
|
/**
|
|
7544
7716
|
* Human-readable descriptions of the editor's formatting and heading shortcuts.
|
|
@@ -7558,7 +7730,7 @@ const EDITOR_KEY_BINDINGS = {
|
|
|
7558
7730
|
"Mod-Alt-5": "Heading 5",
|
|
7559
7731
|
"Mod-Alt-6": "Heading 6",
|
|
7560
7732
|
"Mod-.": "Fold or unfold a bullet",
|
|
7561
|
-
"Mod-/": "Open the code block menu",
|
|
7733
|
+
"Mod-/": "Open the code block or embed menu",
|
|
7562
7734
|
"Shift-Enter": "Insert a line break, or leave a code block from its end",
|
|
7563
7735
|
"Mod-Enter": "Follow the link under the caret, or cycle a checkbox task",
|
|
7564
7736
|
"Mod-Shift-Enter": "Cycle a circle checkbox task",
|
|
@@ -7827,7 +7999,7 @@ const CLOSE_DELAY$1 = 100;
|
|
|
7827
7999
|
function defineLinkHoverHandler(onHoverChange, { canLeave } = {}) {
|
|
7828
8000
|
return defineMarkHoverHandler({
|
|
7829
8001
|
key: linkHoverKey,
|
|
7830
|
-
selector:
|
|
8002
|
+
selector: `.${LINK_TEXT_MARK_CLASS}`,
|
|
7831
8003
|
openDelay: OPEN_DELAY$1,
|
|
7832
8004
|
closeDelay: CLOSE_DELAY$1,
|
|
7833
8005
|
tap: true,
|
|
@@ -7891,9 +8063,9 @@ var VirtualCaretView = class {
|
|
|
7891
8063
|
this.#view = view;
|
|
7892
8064
|
this.#document = view.dom.ownerDocument;
|
|
7893
8065
|
this.#layer = layer;
|
|
7894
|
-
this.#layer.classList.add(
|
|
8066
|
+
this.#layer.classList.add(VIRTUAL_CARET_LAYER_CLASS);
|
|
7895
8067
|
this.#caret = this.#layer.appendChild(this.#document.createElement("div"));
|
|
7896
|
-
this.#caret.className =
|
|
8068
|
+
this.#caret.className = VIRTUAL_CARET_CLASS;
|
|
7897
8069
|
this.#caret.dataset.testid = "virtual-caret";
|
|
7898
8070
|
this.#document.addEventListener("selectionchange", this.#requestReposition);
|
|
7899
8071
|
this.#unsubscribeModality = onIsTouchInputChange(this.#requestReposition);
|
|
@@ -7917,7 +8089,7 @@ var VirtualCaretView = class {
|
|
|
7917
8089
|
this.#view.dom.removeEventListener("blur", this.#handleBlur);
|
|
7918
8090
|
this.#resizeObserver?.disconnect();
|
|
7919
8091
|
this.#caret.remove();
|
|
7920
|
-
this.#layer.classList.remove(
|
|
8092
|
+
this.#layer.classList.remove(VIRTUAL_CARET_LAYER_CLASS);
|
|
7921
8093
|
delete this.#layer.dataset.focused;
|
|
7922
8094
|
this.#view.dom.removeAttribute(DATA_ATTRIBUTE);
|
|
7923
8095
|
}
|
|
@@ -8044,7 +8216,7 @@ const CLOSE_DELAY = 100;
|
|
|
8044
8216
|
function defineWikilinkHoverHandler(onHoverChange, openDelay = OPEN_DELAY, closeDelay = CLOSE_DELAY) {
|
|
8045
8217
|
return defineMarkHoverHandler({
|
|
8046
8218
|
key: wikilinkHoverKey,
|
|
8047
|
-
selector:
|
|
8219
|
+
selector: `.${WIKILINK_VIEW_PREVIEW_CLASS}`,
|
|
8048
8220
|
openDelay,
|
|
8049
8221
|
closeDelay,
|
|
8050
8222
|
tap: false,
|
|
@@ -8148,4 +8320,4 @@ function getVirtualElementFromRange(view, range) {
|
|
|
8148
8320
|
};
|
|
8149
8321
|
}
|
|
8150
8322
|
|
|
8151
|
-
export { EDITOR_KEY_BINDINGS, Priority, buildFileMarkdown, codeBlockLanguages, collectReferenceDefinitions, defaultResolveImageUrl, defaultResolveXPost, defaultResolveYouTubeVideo, defineBackspaceEmptyFirstBlock, defineBulletAfterHeading, defineCodeBlockPreviewPlugin, defineCodeBlockSyntaxHighlight, defineEditorExtension, defineEmbedPaste, defineExitBoundaryHandler, defineFileClickHandler, defineFilePaste, defineFileView, defineFollowLinkHandler, defineHTMLComment, defineImage, defineImageClickHandler, defineLinkClickHandler, defineLinkCommands, defineLinkEditKeymap, defineLinkHoverHandler, defineLinkPaste, defineMath, definePendingReplacementHandler, definePlaceholder, defineReadonly, defineSearchStatusHandler, defineSubstitution, defineTagClickHandler, defineViewAttributes, defineVirtualCaret, defineWikilinkClickHandler, defineWikilinkHoverHandler, defineWikilinkTrigger, defineXPostMediaClickHandler, defineYouTubeVideoClickHandler, dismissLinkHover, docToMarkdown, docToParagraphMarkdown, formatFileSize, formatSizedWikiEmbed, getCodeTokens, getEditorConfig, getFileKind, getIsComposing, getLinkText, getLinkUnitAt, getMarkBuilders, getPendingReplacement, getSearchStatus, getSelectedText, getTableColumnAlign, getTextblockDisplayText, getVirtualElementFromRange, inlineTextToMarkChunks, inlineTextToMarkChunksWithContext, insertLink, isCodeBlockPreviewHiddenDecoration, isLinkTextForHref, isMarkOfType, isModEvent, isNodeOfType, isReferenceDefinitionNode, isSelectionInTableCell, loadKaTeX, markdownToDoc, normalizeHref, paragraphMarkdownToDoc, parsePostEmbedSnapshot, parseWikiEmbed, removeLink, renderMathInto, updateEditorConfig, updateLink, wikiEmbedBasename, withPriority };
|
|
8323
|
+
export { EDITOR_KEY_BINDINGS, Priority, buildFileMarkdown, codeBlockLanguages, collectReferenceDefinitions, convertEmbedToLink, defaultResolveImageUrl, defaultResolveXPost, defaultResolveYouTubeVideo, defineBackspaceEmptyFirstBlock, defineBulletAfterHeading, defineCodeBlockPreviewPlugin, defineCodeBlockSyntaxHighlight, defineEditorExtension, defineEmbedMenuHandler, defineEmbedPaste, defineExitBoundaryHandler, defineFileClickHandler, defineFilePaste, defineFileView, defineFollowLinkHandler, defineHTMLComment, defineImage, defineImageClickHandler, defineLinkClickHandler, defineLinkCommands, defineLinkEditKeymap, defineLinkHoverHandler, defineLinkPaste, defineMath, definePendingReplacementHandler, definePlaceholder, defineReadonly, defineSearchStatusHandler, defineSubstitution, defineTagClickHandler, defineViewAttributes, defineVirtualCaret, defineWikilinkClickHandler, defineWikilinkHoverHandler, defineWikilinkTrigger, defineXPostMediaClickHandler, defineYouTubeVideoClickHandler, deleteBlock, deleteEmbed, dismissLinkHover, docToMarkdown, docToParagraphMarkdown, formatFileSize, formatSizedWikiEmbed, getCodeTokens, getEditorConfig, getFileKind, getIsComposing, getLinkText, getLinkUnitAt, getMarkBuilders, getPendingReplacement, getSearchStatus, getSelectedText, getTableColumnAlign, getTextblockDisplayText, getVirtualElementFromRange, inlineTextToMarkChunks, inlineTextToMarkChunksWithContext, insertLink, insertMarkdown, insertTrigger, isCodeBlockPreviewHiddenDecoration, isLinkTextForHref, isMarkOfType, isModEvent, isNodeOfType, isReferenceDefinitionNode, isSelectionInTableCell, loadKaTeX, markdownToDoc, normalizeHref, paragraphMarkdownToDoc, parsePostEmbedSnapshot, parseWikiEmbed, removeLink, renderMathInto, scrollIntoView, selectText, selectTextBetween, turnIntoText, updateEditorConfig, updateLink, wikiEmbedBasename, withPriority };
|