diff --git a/lib/actions/entry-actions.js b/lib/actions/entry-actions.js --- a/lib/actions/entry-actions.js +++ b/lib/actions/entry-actions.js @@ -16,7 +16,10 @@ } from '../shared/dm-ops/dm-op-utils.js'; import { useProcessAndSendDMOperation } from '../shared/dm-ops/process-dm-ops.js'; import { getNextLocalID } from '../shared/message-utils.js'; -import { type DMCreateEntryOperation } from '../types/dm-ops.js'; +import { + type DMCreateEntryOperation, + type DMEditEntryOperation, +} from '../types/dm-ops.js'; import type { RawEntryInfo, CalendarQuery, @@ -34,7 +37,10 @@ import type { HistoryRevisionInfo } from '../types/history-types.js'; import type { ThreadInfo } from '../types/minimally-encoded-thread-permissions-types'; import { thickThreadTypes } from '../types/thread-types-enum.js'; -import { dateFromString } from '../utils/date-utils.js'; +import { + dateFromString, + dateString as stringFromDate, +} from '../utils/date-utils.js'; import { useSelector } from '../utils/redux-utils.js'; const fetchEntriesActionTypes = Object.freeze({ @@ -294,8 +300,80 @@ }; }; -function useSaveEntry(): (input: SaveEntryInfo) => Promise { - return useKeyserverCall(saveEntry); +export type UseSaveEntryInput = $ReadOnly< + | { + +thick: false, + +saveEntryInfo: SaveEntryInfo, + } + | { + +thick: true, + +threadInfo: ThreadInfo, + +saveEntryInfo: SaveEntryInfo, + }, +>; +function useSaveEntry(): ( + input: UseSaveEntryInput, +) => Promise { + const viewerID = useSelector( + state => state.currentUserInfo && state.currentUserInfo.id, + ); + + const processAndSendDMOperation = useProcessAndSendDMOperation(); + const keyserverCall = useKeyserverCall(saveEntry); + const entryInfos = useSelector(state => state.entryStore.entryInfos); + + return React.useCallback( + async (input: UseSaveEntryInput) => { + if (!input.thick) { + const result = await keyserverCall(input.saveEntryInfo); + return result; + } + const { saveEntryInfo, threadInfo } = input; + const prevEntry = entryInfos[saveEntryInfo.entryID]; + + invariant(viewerID, 'viewerID must be set'); + + const op: DMEditEntryOperation = { + type: 'edit_entry', + threadID: threadInfo.id, + creatorID: viewerID, + creationTime: prevEntry.creationTime, + time: saveEntryInfo.timestamp, + entryID: saveEntryInfo.entryID, + entryDate: stringFromDate( + prevEntry.year, + prevEntry.month, + prevEntry.day, + ), + text: saveEntryInfo.text, + messageID: uuid.v4(), + }; + const opSpecification: OutboundDMOperationSpecification = { + type: dmOperationSpecificationTypes.OUTBOUND, + op, + recipients: { + type: 'all_thread_members', + threadID: + threadInfo.type === thickThreadTypes.THICK_SIDEBAR && + threadInfo.parentThreadID + ? threadInfo.parentThreadID + : threadInfo.id, + }, + }; + + await processAndSendDMOperation(opSpecification); + + return { + entryID: saveEntryInfo.entryID, + newMessageInfos: [], + updatesResult: { + viewerUpdates: [], + userInfos: [], + }, + }; + }, + [keyserverCall, processAndSendDMOperation, viewerID, entryInfos], + ); } const deleteEntryActionTypes = Object.freeze({ diff --git a/native/calendar/entry.react.js b/native/calendar/entry.react.js --- a/native/calendar/entry.react.js +++ b/native/calendar/entry.react.js @@ -26,6 +26,7 @@ useDeleteEntry, useSaveEntry, type UseCreateEntryInput, + type UseSaveEntryInput, } from 'lib/actions/entry-actions.js'; import { extractKeyserverIDFromIDOptional } from 'lib/keyserver-conn/keyserver-call-utils.js'; import { registerFetchKey } from 'lib/reducers/loading-reducer.js'; @@ -38,7 +39,6 @@ CreateEntryPayload, DeleteEntryInfo, DeleteEntryResult, - SaveEntryInfo, SaveEntryPayload, SaveEntryResult, } from 'lib/types/entry-types.js'; @@ -208,7 +208,7 @@ +dispatchActionPromise: DispatchActionPromise, // async functions that hit server APIs +createEntry: (input: UseCreateEntryInput) => Promise, - +saveEntry: (info: SaveEntryInfo) => Promise, + +saveEntry: (input: UseSaveEntryInput) => Promise, +deleteEntry: (info: DeleteEntryInfo) => Promise, +canEditEntry: boolean, }; @@ -697,13 +697,26 @@ ): Promise { const curSaveAttempt = this.nextSaveAttemptIndex++; try { - const response = await this.props.saveEntry({ + const saveEntryInfo = { entryID, text: newText, prevText: this.props.entryInfo.text, timestamp: Date.now(), calendarQuery: this.props.calendarQuery(), - }); + }; + + const useSaveEntryInput = threadTypeIsThick(this.props.threadInfo.type) + ? { + thick: true, + threadInfo: this.props.threadInfo, + saveEntryInfo, + } + : { + thick: false, + saveEntryInfo, + }; + + const response = await this.props.saveEntry(useSaveEntryInput); if (curSaveAttempt + 1 === this.nextSaveAttemptIndex) { this.guardedSetState({ loadingStatus: 'inactive' }); } diff --git a/web/calendar/entry.react.js b/web/calendar/entry.react.js --- a/web/calendar/entry.react.js +++ b/web/calendar/entry.react.js @@ -13,6 +13,7 @@ useDeleteEntry, concurrentModificationResetActionType, type UseCreateEntryInput, + type UseSaveEntryInput, } from 'lib/actions/entry-actions.js'; import { type PushModal, @@ -26,7 +27,6 @@ import { useThreadHasPermission } from 'lib/shared/thread-utils.js'; import { type EntryInfo, - type SaveEntryInfo, type SaveEntryResult, type SaveEntryPayload, type CreateEntryPayload, @@ -73,7 +73,7 @@ +dispatch: Dispatch, +dispatchActionPromise: DispatchActionPromise, +createEntry: (input: UseCreateEntryInput) => Promise, - +saveEntry: (info: SaveEntryInfo) => Promise, + +saveEntry: (input: UseSaveEntryInput) => Promise, +deleteEntry: (info: DeleteEntryInfo) => Promise, +pushModal: PushModal, +popModal: () => void, @@ -378,13 +378,26 @@ const curSaveAttempt = this.nextSaveAttemptIndex++; this.guardedSetState({ loadingStatus: 'loading' }); try { - const response = await this.props.saveEntry({ + const saveEntryInfo = { entryID, text: newText, prevText: this.props.entryInfo.text, timestamp: Date.now(), calendarQuery: this.props.calendarQuery(), - }); + }; + + const useSaveEntryInput = threadTypeIsThick(this.props.threadInfo.type) + ? { + thick: true, + threadInfo: this.props.threadInfo, + saveEntryInfo, + } + : { + thick: false, + saveEntryInfo, + }; + + const response = await this.props.saveEntry(useSaveEntryInput); if (curSaveAttempt + 1 === this.nextSaveAttemptIndex) { this.guardedSetState({ loadingStatus: 'inactive' }); }