@kungal/editor-core 0.16.0 → 0.18.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,54 @@
1
1
  # @kungal/editor-core
2
2
 
3
+ ## 0.18.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 6545866: Toolbar image upload now shows an in-document "uploading…" placeholder.
8
+
9
+ Previously only paste/drop showed the in-flight placeholder (Milkdown's `upload`
10
+ plugin); the toolbar image button uploaded silently — the image just popped in
11
+ when the adapter resolved. Now the toolbar path uses the same placeholder UX
12
+ (ProseMirror's official upload-example pattern).
13
+
14
+ - **editor-core**: `startImageUpload(view, file, { uploadImage, notify, locale })`
15
+ - the `imageUploadPlaceholder` decoration plugin (exported from `/preset`;
16
+ wired by the preset when `uploadImage` is present). Inserts a
17
+ `.kun-editor__uploading` widget at the caret, uploads, then replaces it with the
18
+ image (or removes it + notifies on failure); not an undo step. The paste/drop
19
+ placeholder now uses the same `.kun-editor__uploading` class so both look
20
+ identical.
21
+ - **editor-vue**: `KunEditorToolbarApi` gains `uploadImage(file)`; both the
22
+ headless `EditorToolbar` and the `#toolbar` slot API route uploads through
23
+ `startImageUpload`.
24
+ - **editor-nuxt**: `<KunEditorToolbar>`'s image button uses it.
25
+ - **Headless kept**: the placeholder is a class + widget hook only; style it via
26
+ `.kun-editor__uploading` in the reference `kun-editor.css` (primary-colored).
27
+
28
+ Customizable: style the placeholder via `.kun-editor__uploading`; the text is
29
+ localized (zh/en). Verified in the docs production build: uploading via either
30
+ toolbar inserts the image; the unit tests confirm the placeholder shows during a
31
+ pending upload and is replaced on success / removed on failure.
32
+
33
+ ## 0.17.0
34
+
35
+ ### Minor Changes
36
+
37
+ - 59abbc8: Add a placeholder (empty-state text).
38
+
39
+ - **editor-core**: `createPlaceholderPlugin({ text, mode })` — a `$prose` plugin
40
+ that adds a ProseMirror node DECORATION to the empty block (`.kun-editor__placeholder`
41
+ class + `data-placeholder` attribute). Same mechanism as Milkdown Crepe's v7
42
+ placeholder feature — purely visual (a decoration), so it never enters the doc
43
+ or the markdown output. `mode` `'block'` (default) shows on any empty block,
44
+ `'doc'` only when the whole document is empty; skipped in readonly / code blocks
45
+ / lists / tables. `createKunEditorPlugins(..., { placeholder, placeholderMode })`
46
+ wires it (only when `placeholder` is non-empty). Exported from `/preset`.
47
+ - **editor-vue**: `<KunEditor placeholder="…">` prop, threaded through.
48
+ - **Headless**: the core ships only the class + `data-placeholder` hook; render it
49
+ with `.kun-editor__placeholder::before { content: attr(data-placeholder) }` (added
50
+ to the reference `kun-editor.css`, muted via a KunUI token).
51
+
3
52
  ## 0.16.0
4
53
 
5
54
  ## 0.15.0
@@ -19,13 +19,13 @@ var katex = require('katex');
19
19
  var remarkMath = require('remark-math');
20
20
  var codeBlock = require('@milkdown/kit/component/code-block');
21
21
  var commands$1 = require('@codemirror/commands');
22
- var view = require('@codemirror/view');
22
+ var view$1 = require('@codemirror/view');
23
23
  var languageData = require('@codemirror/language-data');
24
24
  var codemirror = require('codemirror');
25
25
  var language = require('@codemirror/language');
26
26
  var highlight = require('@lezer/highlight');
27
27
  var upload = require('@milkdown/kit/plugin/upload');
28
- var view$1 = require('@milkdown/kit/prose/view');
28
+ var view = require('@milkdown/kit/prose/view');
29
29
 
30
30
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
31
31
 
@@ -374,7 +374,7 @@ var colors = {
374
374
  content2: "var(--color-content2)",
375
375
  content3: "var(--color-content3)"};
376
376
  var kunCMTheme = () => {
377
- return view.EditorView.theme({
377
+ return view$1.EditorView.theme({
378
378
  "&": {
379
379
  backgroundColor: colors.backgroundAlpha,
380
380
  borderRadius: "0.75rem",
@@ -690,8 +690,8 @@ var applyCodeBlockConfig = (ctx, options = {}) => {
690
690
  ...prev,
691
691
  extensions: [
692
692
  kunCM(),
693
- view.EditorView.lineWrapping,
694
- view.keymap.of(commands$1.defaultKeymap.concat(commands$1.indentWithTab)),
693
+ view$1.EditorView.lineWrapping,
694
+ view$1.keymap.of(commands$1.defaultKeymap.concat(commands$1.indentWithTab)),
695
695
  codemirror.basicSetup,
696
696
  ...options.extensions ?? []
697
697
  ],
@@ -958,12 +958,15 @@ var createUploader = (uploadImage, options = {}) => {
958
958
  return nodes.filter((node) => node !== null);
959
959
  };
960
960
  };
961
+ var uploadingWidget = (locale) => {
962
+ const el = document.createElement("span");
963
+ el.className = "kun-editor__uploading";
964
+ el.textContent = uploadingLabel(locale);
965
+ return el;
966
+ };
961
967
  var createUploadWidgetFactory = (options = {}) => {
962
968
  return (pos, spec) => {
963
- const widgetDOM = document.createElement("span");
964
- widgetDOM.textContent = uploadingLabel(options.locale);
965
- widgetDOM.style.color = "var(--color-primary)";
966
- return view$1.Decoration.widget(pos, widgetDOM, spec);
969
+ return view.Decoration.widget(pos, uploadingWidget(options.locale), spec);
967
970
  };
968
971
  };
969
972
  var applyUploadConfig = (ctx, uploadImage, options = {}) => {
@@ -977,6 +980,132 @@ var uploadConfigPlugin = (uploadImage, options) => (ctx) => () => {
977
980
  applyUploadConfig(ctx, uploadImage, options);
978
981
  };
979
982
  var createUploadPlugin = (uploadImage, options = {}) => [...upload.upload, uploadConfigPlugin(uploadImage, options)];
983
+ var placeholderKey = new state.PluginKey("KUN_IMAGE_UPLOAD_PLACEHOLDER");
984
+ var imageUploadPlaceholder = utils.$prose(
985
+ () => new state.Plugin({
986
+ key: placeholderKey,
987
+ state: {
988
+ init: () => view.DecorationSet.empty,
989
+ apply(tr, set) {
990
+ let next = set.map(tr.mapping, tr.doc);
991
+ const meta = tr.getMeta(placeholderKey);
992
+ if (meta?.add) {
993
+ const deco = view.Decoration.widget(
994
+ meta.add.pos,
995
+ uploadingWidget(meta.add.locale),
996
+ { id: meta.add.id }
997
+ );
998
+ next = next.add(tr.doc, [deco]);
999
+ } else if (meta?.remove) {
1000
+ const { id } = meta.remove;
1001
+ next = next.remove(
1002
+ next.find(void 0, void 0, (spec) => spec.id === id)
1003
+ );
1004
+ }
1005
+ return next;
1006
+ }
1007
+ },
1008
+ props: {
1009
+ decorations: (state) => placeholderKey.getState(state)
1010
+ }
1011
+ })
1012
+ );
1013
+ var findPlaceholder = (view, id) => {
1014
+ const set = placeholderKey.getState(view.state);
1015
+ const found = set?.find(void 0, void 0, (spec) => spec.id === id);
1016
+ return found && found.length ? found[0].from : null;
1017
+ };
1018
+ var startImageUpload = async (view, file, options) => {
1019
+ if (!file.type.startsWith("image/")) {
1020
+ return;
1021
+ }
1022
+ const id = {};
1023
+ const tr = view.state.tr;
1024
+ if (!tr.selection.empty) {
1025
+ tr.deleteSelection();
1026
+ }
1027
+ tr.setMeta(placeholderKey, {
1028
+ add: { id, pos: tr.selection.from, locale: options.locale }
1029
+ });
1030
+ tr.setMeta("addToHistory", false);
1031
+ view.dispatch(tr);
1032
+ try {
1033
+ const src = await options.uploadImage(file);
1034
+ const pos = findPlaceholder(view, id);
1035
+ if (pos === null) {
1036
+ return;
1037
+ }
1038
+ const imageType = view.state.schema.nodes.image;
1039
+ const node = imageType?.createAndFill({ src, alt: file.name });
1040
+ if (!node) {
1041
+ return;
1042
+ }
1043
+ view.dispatch(
1044
+ view.state.tr.replaceWith(pos, pos, node).setMeta(placeholderKey, { remove: { id } })
1045
+ );
1046
+ } catch {
1047
+ view.dispatch(
1048
+ view.state.tr.setMeta(placeholderKey, { remove: { id } }).setMeta("addToHistory", false)
1049
+ );
1050
+ options.notify?.(uploadFailedLabel(options.locale), "error");
1051
+ }
1052
+ };
1053
+ var SKIP_PARENTS = /* @__PURE__ */ new Set([
1054
+ "code_block",
1055
+ "bullet_list",
1056
+ "ordered_list",
1057
+ "list_item",
1058
+ "table"
1059
+ ]);
1060
+ var isDocEmpty = (doc) => doc.childCount <= 1 && !doc.firstChild?.content.size;
1061
+ var isInSkippedParent = (state) => {
1062
+ const { $anchor } = state.selection;
1063
+ for (let depth = $anchor.depth; depth > 0; depth--) {
1064
+ if (SKIP_PARENTS.has($anchor.node(depth).type.name)) {
1065
+ return true;
1066
+ }
1067
+ }
1068
+ return false;
1069
+ };
1070
+ var createPlaceholderPlugin = (config = {}) => {
1071
+ const text = config.text ?? "";
1072
+ const mode = config.mode ?? "block";
1073
+ const plugin = utils.$prose(
1074
+ (ctx) => new state.Plugin({
1075
+ key: new state.PluginKey("KUN_PLACEHOLDER"),
1076
+ props: {
1077
+ decorations: (state) => {
1078
+ if (!text) {
1079
+ return null;
1080
+ }
1081
+ const editable = ctx.get(core.editorViewOptionsCtx).editable;
1082
+ if (editable && !editable(state)) {
1083
+ return null;
1084
+ }
1085
+ if (mode === "doc" && !isDocEmpty(state.doc)) {
1086
+ return null;
1087
+ }
1088
+ const { selection } = state;
1089
+ if (!selection.empty || isInSkippedParent(state)) {
1090
+ return null;
1091
+ }
1092
+ const node = selection.$anchor.parent;
1093
+ if (node.content.size > 0) {
1094
+ return null;
1095
+ }
1096
+ const before = selection.$anchor.before();
1097
+ return view.DecorationSet.create(state.doc, [
1098
+ view.Decoration.node(before, before + node.nodeSize, {
1099
+ class: "kun-editor__placeholder",
1100
+ "data-placeholder": text
1101
+ })
1102
+ ]);
1103
+ }
1104
+ }
1105
+ })
1106
+ );
1107
+ return [plugin].flat();
1108
+ };
980
1109
 
981
1110
  // src/preset/index.ts
982
1111
  var createKunEditorPlugins = (adapters = {}, features = {}, options = {}) => {
@@ -1028,9 +1157,18 @@ var createKunEditorPlugins = (adapters = {}, features = {}, options = {}) => {
1028
1157
  notify: adapters.notify
1029
1158
  })
1030
1159
  );
1160
+ plugins.push(imageUploadPlaceholder);
1031
1161
  }
1032
1162
  plugins.push(createStopLinkPlugin());
1033
1163
  plugins.push(createHeadingPlugin());
1164
+ if (options.placeholder) {
1165
+ plugins.push(
1166
+ createPlaceholderPlugin({
1167
+ text: options.placeholder,
1168
+ mode: options.placeholderMode
1169
+ })
1170
+ );
1171
+ }
1034
1172
  return plugins.flat();
1035
1173
  };
1036
1174
 
@@ -1045,6 +1183,7 @@ exports.createHeadingPlugin = createHeadingPlugin;
1045
1183
  exports.createKatexPlugins = createKatexPlugins;
1046
1184
  exports.createKunEditorPlugins = createKunEditorPlugins;
1047
1185
  exports.createMentionPlugin = createMentionPlugin;
1186
+ exports.createPlaceholderPlugin = createPlaceholderPlugin;
1048
1187
  exports.createQuotePlugin = createQuotePlugin;
1049
1188
  exports.createSpoilerPlugin = createSpoilerPlugin;
1050
1189
  exports.createStopLinkPlugin = createStopLinkPlugin;
@@ -1052,6 +1191,7 @@ exports.createUploadPlugin = createUploadPlugin;
1052
1191
  exports.createUploadWidgetFactory = createUploadWidgetFactory;
1053
1192
  exports.createUploader = createUploader;
1054
1193
  exports.editIcon = editIcon;
1194
+ exports.imageUploadPlaceholder = imageUploadPlaceholder;
1055
1195
  exports.insertKunSpoilerCommand = insertKunSpoilerCommand;
1056
1196
  exports.insertMentionCommand = insertMentionCommand;
1057
1197
  exports.insertQuoteCommand = insertQuoteCommand;
@@ -1077,6 +1217,7 @@ exports.searchIcon = searchIcon;
1077
1217
  exports.setHeadingCommand = setHeadingCommand;
1078
1218
  exports.spoilerAttr = spoilerAttr;
1079
1219
  exports.spoilerSchema = spoilerSchema;
1220
+ exports.startImageUpload = startImageUpload;
1080
1221
  exports.stopLinkCommand = stopLinkCommand;
1081
1222
  exports.toggleLatexCommand = toggleLatexCommand;
1082
1223
  exports.visibilityOffIcon = visibilityOffIcon;