fix(threads): polls created from a thread composer land in the thread
PollCreator used the legacy 3-arg sendEvent (threadId null). Thread the composer's threadRootId through and send like the sticker path. Also (#41, same file): composer drafts are persisted as { userId, nodes } and the restore path drops any draft with a different or missing userId. Fixes #35 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
@@ -25,10 +25,12 @@ import { useModalStyle } from '../../hooks/useModalStyle';
|
|||||||
interface PollCreatorProps {
|
interface PollCreatorProps {
|
||||||
roomId: string;
|
roomId: string;
|
||||||
room: Room;
|
room: Room;
|
||||||
|
/** Set when the composer is inside a thread so the poll lands in that thread. */
|
||||||
|
threadRootId?: string;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PollCreator({ roomId, onClose }: PollCreatorProps) {
|
export function PollCreator({ roomId, threadRootId, onClose }: PollCreatorProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const modalStyle = useModalStyle(440);
|
const modalStyle = useModalStyle(440);
|
||||||
const [question, setQuestion] = useState('');
|
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(
|
const fallbackBody = [trimmedQuestion, ...filledOptions.map((o, i) => `${i + 1}. ${o}`)].join(
|
||||||
'\n',
|
'\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': {
|
'm.poll': {
|
||||||
question: { 'm.text': trimmedQuestion },
|
question: { 'm.text': trimmedQuestion },
|
||||||
answers: filledOptions.map((o, i) => ({ 'm.id': `${i}`, 'm.text': o })),
|
answers: filledOptions.map((o, i) => ({ 'm.id': `${i}`, 'm.text': o })),
|
||||||
|
|||||||
@@ -398,27 +398,47 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
|||||||
try {
|
try {
|
||||||
const stored = localStorage.getItem(`draft-msg-${draftKey}`);
|
const stored = localStorage.getItem(`draft-msg-${draftKey}`);
|
||||||
if (stored) {
|
if (stored) {
|
||||||
const nodes = JSON.parse(stored);
|
const parsed = JSON.parse(stored);
|
||||||
if (Array.isArray(nodes) && nodes.length > 0) {
|
// [Gitea #41] Only restore a draft this same account wrote. A legacy
|
||||||
Transforms.insertFragment(editor, nodes);
|
// draft (stored as a bare array, pre-dating user-scoping) or one
|
||||||
// Mirror the restored draft into the atom so the draft indicator
|
// written by a different userId is foreign — drop it rather than
|
||||||
// (reads roomIdToMsgDraftAtomFamily) reflects a persisted draft
|
// risk pre-filling another account's unsent text into the composer.
|
||||||
// after a page reload — not only on same-session room re-entry.
|
const foreign =
|
||||||
setMsgDraft(nodes);
|
!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 {
|
} catch {
|
||||||
// Ignore malformed stored draft
|
// Ignore malformed stored draft
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [editor, msgDraft, draftKey, setMsgDraft]);
|
}, [editor, msgDraft, draftKey, setMsgDraft, mx]);
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() => () => {
|
() => () => {
|
||||||
if (!isEmptyEditor(editor)) {
|
if (!isEmptyEditor(editor)) {
|
||||||
const parsedDraft = JSON.parse(JSON.stringify(editor.children));
|
const parsedDraft = JSON.parse(JSON.stringify(editor.children));
|
||||||
setMsgDraft(parsedDraft);
|
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 {
|
} else {
|
||||||
setMsgDraft([]);
|
setMsgDraft([]);
|
||||||
localStorage.removeItem(`draft-msg-${draftKey}`);
|
localStorage.removeItem(`draft-msg-${draftKey}`);
|
||||||
@@ -426,7 +446,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
|||||||
resetEditor(editor);
|
resetEditor(editor);
|
||||||
resetEditorHistory(editor);
|
resetEditorHistory(editor);
|
||||||
},
|
},
|
||||||
[draftKey, editor, setMsgDraft],
|
[draftKey, editor, setMsgDraft, mx],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFileMetadata = useCallback(
|
const handleFileMetadata = useCallback(
|
||||||
@@ -1480,7 +1500,14 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{pollOpen && <PollCreator room={room} roomId={roomId} onClose={() => setPollOpen(false)} />}
|
{pollOpen && (
|
||||||
|
<PollCreator
|
||||||
|
room={room}
|
||||||
|
roomId={roomId}
|
||||||
|
threadRootId={threadRootId}
|
||||||
|
onClose={() => setPollOpen(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{scheduleOpen && (
|
{scheduleOpen && (
|
||||||
<ScheduleMessageModal
|
<ScheduleMessageModal
|
||||||
roomId={roomId}
|
roomId={roomId}
|
||||||
|
|||||||
Reference in New Issue
Block a user