diff options
-rw-r--r-- | src/client/views/nodes/TaskBox.tsx | 57 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/DailyJournal.tsx | 181 |
2 files changed, 213 insertions, 25 deletions
diff --git a/src/client/views/nodes/TaskBox.tsx b/src/client/views/nodes/TaskBox.tsx index dca01817f..ff1c70b90 100644 --- a/src/client/views/nodes/TaskBox.tsx +++ b/src/client/views/nodes/TaskBox.tsx @@ -9,26 +9,52 @@ import { Doc } from '../../../fields/Doc'; import './TaskBox.scss'; +/** + * Props (reference to document) for Task Box + */ + interface TaskBoxProps { Document: Doc; } +/** + * TaskBox class for adding task information + completing tasks + */ @observer export class TaskBox extends React.Component<TaskBoxProps> { + + /** + * Method to reuturn the + * @param fieldStr + * @returns + */ public static LayoutString(fieldStr: string) { return FieldView.LayoutString(TaskBox, fieldStr); } + /** + * Method to update the task description + * @param e - event of changing the description box input + */ + @action updateText = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => { this.props.Document.text = e.target.value; }; + /** + * Method to update the task title + * @param e - event of changing the title box input + */ @action updateTitle = (e: React.ChangeEvent<HTMLInputElement>) => { this.props.Document.title = e.target.value; }; + /** + * Method to update the all day status + * @param e - event of changing the all day checkbox + */ @action updateAllDay = (e: React.ChangeEvent<HTMLInputElement>) => { this.props.Document.$allDay = e.target.checked; @@ -41,6 +67,10 @@ export class TaskBox extends React.Component<TaskBoxProps> { this.setTaskDateRange(); }; + /** + * Method to update the task start time + * @param e - event of changing the start time input + */ @action updateStart = (e: React.ChangeEvent<HTMLInputElement>) => { const newStart = new Date(e.target.value); @@ -60,8 +90,10 @@ export class TaskBox extends React.Component<TaskBoxProps> { this.setTaskDateRange(); }; - - + /** + * Method to update the task end time + * @param e - event of changing the end time input + */ @action updateEnd = (e: React.ChangeEvent<HTMLInputElement>) => { const newEnd = new Date(e.target.value); @@ -81,9 +113,9 @@ export class TaskBox extends React.Component<TaskBoxProps> { this.setTaskDateRange(); }; - - - + /** + * Method to update the task date range + */ @action setTaskDateRange() { const doc = this.props.Document; @@ -107,11 +139,21 @@ export class TaskBox extends React.Component<TaskBoxProps> { } } + /** + * Method to set task's completion status + * @param e - event of changing the "completed" input checkbox + */ + @action toggleComplete = (e: React.ChangeEvent<HTMLInputElement>) => { this.props.Document.$completed = e.target.checked; }; + /** + * Constructor for the task box + * @param props - props containing the document reference + */ + constructor(props: TaskBoxProps) { super(props); makeObservable(this); @@ -150,6 +192,11 @@ export class TaskBox extends React.Component<TaskBoxProps> { this._widthDisposer?.(); } + /** + * Method to render the task box + * @returns - HTML with taskbox components + */ + render() { function toLocalDateTimeString(date: Date): string { diff --git a/src/client/views/nodes/formattedText/DailyJournal.tsx b/src/client/views/nodes/formattedText/DailyJournal.tsx index d6d30dc13..269a609ef 100644 --- a/src/client/views/nodes/formattedText/DailyJournal.tsx +++ b/src/client/views/nodes/formattedText/DailyJournal.tsx @@ -10,11 +10,18 @@ import { RichTextField } from '../../../../fields/RichTextField'; import { Plugin } from 'prosemirror-state'; import { RTFCast } from '../../../../fields/Types'; import { Mark } from 'prosemirror-model'; +import { observer } from 'mobx-react'; + 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 + @observable typingTimeout: NodeJS.Timeout | null = null; // track typing delay + @observable lastUserText: string = ''; // store last user-entered text + @observable isLoadingPrompts: boolean = false; // track if prompts are loading + @observable showPromptMenu = false; + @observable inlinePromptsEnabled = true; + + _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?'; @@ -42,7 +49,7 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() month: 'long', day: 'numeric', }); - console.log('getFormattedDate():', date); + // console.log('getFormattedDate():', date); return date; } @@ -51,15 +58,15 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() */ @action setDailyTitle() { - console.log('setDailyTitle() called...'); - console.log('Current title before update:', this.dataDoc.title); + // console.log('setDailyTitle() called...'); + // console.log('Current title before update:', this.dataDoc.title); if (!this.dataDoc.title || this.dataDoc.title !== this.journalDate) { - console.log('Updating title to:', this.journalDate); + // console.log('Updating title to:', this.journalDate); this.dataDoc.title = this.journalDate; } - console.log('New title after update:', this.dataDoc.title); + // console.log('New title after update:', this.dataDoc.title); } /** @@ -70,7 +77,7 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() const placeholderText = 'Start writing here...'; const dateText = `${this.journalDate}\n`; - console.log('Checking if dataDoc has text field...'); + // console.log('Checking if dataDoc has text field...'); this.dataDoc[this.fieldKey] = RichTextField.textToRtfFormat( [ @@ -82,9 +89,43 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() placeholderText.length ); - console.log('Current text field:', this.dataDoc[this.fieldKey]); + // console.log('Current text field:', this.dataDoc[this.fieldKey]); + } + + /** + * Method to show/hide the prompts menu + */ + @action.bound togglePromptMenu() { + this.showPromptMenu = !this.showPromptMenu; + } + + /** + * Method to toggle on/off inline predictive prompts + */ + @action.bound toggleInlinePrompts() { + this.inlinePromptsEnabled = !this.inlinePromptsEnabled; + } + + /** + * Method to handle click on document (to close prompt menu) + * @param e - a click on the document + */ + @action.bound + handleDocumentClick(e: MouseEvent) { + const menu = document.getElementById('prompts-menu'); + const button = document.getElementById('prompts-button'); + if ( + this.showPromptMenu && + menu && + !menu.contains(e.target as Node) && + button && + !button.contains(e.target as Node) + ) { + this.showPromptMenu = false; + } } + /** * Method to set initial date of document in the calendar view */ @@ -99,9 +140,9 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() this.dataDoc.date_range = `${localStart.toISOString()}|${localEnd.toISOString()}`; this.dataDoc.allDay = true; - console.log('Set date_range and allDay on journal (from local date):', this.dataDoc.date_range); + // console.log('Set date_range and allDay on journal (from local date):', this.dataDoc.date_range); } else { - console.log('Could not parse journalDate:', this.journalDate); + // console.log('Could not parse journalDate:', this.journalDate); } } } @@ -143,6 +184,8 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() */ @action insertPredictiveQuestion = async () => { + if (!this.inlinePromptsEnabled) return; + const editorView = this._ref.current?.EditorView; if (!editorView) return; @@ -196,6 +239,10 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() this.predictiveText = text; }; + /** + * Method to remove the predictive question upon type/click + * @returns - once predictive text is found, or all text has been checked + */ createPredictiveCleanupPlugin = () => { return new Plugin({ view: () => { @@ -213,7 +260,7 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() if (node.isText && node.text === textToRemove) { const tr = state.tr.delete(pos, pos + node.nodeSize); - // Set the desired default marks for future input + // default marks for input const fontSizeMark = state.schema.marks.pFontSize.create({ fontSize: '14px' }); const fontColorMark = state.schema.marks.pFontColor.create({ fontColor: 'gray' }); tr.setStoredMarks([]); @@ -244,8 +291,9 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() }; componentDidMount(): void { - console.log('componentDidMount() triggered...'); - console.log('Text: ' + RTFCast(this.Document.text)?.Text); + // console.log('componentDidMount() triggered...'); + document.addEventListener('mousedown', this.handleDocumentClick); + // console.log('Text: ' + RTFCast(this.Document.text)?.Text); const editorView = this._ref.current?.EditorView; if (editorView) { @@ -264,16 +312,17 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() const isDefaultTitle = isTitleString && currentTitle.includes('Untitled DailyJournal'); if (isTextEmpty && isDefaultTitle) { - console.log('Journal title and text are default. Initializing...'); + // console.log('Journal title and text are default. Initializing...'); this.setDailyTitle(); this.setDailyText(); this.setInitialDateRange(); } else { - console.log('Journal already has content. Skipping initialization.'); + // console.log('Journal already has content. Skipping initialization.'); } } componentWillUnmount(): void { + document.removeEventListener('mousedown', this.handleDocumentClick); const editorView = this._ref.current?.EditorView; if (editorView) { editorView.dom.removeEventListener('input', this.onTextInput); @@ -281,10 +330,20 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() if (this.typingTimeout) clearTimeout(this.typingTimeout); } + /** + * Method to generate pormpts via GPT + * @returns - if failed + */ @action handleGeneratePrompts = async () => { + if (this.isLoadingPrompts) { + return + } + + this.isLoadingPrompts = true; + const rawText = RTFCast(this.Document.text)?.Text ?? ''; - console.log('Extracted Journal Text:', rawText); - console.log('Before Update:', this.Document.text, 'Type:', typeof this.Document.text); + // console.log('Extracted Journal Text:', rawText); + // console.log('Before Update:', this.Document.text, 'Type:', typeof this.Document.text); if (!rawText.trim()) { alert('Journal is empty! Write something first.'); @@ -321,12 +380,20 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() // Insert formatted text const transaction = state.tr.insert(state.selection.from, headerText).insert(state.selection.from + headerText.nodeSize, responseText); dispatch(transaction); + (this._props as any)?.updateLayout?.(); + } } catch (err) { console.error('Error calling GPT:', err); + } finally { + this.isLoadingPrompts = false; } }; + /** + * Method to render the styled DailyJournal + * @returns - the HTML component for the journal + */ render() { return ( <div @@ -347,6 +414,7 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() }}> {/* GPT Button */} <button + id="prompts-button" style={{ position: 'absolute', bottom: '5px', @@ -359,9 +427,80 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() cursor: 'pointer', zIndex: 10, }} - onClick={this.handleGeneratePrompts}> + onClick={this.togglePromptMenu} + > Prompts </button> + {this.showPromptMenu && ( + <div + id="prompts-menu" + style={{ + position: 'absolute', + bottom: '45px', + right: '5px', + backgroundColor: 'white', + border: '1px solid #ccc', + borderRadius: '4px', + padding: '10px', + boxShadow: '0 2px 6px rgba(0,0,0,0.2)', + zIndex: 20, + minWidth: '170px', + maxWidth: 'fit-content', + overflow: 'auto', + }} + > + <div + style={{ + display: 'flex', + justifyContent: 'flex-end', + alignItems: 'center', + marginBottom: '10px', + }} + > + <label + style={{ + display: 'flex', + alignItems: 'center', + gap: '6px', + fontSize: '14px', + justifyContent: 'flex-end', + width: '100%', + }} + > + Inline Prompting + <input + type="checkbox" + checked={this.inlinePromptsEnabled} + onChange={this.toggleInlinePrompts} + style={{ margin: 0 }} + /> + </label> + + </div> + + <button + onClick={() => { + this.showPromptMenu = false; + this.handleGeneratePrompts(); + }} + disabled={this.isLoadingPrompts} + style={{ + backgroundColor: '#9EAD7C', + color: 'white', + border: 'none', + borderRadius: '4px', + cursor: this.isLoadingPrompts ? 'not-allowed' : 'pointer', + opacity: this.isLoadingPrompts ? 0.6 : 1, + padding: '5px 10px', + float: 'right', + }} + > + Generate Prompts + </button> + </div> + )} + + <FormattedTextBox ref={this._ref} {...this._props} fieldKey={'text'} Document={this.Document} TemplateDataDocument={undefined} /> </div> @@ -369,8 +508,10 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() } } +const ObservedDailyJournal = observer(DailyJournal); + Docs.Prototypes.TemplateMap.set(DocumentType.JOURNAL, { - layout: { view: DailyJournal, dataField: 'text' }, + layout: { view: ObservedDailyJournal, dataField: 'text' }, options: { acl: '', _height: 35, |