fix: ctrl+p print dialog, gallery 400 error, poll multi-choice UX

- Suppress Ctrl+P browser print dialog via SuppressPrintShortcut in
  ClientNonUIFeatures (no UI opened, just preventDefault)
- mxcUrlToHttp: build URL manually instead of delegating to SDK.
  The SDK forces allow_redirect=true when useAuthentication=true;
  Synapse's /_matrix/client/v1/media/thumbnail rejects that with 400.
  Manual construction omits allow_redirect entirely.
- Gallery: redesign using folds color tokens (color.Surface.*) instead
  of non-existent CSS custom properties; add ThumbState so broken
  images show an icon placeholder; use useAuthentication for thumbnails
  now that the URL builder is fixed; "Load More" always visible.
- PollCreator: replace raw <button> with folds Button components so the
  Single/Multiple choice toggle renders with actual visual difference.
- PollContent: support multiple-choice polls end-to-end —
  myVote:string → myVotes:Set<string>; computeVotes collects all
  m.selections (not just [0]); toggle-select for multi, radio for
  single; checkbox/radio indicator icons next to each option;
  "◉ Poll · Multiple choice" / "Single choice" label in header;
  sends full selections array on every vote event.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 11:23:44 -04:00
parent 9232e1ec8e
commit a3f776134f
5 changed files with 294 additions and 214 deletions
+24 -12
View File
@@ -284,18 +284,30 @@ export const mxcUrlToHttp = (
height?: number,
resizeMethod?: string,
allowDirectLinks?: boolean,
// Synapse's thumbnail endpoint returns 400 for allow_redirect=true; keep false everywhere.
allowRedirects = false,
): string | null =>
mx.mxcUrlToHttp(
mxcUrl,
width,
height,
resizeMethod,
allowDirectLinks,
allowRedirects,
useAuthentication,
);
): string | null => {
// Build the URL manually so we never add allow_redirect.
// The SDK forces allow_redirect=true when useAuthentication=true, but Synapse's
// /_matrix/client/v1/media/thumbnail endpoint rejects that parameter with 400.
if (!mxcUrl) return null;
if (!mxcUrl.startsWith('mxc://')) {
return allowDirectLinks ? mxcUrl : null;
}
const parts = mxcUrl.slice(6).split('/');
if (parts.length !== 2 || !parts[0] || !parts[1]) return null;
const [serverName, mediaId] = parts;
const isThumbnail = !!(width || height || resizeMethod);
const verb = isThumbnail ? 'thumbnail' : 'download';
const prefix = useAuthentication
? `/_matrix/client/v1/media/${verb}`
: `/_matrix/media/v3/${verb}`;
const url = new URL(`${prefix}/${serverName}/${mediaId}`, mx.getHomeserverUrl());
if (width) url.searchParams.set('width', String(Math.round(width)));
if (height) url.searchParams.set('height', String(Math.round(height)));
if (resizeMethod) url.searchParams.set('method', resizeMethod);
return url.href;
};
export const downloadMedia = async (src: string): Promise<Blob> => {
// this request is authenticated by service worker