@kungal/editor-vue 0.3.0 → 0.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,49 @@
1
1
  # @kungal/editor-vue
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 53a2769: P3 — add the formatting toolbar to `<KunEditor>`.
8
+
9
+ A self-contained `EditorToolbar` (WYSIWYG mode, hidden when read-only) with:
10
+ bold / italic / strikethrough / inline code, H1–H3, bullet & ordered lists,
11
+ blockquote, code block, divider, spoiler and LaTeX — plus an image-upload button
12
+ that appears only when the host supplies `uploadImage`.
13
+
14
+ Ported from the forum's `Menu.vue` + `_buttonList.ts`, but kept dependency-free:
15
+ inline SVG icons (no `@kungal/ui-vue`), themed via CSS variables so a KunUI host
16
+ still gets matching colours. It reaches the editor with `useInstance()` and fires
17
+ commands via `callCommand`; command keys are read at click time (they're only
18
+ populated once the editor is created). Verified end-to-end in a browser via
19
+ `apps/playground`.
20
+
21
+ ### Patch Changes
22
+
23
+ - @kungal/editor-core@0.5.0
24
+
25
+ ## 0.4.0
26
+
27
+ ### Minor Changes
28
+
29
+ - 56f4a77: P3 — wire the `@mention` autocomplete dropdown.
30
+
31
+ Typing `@` in `<KunEditor>` now opens a debounced, keyboard-navigable dropdown
32
+ (ported from the forum's `MentionDropdown.vue`) that resolves users through the
33
+ injected `searchMentionUsers` adapter and inserts a mention chip serializing to
34
+ `[@name](kungal-user:id)`. Without the adapter the mention schema still
35
+ round-trips — there's just no autocomplete.
36
+
37
+ The adapter reaches the dropdown via a `provide()` on `<KunEditor>`: because
38
+ @prosemirror-adapter/vue renders plugin views as portals that are _siblings_ of
39
+ the editor (children of `<ProsemirrorAdapterProvider>`), only an ancestor of the
40
+ provider can inject into them. Verified end-to-end in a real browser via the new
41
+ `apps/playground` dev app.
42
+
43
+ ### Patch Changes
44
+
45
+ - @kungal/editor-core@0.4.0
46
+
3
47
  ## 0.3.0
4
48
 
5
49
  ### Minor Changes
package/README.md CHANGED
@@ -54,9 +54,19 @@ editor**: a WYSIWYG view built from `@kungal/editor-core/preset`
54
54
  …), plus a markdown-source view, over one `v-model`. The prop/emit surface
55
55
  (`v-model`, `adapters`, `features`, `locale`, `readonly`) is stable.
56
56
 
57
- Still to come in P3 (they build on `@kungal/ui-vue`, hence staged): the rich
58
- formatting toolbar, the `@mention` autocomplete dropdown, the sticker/emoji
59
- picker, and the CodeMirror markdown-source view (currently a `<textarea>`). See
57
+ A **formatting toolbar** is wired: bold / italic / strikethrough / inline code,
58
+ H1–H3, bullet & ordered lists, blockquote, code block, divider, spoiler, LaTeX —
59
+ plus an image-upload button when `uploadImage` is supplied. It's self-contained
60
+ (inline SVG, no `@kungal/ui-vue` dependency), themed through CSS variables so a
61
+ KunUI host still gets matching colours.
62
+
63
+ The **`@mention` autocomplete dropdown** is wired: type `@`, and — when the host
64
+ supplies `searchMentionUsers` — a debounced, keyboard-navigable dropdown offers
65
+ users and inserts a mention chip (`[@name](kungal-user:id)`). Without the adapter
66
+ the mention schema still round-trips; there's just no autocomplete.
67
+
68
+ Still to come in P3: the sticker/emoji picker (over `stickerSource`) and the
69
+ CodeMirror markdown-source view (currently a `<textarea>`). See
60
70
  [`../../docs/architecture.md`](../../docs/architecture.md) § migration.
61
71
 
62
72
  ## Build
@@ -0,0 +1,4 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
4
+ //# sourceMappingURL=EditorToolbar.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EditorToolbar.vue.d.ts","sourceRoot":"","sources":["../../src/components/EditorToolbar.vue"],"names":[],"mappings":"AA0ZA,QAAA,MAAM,YAAY,+QAChB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
@@ -1 +1 @@
1
- {"version":3,"file":"KunEditor.vue.d.ts","sourceRoot":"","sources":["../../src/components/KunEditor.vue"],"names":[],"mappings":"AA2LA,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EAChB,MAAM,qBAAqB,CAAA;AAG5B,KAAK,WAAW,GAAG;IACf,gCAAgC;IAChC,UAAU,EAAE,MAAM,CAAA;IAClB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,iBAAiB,CAAA;IAC5B,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,iBAAiB,CAAA;IAC5B,qDAAqD;IACrD,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAC;AAuKJ,QAAA,MAAM,YAAY;;;;;cA9KH,iBAAiB;cAEjB,iBAAiB;YAEnB,eAAe;cAEb,OAAO;6EA4KpB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
1
+ {"version":3,"file":"KunEditor.vue.d.ts","sourceRoot":"","sources":["../../src/components/KunEditor.vue"],"names":[],"mappings":"AAiNA,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EAChB,MAAM,qBAAqB,CAAA;AAK5B,KAAK,WAAW,GAAG;IACf,gCAAgC;IAChC,UAAU,EAAE,MAAM,CAAA;IAClB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,iBAAiB,CAAA;IAC5B,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,iBAAiB,CAAA;IAC5B,qDAAqD;IACrD,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAC;AA4LJ,QAAA,MAAM,YAAY;;;;;cAnMH,iBAAiB;cAEjB,iBAAiB;YAEnB,eAAe;cAEb,OAAO;6EAiMpB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
@@ -0,0 +1,4 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
4
+ //# sourceMappingURL=MentionDropdown.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MentionDropdown.vue.d.ts","sourceRoot":"","sources":["../../src/components/MentionDropdown.vue"],"names":[],"mappings":"AA8pBA,QAAA,MAAM,YAAY,+QAChB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
@@ -1 +1 @@
1
- {"version":3,"file":"MilkdownEditor.vue.d.ts","sourceRoot":"","sources":["../../src/components/MilkdownEditor.vue"],"names":[],"mappings":"AAsGA,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EAChB,MAAM,qBAAqB,CAAA;AAG5B,KAAK,WAAW,GAAG;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,iBAAiB,CAAA;IAC3B,QAAQ,EAAE,iBAAiB,CAAA;IAC3B,MAAM,EAAE,eAAe,CAAA;IACvB,QAAQ,EAAE,OAAO,CAAA;CAClB,CAAC;AAoFF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
1
+ {"version":3,"file":"MilkdownEditor.vue.d.ts","sourceRoot":"","sources":["../../src/components/MilkdownEditor.vue"],"names":[],"mappings":"AAkIA,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EAChB,MAAM,qBAAqB,CAAA;AAI5B,KAAK,WAAW,GAAG;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,iBAAiB,CAAA;IAC3B,QAAQ,EAAE,iBAAiB,CAAA;IAC3B,MAAM,EAAE,eAAe,CAAA;IACvB,QAAQ,EAAE,OAAO,CAAA;CAClB,CAAC;AA2GF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
@@ -0,0 +1,10 @@
1
+ import type { InjectionKey } from 'vue';
2
+ import type { KunEditorAdapters, KunEditorLocale } from '@kungal/editor-core';
3
+ export interface KunEditorContext {
4
+ /** The host policy bundle (searchMentionUsers, stickerSource, notify, …). */
5
+ readonly adapters: KunEditorAdapters;
6
+ /** UI language, for view chrome (dropdown hints, etc.). */
7
+ readonly locale: KunEditorLocale;
8
+ }
9
+ export declare const KUN_EDITOR_CONTEXT: InjectionKey<KunEditorContext>;
10
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,KAAK,CAAA;AACvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAE7E,MAAM,WAAW,gBAAgB;IAC/B,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAA;IACpC,2DAA2D;IAC3D,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAA;CACjC;AAED,eAAO,MAAM,kBAAkB,EAAE,YAAY,CAAC,gBAAgB,CAChC,CAAA"}
package/dist/index.js CHANGED
@@ -1,13 +1,140 @@
1
- import { computed as e, createBlock as t, createElementBlock as n, createElementVNode as r, createVNode as i, defineComponent as a, openBlock as o, ref as s, toDisplayString as c, unref as l, vShow as u, watch as d, withCtx as f, withDirectives as p } from "vue";
2
- import { Milkdown as m, MilkdownProvider as h, useEditor as g } from "@milkdown/vue";
3
- import { ProsemirrorAdapterProvider as _ } from "@prosemirror-adapter/vue";
4
- import { Editor as v, defaultValueCtx as y, editorViewOptionsCtx as b, rootCtx as x } from "@milkdown/kit/core";
5
- import { listenerCtx as S } from "@milkdown/kit/plugin/listener";
6
- import { replaceAll as C } from "@milkdown/kit/utils";
7
- import { createKunEditorPlugins as w } from "@kungal/editor-core/preset";
8
- //#endregion
9
- //#region src/components/MilkdownEditor.vue
10
- var T = /* @__PURE__ */ a({
1
+ import { Fragment as e, computed as t, createBlock as n, createCommentVNode as r, createElementBlock as i, createElementVNode as a, createVNode as o, defineComponent as s, inject as c, onBeforeUnmount as l, onMounted as u, openBlock as d, provide as f, ref as p, renderList as m, toDisplayString as h, unref as g, vShow as _, watch as v, withCtx as y, withDirectives as b, withModifiers as x } from "vue";
2
+ import { Milkdown as S, MilkdownProvider as C, useEditor as w, useInstance as T } from "@milkdown/vue";
3
+ import { ProsemirrorAdapterProvider as E, usePluginViewContext as D, usePluginViewFactory as O } from "@prosemirror-adapter/vue";
4
+ import { Editor as k, defaultValueCtx as A, editorViewOptionsCtx as j, rootCtx as M } from "@milkdown/kit/core";
5
+ import { listenerCtx as N } from "@milkdown/kit/plugin/listener";
6
+ import { SlashProvider as P, slashFactory as F } from "@milkdown/kit/plugin/slash";
7
+ import { callCommand as I, replaceAll as L } from "@milkdown/kit/utils";
8
+ import { createKunEditorPlugins as R, insertKunSpoilerCommand as ee, mentionId as te, toggleLatexCommand as ne } from "@kungal/editor-core/preset";
9
+ import { TextSelection as re } from "@milkdown/kit/prose/state";
10
+ import { createCodeBlockCommand as ie, insertHrCommand as ae, insertImageCommand as z, toggleEmphasisCommand as B, toggleInlineCodeCommand as V, toggleStrongCommand as H, wrapInBlockquoteCommand as U, wrapInBulletListCommand as W, wrapInHeadingCommand as G, wrapInOrderedListCommand as K } from "@milkdown/kit/preset/commonmark";
11
+ import { toggleStrikethroughCommand as q } from "@milkdown/kit/preset/gfm";
12
+ //#region src/context.ts
13
+ var J = Symbol("kun-editor-context"), oe = [
14
+ "data-active",
15
+ "onMousedown",
16
+ "onMouseenter"
17
+ ], se = ["src", "alt"], ce = {
18
+ key: 1,
19
+ class: "kun-mention-dropdown__avatar kun-mention-dropdown__avatar--fallback"
20
+ }, le = { class: "kun-mention-dropdown__name" }, ue = {
21
+ key: 1,
22
+ class: "kun-mention-dropdown__hint"
23
+ }, de = {
24
+ key: 2,
25
+ class: "kun-mention-dropdown__hint"
26
+ }, fe = {
27
+ key: 3,
28
+ class: "kun-mention-dropdown__hint"
29
+ }, pe = /* @__PURE__ */ s({
30
+ __name: "MentionDropdown",
31
+ setup(n) {
32
+ let r = /(?:^|\s)@([^\s@]{0,30})$/, o = c(J), s = o?.adapters.searchMentionUsers, f = t(() => (o?.locale ?? "zh-cn").toLowerCase().startsWith("en")), g = t(() => f.value ? {
33
+ searching: "Searching…",
34
+ empty: "No matching users",
35
+ prompt: "Type a username to search"
36
+ } : {
37
+ searching: "搜索中…",
38
+ empty: "无匹配用户",
39
+ prompt: "输入用户名以搜索"
40
+ }), { view: _, prevState: y } = D(), b = p(null), S, C = p(!1), w = p(""), T = p([]), E = p(0), O = p(!1), k = null, A = 0, j = null, M = () => {
41
+ j &&= (clearTimeout(j), null), w.value = "", T.value = [], E.value = 0, O.value = !1, k = null;
42
+ }, N = (e) => {
43
+ if (j &&= (clearTimeout(j), null), !e || !s) {
44
+ T.value = [], O.value = !1;
45
+ return;
46
+ }
47
+ O.value = !0, j = setTimeout(async () => {
48
+ let t = ++A, n = [];
49
+ try {
50
+ n = await s(e);
51
+ } catch {
52
+ n = [];
53
+ }
54
+ t === A && (T.value = n ?? [], E.value = 0, O.value = !1);
55
+ }, 180);
56
+ }, F = (e) => {
57
+ let { selection: t } = e.state;
58
+ if (!t.empty) return null;
59
+ let { $from: n } = t, i = n.parent.textBetween(Math.max(0, n.parentOffset - 100), n.parentOffset, void 0, "").match(r);
60
+ return i ? i[1] ?? "" : null;
61
+ }, I = (e) => {
62
+ let t = F(e);
63
+ if (t === null) return !1;
64
+ let n = e.state.selection.$from.pos;
65
+ return k = {
66
+ from: n - (t.length + 1),
67
+ to: n
68
+ }, t !== w.value && (w.value = t, N(t)), !0;
69
+ }, L = (e) => {
70
+ let t = _.value;
71
+ if (!t || !k) return;
72
+ let n = t.state.schema.nodes[te];
73
+ if (!n) return;
74
+ let r = n.create({
75
+ userId: e.id,
76
+ name: e.name
77
+ }), { from: i, to: a } = k, o = t.state.tr.replaceWith(i, a, r), s = i + r.nodeSize;
78
+ o.insertText(" ", s), o.setSelection(re.create(o.doc, s + 1)), t.dispatch(o.scrollIntoView()), t.focus(), S.hide();
79
+ }, R = (e) => {
80
+ if (!C.value || !T.value.length) {
81
+ C.value && e.key === "Escape" && (e.preventDefault(), S.hide());
82
+ return;
83
+ }
84
+ let t = T.value.length - 1;
85
+ switch (e.key) {
86
+ case "ArrowDown":
87
+ e.preventDefault(), e.stopPropagation(), E.value = E.value >= t ? 0 : E.value + 1;
88
+ break;
89
+ case "ArrowUp":
90
+ e.preventDefault(), e.stopPropagation(), E.value = E.value <= 0 ? t : E.value - 1;
91
+ break;
92
+ case "Enter":
93
+ case "Tab": {
94
+ let t = T.value[E.value];
95
+ t && (e.preventDefault(), e.stopPropagation(), L(t));
96
+ break;
97
+ }
98
+ case "Escape":
99
+ e.preventDefault(), e.stopPropagation(), S.hide();
100
+ break;
101
+ }
102
+ };
103
+ return u(() => {
104
+ S = new P({
105
+ content: b.value,
106
+ trigger: "@",
107
+ shouldShow: I,
108
+ debounce: 0
109
+ }), S.onShow = () => {
110
+ C.value = !0;
111
+ }, S.onHide = () => {
112
+ C.value = !1, M();
113
+ }, S.update(_.value, y.value), window.addEventListener("keydown", R, !0);
114
+ }), v([_, y], () => {
115
+ S?.update(_.value, y.value);
116
+ }), l(() => {
117
+ j && clearTimeout(j), window.removeEventListener("keydown", R, !0), S?.destroy();
118
+ }), (t, n) => (d(), i("div", {
119
+ ref_key: "divRef",
120
+ ref: b,
121
+ class: "kun-mention-dropdown",
122
+ "data-show": "false"
123
+ }, [T.value.length ? (d(!0), i(e, { key: 0 }, m(T.value, (e, t) => (d(), i("button", {
124
+ key: e.id,
125
+ type: "button",
126
+ class: "kun-mention-dropdown__item",
127
+ "data-active": t === E.value,
128
+ onMousedown: x((t) => L(e), ["prevent"]),
129
+ onMouseenter: (e) => E.value = t
130
+ }, [e.avatar ? (d(), i("img", {
131
+ key: 0,
132
+ src: e.avatar,
133
+ alt: e.name,
134
+ class: "kun-mention-dropdown__avatar"
135
+ }, null, 8, se)) : (d(), i("span", ce, h(e.name.charAt(0)), 1)), a("span", le, h(e.name), 1)], 40, oe))), 128)) : O.value ? (d(), i("div", ue, h(g.value.searching), 1)) : w.value ? (d(), i("div", de, h(g.value.empty), 1)) : (d(), i("div", fe, h(g.value.prompt), 1))], 512));
136
+ }
137
+ }), me = /* @__PURE__ */ s({
11
138
  __name: "MilkdownEditor",
12
139
  props: {
13
140
  modelValue: {},
@@ -17,30 +144,210 @@ var T = /* @__PURE__ */ a({
17
144
  readonly: { type: Boolean }
18
145
  },
19
146
  emits: ["update:modelValue"],
20
- setup(e, { emit: n }) {
21
- let r = e, i = n, a = r.modelValue, s = g((e) => v.make().config((t) => {
22
- t.set(x, e), t.set(y, r.modelValue), t.get(S).markdownUpdated((e, t, n) => {
23
- t !== n && (a = t, i("update:modelValue", t));
24
- }), t.update(b, (e) => ({
25
- ...e,
26
- editable: () => !r.readonly
27
- }));
28
- }).use(w(r.adapters, r.features, { locale: r.locale })));
29
- return d(() => r.modelValue, (e) => {
30
- e !== a && (a = e, s.get()?.action(C(e)));
31
- }), (e, n) => (o(), t(l(m)));
147
+ setup(e, { emit: t }) {
148
+ let r = e, i = t, a = r.modelValue, o = O(), s = F("kunMention"), c = r.features.mention !== !1 && !!r.adapters.searchMentionUsers && !!o, l = w((e) => {
149
+ let t = k.make().config((t) => {
150
+ t.set(M, e), t.set(A, r.modelValue), t.get(N).markdownUpdated((e, t, n) => {
151
+ t !== n && (a = t, i("update:modelValue", t));
152
+ }), t.update(j, (e) => ({
153
+ ...e,
154
+ editable: () => !r.readonly
155
+ })), c && t.set(s.key, { view: o({ component: pe }) });
156
+ }).use(R(r.adapters, r.features, { locale: r.locale }));
157
+ return c && t.use(s), t;
158
+ });
159
+ return v(() => r.modelValue, (e) => {
160
+ e !== a && (a = e, l.get()?.action(L(e)));
161
+ }), (e, t) => (d(), n(g(S)));
32
162
  }
33
- }), E = {
163
+ }), Y = (e) => `<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${e}</svg>`, X = (e, t = 13) => `<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><text x="12" y="12" dominant-baseline="central" text-anchor="middle" font-size="${t}" font-weight="700" font-family="ui-sans-serif, system-ui, sans-serif" fill="currentColor">${e}</text></svg>`, Z = {
164
+ bold: Y("<path d=\"M6 4h8a4 4 0 0 1 0 8H6z\"/><path d=\"M6 12h9a4 4 0 0 1 0 8H6z\"/>"),
165
+ italic: Y("<line x1=\"19\" y1=\"4\" x2=\"10\" y2=\"4\"/><line x1=\"14\" y1=\"20\" x2=\"5\" y2=\"20\"/><line x1=\"15\" y1=\"4\" x2=\"9\" y2=\"20\"/>"),
166
+ strike: Y("<path d=\"M16 4H9a3 3 0 0 0-2.83 4\"/><path d=\"M14 12a4 4 0 0 1 0 8H6\"/><line x1=\"4\" y1=\"12\" x2=\"20\" y2=\"12\"/>"),
167
+ code: Y("<path d=\"m18 16 4-4-4-4\"/><path d=\"m6 8-4 4 4 4\"/><path d=\"m14.5 4-5 16\"/>"),
168
+ h1: X("H1", 11),
169
+ h2: X("H2", 11),
170
+ h3: X("H3", 11),
171
+ bulletList: Y("<line x1=\"8\" y1=\"6\" x2=\"21\" y2=\"6\"/><line x1=\"8\" y1=\"12\" x2=\"21\" y2=\"12\"/><line x1=\"8\" y1=\"18\" x2=\"21\" y2=\"18\"/><circle cx=\"3.5\" cy=\"6\" r=\"1.2\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"3.5\" cy=\"12\" r=\"1.2\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"3.5\" cy=\"18\" r=\"1.2\" fill=\"currentColor\" stroke=\"none\"/>"),
172
+ orderedList: Y("<line x1=\"10\" y1=\"6\" x2=\"21\" y2=\"6\"/><line x1=\"10\" y1=\"12\" x2=\"21\" y2=\"12\"/><line x1=\"10\" y1=\"18\" x2=\"21\" y2=\"18\"/><path d=\"M4 6h1v4\"/><path d=\"M4 10h2\"/><path d=\"M6 18H4c0-1 2-1.5 2-2.5S5 14 4 14.5\"/>"),
173
+ quote: Y("<path d=\"M6 17h3l2-4V7H5v6h3zm8 0h3l2-4V7h-6v6h3z\" fill=\"currentColor\" stroke=\"none\"/>"),
174
+ codeBlock: Y("<rect x=\"3\" y=\"4\" width=\"18\" height=\"16\" rx=\"2\"/><path d=\"m9 10-2 2 2 2\"/><path d=\"m15 10 2 2-2 2\"/>"),
175
+ hr: Y("<line x1=\"4\" y1=\"12\" x2=\"20\" y2=\"12\"/>"),
176
+ image: Y("<rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/><circle cx=\"9\" cy=\"9\" r=\"1.8\" fill=\"currentColor\" stroke=\"none\"/><path d=\"m21 15-4.5-4.5a2 2 0 0 0-2.8 0L5 19\"/>"),
177
+ spoiler: Y("<path d=\"M9.9 4.24A9.1 9.1 0 0 1 12 4c7 0 10 8 10 8a13.2 13.2 0 0 1-1.67 2.68\"/><path d=\"M6.6 6.6A13.5 13.5 0 0 0 2 12s3 8 10 8a9.7 9.7 0 0 0 5.4-1.6\"/><line x1=\"2\" y1=\"2\" x2=\"22\" y2=\"22\"/>"),
178
+ latex: X("Σ", 15)
179
+ }, he = {
180
+ class: "kun-editor__format-toolbar",
181
+ role: "toolbar"
182
+ }, ge = {
183
+ key: 0,
184
+ class: "kun-editor__toolbar-divider",
185
+ "aria-hidden": "true"
186
+ }, _e = [
187
+ "title",
188
+ "aria-label",
189
+ "onClick"
190
+ ], ve = ["innerHTML"], ye = ["title", "aria-label"], be = ["innerHTML"], xe = /* @__PURE__ */ s({
191
+ __name: "EditorToolbar",
192
+ setup(n) {
193
+ let [, o] = T(), s = c(J), l = t(() => s?.adapters.uploadImage), u = () => s?.adapters.notify, f = t(() => (s?.locale ?? "zh-cn").toLowerCase().startsWith("en")), h = (e, t) => {
194
+ o()?.action(I(e, t));
195
+ }, _ = t(() => {
196
+ let e = f.value;
197
+ return {
198
+ bold: e ? "Bold" : "加粗",
199
+ italic: e ? "Italic" : "斜体",
200
+ strike: e ? "Strikethrough" : "删除线",
201
+ code: e ? "Inline code" : "行内代码",
202
+ h1: e ? "Heading 1" : "一级标题",
203
+ h2: e ? "Heading 2" : "二级标题",
204
+ h3: e ? "Heading 3" : "三级标题",
205
+ bulletList: e ? "Bullet list" : "无序列表",
206
+ orderedList: e ? "Ordered list" : "有序列表",
207
+ quote: e ? "Blockquote" : "引用块",
208
+ codeBlock: e ? "Code block" : "代码块",
209
+ hr: e ? "Divider" : "分隔线",
210
+ spoiler: e ? "Spoiler" : "隐藏文本",
211
+ latex: e ? "Math (LaTeX)" : "公式",
212
+ image: e ? "Upload image" : "上传图片"
213
+ };
214
+ }), v = t(() => [
215
+ [
216
+ {
217
+ svg: Z.bold,
218
+ title: _.value.bold,
219
+ run: () => h(H.key)
220
+ },
221
+ {
222
+ svg: Z.italic,
223
+ title: _.value.italic,
224
+ run: () => h(B.key)
225
+ },
226
+ {
227
+ svg: Z.strike,
228
+ title: _.value.strike,
229
+ run: () => h(q.key)
230
+ },
231
+ {
232
+ svg: Z.code,
233
+ title: _.value.code,
234
+ run: () => h(V.key)
235
+ }
236
+ ],
237
+ [
238
+ {
239
+ svg: Z.h1,
240
+ title: _.value.h1,
241
+ run: () => h(G.key, 1)
242
+ },
243
+ {
244
+ svg: Z.h2,
245
+ title: _.value.h2,
246
+ run: () => h(G.key, 2)
247
+ },
248
+ {
249
+ svg: Z.h3,
250
+ title: _.value.h3,
251
+ run: () => h(G.key, 3)
252
+ }
253
+ ],
254
+ [
255
+ {
256
+ svg: Z.bulletList,
257
+ title: _.value.bulletList,
258
+ run: () => h(W.key)
259
+ },
260
+ {
261
+ svg: Z.orderedList,
262
+ title: _.value.orderedList,
263
+ run: () => h(K.key)
264
+ },
265
+ {
266
+ svg: Z.quote,
267
+ title: _.value.quote,
268
+ run: () => h(U.key)
269
+ },
270
+ {
271
+ svg: Z.codeBlock,
272
+ title: _.value.codeBlock,
273
+ run: () => h(ie.key, "")
274
+ },
275
+ {
276
+ svg: Z.hr,
277
+ title: _.value.hr,
278
+ run: () => h(ae.key)
279
+ }
280
+ ],
281
+ [{
282
+ svg: Z.spoiler,
283
+ title: _.value.spoiler,
284
+ run: () => h(ee.key)
285
+ }, {
286
+ svg: Z.latex,
287
+ title: _.value.latex,
288
+ run: () => h(ne.key)
289
+ }]
290
+ ]), y = p(null), b = () => y.value?.click(), x = async (e) => {
291
+ let t = e.target, n = t.files?.[0];
292
+ t.value = "";
293
+ let r = l.value;
294
+ if (!(!n || !r)) try {
295
+ let e = await r(n);
296
+ h(z.key, {
297
+ src: e,
298
+ title: n.name,
299
+ alt: n.name
300
+ });
301
+ } catch {
302
+ u()?.(f.value ? "Image upload failed" : "图片上传失败", "error");
303
+ }
304
+ };
305
+ return (t, n) => (d(), i("div", he, [(d(!0), i(e, null, m(v.value, (t, n) => (d(), i(e, { key: n }, [n > 0 ? (d(), i("span", ge)) : r("", !0), (d(!0), i(e, null, m(t, (e, t) => (d(), i("button", {
306
+ key: t,
307
+ type: "button",
308
+ class: "kun-editor__tool",
309
+ title: e.title,
310
+ "aria-label": e.title,
311
+ onClick: (t) => e.run()
312
+ }, [a("span", {
313
+ class: "kun-editor__icon",
314
+ innerHTML: e.svg
315
+ }, null, 8, ve)], 8, _e))), 128))], 64))), 128)), l.value ? (d(), i(e, { key: 0 }, [
316
+ n[0] ||= a("span", {
317
+ class: "kun-editor__toolbar-divider",
318
+ "aria-hidden": "true"
319
+ }, null, -1),
320
+ a("button", {
321
+ type: "button",
322
+ class: "kun-editor__tool",
323
+ title: _.value.image,
324
+ "aria-label": _.value.image,
325
+ onClick: b
326
+ }, [a("span", {
327
+ class: "kun-editor__icon",
328
+ innerHTML: g(Z).image
329
+ }, null, 8, be)], 8, ye),
330
+ a("input", {
331
+ ref_key: "fileInput",
332
+ ref: y,
333
+ type: "file",
334
+ accept: ".jpg,.jpeg,.png,.webp,.gif",
335
+ hidden: "",
336
+ onChange: x
337
+ }, null, 544)
338
+ ], 64)) : r("", !0)]));
339
+ }
340
+ }), Q = {
34
341
  class: "kun-editor",
35
342
  "data-kun-editor": ""
36
- }, D = {
343
+ }, Se = {
37
344
  class: "kun-editor__toolbar",
38
345
  role: "tablist"
39
- }, O = ["aria-selected", "data-active"], k = ["aria-selected", "data-active"], A = { class: "kun-editor__wysiwyg" }, j = [
346
+ }, Ce = ["aria-selected", "data-active"], we = ["aria-selected", "data-active"], Te = { class: "kun-editor__wysiwyg" }, Ee = [
40
347
  "value",
41
348
  "readonly",
42
349
  "lang"
43
- ], M = /* @__PURE__ */ a({
350
+ ], $ = /* @__PURE__ */ s({
44
351
  __name: "KunEditor",
45
352
  props: {
46
353
  modelValue: {},
@@ -53,60 +360,69 @@ var T = /* @__PURE__ */ a({
53
360
  }
54
361
  },
55
362
  emits: ["update:modelValue"],
56
- setup(t, { emit: a }) {
57
- let d = t, m = a, g = s("wysiwyg"), v = e(() => d.locale.toLowerCase().startsWith("en")), y = e(() => v.value ? {
363
+ setup(e, { emit: n }) {
364
+ let r = e, s = n;
365
+ f(J, {
366
+ get adapters() {
367
+ return r.adapters;
368
+ },
369
+ get locale() {
370
+ return r.locale;
371
+ }
372
+ });
373
+ let c = p("wysiwyg"), l = t(() => r.locale.toLowerCase().startsWith("en")), u = t(() => l.value ? {
58
374
  wysiwyg: "Preview",
59
375
  source: "Markdown"
60
376
  } : {
61
377
  wysiwyg: "预览",
62
378
  source: "Markdown"
63
- }), b = (e) => m("update:modelValue", e), x = (e) => m("update:modelValue", e.target.value);
64
- return (e, a) => (o(), n("div", E, [
65
- r("div", D, [r("button", {
379
+ }), m = (e) => s("update:modelValue", e), v = (e) => s("update:modelValue", e.target.value);
380
+ return (t, n) => (d(), i("div", Q, [
381
+ a("div", Se, [a("button", {
66
382
  type: "button",
67
383
  class: "kun-editor__tab",
68
- "aria-selected": g.value === "wysiwyg",
69
- "data-active": g.value === "wysiwyg",
70
- onClick: a[0] ||= (e) => g.value = "wysiwyg"
71
- }, c(y.value.wysiwyg), 9, O), r("button", {
384
+ "aria-selected": c.value === "wysiwyg",
385
+ "data-active": c.value === "wysiwyg",
386
+ onClick: n[0] ||= (e) => c.value = "wysiwyg"
387
+ }, h(u.value.wysiwyg), 9, Ce), a("button", {
72
388
  type: "button",
73
389
  class: "kun-editor__tab",
74
- "aria-selected": g.value === "source",
75
- "data-active": g.value === "source",
76
- onClick: a[1] ||= (e) => g.value = "source"
77
- }, c(y.value.source), 9, k)]),
78
- i(l(h), null, {
79
- default: f(() => [i(l(_), null, {
80
- default: f(() => [p(r("div", A, [i(T, {
81
- "model-value": t.modelValue,
82
- adapters: t.adapters,
83
- features: t.features,
84
- locale: t.locale,
85
- readonly: t.readonly,
86
- "onUpdate:modelValue": b
390
+ "aria-selected": c.value === "source",
391
+ "data-active": c.value === "source",
392
+ onClick: n[1] ||= (e) => c.value = "source"
393
+ }, h(u.value.source), 9, we)]),
394
+ o(g(C), null, {
395
+ default: y(() => [o(g(E), null, {
396
+ default: y(() => [b(o(xe, null, null, 512), [[_, c.value === "wysiwyg" && !e.readonly]]), b(a("div", Te, [o(me, {
397
+ "model-value": e.modelValue,
398
+ adapters: e.adapters,
399
+ features: e.features,
400
+ locale: e.locale,
401
+ readonly: e.readonly,
402
+ "onUpdate:modelValue": m
87
403
  }, null, 8, [
88
404
  "model-value",
89
405
  "adapters",
90
406
  "features",
91
407
  "locale",
92
408
  "readonly"
93
- ])], 512), [[u, g.value === "wysiwyg"]])]),
409
+ ])], 512), [[_, c.value === "wysiwyg"]])]),
94
410
  _: 1
95
411
  })]),
96
412
  _: 1
97
413
  }),
98
- p(r("textarea", {
414
+ b(a("textarea", {
99
415
  class: "kun-editor__source",
100
- value: t.modelValue,
101
- readonly: t.readonly,
102
- lang: t.locale,
416
+ value: e.modelValue,
417
+ readonly: e.readonly,
418
+ lang: e.locale,
103
419
  spellcheck: "false",
104
- onInput: x
105
- }, null, 40, j), [[u, g.value === "source"]])
420
+ onInput: v
421
+ }, null, 40, Ee), [[_, c.value === "source"]])
106
422
  ]));
107
423
  }
108
- }), N = { install(e) {
109
- e.component("KunEditor", M);
424
+ }), De = { install(e) {
425
+ e.component("KunEditor", $);
110
426
  } };
111
427
  //#endregion
112
- export { M as KunEditor, N as KunEditorPlugin };
428
+ export { $ as KunEditor, De as KunEditorPlugin };
package/dist/style.css CHANGED
@@ -1,2 +1,2 @@
1
- .kun-editor{flex-direction:column;gap:.5rem;display:flex}.kun-editor__toolbar{gap:.25rem;display:flex}.kun-editor__tab{border:1px solid var(--color-default-200,#e4e4e7);font:inherit;cursor:pointer;background:0 0;border-radius:.5rem;padding:.25rem .75rem}.kun-editor__tab[data-active=true]{background:var(--color-primary,#006fee);border-color:var(--color-primary,#006fee);color:#fff}.kun-editor__wysiwyg .milkdown{outline:none}.kun-editor__wysiwyg .ProseMirror{border:1px solid var(--color-default-200,#e4e4e7);border-radius:.5rem;outline:none;min-height:8rem;padding:.75rem}.kun-editor__source{border:1px solid var(--color-default-200,#e4e4e7);resize:vertical;border-radius:.5rem;width:100%;min-height:8rem;padding:.75rem;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.875rem}
1
+ .kun-mention-dropdown{z-index:30;background:var(--color-background,#fff);border:1px solid var(--color-default-200,#e4e4e7);border-radius:.5rem;min-width:12rem;max-width:20rem;max-height:16rem;padding:.25rem;display:none;position:absolute;overflow-y:auto;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a}.kun-mention-dropdown[data-show=true]{display:block}.kun-mention-dropdown__item{width:100%;font:inherit;text-align:left;cursor:pointer;background:0 0;border:none;border-radius:.375rem;align-items:center;gap:.5rem;padding:.375rem .5rem;display:flex}.kun-mention-dropdown__item:hover,.kun-mention-dropdown__item[data-active=true]{background:var(--color-default-100,#f4f4f5)}.kun-mention-dropdown__avatar{object-fit:cover;border-radius:9999px;flex-shrink:0;width:1.5rem;height:1.5rem}.kun-mention-dropdown__avatar--fallback{background:var(--color-default-200,#e4e4e7);color:var(--color-default-600,#52525b);justify-content:center;align-items:center;font-size:.75rem;display:flex}.kun-mention-dropdown__name{text-overflow:ellipsis;white-space:nowrap;font-size:.875rem;overflow:hidden}.kun-mention-dropdown__hint{color:var(--color-default-400,#a1a1aa);padding:.375rem .5rem;font-size:.875rem}.kun-editor__format-toolbar{flex-wrap:wrap;align-items:center;gap:.125rem;display:flex}.kun-editor__toolbar-divider{background:var(--color-default-200,#e4e4e7);align-self:stretch;width:1px;margin:.25rem}.kun-editor__tool{width:1.75rem;height:1.75rem;color:var(--color-foreground,#27272a);cursor:pointer;background:0 0;border:none;border-radius:.375rem;justify-content:center;align-items:center;padding:0;display:inline-flex}.kun-editor__tool:hover{background:var(--color-default-100,#f4f4f5)}.kun-editor__icon{display:inline-flex}.kun-editor{flex-direction:column;gap:.5rem;display:flex}.kun-editor__toolbar{gap:.25rem;display:flex}.kun-editor__tab{border:1px solid var(--color-default-200,#e4e4e7);font:inherit;cursor:pointer;background:0 0;border-radius:.5rem;padding:.25rem .75rem}.kun-editor__tab[data-active=true]{background:var(--color-primary,#006fee);border-color:var(--color-primary,#006fee);color:#fff}.kun-editor__wysiwyg .milkdown{outline:none}.kun-editor__wysiwyg .ProseMirror{border:1px solid var(--color-default-200,#e4e4e7);white-space:pre-wrap;word-wrap:break-word;border-radius:.5rem;outline:none;min-height:8rem;padding:.75rem}.kun-editor__source{border:1px solid var(--color-default-200,#e4e4e7);resize:vertical;border-radius:.5rem;width:100%;min-height:8rem;padding:.75rem;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.875rem}
2
2
  /*$vite$:1*/
@@ -0,0 +1,18 @@
1
+ export declare const TOOLBAR_ICONS: {
2
+ readonly bold: string;
3
+ readonly italic: string;
4
+ readonly strike: string;
5
+ readonly code: string;
6
+ readonly h1: string;
7
+ readonly h2: string;
8
+ readonly h3: string;
9
+ readonly bulletList: string;
10
+ readonly orderedList: string;
11
+ readonly quote: string;
12
+ readonly codeBlock: string;
13
+ readonly hr: string;
14
+ readonly image: string;
15
+ readonly spoiler: string;
16
+ readonly latex: string;
17
+ };
18
+ //# sourceMappingURL=toolbar-icons.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolbar-icons.d.ts","sourceRoot":"","sources":["../src/toolbar-icons.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;CAgBhB,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kungal/editor-vue",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "KunEditor Vue 3 layer — the <KunEditor> component (dual WYSIWYG / markdown-source), toolbar and plugin views. Consumes @kungal/editor-core; pairs with @kungal/ui-vue.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@prosemirror-adapter/vue": "^0.4.6",
47
- "@kungal/editor-core": "0.3.0"
47
+ "@kungal/editor-core": "0.5.0"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "@kungal/ui-vue": "^2.7.0",
@@ -63,7 +63,7 @@
63
63
  "vite": "^8.0.16",
64
64
  "vue": "^3.5.35",
65
65
  "vue-tsc": "^3.3.3",
66
- "@kungal/editor-core": "0.3.0"
66
+ "@kungal/editor-core": "0.5.0"
67
67
  },
68
68
  "scripts": {
69
69
  "build": "vite build && vue-tsc -p tsconfig.build.json",