|
7 | 7 | <div :class="fullScreenMode && wrapperClasses"> |
8 | 8 | <div |
9 | 9 | class="bard-fieldtype antialiased with-contrast:border-gray-500 shadow-ui-sm" |
10 | | - :class="{ 'bard-fullscreen': fullScreenMode }" |
| 10 | + :class="{ 'bard-fullscreen': fullScreenMode, 'bard-bulk-op': suppressTransitions }" |
11 | 11 | ref="container" |
12 | 12 | @dragstart.stop="ignorePageHeader(true)" |
13 | 13 | @dragend="ignorePageHeader(false)" |
@@ -177,6 +177,8 @@ import 'highlight.js/styles/github.css'; |
177 | 177 | import importTiptap from '@/util/tiptap.js'; |
178 | 178 | import { computed } from 'vue'; |
179 | 179 | import { data_get } from "@/bootstrap/globals.js"; |
| 180 | +import extractBardText from "@/util/extractBardText"; |
| 181 | +import { createMountScheduler } from "@/util/createMountScheduler.js"; |
180 | 182 |
|
181 | 183 | const lowlight = createLowlight(common); |
182 | 184 | let tiptap = null; |
@@ -213,10 +215,12 @@ export default { |
213 | 215 | escBinding: null, |
214 | 216 | showAddSetButton: false, |
215 | 217 | hasBeenFocused: false, |
| 218 | + suppressTransitions: false, |
216 | 219 | provide: { |
217 | 220 | bard: this.makeBardProvide(), |
218 | 221 | bardSets: this.config.sets, |
219 | 222 | showReplicatorFieldPreviews: this.config.previews, |
| 223 | + mountScheduler: createMountScheduler(), |
220 | 224 | }, |
221 | 225 | errorsById: {}, |
222 | 226 | debounceNextUpdate: true, |
@@ -294,25 +298,7 @@ export default { |
294 | 298 |
|
295 | 299 | replicatorPreview() { |
296 | 300 | if (!this.showFieldPreviews) return; |
297 | | - const stack = [...this.value]; |
298 | | - let text = ''; |
299 | | - while (stack.length) { |
300 | | - const node = stack.shift(); |
301 | | - if (node.type === 'text') { |
302 | | - text += ` ${node.text || ''}`; |
303 | | - } else if (node.type === 'set') { |
304 | | - const handle = node.attrs.values.type; |
305 | | - const set = this.setConfigs.find((set) => set.handle === handle); |
306 | | - text += ` [${__(set ? set.display : handle)}]`; |
307 | | - } |
308 | | - if (text.length > 150) { |
309 | | - break; |
310 | | - } |
311 | | - if (node.content) { |
312 | | - stack.unshift(...node.content); |
313 | | - } |
314 | | - } |
315 | | - return text; |
| 301 | + return extractBardText(this.value, 150, this.setConfigs); |
316 | 302 | }, |
317 | 303 |
|
318 | 304 | inputIsInline() { |
@@ -391,6 +377,7 @@ export default { |
391 | 377 |
|
392 | 378 | this.json = this.editor.getJSON().content; |
393 | 379 | this.html = this.editor.getHTML(); |
| 380 | + this._lastDocSize = this.editor.state.doc.content.size; |
394 | 381 |
|
395 | 382 | this.$nextTick(() => this.mounted = true); |
396 | 383 |
|
@@ -643,11 +630,19 @@ export default { |
643 | 630 | }, |
644 | 631 |
|
645 | 632 | collapseAll() { |
| 633 | + this.suppressTransitions = true; |
646 | 634 | this.collapsed = Object.keys(this.meta.existing); |
| 635 | + this.$nextTick(() => requestAnimationFrame(() => { |
| 636 | + this.suppressTransitions = false; |
| 637 | + })); |
647 | 638 | }, |
648 | 639 |
|
649 | 640 | expandAll() { |
| 641 | + this.suppressTransitions = true; |
650 | 642 | this.collapsed = []; |
| 643 | + this.$nextTick(() => requestAnimationFrame(() => { |
| 644 | + this.suppressTransitions = false; |
| 645 | + })); |
651 | 646 | }, |
652 | 647 |
|
653 | 648 | toggleCollapseSets() { |
@@ -868,24 +863,19 @@ export default { |
868 | 863 | } |
869 | 864 | }, 1); |
870 | 865 | }, |
871 | | - onUpdate: () => { |
872 | | - const oldJson = this.json; |
873 | | - const newJson = clone(this.editor.getJSON().content); |
874 | | -
|
875 | | - const countNodes = (nodes) => { |
876 | | - if (!nodes || !Array.isArray(nodes)) return 0; |
877 | | - let count = nodes.length; |
878 | | - nodes.forEach(node => { |
879 | | - if (node.content) { |
880 | | - count += countNodes(node.content); |
881 | | - } |
882 | | - }); |
883 | | - return count; |
884 | | - }; |
| 866 | + onUpdate: ({ transaction }) => { |
| 867 | + // Filter out non-doc transactions (selection, metadata, etc.). |
| 868 | + if (!transaction.docChanged) return; |
| 869 | +
|
| 870 | + const newDocSize = this.editor.state.doc.content.size; |
| 871 | + const oldDocSize = this._lastDocSize ?? newDocSize; |
885 | 872 |
|
886 | | - if (countNodes(oldJson) !== countNodes(newJson)) this.debounceNextUpdate = false; |
| 873 | + // Structural size change -> sync immediately, skip the usual debounce window. |
| 874 | + if (oldDocSize !== newDocSize) this.debounceNextUpdate = false; |
| 875 | + this._lastDocSize = newDocSize; |
887 | 876 |
|
888 | | - this.json = newJson; |
| 877 | + // getJSON() already returns a fresh plain object — no clone() needed. |
| 878 | + this.json = this.editor.getJSON().content; |
889 | 879 | this.html = this.editor.getHTML(); |
890 | 880 | }, |
891 | 881 | onCreate: ({ editor }) => { |
|
0 commit comments