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 {
|
||||
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 })),
|
||||
|
||||
@@ -398,7 +398,20 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
try {
|
||||
const stored = localStorage.getItem(`draft-msg-${draftKey}`);
|
||||
if (stored) {
|
||||
const nodes = JSON.parse(stored);
|
||||
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
|
||||
@@ -407,18 +420,25 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
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<HTMLDivElement, RoomInputProps>(
|
||||
resetEditor(editor);
|
||||
resetEditorHistory(editor);
|
||||
},
|
||||
[draftKey, editor, setMsgDraft],
|
||||
[draftKey, editor, setMsgDraft, mx],
|
||||
);
|
||||
|
||||
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 && (
|
||||
<ScheduleMessageModal
|
||||
roomId={roomId}
|
||||
|
||||
Reference in New Issue
Block a user