aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/documents/DocumentTypes.ts3
-rw-r--r--src/client/documents/Documents.ts74
-rw-r--r--src/client/util/CurrentUserUtils.ts4
-rw-r--r--src/client/views/Main.tsx2
-rw-r--r--src/client/views/nodes/TaskManagerTask.scss72
-rw-r--r--src/client/views/nodes/TaskManagerTask.tsx251
-rw-r--r--src/client/views/nodes/calendarBox/CalendarBox.scss19
-rw-r--r--src/client/views/nodes/calendarBox/CalendarBox.tsx68
-rw-r--r--src/client/views/nodes/formattedText/DailyJournal.tsx61
9 files changed, 516 insertions, 38 deletions
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index cef44e999..a73d8ba59 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -44,8 +44,9 @@ export enum DocumentType {
SCRIPTDB = 'scriptdb', // database of scripts
GROUPDB = 'groupdb', // database of groups
+ JOURNAL = 'journal', // AARAV ADD JOURNAL
+ TASK = 'task', // AARAV ADD TASK
SCRAPBOOK = 'scrapbook',
- JOURNAL = 'journal', // AARAV ADD
}
export enum CollectionViewType {
// general collections
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a4a668085..217967c52 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -11,7 +11,7 @@ import { List } from '../../fields/List';
import { RichTextField } from '../../fields/RichTextField';
import { SchemaHeaderField } from '../../fields/SchemaHeaderField';
import { ComputedField, ScriptField } from '../../fields/ScriptField';
-import { ScriptCast, StrCast } from '../../fields/Types';
+import { DocCast, ScriptCast, StrCast } from '../../fields/Types';
import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from '../../fields/URLField';
import { SharingPermissions } from '../../fields/util';
import { PointData } from '../../pen-gestures/GestureTypes';
@@ -526,6 +526,16 @@ export class DocumentOptions {
ai_firefly_seed?: number;
ai_firefly_prompt?: string;
+ // AARAV ADD DOC OPTIONS -- TASK MANAGER
+
+ /** Task start date/time picker (metadata and default) */
+ startTime?: DateInfo | DateField = new DateInfo('start date and time', /*filterable*/ false);
+ /** Task end date/time picker (metadata and default) */
+ endTime?: DateInfo | DateField = new DateInfo('end date and time', /*filterable*/ false);
+ /** Treat this as an all-day task (metadata and default) */
+ allDay?: BoolInfo | boolean = new BoolInfo('all-day task', /*filterable*/ false);
+ /** Whether the task is completed */
+ completed?: BoolInfo | boolean = new BoolInfo('whether the task is completed', /*filterable*/ false);
/**
* JSON‐stringified slot configuration for ScrapbookBox
*/
@@ -584,6 +594,30 @@ export namespace Docs {
options: { acl: '' },
},
],
+
+ // AARAV ADD //
+ [
+ DocumentType.JOURNAL,
+ {
+ layout: { view: EmptyBox, dataField: 'text' },
+ options: {
+ title: 'Daily Journal',
+ acl_Guest: SharingPermissions.View,
+ },
+ },
+ ],
+
+ [
+ DocumentType.TASK,
+ {
+ layout: { view: EmptyBox, dataField: 'text' },
+ options: {
+ title: 'Task',
+ acl_Guest: SharingPermissions.View,
+ },
+ },
+ ],
+ // AARAV ADD //
]);
const suffix = 'Proto';
@@ -938,31 +972,6 @@ export namespace Docs {
// AARAV ADD //
export function DailyJournalDocument(text: string | RichTextField, options: DocumentOptions = {}, fieldKey: string = 'text') {
- // const getFormattedDate = () => {
- // const date = new Date().toLocaleDateString(undefined, {
- // weekday: 'long',
- // year: 'numeric',
- // month: 'long',
- // day: 'numeric',
- // });
- // return date;
- // };
-
- // const getDailyText = () => {
- // const placeholderText = 'Start writing here...';
- // const dateText = `${getFormattedDate()}`;
-
- // return RichTextField.textToRtfFormat(
- // [
- // { text: 'Journal Entry:', styles: { bold: true, color: 'black', fontSize: 20 } },
- // { text: dateText, styles: { italic: true, color: 'gray', fontSize: 15 } },
- // { text: placeholderText, styles: { fontSize: 14, color: 'gray' } },
- // ],
- // undefined,
- // placeholderText.length
- // );
- // };
-
return InstanceFromProto(
Prototypes.get(DocumentType.JOURNAL),
'',
@@ -975,7 +984,18 @@ export namespace Docs {
);
}
- // AARAV ADD //
+ export function TaskDocument(text = '', options: DocumentOptions = {}, fieldKey = 'text') {
+ return InstanceFromProto(
+ Prototypes.get(DocumentType.TASK),
+ '',
+ {
+ title: '',
+ ...options,
+ },
+ undefined,
+ fieldKey
+ );
+ }
export function LinkDocument(source: Doc, target: Doc, options: DocumentOptions = {}, id?: string) {
const linkDoc = InstanceFromProto(
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 8f4d568ab..1288abd3e 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -407,7 +407,8 @@ pie title Minerals in my tap water
{key: "DataViz", creator: opts => Docs.Create.DataVizDocument("", opts), opts: { _width: 300, _height: 300, }},
// AARAV ADD //
{key: "DailyJournal",creator:opts => Docs.Create.DailyJournalDocument("", opts),opts: { _width: 300, _height: 300, }},
- {key: "Scrapbook",creator:opts => Docs.Create.ScrapbookDocument([], opts),opts:{ _width: 300, _height: 300}},
+ {key: "Task", creator: opts => Docs.Create.TaskDocument("", opts), opts: { _width: 250, _height: 100, _layout_autoHeight: true, title: "Task", }},
+ {key: "Scrapbook", creator: opts => Docs.Create.ScrapbookDocument([], opts), opts: { _width: 300, _height: 300}},
//{key: "Scrapbook",creator:opts => Docs.Create.ScrapbookDocument([], opts),opts:{ _width: 300, _height: 300}},
{key: "Chat", creator: Docs.Create.ChatDocument, opts: { _width: 500, _height: 500, _layout_fitWidth: true, }},
{key: "MetaNote", creator: metaNoteTemplate, opts: { _width: 300, _height: 120, _header_pointerEvents: "all", _header_height: 50, _header_fontSize: 9,_layout_autoHeightMargins: 50, _layout_autoHeight: true, treeView_HideUnrendered: true}},
@@ -453,6 +454,7 @@ pie title Minerals in my tap water
{ toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "chart-bar", dragFactory: doc.emptyDataViz as Doc, clickFactory: DocCast(doc.emptyDataViz)},
{ toolTip: "Tap or drag to create a scrapbook template", title: "Scrapbook", icon: "palette", dragFactory: doc.emptyScrapbook as Doc,clickFactory:DocCast(doc.emptyScrapbook), },
{ toolTip: "Tap or drag to create a journal entry", title: "Journal", icon: "book", dragFactory:doc.emptyDailyJournal as Doc,clickFactory: DocCast(doc.emptyDataJournal), },
+ { toolTip: "Tap or drag to create a task", title: "Task", icon: "check-square", dragFactory: doc.emptyTask as Doc, clickFactory: DocCast(doc.emptyTask), },
{ toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon:"person-chalkboard",dragFactory: doc.emptySlide as Doc, clickFactory: DocCast(doc.emptySlide), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
{ toolTip: "Tap or drag to create a view slide", title: "View Slide", icon: "address-card", dragFactory: doc.emptyViewSlide as Doc, clickFactory: DocCast(doc.emptyViewSlide), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
{ toolTip: "Tap or drag to create a data note", title: "MetaNote", icon: "window-maximize", dragFactory: doc.emptyMetaNote as Doc, clickFactory: DocCast(doc.emptyMetaNote), openFactoryAsDelegate: true, funcs: { hidden: "IsNoviceMode()"} },
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index b884eb8c8..ecbef3497 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -65,6 +65,7 @@ import { PresBox, PresSlideBox } from './nodes/trails';
import { FaceRecognitionHandler } from './search/FaceRecognitionHandler';
import { SearchBox } from './search/SearchBox';
import { StickerPalette } from './smartdraw/StickerPalette';
+import { TaskManagerTask } from './nodes/TaskManagerTask';
import { ScrapbookBox } from './nodes/scrapbook/ScrapbookBox';
dotenv.config();
@@ -120,6 +121,7 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' };
StickerPalette: StickerPalette,
FormattedTextBox,
DailyJournal, // AARAV
+ TaskManagerTask, // AARAV
ImageBox,
FontIconBox,
LabelBox,
diff --git a/src/client/views/nodes/TaskManagerTask.scss b/src/client/views/nodes/TaskManagerTask.scss
new file mode 100644
index 000000000..0fcc2f955
--- /dev/null
+++ b/src/client/views/nodes/TaskManagerTask.scss
@@ -0,0 +1,72 @@
+.task-manager-container {
+ display: flex;
+ flex-direction: column;
+ padding: 8px;
+ gap: 10px;
+ width: 100%;
+ height: 100%;
+ box-sizing: border-box;
+}
+
+.task-manager-title {
+ width: 100%;
+ font-size: 1.25rem;
+ font-weight: 600;
+ padding: 6px 10px;
+ border: 1px solid #ccc;
+ border-radius: 6px;
+ box-sizing: border-box;
+}
+
+.task-manager-description {
+ width: 100%;
+ font-size: 1rem;
+ padding: 8px 10px;
+ border: 1px solid #ccc;
+ border-radius: 6px;
+ min-height: 40px;
+ box-sizing: border-box;
+ vertical-align: top;
+ text-align: start;
+ resize: none;
+ line-height: 1.4;
+ resize: none;
+ flex-grow: 1
+}
+
+.task-manager-checkboxes {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+}
+
+.task-manager-allday, .task-manager-complete {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 0.95rem;
+}
+
+.task-manager-times {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ width: 100%;
+}
+
+.task-manager-times label {
+ display: flex;
+ flex-direction: column;
+ font-size: 0.9rem;
+ font-weight: 500;
+ gap: 4px;
+}
+
+input[type="datetime-local"] {
+ width: 100%;
+ font-size: 0.9rem;
+ padding: 6px 8px;
+ border: 1px solid #ccc;
+ border-radius: 6px;
+ box-sizing: border-box;
+}
diff --git a/src/client/views/nodes/TaskManagerTask.tsx b/src/client/views/nodes/TaskManagerTask.tsx
new file mode 100644
index 000000000..2d2444275
--- /dev/null
+++ b/src/client/views/nodes/TaskManagerTask.tsx
@@ -0,0 +1,251 @@
+import { action, observable, makeObservable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Docs } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { FieldView } from './FieldView';
+import { DateField } from '../../../fields/DateField';
+import { Doc } from '../../../fields/Doc';
+
+import './TaskManagerTask.scss';
+
+interface TaskManagerProps {
+ Document: Doc;
+}
+
+@observer
+export class TaskManagerTask extends React.Component<TaskManagerProps> {
+ public static LayoutString(fieldStr: string) {
+ return FieldView.LayoutString(TaskManagerTask, fieldStr);
+ }
+
+ @action
+ updateText = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
+ this.props.Document.text = e.target.value;
+ };
+
+ @action
+ updateTitle = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this.props.Document.title = e.target.value;
+ };
+
+ @action
+ updateAllDay = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this.props.Document.allDay = e.target.checked;
+
+ if (e.target.checked) {
+ delete this.props.Document.startTime;
+ delete this.props.Document.endTime;
+ }
+
+ this.setTaskDateRange();
+ };
+
+ @action
+ updateStart = (e: React.ChangeEvent<HTMLInputElement>) => {
+ const newStart = new Date(e.target.value);
+
+ this.props.Document.startTime = new DateField(newStart);
+
+ const endDate = this.props.Document.endTime instanceof DateField ? this.props.Document.endTime.date : undefined;
+ if (endDate && newStart > endDate) {
+ // Alert user
+ alert('Start time cannot be after end time. End time has been adjusted.');
+
+ // Fix end time
+ const adjustedEnd = new Date(newStart.getTime() + 60 * 60 * 1000);
+ this.props.Document.endTime = new DateField(adjustedEnd);
+ }
+
+ this.setTaskDateRange();
+ };
+
+
+
+ @action
+ updateEnd = (e: React.ChangeEvent<HTMLInputElement>) => {
+ const newEnd = new Date(e.target.value);
+
+ this.props.Document.endTime = new DateField(newEnd);
+
+ const startDate = this.props.Document.startTime instanceof DateField ? this.props.Document.startTime.date : undefined;
+ if (startDate && newEnd < startDate) {
+ // Alert user
+ alert('End time cannot be before start time. Start time has been adjusted.');
+
+ // Fix start time
+ const adjustedStart = new Date(newEnd.getTime() - 60 * 60 * 1000);
+ this.props.Document.startTime = new DateField(adjustedStart);
+ }
+
+ this.setTaskDateRange();
+ };
+
+
+
+
+ @action
+ setTaskDateRange() {
+ const doc = this.props.Document;
+
+ if (doc.allDay) {
+ // All-day task → use date only
+ if (!doc.title) return;
+
+ const parsedDate = new Date(doc.title as string);
+ if (!isNaN(parsedDate.getTime())) {
+ const localStart = new Date(parsedDate.getFullYear(), parsedDate.getMonth(), parsedDate.getDate());
+ const localEnd = new Date(localStart);
+ doc.date_range = `${localStart.toISOString()}|${localEnd.toISOString()}`;
+ doc.allDay = true;
+ }
+ } else {
+ // Timed task → use full startTime and endTime
+ const startField = doc.startTime;
+ const endField = doc.endTime;
+ const startDate = startField instanceof DateField ? startField.date : null;
+ const endDate = endField instanceof DateField ? endField.date : null;
+
+ if (startDate && endDate && !isNaN(startDate.getTime()) && !isNaN(endDate.getTime())) {
+ doc.date_range = `${startDate.toISOString()}|${endDate.toISOString()}`;
+ doc.allDay = false;
+ } else {
+ console.warn('startTime or endTime is invalid');
+ }
+ }
+ }
+
+ @action
+ toggleComplete = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this.props.Document.completed = e.target.checked;
+ };
+
+ constructor(props: TaskManagerProps) {
+ super(props);
+ makeObservable(this);
+ }
+
+ componentDidMount() {
+ this.setTaskDateRange();
+ }
+
+
+ render() {
+
+ function toLocalDateTimeString(date: Date): string {
+ const pad = (n: number) => n.toString().padStart(2, '0');
+ return (
+ date.getFullYear() +
+ '-' +
+ pad(date.getMonth() + 1) +
+ '-' +
+ pad(date.getDate()) +
+ 'T' +
+ pad(date.getHours()) +
+ ':' +
+ pad(date.getMinutes())
+ );
+ }
+
+ const doc = this.props.Document;
+
+ const taskDesc = typeof doc.text === 'string' ? doc.text : '';
+ const taskTitle = typeof doc.title === 'string' ? doc.title : '';
+ const allDay = !!doc.allDay;
+ const isCompleted = !!this.props.Document.completed;
+
+ const startTime = doc.startTime instanceof DateField && doc.startTime.date instanceof Date
+ ? toLocalDateTimeString(doc.startTime.date)
+ : '';
+
+ const endTime = doc.endTime instanceof DateField && doc.endTime.date instanceof Date
+ ? toLocalDateTimeString(doc.endTime.date)
+ : '';
+
+
+
+ return (
+ <div className="task-manager-container">
+ <input
+ className="task-manager-title"
+ type="text"
+ placeholder="Task Title"
+ value={taskTitle}
+ onChange={this.updateTitle}
+ disabled={isCompleted}
+ style={{opacity: isCompleted ? 0.7 : 1,}}
+ />
+
+ <textarea
+ className="task-manager-description"
+ placeholder="What’s your task?"
+ value={taskDesc}
+ onChange={this.updateText}
+ disabled={isCompleted}
+ style={{opacity: isCompleted ? 0.7 : 1,}}
+ />
+
+ <div className="task-manager-checkboxes">
+
+ <label className="task-manager-allday" style={{opacity: isCompleted ? 0.7 : 1,}}>
+ <input
+ type="checkbox"
+ checked={allDay}
+ onChange={this.updateAllDay}
+ disabled={isCompleted}
+ />
+ All day
+ </label>
+
+ <label className="task-manager-complete">
+ <input type="checkbox" checked={isCompleted} onChange={this.toggleComplete} />
+ Complete
+ </label>
+
+ </div>
+
+ {!allDay && (
+ <div
+ className="task-manager-times"
+ style={{ opacity: isCompleted ? 0.7 : 1 }}
+ >
+ <label>
+ Start:
+ <input
+ type="datetime-local"
+ value={startTime}
+ onChange={this.updateStart}
+ disabled={isCompleted}
+ />
+ </label>
+ <label>
+ End:
+ <input
+ type="datetime-local"
+ value={endTime}
+ onChange={this.updateEnd}
+ disabled={isCompleted}
+ />
+ </label>
+ </div>
+ )}
+ </div>
+ );
+ }
+}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.TASK, {
+ layout: { view: TaskManagerTask, dataField: 'text' },
+ options: {
+ acl: '',
+ _height: 35,
+ _xMargin: 10,
+ _yMargin: 10,
+ _layout_autoHeight: true,
+ _layout_nativeDimEditable: true,
+ _layout_reflowVertical: true,
+ _layout_reflowHorizontal: true,
+ defaultDoubleClick: 'ignore',
+ systemIcon: 'BsCheckSquare', // or whatever icon you like
+ },
+});
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.scss b/src/client/views/nodes/calendarBox/CalendarBox.scss
index f8ac4b2d1..f5d3613e3 100644
--- a/src/client/views/nodes/calendarBox/CalendarBox.scss
+++ b/src/client/views/nodes/calendarBox/CalendarBox.scss
@@ -23,3 +23,22 @@
}
}
}
+
+// AARAV ADD
+
+/* Existing styles */
+.fc-event.mother {
+ font-weight: 500;
+ border-radius: 4px;
+ padding: 2px 4px;
+ border-width: 2px;
+ }
+
+ /* New styles for completed tasks */
+ .fc-event.completed-task {
+ opacity: 1;
+ filter: grayscale(70%) brightness(90%);
+ text-decoration: line-through;
+ color: #ffffff;
+ }
+ \ No newline at end of file
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx
index fa863e123..84fa25dba 100644
--- a/src/client/views/nodes/calendarBox/CalendarBox.tsx
+++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx
@@ -1,4 +1,5 @@
import { Calendar, EventClickArg, EventDropArg, EventSourceInput } from '@fullcalendar/core';
+import { EventResizeDoneArg } from '@fullcalendar/interaction';
import dayGridPlugin from '@fullcalendar/daygrid';
import interactionPlugin from '@fullcalendar/interaction';
import multiMonthPlugin from '@fullcalendar/multimonth';
@@ -17,6 +18,7 @@ import { ContextMenu } from '../../ContextMenu';
import { DocumentView } from '../DocumentView';
import { OpenWhere } from '../OpenWhere';
import './CalendarBox.scss';
+import { DateField } from '../../../../fields/DateField';
type CalendarView = 'multiMonth' | 'dayGridMonth' | 'timeGridWeek' | 'timeGridDay';
@@ -66,6 +68,7 @@ export class CalendarBox extends CollectionSubView() {
@computed get calendarEvents(): EventSourceInput | undefined {
return this.childDocs.map(doc => {
const { start, end } = dateRangeStrToDates(StrCast(doc.date_range));
+ const isCompleted = BoolCast(doc.completed); // AARAV ADD
return {
title: StrCast(doc.title),
start,
@@ -74,7 +77,7 @@ export class CalendarBox extends CollectionSubView() {
startEditable: true,
endEditable: true,
allDay: BoolCast(doc.allDay),
- classNames: ['mother'], // will determine the style
+ classNames: ['mother', isCompleted ? 'completed-task' : ''], // will determine the style
editable: true, // subject to change in the future
backgroundColor: this.eventToColor(doc),
borderColor: this.eventToColor(doc),
@@ -105,7 +108,11 @@ export class CalendarBox extends CollectionSubView() {
// TODO: Return a different color based on the event type
eventToColor = (event: Doc): string => {
- return 'red' + event;
+ const docType = StrCast(event.type);
+ if (docType === 'task') {
+ return '#20B2AA'; // teal for tasks
+ }
+ return 'red';
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -123,9 +130,30 @@ export class CalendarBox extends CollectionSubView() {
return false;
};
- handleEventDrop = (arg: EventDropArg) => {
+ handleEventDrop = (arg: EventDropArg | EventResizeDoneArg) => {
const doc = DocServer.GetCachedRefField(arg.event._def.groupId ?? '');
- doc && arg.event.start && (doc.date_range = arg.event.start?.toString() + '|' + (arg.event.end ?? arg.event.start).toString());
+ // doc && arg.event.start && (doc.date_range = arg.event.start?.toString() + '|' + (arg.event.end ?? arg.event.start).toString());
+ if (!doc || !arg.event.start) return;
+
+ // get the new start and end dates
+ const startDate = new Date(arg.event.start);
+ const endDate = new Date(arg.event.end ?? arg.event.start);
+
+ // update date range, time range, and all day status
+ doc.date_range = `${startDate.toISOString()}|${endDate.toISOString()}`;
+
+ const allDayStatus = arg.event.allDay ?? false;
+ if (doc.allDay !== allDayStatus) {
+ doc.allDay = allDayStatus;
+ }
+
+ if (doc.allDay) {
+ delete doc.startTime;
+ delete doc.endTime;
+ } else {
+ doc.startTime = new DateField(startDate);
+ doc.endTime = new DateField(endDate);
+ }
};
handleEventClick = (arg: EventClickArg) => {
@@ -171,7 +199,39 @@ export class CalendarBox extends CollectionSubView() {
events: this.calendarEvents,
eventClick: this.handleEventClick,
eventDrop: this.handleEventDrop,
+ eventResize: this.handleEventDrop,
eventDidMount: arg => {
+ const doc = DocServer.GetCachedRefField(arg.event._def.groupId ?? '');
+ if (!doc) return;
+
+ if (doc.type === 'task') {
+ const checkButton = document.createElement('button');
+ checkButton.innerText = doc.completed ? '✅' : '⬜';
+ checkButton.style.position = 'absolute';
+ checkButton.style.right = '5px';
+ checkButton.style.top = '50%';
+ checkButton.style.transform = 'translateY(-50%)';
+ checkButton.style.background = 'transparent';
+ checkButton.style.border = 'none';
+ checkButton.style.cursor = 'pointer';
+ checkButton.style.fontSize = '18px';
+ checkButton.style.zIndex = '1000';
+ checkButton.style.padding = '0';
+ checkButton.style.margin = '0';
+
+ checkButton.onclick = ev => {
+ ev.stopPropagation();
+ doc.completed = !doc.completed;
+ this._calendar?.refetchEvents();
+ };
+
+ // Make sure the parent box is positioned relative
+ arg.el.style.position = 'relative';
+ arg.el.appendChild(checkButton);
+ }
+
+ // (keep your other pointerup/contextmenu handlers here)
+
arg.el.addEventListener('pointerdown', ev => {
ev.button && ev.stopPropagation();
});
diff --git a/src/client/views/nodes/formattedText/DailyJournal.tsx b/src/client/views/nodes/formattedText/DailyJournal.tsx
index 871c556e6..d6d30dc13 100644
--- a/src/client/views/nodes/formattedText/DailyJournal.tsx
+++ b/src/client/views/nodes/formattedText/DailyJournal.tsx
@@ -9,6 +9,7 @@ import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
import { RichTextField } from '../../../../fields/RichTextField';
import { Plugin } from 'prosemirror-state';
import { RTFCast } from '../../../../fields/Types';
+import { Mark } from 'prosemirror-model';
export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@observable journalDate: string;
@@ -17,6 +18,7 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>()
_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?';
+ private prePredictiveMarks: Mark[] = [];
public static LayoutString(fieldStr: string) {
return FieldView.LayoutString(DailyJournal, fieldStr);
@@ -84,6 +86,27 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>()
}
/**
+ * Method to set initial date of document in the calendar view
+ */
+
+ @action setInitialDateRange() {
+ if (!this.dataDoc.date_range && this.journalDate) {
+ const parsedDate = new Date(this.journalDate);
+ if (!isNaN(parsedDate.getTime())) {
+ const localStart = new Date(parsedDate.getFullYear(), parsedDate.getMonth(), parsedDate.getDate());
+ const localEnd = new Date(localStart); // same day
+
+ 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);
+ } else {
+ console.log('Could not parse journalDate:', this.journalDate);
+ }
+ }
+ }
+
+ /**
* Tracks user typing text inout into the node, to call the insert predicted
* text function when appropriate (i.e. when the user stops typing)
*/
@@ -94,6 +117,22 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>()
if (this.typingTimeout) clearTimeout(this.typingTimeout);
+ const { state } = editorView;
+ const cursorPos = state.selection.from;
+
+ // characters before cursor
+ const triggerText = state.doc.textBetween(Math.max(0, cursorPos - 4), cursorPos);
+
+ if (triggerText === '/ask') {
+ // remove /ask text
+ const tr = state.tr.delete(cursorPos - 4, cursorPos);
+ editorView.dispatch(tr);
+
+ // insert predicted question
+ this.insertPredictiveQuestion();
+ return;
+ }
+
this.typingTimeout = setTimeout(() => {
this.insertPredictiveQuestion();
}, 3500);
@@ -129,11 +168,17 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>()
// 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' });
+ // Save current marks at cursor
+ const currentMarks = state.storedMarks || resolvedPos.marks();
+ this.prePredictiveMarks = [...currentMarks];
+
+ // color and italics are preset for predictive question, font and size are adaptive
const fontColorMark = schema.marks.pFontColor.create({ fontColor: 'lightgray' });
const fontItalicsMark = schema.marks.em.create();
+ const fontSizeMark = this.prePredictiveMarks.find(m => m.type.name === 'pFontSize');
+ const fontFamilyMark = this.prePredictiveMarks.find(m => m.type.name === 'pFontFamily'); // if applicable
- this.predictiveText = ' ...'; // placeholder for now
+ this.predictiveText = ' ...'; // placeholder
const fullTextUpToCursor = state.doc.textBetween(0, state.selection.to, '\n', '\n');
const gptPrompt = `Given the following incomplete journal entry, generate a single 2-5 word question that continues the user's thought:\n\n"${fullTextUpToCursor}"`;
@@ -142,10 +187,10 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>()
// styled text node
const text = ` ... ${res.trim()}`;
- const predictedText = schema.text(text, [fontSizeMark, fontColorMark, fontItalicsMark]);
+ const predictedText = schema.text(text, [fontColorMark, fontItalicsMark, ...(fontSizeMark ? [fontSizeMark] : []), ...(fontFamilyMark ? [fontFamilyMark] : [])]);
// Insert styled text at cursor position
- const transaction = state.tr.insert(insertPos, predictedText).setStoredMarks([state.schema.marks.pFontColor.create({ fontColor: 'gray' })]); // should probably instead inquire marks before predictive prompt
+ const transaction = state.tr.insert(insertPos, predictedText).setStoredMarks(this.prePredictiveMarks);
dispatch(transaction);
this.predictiveText = text;
@@ -172,11 +217,16 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>()
const fontSizeMark = state.schema.marks.pFontSize.create({ fontSize: '14px' });
const fontColorMark = state.schema.marks.pFontColor.create({ fontColor: 'gray' });
tr.setStoredMarks([]);
- tr.setStoredMarks([fontSizeMark, fontColorMark]);
+ if (this.prePredictiveMarks.length > 0) {
+ tr.setStoredMarks(this.prePredictiveMarks);
+ } else {
+ tr.setStoredMarks([fontSizeMark, fontColorMark]);
+ }
dispatch(tr);
this.predictiveText = null;
+ this.prePredictiveMarks = [];
return false;
}
return true;
@@ -217,6 +267,7 @@ export class DailyJournal extends ViewBoxAnnotatableComponent<FieldViewProps>()
console.log('Journal title and text are default. Initializing...');
this.setDailyTitle();
this.setDailyText();
+ this.setInitialDateRange();
} else {
console.log('Journal already has content. Skipping initialization.');
}