Skip to content

Commit ced3330

Browse files
AlexVelezLlclaude
andcommitted
refactor(TipTapEditor): emit markdown on blur and cache last emitted value
Previously the editor emitted an `update` on every editor state change via a deep watcher on `editor.state`. This caused frequent re-serialization and required an `isUpdatingFromOutside` flag to break the feedback loop when applying parent-driven content updates. Now the markdown update is emitted only when the editor loses focus (blur), and the latest emitted markdown is cached. When `prop.value` changes, it is compared against the cache so an echoed-back value skips unnecessary re-rendering of the editor content. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 30a0bf1 commit ced3330

1 file changed

Lines changed: 29 additions & 24 deletions

File tree

  • contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor

contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,10 @@
197197
return editor.value.storage.markdown.getMarkdown();
198198
};
199199
200-
let isUpdatingFromOutside = false; // A flag to prevent infinite update loops
200+
// Cache of the latest markdown value emitted by this component. Used to
201+
// detect when an incoming prop.value is just our own emitted value echoed
202+
// back, so we can skip unnecessary re-rendering of the editor content.
203+
let lastEmittedMarkdown = null;
201204
202205
watch(
203206
() => props.mode,
@@ -217,6 +220,13 @@
217220
watch(
218221
() => props.value,
219222
newValue => {
223+
// If the incoming value matches what we last emitted, the editor
224+
// already reflects this content, so skip re-rendering to avoid
225+
// unnecessary work and resetting the editor state.
226+
if (newValue === lastEmittedMarkdown) {
227+
return;
228+
}
229+
220230
const processedContent = preprocessMarkdown(newValue);
221231
222232
if (!editor.value) {
@@ -228,36 +238,31 @@
228238
229239
const editorContent = getMarkdownContent();
230240
if (editorContent !== newValue) {
231-
isUpdatingFromOutside = true;
232241
editor.value.commands.setContent(processedContent, false);
233-
nextTick(() => {
234-
isUpdatingFromOutside = false;
235-
});
236242
}
237243
},
238244
{ immediate: true },
239245
);
240246
241-
// sync changes from the editor to the parent component
242-
watch(
243-
() => editor.value?.state,
244-
() => {
245-
if (
246-
!editor.value ||
247-
!isReady.value ||
248-
isUpdatingFromOutside ||
249-
!editor.value.storage?.markdown
250-
) {
251-
return;
252-
}
247+
// sync changes from the editor to the parent component, only on blur
248+
const emitMarkdownUpdate = () => {
249+
if (!editor.value || !isReady.value || !editor.value.storage?.markdown) {
250+
return;
251+
}
253252
254-
const markdown = getMarkdownContent();
255-
if (markdown !== props.value) {
256-
emit('update', markdown);
257-
}
258-
},
259-
{ deep: true },
260-
);
253+
const markdown = getMarkdownContent();
254+
if (markdown !== props.value) {
255+
lastEmittedMarkdown = markdown;
256+
emit('update', markdown);
257+
}
258+
};
259+
260+
// Emit the markdown update only when the editor loses focus (blur).
261+
watch(isFocused, (focused, wasFocused) => {
262+
if (wasFocused && !focused) {
263+
emitMarkdownUpdate();
264+
}
265+
});
261266
262267
const handleContainerKeydown = event => {
263268
if (event.key === 'Enter') {

0 commit comments

Comments
 (0)