@milkdown/plugin-slash 7.5.0 → 7.5.9
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/lib/index.es.js +57 -51
- package/lib/index.es.js.map +1 -1
- package/lib/slash-plugin.d.ts +4 -1
- package/lib/slash-plugin.d.ts.map +1 -1
- package/lib/slash-provider.d.ts +3 -0
- package/lib/slash-provider.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/slash-plugin.ts +8 -2
- package/src/slash-provider.ts +60 -32
package/lib/index.es.js
CHANGED
|
@@ -1,23 +1,21 @@
|
|
|
1
|
-
var
|
|
2
|
-
|
|
3
|
-
throw TypeError("Cannot " + t);
|
|
1
|
+
var H = (s) => {
|
|
2
|
+
throw TypeError(s);
|
|
4
3
|
};
|
|
5
|
-
var
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
key: new M(`${s}_SLASH`),
|
|
4
|
+
var C = (s, e, t) => e.has(s) || H("Cannot " + t);
|
|
5
|
+
var o = (s, e, t) => (C(s, e, "read from private field"), t ? t.call(s) : e.get(s)), i = (s, e, t) => e.has(s) ? H("Cannot add the same private member more than once") : e instanceof WeakSet ? e.add(s) : e.set(s, t), a = (s, e, t, n) => (C(s, e, "write to private field"), n ? n.call(s, t) : e.set(s, t), t), A = (s, e, t) => (C(s, e, "access private method"), t);
|
|
6
|
+
import { Plugin as N, PluginKey as T, TextSelection as _ } from "@milkdown/prose/state";
|
|
7
|
+
import { $ctx as F, $prose as I } from "@milkdown/utils";
|
|
8
|
+
import { findParentNode as R, posToDOMRect as U } from "@milkdown/prose";
|
|
9
|
+
import q from "lodash.debounce";
|
|
10
|
+
import { computePosition as K, flip as L, offset as j } from "@floating-ui/dom";
|
|
11
|
+
function W(s) {
|
|
12
|
+
const e = F(
|
|
13
|
+
{},
|
|
14
|
+
`${s}_SLASH_SPEC`
|
|
15
|
+
), t = I((r) => {
|
|
16
|
+
const m = r.get(e.key);
|
|
17
|
+
return new N({
|
|
18
|
+
key: new T(`${s}_SLASH`),
|
|
21
19
|
...m
|
|
22
20
|
});
|
|
23
21
|
}), n = [e, t];
|
|
@@ -29,66 +27,74 @@ function J(s) {
|
|
|
29
27
|
displayName: `Prose<slash>|${s}`
|
|
30
28
|
}, n;
|
|
31
29
|
}
|
|
32
|
-
var
|
|
33
|
-
class
|
|
30
|
+
var c, d, u, f, h, g, y, S, x, B;
|
|
31
|
+
class X {
|
|
34
32
|
constructor(e) {
|
|
33
|
+
i(this, x);
|
|
34
|
+
i(this, c);
|
|
35
35
|
/// @internal
|
|
36
|
-
|
|
37
|
-
a(this, l, void 0);
|
|
36
|
+
i(this, d);
|
|
38
37
|
/// @internal
|
|
39
|
-
|
|
38
|
+
i(this, u);
|
|
40
39
|
/// @internal
|
|
41
|
-
|
|
40
|
+
i(this, f);
|
|
42
41
|
/// @internal
|
|
43
|
-
|
|
42
|
+
i(this, h);
|
|
43
|
+
/// @internal
|
|
44
|
+
i(this, g);
|
|
44
45
|
/// The offset to get the block. Default is 0.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
i(this, y);
|
|
47
|
+
i(this, S);
|
|
48
|
+
a(this, c, !1), this.onShow = () => {
|
|
48
49
|
}, this.onHide = () => {
|
|
49
|
-
},
|
|
50
|
-
var
|
|
51
|
-
const { state:
|
|
52
|
-
if (o(this,
|
|
53
|
-
|
|
54
|
-
if (!o(this, d).call(this, t, n)) {
|
|
50
|
+
}, a(this, S, (t, n) => {
|
|
51
|
+
var E;
|
|
52
|
+
const { state: r, composing: m } = t, { selection: l, doc: $ } = r, { ranges: k } = l, w = Math.min(...k.map((p) => p.$from.pos)), P = Math.max(...k.map((p) => p.$to.pos)), b = n && n.doc.eq($) && n.selection.eq(l);
|
|
53
|
+
if (o(this, c) || ((E = t.dom.parentElement) == null || E.appendChild(this.element), a(this, c, !0)), m || b) return;
|
|
54
|
+
if (!o(this, g).call(this, t, n)) {
|
|
55
55
|
this.hide();
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
58
|
-
|
|
59
|
-
getBoundingClientRect: () =>
|
|
58
|
+
K({
|
|
59
|
+
getBoundingClientRect: () => U(t, w, P)
|
|
60
60
|
}, this.element, {
|
|
61
61
|
placement: "bottom-start",
|
|
62
|
-
middleware: [
|
|
63
|
-
|
|
62
|
+
middleware: [L(), j(o(this, y)), ...o(this, d)],
|
|
63
|
+
...o(this, u)
|
|
64
|
+
}).then(({ x: p, y: M }) => {
|
|
64
65
|
Object.assign(this.element.style, {
|
|
65
66
|
left: `${p}px`,
|
|
66
|
-
top: `${
|
|
67
|
+
top: `${M}px`
|
|
67
68
|
});
|
|
68
69
|
}), this.show();
|
|
69
70
|
}), this.update = (t, n) => {
|
|
70
|
-
|
|
71
|
-
}, this.getContent = (t, n = (
|
|
72
|
-
const { selection:
|
|
73
|
-
if (!(
|
|
74
|
-
return
|
|
71
|
+
q(o(this, S), o(this, f))(t, n);
|
|
72
|
+
}, this.getContent = (t, n = (r) => r.type.name === "paragraph") => {
|
|
73
|
+
const { selection: r } = t.state, { empty: m, $from: l } = r, $ = t.state.selection instanceof _, k = this.element.contains(document.activeElement), w = !t.hasFocus() && !k, P = !t.editable, O = !R(n)(t.state.selection);
|
|
74
|
+
if (!(w || P || !m || !$ || O))
|
|
75
|
+
return l.parent.textBetween(
|
|
76
|
+
Math.max(0, l.parentOffset - 500),
|
|
77
|
+
l.parentOffset,
|
|
78
|
+
void 0,
|
|
79
|
+
""
|
|
80
|
+
);
|
|
75
81
|
}, this.destroy = () => {
|
|
76
82
|
}, this.show = () => {
|
|
77
83
|
this.element.dataset.show = "true", this.onShow();
|
|
78
84
|
}, this.hide = () => {
|
|
79
85
|
this.element.dataset.show = "false", this.onHide();
|
|
80
|
-
}, this.element = e.content,
|
|
86
|
+
}, this.element = e.content, a(this, f, e.debounce ?? 200), a(this, g, e.shouldShow ?? A(this, x, B)), a(this, h, e.trigger ?? "/"), a(this, y, e.offset), a(this, d, e.middleware ?? []), a(this, u, e.floatingUIOptions ?? {});
|
|
81
87
|
}
|
|
82
88
|
}
|
|
83
|
-
|
|
89
|
+
c = new WeakMap(), d = new WeakMap(), u = new WeakMap(), f = new WeakMap(), h = new WeakMap(), g = new WeakMap(), y = new WeakMap(), S = new WeakMap(), x = new WeakSet(), /// @internal
|
|
90
|
+
B = function(e) {
|
|
84
91
|
const t = this.getContent(e);
|
|
85
|
-
if (!t)
|
|
86
|
-
return !1;
|
|
92
|
+
if (!t) return !1;
|
|
87
93
|
const n = t.at(-1);
|
|
88
94
|
return n ? Array.isArray(o(this, h)) ? o(this, h).includes(n) : o(this, h) === n : !1;
|
|
89
95
|
};
|
|
90
96
|
export {
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
X as SlashProvider,
|
|
98
|
+
W as slashFactory
|
|
93
99
|
};
|
|
94
100
|
//# sourceMappingURL=index.es.js.map
|
package/lib/index.es.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.es.js","sources":["../src/slash-plugin.ts","../src/slash-provider.ts"],"sourcesContent":["import type { SliceType } from '@milkdown/ctx'\nimport type { PluginSpec } from '@milkdown/prose/state'\nimport { Plugin, PluginKey } from '@milkdown/prose/state'\nimport type { $Ctx, $Prose } from '@milkdown/utils'\nimport { $ctx, $prose } from '@milkdown/utils'\n\n/// @internal\nexport type SlashPluginSpecId<Id extends string> = `${Id}_SLASH_SPEC`\n\n/// @internal\nexport type SlashPlugin<Id extends string, State = any> = [$Ctx<PluginSpec<State>, SlashPluginSpecId<Id>>, $Prose] & {\n key: SliceType<PluginSpec<State>, SlashPluginSpecId<Id>>\n pluginKey: $Prose['key']\n}\n\n/// Create a slash plugin with a unique id.\nexport function slashFactory<Id extends string, State = any>(id: Id) {\n const slashSpec = $ctx<PluginSpec<State>, SlashPluginSpecId<Id>>({}, `${id}_SLASH_SPEC`)\n const slashPlugin = $prose((ctx) => {\n const spec = ctx.get(slashSpec.key)\n return new Plugin({\n key: new PluginKey(`${id}_SLASH`),\n ...spec,\n })\n })\n const result = [slashSpec, slashPlugin] as SlashPlugin<Id>\n result.key = slashSpec.key\n result.pluginKey = slashPlugin.key\n slashSpec.meta = {\n package: '@milkdown/plugin-slash',\n displayName: `Ctx<slashSpec>|${id}`,\n }\n slashPlugin.meta = {\n package: '@milkdown/plugin-slash',\n displayName: `Prose<slash>|${id}`,\n }\n\n return result\n}\n","import { findParentNode, posToDOMRect } from '@milkdown/prose'\nimport type { EditorState } from '@milkdown/prose/state'\nimport type { Node } from '@milkdown/prose/model'\nimport { TextSelection } from '@milkdown/prose/state'\nimport type { EditorView } from '@milkdown/prose/view'\nimport debounce from 'lodash.debounce'\nimport type { VirtualElement } from '@floating-ui/dom'\nimport { computePosition, flip, offset } from '@floating-ui/dom'\n\n/// Options for slash provider.\nexport interface SlashProviderOptions {\n /// The slash content.\n content: HTMLElement\n /// The debounce time for updating slash, 200ms by default.\n debounce?: number\n /// The function to determine whether the tooltip should be shown.\n shouldShow?: (view: EditorView, prevState?: EditorState) => boolean\n /// The key trigger for shouldShow, '/' by default.\n trigger?: string | string[]\n /// The offset to get the block. Default is 0.\n offset?: number | {\n mainAxis?: number\n crossAxis?: number\n alignmentAxis?: number | null\n }\n}\n\n/// A provider for creating slash.\nexport class SlashProvider {\n /// The root element of the slash.\n element: HTMLElement\n\n /// @internal\n #initialized = false\n\n /// @internal\n readonly #debounce: number\n\n /// @internal\n readonly #trigger: string | string[]\n\n /// @internal\n readonly #shouldShow: (view: EditorView, prevState?: EditorState) => boolean\n\n /// The offset to get the block. Default is 0.\n readonly #offset?: number | {\n mainAxis?: number\n crossAxis?: number\n alignmentAxis?: number | null\n }\n\n /// On show callback.\n onShow = () => {}\n\n /// On hide callback.\n onHide = () => {}\n\n constructor(options: SlashProviderOptions) {\n this.element = options.content\n this.#debounce = options.debounce ?? 200\n this.#shouldShow = options.shouldShow ?? this.#_shouldShow\n this.#trigger = options.trigger ?? '/'\n this.#offset = options.offset\n }\n\n /// @internal\n #onUpdate = (view: EditorView, prevState?: EditorState): void => {\n const { state, composing } = view\n const { selection, doc } = state\n const { ranges } = selection\n const from = Math.min(...ranges.map(range => range.$from.pos))\n const to = Math.max(...ranges.map(range => range.$to.pos))\n const isSame = prevState && prevState.doc.eq(doc) && prevState.selection.eq(selection)\n\n if (!this.#initialized) {\n view.dom.parentElement?.appendChild(this.element)\n this.#initialized = true\n }\n\n if (composing || isSame)\n return\n\n if (!this.#shouldShow(view, prevState)) {\n this.hide()\n return\n }\n\n const virtualEl: VirtualElement = {\n getBoundingClientRect: () => posToDOMRect(view, from, to),\n }\n computePosition(virtualEl, this.element, {\n placement: 'bottom-start',\n middleware: [flip(), offset(this.#offset)],\n })\n .then(({ x, y }) => {\n Object.assign(this.element.style, {\n left: `${x}px`,\n top: `${y}px`,\n })\n })\n\n this.show()\n }\n\n /// @internal\n #_shouldShow(view: EditorView): boolean {\n const currentTextBlockContent = this.getContent(view)\n\n if (!currentTextBlockContent)\n return false\n\n const target = currentTextBlockContent.at(-1)\n\n if (!target)\n return false\n\n return Array.isArray(this.#trigger) ? this.#trigger.includes(target) : this.#trigger === target\n }\n\n /// Update provider state by editor view.\n update = (view: EditorView, prevState?: EditorState): void => {\n const updater = debounce(this.#onUpdate, this.#debounce)\n\n updater(view, prevState)\n }\n\n /// Get the content of the current text block.\n /// Pass the `matchNode` function to determine whether the current node should be matched, by default, it will match the paragraph node.\n getContent = (view: EditorView, matchNode: (node: Node) => boolean = node => node.type.name === 'paragraph'): string | undefined => {\n const { selection } = view.state\n const { empty, $from } = selection\n const isTextBlock = view.state.selection instanceof TextSelection\n\n const isSlashChildren = this.element.contains(document.activeElement)\n\n const notHasFocus = !view.hasFocus() && !isSlashChildren\n\n const isReadonly = !view.editable\n\n const paragraph = findParentNode(matchNode)(view.state.selection)\n\n const isNotInParagraph = !paragraph\n\n if (notHasFocus || isReadonly || !empty || !isTextBlock || isNotInParagraph)\n return\n\n return $from.parent.textBetween(Math.max(0, $from.parentOffset - 500), $from.parentOffset, undefined, '\\uFFFC')\n }\n\n /// Destroy the slash.\n destroy = () => {\n }\n\n /// Show the slash.\n show = () => {\n this.element.dataset.show = 'true'\n this.onShow()\n }\n\n /// Hide the slash.\n hide = () => {\n this.element.dataset.show = 'false'\n this.onHide()\n }\n}\n"],"names":["slashFactory","id","slashSpec","$ctx","slashPlugin","$prose","ctx","spec","Plugin","PluginKey","result","SlashProvider","options","__privateAdd","__shouldShow","_initialized","_debounce","_trigger","_shouldShow","_offset","_onUpdate","__privateSet","view","prevState","state","composing","selection","doc","ranges","from","range","to","isSame","__privateGet","_a","computePosition","posToDOMRect","flip","offset","x","y","debounce","matchNode","node","empty","$from","isTextBlock","TextSelection","isSlashChildren","notHasFocus","isReadonly","isNotInParagraph","findParentNode","__privateMethod","_shouldShow_fn","currentTextBlockContent","target"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.es.js","sources":["../src/slash-plugin.ts","../src/slash-provider.ts"],"sourcesContent":["import type { SliceType } from '@milkdown/ctx'\nimport type { PluginSpec } from '@milkdown/prose/state'\nimport { Plugin, PluginKey } from '@milkdown/prose/state'\nimport type { $Ctx, $Prose } from '@milkdown/utils'\nimport { $ctx, $prose } from '@milkdown/utils'\n\n/// @internal\nexport type SlashPluginSpecId<Id extends string> = `${Id}_SLASH_SPEC`\n\n/// @internal\nexport type SlashPlugin<Id extends string, State = any> = [\n $Ctx<PluginSpec<State>, SlashPluginSpecId<Id>>,\n $Prose,\n] & {\n key: SliceType<PluginSpec<State>, SlashPluginSpecId<Id>>\n pluginKey: $Prose['key']\n}\n\n/// Create a slash plugin with a unique id.\nexport function slashFactory<Id extends string, State = any>(id: Id) {\n const slashSpec = $ctx<PluginSpec<State>, SlashPluginSpecId<Id>>(\n {},\n `${id}_SLASH_SPEC`\n )\n const slashPlugin = $prose((ctx) => {\n const spec = ctx.get(slashSpec.key)\n return new Plugin({\n key: new PluginKey(`${id}_SLASH`),\n ...spec,\n })\n })\n const result = [slashSpec, slashPlugin] as SlashPlugin<Id>\n result.key = slashSpec.key\n result.pluginKey = slashPlugin.key\n slashSpec.meta = {\n package: '@milkdown/plugin-slash',\n displayName: `Ctx<slashSpec>|${id}`,\n }\n slashPlugin.meta = {\n package: '@milkdown/plugin-slash',\n displayName: `Prose<slash>|${id}`,\n }\n\n return result\n}\n","import { findParentNode, posToDOMRect } from '@milkdown/prose'\nimport type { EditorState } from '@milkdown/prose/state'\nimport type { Node } from '@milkdown/prose/model'\nimport { TextSelection } from '@milkdown/prose/state'\nimport type { EditorView } from '@milkdown/prose/view'\nimport debounce from 'lodash.debounce'\nimport type {\n ComputePositionConfig,\n Middleware,\n VirtualElement,\n} from '@floating-ui/dom'\nimport { computePosition, flip, offset } from '@floating-ui/dom'\n\n/// Options for slash provider.\nexport interface SlashProviderOptions {\n /// The slash content.\n content: HTMLElement\n /// The debounce time for updating slash, 200ms by default.\n debounce?: number\n /// The function to determine whether the tooltip should be shown.\n shouldShow?: (view: EditorView, prevState?: EditorState) => boolean\n /// The key trigger for shouldShow, '/' by default.\n trigger?: string | string[]\n /// The offset to get the block. Default is 0.\n offset?:\n | number\n | {\n mainAxis?: number\n crossAxis?: number\n alignmentAxis?: number | null\n }\n /// Other middlewares for floating ui. This will be added after the internal middlewares.\n middleware?: Middleware[]\n /// Options for floating ui. If you pass `middleware` or `placement`, it will override the internal settings.\n floatingUIOptions?: Partial<ComputePositionConfig>\n}\n\n/// A provider for creating slash.\nexport class SlashProvider {\n /// The root element of the slash.\n element: HTMLElement\n\n /// @internal\n #initialized = false\n\n /// @internal\n readonly #middleware: Middleware[]\n\n /// @internal\n readonly #floatingUIOptions: Partial<ComputePositionConfig>\n\n /// @internal\n readonly #debounce: number\n\n /// @internal\n readonly #trigger: string | string[]\n\n /// @internal\n readonly #shouldShow: (view: EditorView, prevState?: EditorState) => boolean\n\n /// The offset to get the block. Default is 0.\n readonly #offset?:\n | number\n | {\n mainAxis?: number\n crossAxis?: number\n alignmentAxis?: number | null\n }\n\n /// On show callback.\n onShow = () => {}\n\n /// On hide callback.\n onHide = () => {}\n\n constructor(options: SlashProviderOptions) {\n this.element = options.content\n this.#debounce = options.debounce ?? 200\n this.#shouldShow = options.shouldShow ?? this.#_shouldShow\n this.#trigger = options.trigger ?? '/'\n this.#offset = options.offset\n this.#middleware = options.middleware ?? []\n this.#floatingUIOptions = options.floatingUIOptions ?? {}\n }\n\n /// @internal\n #onUpdate = (view: EditorView, prevState?: EditorState): void => {\n const { state, composing } = view\n const { selection, doc } = state\n const { ranges } = selection\n const from = Math.min(...ranges.map((range) => range.$from.pos))\n const to = Math.max(...ranges.map((range) => range.$to.pos))\n const isSame =\n prevState && prevState.doc.eq(doc) && prevState.selection.eq(selection)\n\n if (!this.#initialized) {\n view.dom.parentElement?.appendChild(this.element)\n this.#initialized = true\n }\n\n if (composing || isSame) return\n\n if (!this.#shouldShow(view, prevState)) {\n this.hide()\n return\n }\n\n const virtualEl: VirtualElement = {\n getBoundingClientRect: () => posToDOMRect(view, from, to),\n }\n computePosition(virtualEl, this.element, {\n placement: 'bottom-start',\n middleware: [flip(), offset(this.#offset), ...this.#middleware],\n ...this.#floatingUIOptions,\n }).then(({ x, y }) => {\n Object.assign(this.element.style, {\n left: `${x}px`,\n top: `${y}px`,\n })\n })\n\n this.show()\n }\n\n /// @internal\n #_shouldShow(view: EditorView): boolean {\n const currentTextBlockContent = this.getContent(view)\n\n if (!currentTextBlockContent) return false\n\n const target = currentTextBlockContent.at(-1)\n\n if (!target) return false\n\n return Array.isArray(this.#trigger)\n ? this.#trigger.includes(target)\n : this.#trigger === target\n }\n\n /// Update provider state by editor view.\n update = (view: EditorView, prevState?: EditorState): void => {\n const updater = debounce(this.#onUpdate, this.#debounce)\n\n updater(view, prevState)\n }\n\n /// Get the content of the current text block.\n /// Pass the `matchNode` function to determine whether the current node should be matched, by default, it will match the paragraph node.\n getContent = (\n view: EditorView,\n matchNode: (node: Node) => boolean = (node) =>\n node.type.name === 'paragraph'\n ): string | undefined => {\n const { selection } = view.state\n const { empty, $from } = selection\n const isTextBlock = view.state.selection instanceof TextSelection\n\n const isSlashChildren = this.element.contains(document.activeElement)\n\n const notHasFocus = !view.hasFocus() && !isSlashChildren\n\n const isReadonly = !view.editable\n\n const paragraph = findParentNode(matchNode)(view.state.selection)\n\n const isNotInParagraph = !paragraph\n\n if (notHasFocus || isReadonly || !empty || !isTextBlock || isNotInParagraph)\n return\n\n return $from.parent.textBetween(\n Math.max(0, $from.parentOffset - 500),\n $from.parentOffset,\n undefined,\n '\\uFFFC'\n )\n }\n\n /// Destroy the slash.\n destroy = () => {}\n\n /// Show the slash.\n show = () => {\n this.element.dataset.show = 'true'\n this.onShow()\n }\n\n /// Hide the slash.\n hide = () => {\n this.element.dataset.show = 'false'\n this.onHide()\n }\n}\n"],"names":["slashFactory","id","slashSpec","$ctx","slashPlugin","$prose","ctx","spec","Plugin","PluginKey","result","SlashProvider","options","__privateAdd","_SlashProvider_instances","_initialized","_middleware","_floatingUIOptions","_debounce","_trigger","_shouldShow","_offset","_onUpdate","__privateSet","view","prevState","state","composing","selection","doc","ranges","from","range","to","isSame","__privateGet","_a","computePosition","posToDOMRect","flip","offset","x","y","debounce","matchNode","node","empty","$from","isTextBlock","TextSelection","isSlashChildren","notHasFocus","isReadonly","isNotInParagraph","findParentNode","__privateMethod","_shouldShow_fn","currentTextBlockContent","target"],"mappings":"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"}
|
package/lib/slash-plugin.d.ts
CHANGED
|
@@ -2,7 +2,10 @@ import type { SliceType } from '@milkdown/ctx';
|
|
|
2
2
|
import type { PluginSpec } from '@milkdown/prose/state';
|
|
3
3
|
import type { $Ctx, $Prose } from '@milkdown/utils';
|
|
4
4
|
export type SlashPluginSpecId<Id extends string> = `${Id}_SLASH_SPEC`;
|
|
5
|
-
export type SlashPlugin<Id extends string, State = any> = [
|
|
5
|
+
export type SlashPlugin<Id extends string, State = any> = [
|
|
6
|
+
$Ctx<PluginSpec<State>, SlashPluginSpecId<Id>>,
|
|
7
|
+
$Prose
|
|
8
|
+
] & {
|
|
6
9
|
key: SliceType<PluginSpec<State>, SlashPluginSpecId<Id>>;
|
|
7
10
|
pluginKey: $Prose['key'];
|
|
8
11
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slash-plugin.d.ts","sourceRoot":"","sources":["../src/slash-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAEvD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAInD,MAAM,MAAM,iBAAiB,CAAC,EAAE,SAAS,MAAM,IAAI,GAAG,EAAE,aAAa,CAAA;AAGrE,MAAM,MAAM,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,KAAK,GAAG,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"slash-plugin.d.ts","sourceRoot":"","sources":["../src/slash-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAEvD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAInD,MAAM,MAAM,iBAAiB,CAAC,EAAE,SAAS,MAAM,IAAI,GAAG,EAAE,aAAa,CAAA;AAGrE,MAAM,MAAM,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,KAAK,GAAG,GAAG,IAAI;IACxD,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,iBAAiB,CAAC,EAAE,CAAC,CAAC;IAC9C,MAAM;CACP,GAAG;IACF,GAAG,EAAE,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,iBAAiB,CAAC,EAAE,CAAC,CAAC,CAAA;IACxD,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,CAAA;CACzB,CAAA;AAGD,wBAAgB,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,KAAK,GAAG,GAAG,EAAE,EAAE,EAAE,EAAE,wBAyBlE"}
|
package/lib/slash-provider.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { EditorState } from '@milkdown/prose/state';
|
|
2
2
|
import type { Node } from '@milkdown/prose/model';
|
|
3
3
|
import type { EditorView } from '@milkdown/prose/view';
|
|
4
|
+
import type { ComputePositionConfig, Middleware } from '@floating-ui/dom';
|
|
4
5
|
export interface SlashProviderOptions {
|
|
5
6
|
content: HTMLElement;
|
|
6
7
|
debounce?: number;
|
|
@@ -11,6 +12,8 @@ export interface SlashProviderOptions {
|
|
|
11
12
|
crossAxis?: number;
|
|
12
13
|
alignmentAxis?: number | null;
|
|
13
14
|
};
|
|
15
|
+
middleware?: Middleware[];
|
|
16
|
+
floatingUIOptions?: Partial<ComputePositionConfig>;
|
|
14
17
|
}
|
|
15
18
|
export declare class SlashProvider {
|
|
16
19
|
#private;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slash-provider.d.ts","sourceRoot":"","sources":["../src/slash-provider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAA;AAEjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"slash-provider.d.ts","sourceRoot":"","sources":["../src/slash-provider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAA;AAEjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAEtD,OAAO,KAAK,EACV,qBAAqB,EACrB,UAAU,EAEX,MAAM,kBAAkB,CAAA;AAIzB,MAAM,WAAW,oBAAoB;IAEnC,OAAO,EAAE,WAAW,CAAA;IAEpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IAEjB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,WAAW,KAAK,OAAO,CAAA;IAEnE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IAE3B,MAAM,CAAC,EACH,MAAM,GACN;QACE,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAC9B,CAAA;IAEL,UAAU,CAAC,EAAE,UAAU,EAAE,CAAA;IAEzB,iBAAiB,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,CAAA;CACnD;AAGD,qBAAa,aAAa;;IAExB,OAAO,EAAE,WAAW,CAAA;IA8BpB,MAAM,aAAW;IAGjB,MAAM,aAAW;gBAEL,OAAO,EAAE,oBAAoB;IAiEzC,MAAM,SAAU,UAAU,cAAc,WAAW,KAAG,IAAI,CAIzD;IAID,UAAU,SACF,UAAU,cACL,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,KAEjC,MAAM,GAAG,SAAS,CAwBpB;IAGD,OAAO,aAAW;IAGlB,IAAI,aAGH;IAGD,IAAI,aAGH;CACF"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@milkdown/plugin-slash",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "7.5.
|
|
4
|
+
"version": "7.5.9",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -34,13 +34,13 @@
|
|
|
34
34
|
"@types/lodash.debounce": "^4.0.7",
|
|
35
35
|
"lodash.debounce": "^4.0.8",
|
|
36
36
|
"tslib": "^2.5.0",
|
|
37
|
-
"@milkdown/exception": "7.5.
|
|
38
|
-
"@milkdown/utils": "7.5.
|
|
37
|
+
"@milkdown/exception": "7.5.9",
|
|
38
|
+
"@milkdown/utils": "7.5.9"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@milkdown/
|
|
42
|
-
"@milkdown/
|
|
43
|
-
"@milkdown/
|
|
41
|
+
"@milkdown/prose": "7.5.9",
|
|
42
|
+
"@milkdown/core": "7.5.9",
|
|
43
|
+
"@milkdown/ctx": "7.5.9"
|
|
44
44
|
},
|
|
45
45
|
"nx": {
|
|
46
46
|
"targets": {
|
package/src/slash-plugin.ts
CHANGED
|
@@ -8,14 +8,20 @@ import { $ctx, $prose } from '@milkdown/utils'
|
|
|
8
8
|
export type SlashPluginSpecId<Id extends string> = `${Id}_SLASH_SPEC`
|
|
9
9
|
|
|
10
10
|
/// @internal
|
|
11
|
-
export type SlashPlugin<Id extends string, State = any> = [
|
|
11
|
+
export type SlashPlugin<Id extends string, State = any> = [
|
|
12
|
+
$Ctx<PluginSpec<State>, SlashPluginSpecId<Id>>,
|
|
13
|
+
$Prose,
|
|
14
|
+
] & {
|
|
12
15
|
key: SliceType<PluginSpec<State>, SlashPluginSpecId<Id>>
|
|
13
16
|
pluginKey: $Prose['key']
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
/// Create a slash plugin with a unique id.
|
|
17
20
|
export function slashFactory<Id extends string, State = any>(id: Id) {
|
|
18
|
-
const slashSpec = $ctx<PluginSpec<State>, SlashPluginSpecId<Id>>(
|
|
21
|
+
const slashSpec = $ctx<PluginSpec<State>, SlashPluginSpecId<Id>>(
|
|
22
|
+
{},
|
|
23
|
+
`${id}_SLASH_SPEC`
|
|
24
|
+
)
|
|
19
25
|
const slashPlugin = $prose((ctx) => {
|
|
20
26
|
const spec = ctx.get(slashSpec.key)
|
|
21
27
|
return new Plugin({
|
package/src/slash-provider.ts
CHANGED
|
@@ -4,7 +4,11 @@ import type { Node } from '@milkdown/prose/model'
|
|
|
4
4
|
import { TextSelection } from '@milkdown/prose/state'
|
|
5
5
|
import type { EditorView } from '@milkdown/prose/view'
|
|
6
6
|
import debounce from 'lodash.debounce'
|
|
7
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
ComputePositionConfig,
|
|
9
|
+
Middleware,
|
|
10
|
+
VirtualElement,
|
|
11
|
+
} from '@floating-ui/dom'
|
|
8
12
|
import { computePosition, flip, offset } from '@floating-ui/dom'
|
|
9
13
|
|
|
10
14
|
/// Options for slash provider.
|
|
@@ -18,11 +22,17 @@ export interface SlashProviderOptions {
|
|
|
18
22
|
/// The key trigger for shouldShow, '/' by default.
|
|
19
23
|
trigger?: string | string[]
|
|
20
24
|
/// The offset to get the block. Default is 0.
|
|
21
|
-
offset?:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
offset?:
|
|
26
|
+
| number
|
|
27
|
+
| {
|
|
28
|
+
mainAxis?: number
|
|
29
|
+
crossAxis?: number
|
|
30
|
+
alignmentAxis?: number | null
|
|
31
|
+
}
|
|
32
|
+
/// Other middlewares for floating ui. This will be added after the internal middlewares.
|
|
33
|
+
middleware?: Middleware[]
|
|
34
|
+
/// Options for floating ui. If you pass `middleware` or `placement`, it will override the internal settings.
|
|
35
|
+
floatingUIOptions?: Partial<ComputePositionConfig>
|
|
26
36
|
}
|
|
27
37
|
|
|
28
38
|
/// A provider for creating slash.
|
|
@@ -33,6 +43,12 @@ export class SlashProvider {
|
|
|
33
43
|
/// @internal
|
|
34
44
|
#initialized = false
|
|
35
45
|
|
|
46
|
+
/// @internal
|
|
47
|
+
readonly #middleware: Middleware[]
|
|
48
|
+
|
|
49
|
+
/// @internal
|
|
50
|
+
readonly #floatingUIOptions: Partial<ComputePositionConfig>
|
|
51
|
+
|
|
36
52
|
/// @internal
|
|
37
53
|
readonly #debounce: number
|
|
38
54
|
|
|
@@ -43,11 +59,13 @@ export class SlashProvider {
|
|
|
43
59
|
readonly #shouldShow: (view: EditorView, prevState?: EditorState) => boolean
|
|
44
60
|
|
|
45
61
|
/// The offset to get the block. Default is 0.
|
|
46
|
-
readonly #offset?:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
62
|
+
readonly #offset?:
|
|
63
|
+
| number
|
|
64
|
+
| {
|
|
65
|
+
mainAxis?: number
|
|
66
|
+
crossAxis?: number
|
|
67
|
+
alignmentAxis?: number | null
|
|
68
|
+
}
|
|
51
69
|
|
|
52
70
|
/// On show callback.
|
|
53
71
|
onShow = () => {}
|
|
@@ -61,6 +79,8 @@ export class SlashProvider {
|
|
|
61
79
|
this.#shouldShow = options.shouldShow ?? this.#_shouldShow
|
|
62
80
|
this.#trigger = options.trigger ?? '/'
|
|
63
81
|
this.#offset = options.offset
|
|
82
|
+
this.#middleware = options.middleware ?? []
|
|
83
|
+
this.#floatingUIOptions = options.floatingUIOptions ?? {}
|
|
64
84
|
}
|
|
65
85
|
|
|
66
86
|
/// @internal
|
|
@@ -68,17 +88,17 @@ export class SlashProvider {
|
|
|
68
88
|
const { state, composing } = view
|
|
69
89
|
const { selection, doc } = state
|
|
70
90
|
const { ranges } = selection
|
|
71
|
-
const from = Math.min(...ranges.map(range => range.$from.pos))
|
|
72
|
-
const to = Math.max(...ranges.map(range => range.$to.pos))
|
|
73
|
-
const isSame =
|
|
91
|
+
const from = Math.min(...ranges.map((range) => range.$from.pos))
|
|
92
|
+
const to = Math.max(...ranges.map((range) => range.$to.pos))
|
|
93
|
+
const isSame =
|
|
94
|
+
prevState && prevState.doc.eq(doc) && prevState.selection.eq(selection)
|
|
74
95
|
|
|
75
96
|
if (!this.#initialized) {
|
|
76
97
|
view.dom.parentElement?.appendChild(this.element)
|
|
77
98
|
this.#initialized = true
|
|
78
99
|
}
|
|
79
100
|
|
|
80
|
-
if (composing || isSame)
|
|
81
|
-
return
|
|
101
|
+
if (composing || isSame) return
|
|
82
102
|
|
|
83
103
|
if (!this.#shouldShow(view, prevState)) {
|
|
84
104
|
this.hide()
|
|
@@ -90,14 +110,14 @@ export class SlashProvider {
|
|
|
90
110
|
}
|
|
91
111
|
computePosition(virtualEl, this.element, {
|
|
92
112
|
placement: 'bottom-start',
|
|
93
|
-
middleware: [flip(), offset(this.#offset)],
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
})
|
|
113
|
+
middleware: [flip(), offset(this.#offset), ...this.#middleware],
|
|
114
|
+
...this.#floatingUIOptions,
|
|
115
|
+
}).then(({ x, y }) => {
|
|
116
|
+
Object.assign(this.element.style, {
|
|
117
|
+
left: `${x}px`,
|
|
118
|
+
top: `${y}px`,
|
|
100
119
|
})
|
|
120
|
+
})
|
|
101
121
|
|
|
102
122
|
this.show()
|
|
103
123
|
}
|
|
@@ -106,15 +126,15 @@ export class SlashProvider {
|
|
|
106
126
|
#_shouldShow(view: EditorView): boolean {
|
|
107
127
|
const currentTextBlockContent = this.getContent(view)
|
|
108
128
|
|
|
109
|
-
if (!currentTextBlockContent)
|
|
110
|
-
return false
|
|
129
|
+
if (!currentTextBlockContent) return false
|
|
111
130
|
|
|
112
131
|
const target = currentTextBlockContent.at(-1)
|
|
113
132
|
|
|
114
|
-
if (!target)
|
|
115
|
-
return false
|
|
133
|
+
if (!target) return false
|
|
116
134
|
|
|
117
|
-
return Array.isArray(this.#trigger)
|
|
135
|
+
return Array.isArray(this.#trigger)
|
|
136
|
+
? this.#trigger.includes(target)
|
|
137
|
+
: this.#trigger === target
|
|
118
138
|
}
|
|
119
139
|
|
|
120
140
|
/// Update provider state by editor view.
|
|
@@ -126,7 +146,11 @@ export class SlashProvider {
|
|
|
126
146
|
|
|
127
147
|
/// Get the content of the current text block.
|
|
128
148
|
/// Pass the `matchNode` function to determine whether the current node should be matched, by default, it will match the paragraph node.
|
|
129
|
-
getContent = (
|
|
149
|
+
getContent = (
|
|
150
|
+
view: EditorView,
|
|
151
|
+
matchNode: (node: Node) => boolean = (node) =>
|
|
152
|
+
node.type.name === 'paragraph'
|
|
153
|
+
): string | undefined => {
|
|
130
154
|
const { selection } = view.state
|
|
131
155
|
const { empty, $from } = selection
|
|
132
156
|
const isTextBlock = view.state.selection instanceof TextSelection
|
|
@@ -144,12 +168,16 @@ export class SlashProvider {
|
|
|
144
168
|
if (notHasFocus || isReadonly || !empty || !isTextBlock || isNotInParagraph)
|
|
145
169
|
return
|
|
146
170
|
|
|
147
|
-
return $from.parent.textBetween(
|
|
171
|
+
return $from.parent.textBetween(
|
|
172
|
+
Math.max(0, $from.parentOffset - 500),
|
|
173
|
+
$from.parentOffset,
|
|
174
|
+
undefined,
|
|
175
|
+
'\uFFFC'
|
|
176
|
+
)
|
|
148
177
|
}
|
|
149
178
|
|
|
150
179
|
/// Destroy the slash.
|
|
151
|
-
destroy = () => {
|
|
152
|
-
}
|
|
180
|
+
destroy = () => {}
|
|
153
181
|
|
|
154
182
|
/// Show the slash.
|
|
155
183
|
show = () => {
|