@blokkli/editor 2.0.0-alpha.44 → 2.0.0-alpha.46

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.
Files changed (30) hide show
  1. package/dist/module.d.mts +2 -2
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +22 -1
  4. package/dist/modules/agent/index.d.mts +1 -1
  5. package/dist/modules/charts/index.d.mts +1 -1
  6. package/dist/modules/drupal/graphql/mutations/add_file.graphql +2 -0
  7. package/dist/modules/drupal/graphql/mutations/add_image.graphql +2 -0
  8. package/dist/modules/drupal/graphql/mutations/add_video_remote.graphql +2 -0
  9. package/dist/modules/drupal/index.d.mts +1 -1
  10. package/dist/modules/drupal/runtime/adapter/index.js +6 -3
  11. package/dist/modules/table-of-contents/index.d.mts +1 -1
  12. package/dist/runtime/editor/components/FlexTextarea/index.vue +220 -11
  13. package/dist/runtime/editor/css/output.css +1 -1
  14. package/dist/runtime/editor/features/changelog/Dialog/index.d.vue.ts +7 -0
  15. package/dist/runtime/editor/features/changelog/Dialog/index.vue +43 -0
  16. package/dist/runtime/editor/features/changelog/Dialog/index.vue.d.ts +7 -0
  17. package/dist/runtime/editor/features/changelog/changelog.json +42 -0
  18. package/dist/runtime/editor/features/changelog/index.d.vue.ts +3 -0
  19. package/dist/runtime/editor/features/changelog/index.vue +56 -0
  20. package/dist/runtime/editor/features/changelog/index.vue.d.ts +3 -0
  21. package/dist/runtime/editor/features/clipboard/index.vue +4 -27
  22. package/dist/runtime/editor/features/clipboard/types.d.ts +6 -0
  23. package/dist/runtime/editor/features/editable-field/Overlay/Plaintext/index.vue +0 -1
  24. package/dist/runtime/editor/translations/de.json +20 -0
  25. package/dist/runtime/editor/translations/fr.json +20 -0
  26. package/dist/runtime/editor/translations/gsw_CH.json +20 -0
  27. package/dist/runtime/editor/translations/it.json +20 -0
  28. package/dist/shared/{editor.BdBm1Z7C.d.mts → editor.BVregnEC.d.mts} +24 -0
  29. package/dist/types.d.mts +1 -1
  30. package/package.json +2 -1
package/dist/module.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { NuxtModule } from 'nuxt/schema';
2
- import { M as ModuleOptions } from './shared/editor.BdBm1Z7C.mjs';
3
- export { a as ModuleHooks } from './shared/editor.BdBm1Z7C.mjs';
2
+ import { M as ModuleOptions } from './shared/editor.BVregnEC.mjs';
3
+ export { a as ModuleHooks } from './shared/editor.BVregnEC.mjs';
4
4
  import 'consola';
5
5
  import '../dist/global/types/definitions.js';
6
6
  import '../dist/global/types/theme.js';
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blokkli/editor",
3
3
  "configKey": "blokkli",
4
- "version": "2.0.0-alpha.44",
4
+ "version": "2.0.0-alpha.46",
5
5
  "compatibility": {
6
6
  "nuxt": ">=3.15.0"
7
7
  },
package/dist/module.mjs CHANGED
@@ -18,7 +18,7 @@ import 'typescript';
18
18
  import 'oxc-walker';
19
19
 
20
20
  const name = "@blokkli/editor";
21
- const version = "2.0.0-alpha.44";
21
+ const version = "2.0.0-alpha.46";
22
22
 
23
23
  function validateOption(optionKey, option, icons) {
24
24
  const errors = [];
@@ -1426,6 +1426,7 @@ const USED_MATERIAL_ICONS = [
1426
1426
  "bk_mdi_build-fill",
1427
1427
  "bk_mdi_calendar_clock",
1428
1428
  "bk_mdi_calendar_month",
1429
+ "bk_mdi_campaign",
1429
1430
  "bk_mdi_chat",
1430
1431
  "bk_mdi_check",
1431
1432
  "bk_mdi_check_box",
@@ -2662,6 +2663,17 @@ export const forceDefaultLanguage = ${JSON.stringify(
2662
2663
  )}
2663
2664
 
2664
2665
  export const featureFragmentNames = ${JSON.stringify(featureFragmentNames)}
2666
+
2667
+ export const textAutoReplace = ${(() => {
2668
+ const opt = ctx.helper.options.textAutoReplace;
2669
+ const all = opt === void 0 || opt === true;
2670
+ const obj = typeof opt === "object" && opt !== null ? opt : null;
2671
+ return JSON.stringify({
2672
+ quotes: obj ? obj.quotes ?? true : all,
2673
+ ellipsis: obj ? obj.ellipsis ?? true : all,
2674
+ enDash: obj ? obj.enDash ?? true : all
2675
+ });
2676
+ })()}
2665
2677
  `;
2666
2678
  },
2667
2679
  (ctx) => {
@@ -2719,6 +2731,15 @@ export declare const forceDefaultLanguage: boolean
2719
2731
  * The fragment names provided by features.
2720
2732
  */
2721
2733
  export declare const featureFragmentNames: string[]
2734
+
2735
+ /**
2736
+ * Text auto-replace configuration.
2737
+ */
2738
+ export declare const textAutoReplace: {
2739
+ quotes: boolean
2740
+ ellipsis: boolean
2741
+ enDash: boolean
2742
+ }
2722
2743
  `;
2723
2744
  }
2724
2745
  );
@@ -1,4 +1,4 @@
1
- import { B as BlokkliModule } from '../../shared/editor.BdBm1Z7C.mjs';
1
+ import { B as BlokkliModule } from '../../shared/editor.BVregnEC.mjs';
2
2
  import 'nuxt/schema';
3
3
  import 'consola';
4
4
  import '../../../dist/global/types/definitions.js';
@@ -1,4 +1,4 @@
1
- import { B as BlokkliModule } from '../../shared/editor.BdBm1Z7C.mjs';
1
+ import { B as BlokkliModule } from '../../shared/editor.BVregnEC.mjs';
2
2
  import 'nuxt/schema';
3
3
  import 'consola';
4
4
  import '../../../dist/global/types/definitions.js';
@@ -8,6 +8,7 @@ mutation pbAddFile(
8
8
  $hostUuid: String!
9
9
  $hostFieldName: String!
10
10
  $afterUuid: String
11
+ $paragraphBundle: String!
11
12
  ) {
12
13
  state: paragraphsEditMutationState(
13
14
  entityType: $entityType
@@ -20,6 +21,7 @@ mutation pbAddFile(
20
21
  hostType: $hostType
21
22
  hostUuid: $hostUuid
22
23
  hostFieldName: $hostFieldName
24
+ paragraphBundle: $paragraphBundle
23
25
  ) {
24
26
  ...paragraphsBlokkliMutationResult
25
27
  }
@@ -8,6 +8,7 @@ mutation pbAddImage(
8
8
  $hostUuid: String!
9
9
  $hostFieldName: String!
10
10
  $afterUuid: String
11
+ $paragraphBundle: String!
11
12
  ) {
12
13
  state: paragraphsEditMutationState(
13
14
  entityType: $entityType
@@ -20,6 +21,7 @@ mutation pbAddImage(
20
21
  hostType: $hostType
21
22
  hostUuid: $hostUuid
22
23
  hostFieldName: $hostFieldName
24
+ paragraphBundle: $paragraphBundle
23
25
  ) {
24
26
  ...paragraphsBlokkliMutationResult
25
27
  }
@@ -7,6 +7,7 @@ mutation pbAddVideoRemote(
7
7
  $hostUuid: String!
8
8
  $hostFieldName: String!
9
9
  $afterUuid: String
10
+ $paragraphBundle: String!
10
11
  ) {
11
12
  state: paragraphsEditMutationState(
12
13
  entityType: $entityType
@@ -18,6 +19,7 @@ mutation pbAddVideoRemote(
18
19
  hostType: $hostType
19
20
  hostUuid: $hostUuid
20
21
  hostFieldName: $hostFieldName
22
+ paragraphBundle: $paragraphBundle
21
23
  ) {
22
24
  ...paragraphsBlokkliMutationResult
23
25
  }
@@ -1,4 +1,4 @@
1
- import { B as BlokkliModule } from '../../shared/editor.BdBm1Z7C.mjs';
1
+ import { B as BlokkliModule } from '../../shared/editor.BVregnEC.mjs';
2
2
  import 'nuxt/schema';
3
3
  import 'consola';
4
4
  import '../../../dist/global/types/definitions.js';
@@ -887,7 +887,8 @@ export default defineBlokkliEditAdapter(
887
887
  hostType: e.host.type,
888
888
  hostUuid: e.host.uuid,
889
889
  hostFieldName: e.host.fieldName,
890
- afterUuid: e.afterUuid
890
+ afterUuid: e.afterUuid,
891
+ paragraphBundle: e.blockBundle
891
892
  }).then(mapMutation);
892
893
  } else if (e.item.type === "file" && hasMutation("pbAddFile")) {
893
894
  return useGraphqlMutation("pbAddFile", {
@@ -897,7 +898,8 @@ export default defineBlokkliEditAdapter(
897
898
  hostType: e.host.type,
898
899
  hostUuid: e.host.uuid,
899
900
  hostFieldName: e.host.fieldName,
900
- afterUuid: e.afterUuid
901
+ afterUuid: e.afterUuid,
902
+ paragraphBundle: e.blockBundle
901
903
  }).then(mapMutation);
902
904
  } else if (e.item.type === "video" && hasMutation("pbAddVideoRemote")) {
903
905
  return useGraphqlMutation("pbAddVideoRemote", {
@@ -906,7 +908,8 @@ export default defineBlokkliEditAdapter(
906
908
  hostType: e.host.type,
907
909
  hostUuid: e.host.uuid,
908
910
  hostFieldName: e.host.fieldName,
909
- afterUuid: e.afterUuid
911
+ afterUuid: e.afterUuid,
912
+ paragraphBundle: e.blockBundle
910
913
  }).then(mapMutation);
911
914
  }
912
915
  };
@@ -1,4 +1,4 @@
1
- import { B as BlokkliModule } from '../../shared/editor.BdBm1Z7C.mjs';
1
+ import { B as BlokkliModule } from '../../shared/editor.BVregnEC.mjs';
2
2
  import 'nuxt/schema';
3
3
  import 'consola';
4
4
  import '../../../dist/global/types/definitions.js';
@@ -22,9 +22,11 @@
22
22
  </template>
23
23
 
24
24
  <script setup>
25
- import { useTemplateRef, ref, computed, watch, onMounted } from "#imports";
25
+ import { useTemplateRef, ref, computed, watch, onMounted, useBlokkli } from "#imports";
26
26
  import { onBlokkliEvent } from "#blokkli/editor/composables";
27
27
  import { ClipboardData } from "#blokkli/editor/helpers/clipboardData";
28
+ import { textAutoReplace } from "#blokkli-build/editor-config";
29
+ const GUILLEMET_LANGUAGES = ["de", "fr"];
28
30
  defineOptions({
29
31
  inheritAttrs: false
30
32
  });
@@ -38,6 +40,10 @@ const props = defineProps({
38
40
  autofocus: { type: Boolean, required: false }
39
41
  });
40
42
  const emit = defineEmits(["submit", "keydown"]);
43
+ const { ui } = useBlokkli();
44
+ const shouldReplaceQuotes = computed(
45
+ () => textAutoReplace.quotes && GUILLEMET_LANGUAGES.includes(ui.interfaceLanguage.value)
46
+ );
41
47
  const modelValue = defineModel({ type: String, ...{ required: true } });
42
48
  const textarea = useTemplateRef("textarea");
43
49
  const height = ref(props.minHeight);
@@ -45,12 +51,223 @@ const isScrollable = computed(() => {
45
51
  if (!props.maxHeight) return false;
46
52
  return height.value >= props.maxHeight;
47
53
  });
54
+ const IDLE_MS = 400;
55
+ let history = [];
56
+ let historyIndex = -1;
57
+ let lastActionType = null;
58
+ let idleTimer = null;
59
+ let restoringSnapshot = false;
60
+ function currentSnapshot() {
61
+ const el = textarea.value;
62
+ return {
63
+ text: modelValue.value,
64
+ cursor: el?.selectionStart ?? modelValue.value.length
65
+ };
66
+ }
67
+ function pushSnapshot(entry) {
68
+ const snap = entry ?? currentSnapshot();
69
+ if (history[historyIndex]?.text === snap.text) {
70
+ return;
71
+ }
72
+ history = history.slice(0, historyIndex + 1);
73
+ history.push(snap);
74
+ historyIndex = history.length - 1;
75
+ }
76
+ function pushUndoSnapshot() {
77
+ clearIdleTimer();
78
+ pushSnapshot();
79
+ }
80
+ function clearIdleTimer() {
81
+ if (idleTimer !== null) {
82
+ clearTimeout(idleTimer);
83
+ idleTimer = null;
84
+ }
85
+ }
86
+ function scheduleIdleSnapshot() {
87
+ clearIdleTimer();
88
+ idleTimer = setTimeout(() => {
89
+ pushSnapshot();
90
+ }, IDLE_MS);
91
+ }
92
+ function undo() {
93
+ if (historyIndex < 0) {
94
+ return;
95
+ }
96
+ if (historyIndex === history.length - 1) {
97
+ const snap = currentSnapshot();
98
+ if (history[historyIndex]?.text !== snap.text) {
99
+ pushSnapshot(snap);
100
+ }
101
+ }
102
+ if (historyIndex <= 0) {
103
+ return;
104
+ }
105
+ historyIndex--;
106
+ restoreSnapshot(history[historyIndex]);
107
+ }
108
+ function redo() {
109
+ if (historyIndex >= history.length - 1) {
110
+ return;
111
+ }
112
+ historyIndex++;
113
+ restoreSnapshot(history[historyIndex]);
114
+ }
115
+ function restoreSnapshot(entry) {
116
+ restoringSnapshot = true;
117
+ modelValue.value = entry.text;
118
+ requestAnimationFrame(() => {
119
+ textarea.value?.setSelectionRange(entry.cursor, entry.cursor);
120
+ restoringSnapshot = false;
121
+ });
122
+ }
123
+ function onInput() {
124
+ if (restoringSnapshot) {
125
+ return;
126
+ }
127
+ const el = textarea.value;
128
+ if (!el) {
129
+ return;
130
+ }
131
+ const text = el.value;
132
+ const prevText = history[historyIndex]?.text ?? "";
133
+ const actionType = text.length > prevText.length ? "insert" : text.length < prevText.length ? "delete" : "other";
134
+ if (lastActionType !== null && actionType !== lastActionType) {
135
+ pushSnapshot({
136
+ text: prevText,
137
+ cursor: history[historyIndex]?.cursor ?? el.selectionStart
138
+ });
139
+ }
140
+ lastActionType = actionType;
141
+ scheduleIdleSnapshot();
142
+ }
143
+ function tryReplaceQuotes() {
144
+ const el = textarea.value;
145
+ if (!el) {
146
+ return;
147
+ }
148
+ const cursorPos = el.selectionStart;
149
+ const text = modelValue.value;
150
+ const closePos = cursorPos - 1;
151
+ if (text[closePos] !== '"') {
152
+ return;
153
+ }
154
+ let quoteCount = 0;
155
+ for (let i = 0; i < closePos; i++) {
156
+ if (text[i] === '"') {
157
+ quoteCount++;
158
+ }
159
+ }
160
+ if (quoteCount % 2 === 0) {
161
+ return;
162
+ }
163
+ const openPos = text.lastIndexOf('"', closePos - 1);
164
+ if (openPos === -1 || closePos - openPos < 2) {
165
+ return;
166
+ }
167
+ const inner = text.slice(openPos + 1, closePos);
168
+ if (inner.startsWith(" ") || inner.endsWith(" ")) {
169
+ return;
170
+ }
171
+ pushUndoSnapshot();
172
+ modelValue.value = text.slice(0, openPos) + "\xAB" + inner + "\xBB" + text.slice(closePos + 1);
173
+ requestAnimationFrame(() => {
174
+ el.setSelectionRange(cursorPos, cursorPos);
175
+ });
176
+ }
177
+ function tryReplaceEllipsis() {
178
+ const el = textarea.value;
179
+ if (!el) {
180
+ return;
181
+ }
182
+ const cursorPos = el.selectionStart;
183
+ const text = modelValue.value;
184
+ if (cursorPos < 3) {
185
+ return;
186
+ }
187
+ if (text.slice(cursorPos - 3, cursorPos) !== "...") {
188
+ return;
189
+ }
190
+ pushUndoSnapshot();
191
+ const newCursor = cursorPos - 2;
192
+ modelValue.value = text.slice(0, cursorPos - 3) + "\u2026" + text.slice(cursorPos);
193
+ requestAnimationFrame(() => {
194
+ el.setSelectionRange(newCursor, newCursor);
195
+ });
196
+ }
197
+ function tryReplaceEnDash() {
198
+ const el = textarea.value;
199
+ if (!el) {
200
+ return;
201
+ }
202
+ const cursorPos = el.selectionStart;
203
+ const text = modelValue.value;
204
+ if (cursorPos < 2) {
205
+ return;
206
+ }
207
+ if (text.slice(cursorPos - 2, cursorPos) !== "--") {
208
+ return;
209
+ }
210
+ if (cursorPos >= 3 && text[cursorPos - 3] === "-") {
211
+ return;
212
+ }
213
+ pushUndoSnapshot();
214
+ const newCursor = cursorPos - 1;
215
+ modelValue.value = text.slice(0, cursorPos - 2) + "\u2013" + text.slice(cursorPos);
216
+ requestAnimationFrame(() => {
217
+ el.setSelectionRange(newCursor, newCursor);
218
+ });
219
+ }
220
+ onMounted(() => {
221
+ pushSnapshot({
222
+ text: modelValue.value,
223
+ cursor: modelValue.value.length
224
+ });
225
+ textarea.value?.addEventListener("input", onInput);
226
+ if (props.autofocus && textarea.value) {
227
+ textarea.value.focus();
228
+ }
229
+ });
230
+ watch(modelValue, () => {
231
+ if (restoringSnapshot) {
232
+ return;
233
+ }
234
+ if (!modelValue.value) {
235
+ height.value = props.minHeight;
236
+ }
237
+ });
48
238
  function onPointerDown(e) {
49
239
  if (e.target instanceof HTMLElement) {
50
240
  e.target.setPointerCapture(e.pointerId);
51
241
  }
52
242
  }
53
243
  function onKeydown(e) {
244
+ if ((e.ctrlKey || e.metaKey) && !e.altKey) {
245
+ if (e.key.toLowerCase() === "z" && !e.shiftKey) {
246
+ e.preventDefault();
247
+ undo();
248
+ return;
249
+ }
250
+ if (e.key.toLowerCase() === "z" && e.shiftKey || e.key === "y") {
251
+ e.preventDefault();
252
+ redo();
253
+ return;
254
+ }
255
+ }
256
+ if (shouldReplaceQuotes.value && e.key === '"') {
257
+ requestAnimationFrame(() => {
258
+ tryReplaceQuotes();
259
+ });
260
+ }
261
+ if (textAutoReplace.ellipsis && e.key === ".") {
262
+ requestAnimationFrame(() => {
263
+ tryReplaceEllipsis();
264
+ });
265
+ }
266
+ if (textAutoReplace.enDash && e.key === "-") {
267
+ requestAnimationFrame(() => {
268
+ tryReplaceEnDash();
269
+ });
270
+ }
54
271
  emit("keydown", e);
55
272
  if (props.submitOnEnter && e.key === "Enter" && !e.shiftKey) {
56
273
  e.preventDefault();
@@ -66,6 +283,7 @@ function onPaste(e) {
66
283
  }
67
284
  if (!props.pasteMarkdown || !data.hasHtml()) return;
68
285
  e.preventDefault();
286
+ pushSnapshot();
69
287
  const markdown = data.toMarkdown();
70
288
  const el = textarea.value;
71
289
  if (!el) return;
@@ -77,23 +295,14 @@ function onPaste(e) {
77
295
  const newPos = start + markdown.length;
78
296
  requestAnimationFrame(() => {
79
297
  el.setSelectionRange(newPos, newPos);
298
+ pushSnapshot();
80
299
  });
81
300
  }
82
- watch(modelValue, (newValue) => {
83
- if (!newValue) {
84
- height.value = props.minHeight;
85
- }
86
- });
87
301
  onBlokkliEvent("animationFrame", () => {
88
302
  const scrollHeight = textarea.value?.scrollHeight ?? props.minHeight;
89
303
  const newHeight = Math.max(scrollHeight, props.minHeight);
90
304
  height.value = props.maxHeight ? Math.min(newHeight, props.maxHeight) : newHeight;
91
305
  });
92
- onMounted(() => {
93
- if (props.autofocus && textarea.value) {
94
- textarea.value.focus();
95
- }
96
- });
97
306
  defineExpose({
98
307
  focus: () => textarea.value?.focus(),
99
308
  blur: () => textarea.value?.blur(),