diff --git a/src/app/features/room/PollCreator.tsx b/src/app/features/room/PollCreator.tsx index 1e8c58341..6e65a4461 100644 --- a/src/app/features/room/PollCreator.tsx +++ b/src/app/features/room/PollCreator.tsx @@ -25,10 +25,12 @@ import { useModalStyle } from '../../hooks/useModalStyle'; interface PollCreatorProps { roomId: string; room: Room; + /** Set when the composer is inside a thread so the poll lands in that thread. */ + threadRootId?: string; onClose: () => void; } -export function PollCreator({ roomId, onClose }: PollCreatorProps) { +export function PollCreator({ roomId, threadRootId, onClose }: PollCreatorProps) { const mx = useMatrixClient(); const modalStyle = useModalStyle(440); const [question, setQuestion] = useState(''); @@ -85,7 +87,9 @@ export function PollCreator({ roomId, onClose }: PollCreatorProps) { const fallbackBody = [trimmedQuestion, ...filledOptions.map((o, i) => `${i + 1}. ${o}`)].join( '\n', ); - await mx.sendEvent(roomId, 'm.poll.start' as any, { + // Pass the thread id explicitly (like the sticker path in RoomInput); the + // legacy 3-arg form always resolves to the main timeline. + await mx.sendEvent(roomId, threadRootId ?? null, 'm.poll.start' as any, { 'm.poll': { question: { 'm.text': trimmedQuestion }, answers: filledOptions.map((o, i) => ({ 'm.id': `${i}`, 'm.text': o })), diff --git a/src/app/features/room/RoomInput.tsx b/src/app/features/room/RoomInput.tsx index 834fd98f1..cd0ceb3ad 100644 --- a/src/app/features/room/RoomInput.tsx +++ b/src/app/features/room/RoomInput.tsx @@ -398,27 +398,47 @@ export const RoomInput = forwardRef( try { const stored = localStorage.getItem(`draft-msg-${draftKey}`); if (stored) { - const nodes = JSON.parse(stored); - if (Array.isArray(nodes) && nodes.length > 0) { - Transforms.insertFragment(editor, nodes); - // Mirror the restored draft into the atom so the draft indicator - // (reads roomIdToMsgDraftAtomFamily) reflects a persisted draft - // after a page reload — not only on same-session room re-entry. - setMsgDraft(nodes); + const parsed = JSON.parse(stored); + // [Gitea #41] Only restore a draft this same account wrote. A legacy + // draft (stored as a bare array, pre-dating user-scoping) or one + // written by a different userId is foreign — drop it rather than + // risk pre-filling another account's unsent text into the composer. + const foreign = + !parsed || + typeof parsed !== 'object' || + Array.isArray(parsed) || + parsed.userId !== mx.getUserId(); + if (foreign) { + localStorage.removeItem(`draft-msg-${draftKey}`); + } else { + const nodes = parsed.nodes; + if (Array.isArray(nodes) && nodes.length > 0) { + Transforms.insertFragment(editor, nodes); + // Mirror the restored draft into the atom so the draft indicator + // (reads roomIdToMsgDraftAtomFamily) reflects a persisted draft + // after a page reload — not only on same-session room re-entry. + setMsgDraft(nodes); + } } } } catch { // Ignore malformed stored draft } } - }, [editor, msgDraft, draftKey, setMsgDraft]); + }, [editor, msgDraft, draftKey, setMsgDraft, mx]); useEffect( () => () => { if (!isEmptyEditor(editor)) { const parsedDraft = JSON.parse(JSON.stringify(editor.children)); setMsgDraft(parsedDraft); - localStorage.setItem(`draft-msg-${draftKey}`, JSON.stringify(parsedDraft)); + // [Gitea #41] Tag the persisted draft with the writing user's id so a + // different account logging into this browser can't have it hydrated + // into their composer (see useHydrateMsgDrafts / clearPlaintextCaches). + localStorage.setItem( + `draft-msg-${draftKey}`, + JSON.stringify({ userId: mx.getUserId(), nodes: parsedDraft }), + ); } else { setMsgDraft([]); localStorage.removeItem(`draft-msg-${draftKey}`); @@ -426,7 +446,7 @@ export const RoomInput = forwardRef( resetEditor(editor); resetEditorHistory(editor); }, - [draftKey, editor, setMsgDraft], + [draftKey, editor, setMsgDraft, mx], ); const handleFileMetadata = useCallback( @@ -1480,7 +1500,14 @@ export const RoomInput = forwardRef( } /> - {pollOpen && setPollOpen(false)} />} + {pollOpen && ( + setPollOpen(false)} + /> + )} {scheduleOpen && (