diff options
author | aaravkumar <aarav.kumar1510@gmail.com> | 2025-04-03 22:25:13 -0400 |
---|---|---|
committer | aaravkumar <aarav.kumar1510@gmail.com> | 2025-04-03 22:25:13 -0400 |
commit | 5315ae28692e8214e6661d2336b6679fed9f90e2 (patch) | |
tree | 4ab78485242e0eec4bd8b6798815bc87f7ba757b /src/client/views/nodes/formattedText/DailyJournal.tsx | |
parent | 858f5d2f1621695a703b0e3f8297521c3ebe692d (diff) |
code cleanup + added basic functionality for predictive text (only mock text right now, and doesn't automatically disappear yet)
Diffstat (limited to 'src/client/views/nodes/formattedText/DailyJournal.tsx')
-rw-r--r-- | src/client/views/nodes/formattedText/DailyJournal.tsx | 119 |
1 files changed, 103 insertions, 16 deletions
diff --git a/src/client/views/nodes/formattedText/DailyJournal.tsx b/src/client/views/nodes/formattedText/DailyJournal.tsx index 31108f05a..697cf4a2a 100644 --- a/src/client/views/nodes/formattedText/DailyJournal.tsx +++ b/src/client/views/nodes/formattedText/DailyJournal.tsx @@ -11,6 +11,9 @@ import { TextSelection } 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 public static LayoutString(fieldStr: string) { return FieldView.LayoutString(DailyJournal, fieldStr); @@ -22,6 +25,11 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() this.journalDate = this.getFormattedDate(); } + /** + * Method to get the current date in standard format + * @returns - date in standard long format + */ + getFormattedDate(): string { const date = new Date().toLocaleDateString(undefined, { weekday: 'long', @@ -33,6 +41,9 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() return date; } + /** + * Method to set the title of the node to the date + */ @action setDailyTitle() { console.log('setDailyTitle() called...'); @@ -46,6 +57,9 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() console.log('New title after update:', this.dataDoc.title); } + /** + * Method to set the standard text of the node (to the current date) + */ @action setDailyText() { const placeholderText = 'Start writing here...'; @@ -66,16 +80,88 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() console.log('Current text field:', this.dataDoc[this.fieldKey]); } + /** + * Tracks user typing text inout into the node, to call the insert predicted + * text function when appropriate (i.e. when the user stops typing) + */ + + @action onTextInput = () => { + 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 + }; + + /** + * Inserts predictive text at the end of what the user is typing + */ + + @action insertPredictiveQuestion = () => { + const editorView = this._ref.current?.EditorView; + if (!editorView) return; + + const { state, dispatch } = editorView; + const { schema } = state; + const { from, to } = state.selection; + const insertPos = from === to ? from : 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 + let hasNewlineAfter = false; + try { + const nextNode = parentNode.child(indexInParent); + hasNewlineAfter = nextNode.type.name === 'hard_break' || nextNode.type.name === 'paragraph'; + } catch { + hasNewlineAfter = false; + } + + // Only insert if we're at end of node, or there's a newline node after + if (!isAtEndOfParent && !hasNewlineAfter) return; + + const fontSizeMark = schema.marks.pFontSize.create({ fontSize: '14px' }); + const fontColorMark = schema.marks.pFontColor.create({ fontColor: 'lightgray' }); + const fontItalicsMark = schema.marks.em.create(); + + // styled text node + const predictedText = schema.text(' ... why?', [fontSizeMark, fontColorMark, fontItalicsMark]); + + // Insert styled text at cursor position + const transaction = state.tr.insert(insertPos, predictedText); + dispatch(transaction); + }; + componentDidMount(): void { console.log('componentDidMount() triggered...'); - console.log("Text: " + (this.Document.text as any)?.Text); + console.log('Text: ' + (this.Document.text as any)?.Text); + + const editorView = this._ref.current?.EditorView; + if (editorView) { + editorView.dom.addEventListener('input', this.onTextInput); + } const rawText = (this.Document.text as any)?.Text ?? ''; - const isTextEmpty = !rawText || rawText === ""; + const isTextEmpty = !rawText || rawText === ''; - const currentTitle = this.dataDoc.title || ""; + const currentTitle = this.dataDoc.title || ''; const isTitleString = typeof currentTitle === 'string'; - const isDefaultTitle = isTitleString && currentTitle.includes("Untitled DailyJournal"); + const isDefaultTitle = isTitleString && currentTitle.includes('Untitled DailyJournal'); if (isTextEmpty && isDefaultTitle) { console.log('Journal title and text are default. Initializing...'); @@ -86,6 +172,14 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() } } + componentWillUnmount(): void { + const editorView = this._ref.current?.EditorView; + if (editorView) { + editorView.dom.removeEventListener('input', this.onTextInput); + } + if (this.typingTimeout) clearTimeout(this.typingTimeout); + } + @action handleGeneratePrompts = async () => { const rawText = (this.Document.text as any)?.Text ?? ''; console.log('Extracted Journal Text:', rawText); @@ -109,36 +203,29 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() if (!editorView) { console.error('EditorView is not available.'); return; - } - - else { + } else { const { state, dispatch } = editorView; const { schema } = state; - // Use available marks + // Use available marks const boldMark = schema.marks.strong.create(); const italicMark = schema.marks.em.create(); - const fontSizeMark = schema.marks.pFontSize.create({ fontSize: "14px" }); - const fontColorMark = schema.marks.pFontColor.create({ fontColor: "gray" }); + const fontSizeMark = schema.marks.pFontSize.create({ fontSize: '14px' }); + const fontColorMark = schema.marks.pFontColor.create({ fontColor: 'gray' }); // Create text nodes with formatting const headerText = schema.text('\n\n# Suggested Prompts:\n', [boldMark, italicMark, fontSizeMark, fontColorMark]); const responseText = schema.text(res, [fontSizeMark, fontColorMark]); // Insert formatted text - const transaction = state.tr.insert( - state.selection.from, headerText).insert( - state.selection.from + headerText.nodeSize, responseText); + const transaction = state.tr.insert(state.selection.from, headerText).insert(state.selection.from + headerText.nodeSize, responseText); dispatch(transaction); } - } catch (err) { console.error('Error calling GPT:', err); } }; - _ref = React.createRef<FormattedTextBox>(); - render() { return ( <div |