diff options
-rw-r--r-- | src/client/views/nodes/formattedText/DailyJournal.tsx | 64 |
1 files changed, 52 insertions, 12 deletions
diff --git a/src/client/views/nodes/formattedText/DailyJournal.tsx b/src/client/views/nodes/formattedText/DailyJournal.tsx index 697cf4a2a..da96b8b2f 100644 --- a/src/client/views/nodes/formattedText/DailyJournal.tsx +++ b/src/client/views/nodes/formattedText/DailyJournal.tsx @@ -8,12 +8,15 @@ import { FormattedTextBox, FormattedTextBoxProps } from './FormattedTextBox'; import { gptAPICall, GPTCallType, gptImageLabel } from '../../../apis/gpt/GPT'; import { RichTextField } from '../../../../fields/RichTextField'; import { TextSelection } from 'prosemirror-state'; +import { Plugin } from 'prosemirror-state'; export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() { @observable journalDate: string; @observable typingTimeout: NodeJS.Timeout | null = null; // Track typing delay @observable lastUserText: string = ''; // Store last user-entered text _ref = React.createRef<FormattedTextBox>(); // reference to the formatted textbox + predictiveTextRange: { from: number; to: number } | null = null; // where predictive text starts and ends + private predictiveText: string | null = ' ... why?'; public static LayoutString(fieldStr: string) { return FieldView.LayoutString(DailyJournal, fieldStr); @@ -89,19 +92,11 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() const editorView = this._ref.current?.EditorView; if (!editorView) return; - const { state } = editorView; - const currentText = state.doc.textBetween(0, state.doc.content.size); - - // Store the last user text (before adding the prediction) - this.lastUserText = currentText; - - // If user types, clear any existing timeout if (this.typingTimeout) clearTimeout(this.typingTimeout); - // Set a new timeout to add predictive text after 3.5 seconds this.typingTimeout = setTimeout(() => { this.insertPredictiveQuestion(); - }, 3500); // 3.5 seconds + }, 3500); }; /** @@ -115,12 +110,11 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() const { state, dispatch } = editorView; const { schema } = state; const { from, to } = state.selection; - const insertPos = from === to ? from : to; // cursor position + const insertPos = to; // cursor position const resolvedPos = state.doc.resolve(insertPos); const parentNode = resolvedPos.parent; const indexInParent = resolvedPos.index(); - const isAtEndOfParent = indexInParent >= parentNode.childCount; // Check if there's a line break or paragraph node after the current position @@ -140,11 +134,50 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() const fontItalicsMark = schema.marks.em.create(); // styled text node - const predictedText = schema.text(' ... why?', [fontSizeMark, fontColorMark, fontItalicsMark]); + const text = ' ... why?'; + const predictedText = schema.text(text, [fontSizeMark, fontColorMark, fontItalicsMark]); // Insert styled text at cursor position const transaction = state.tr.insert(insertPos, predictedText); dispatch(transaction); + + this.predictiveText = text; + }; + + createPredictiveCleanupPlugin = () => { + return new Plugin({ + view: () => { + return { + update: (view, prevState) => { + const { state, dispatch } = view; + if (!this.predictiveText) return; + + // Check if doc or selection changed + if (!prevState.doc.eq(state.doc) || !prevState.selection.eq(state.selection)) { + let found = false; + const textToRemove = this.predictiveText; + + state.doc.descendants((node, pos) => { + if (node.isText && node.text === textToRemove) { + // Remove the predictive text node + const tr = state.tr.delete(pos, pos + node.nodeSize); + dispatch(tr); + this.predictiveText = null; + found = true; + return false; // stop traversal + } + return true; + }); + + if (!found) { + // fallback cleanup + this.predictiveText = null; + } + } + }, + }; + }, + }); }; componentDidMount(): void { @@ -154,6 +187,13 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() const editorView = this._ref.current?.EditorView; if (editorView) { editorView.dom.addEventListener('input', this.onTextInput); + + // Add plugin to state if not already added + const cleanupPlugin = this.createPredictiveCleanupPlugin(); + const newState = editorView.state.reconfigure({ + plugins: [...editorView.state.plugins, cleanupPlugin], + }); + editorView.updateState(newState); } const rawText = (this.Document.text as any)?.Text ?? ''; |