aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/formattedText/DailyJournal.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/formattedText/DailyJournal.tsx')
-rw-r--r--src/client/views/nodes/formattedText/DailyJournal.tsx119
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