Compare commits
2 Commits
ffa490e767
...
f054abfbd2
| Author | SHA1 | Date | |
|---|---|---|---|
| f054abfbd2 | |||
| 2b5c6fd606 |
+6
-4
@@ -283,10 +283,11 @@ Themes:
|
|||||||
**What:** Use `IntersectionObserver` to trigger media decryption and loading only when components approach the viewport.
|
**What:** Use `IntersectionObserver` to trigger media decryption and loading only when components approach the viewport.
|
||||||
**Approach:** Reduce initial memory footprint and improve timeline load times by deferring decryption of images/videos until they are visible.
|
**Approach:** Reduce initial memory footprint and improve timeline load times by deferring decryption of images/videos until they are visible.
|
||||||
|
|
||||||
### [ ] P5-6 · Context-Aware Thumbnail Previews
|
### [x] P5-6 · Context-Aware Thumbnail Previews ⚠️ UNTESTED
|
||||||
|
|
||||||
**What:** Enhance thumbnail rendering in the timeline for consistent, polished aesthetics.
|
**What:** Enhance thumbnail rendering in the timeline for consistent, polished aesthetics.
|
||||||
**Approach:** Use CSS `object-fit: cover` with improved focal-point centering within `ThumbnailContent` to prevent media stretching or awkward aspect-ratio cropping.
|
**Approach:** Use CSS `object-fit: cover` with improved focal-point centering within `ThumbnailContent` to prevent media stretching or awkward aspect-ratio cropping.
|
||||||
|
**Fix Applied:** Added `objectPosition: 'center top'` to: (1) `media.css.ts` → `Image` component (timeline images), (2) video thumbnail inline style in `RenderMessageContent.tsx`, (3) `GalleryTile` `<img>` in `MediaGallery.tsx`. Full-size viewers retain `objectFit: 'contain'` — no change. `objectPosition: 'center top'` prevents face/subject cropping on tall portrait images capped at 600px by `AttachmentBox`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -298,11 +299,12 @@ Themes:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### [ ] P5-20 · Quick Reply from Browser Notification
|
### [~] P5-20 · Quick Reply from Browser Notification
|
||||||
|
|
||||||
**What:** Inline reply field in browser notification toasts via Notification Actions API. Reply sends as threaded reply to the triggering message.
|
**What:** Inline reply field in browser notification toasts via Notification Actions API. Reply sends as threaded reply to the triggering message.
|
||||||
**[AUDIT REQUIRED]** (1) Verify browser Notification Actions API support in target browsers. (2) Confirmed: service worker EXISTS at `src/sw.ts` — add `notificationclick` handler there.
|
**[AUDIT REQUIRED]** (1) Verify browser Notification Actions API support in target browsers. (2) Confirmed: service worker EXISTS at `src/sw.ts` — add `notificationclick` handler there.
|
||||||
**Complexity:** Medium-High.
|
**Complexity:** Medium-High.
|
||||||
|
**Partial Fix Applied ⚠️ UNTESTED:** Notifications now (a) show the real message body (`username: message` instead of "New inbox notification from..."), (b) click navigates directly to the room at the specific event (not the inbox), (c) `window.focus()` called on click so the tab comes to front, (d) reminder toasts also link to the specific event. Full inline-reply via Notification Actions API still needs the SW `push`+`notificationclick` pipeline (requires switching from `new Notification()` to `showNotification()` through the SW).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ export function RenderMessageContent({
|
|||||||
title={body}
|
title={body}
|
||||||
src={src}
|
src={src}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
style={{ objectFit: 'cover', width: '100%', height: '100%' }}
|
style={{ objectFit: 'cover', objectPosition: 'center top', width: '100%', height: '100%' }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export const Image = style([
|
|||||||
DefaultReset,
|
DefaultReset,
|
||||||
{
|
{
|
||||||
objectFit: 'cover',
|
objectFit: 'cover',
|
||||||
|
objectPosition: 'center top',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -422,7 +422,7 @@ function GalleryTile({
|
|||||||
<img
|
<img
|
||||||
src={media.url}
|
src={media.url}
|
||||||
alt={body}
|
alt={body}
|
||||||
style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}
|
style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center top', display: 'block' }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import {
|
|||||||
getDirectRoomPath,
|
getDirectRoomPath,
|
||||||
getHomeRoomPath,
|
getHomeRoomPath,
|
||||||
getInboxInvitesPath,
|
getInboxInvitesPath,
|
||||||
getInboxNotificationsPath,
|
|
||||||
} from '../pathUtils';
|
} from '../pathUtils';
|
||||||
import { mDirectAtom } from '../../state/mDirectList';
|
import { mDirectAtom } from '../../state/mDirectList';
|
||||||
import {
|
import {
|
||||||
@@ -255,6 +254,10 @@ function MessageNotifications() {
|
|||||||
eventId: string;
|
eventId: string;
|
||||||
body?: string;
|
body?: string;
|
||||||
}) => {
|
}) => {
|
||||||
|
const roomPath = mDirects.has(roomId)
|
||||||
|
? getDirectRoomPath(roomId, eventId)
|
||||||
|
: getHomeRoomPath(roomId, eventId);
|
||||||
|
|
||||||
if (document.hasFocus()) {
|
if (document.hasFocus()) {
|
||||||
setToast({
|
setToast({
|
||||||
id: `${roomId}-${eventId}-${Date.now()}`,
|
id: `${roomId}-${eventId}-${Date.now()}`,
|
||||||
@@ -263,7 +266,7 @@ function MessageNotifications() {
|
|||||||
body: (body ?? '').slice(0, 80),
|
body: (body ?? '').slice(0, 80),
|
||||||
roomName,
|
roomName,
|
||||||
roomId,
|
roomId,
|
||||||
hashPath: mDirects.has(roomId) ? getDirectRoomPath(roomId) : getHomeRoomPath(roomId),
|
hashPath: roomPath,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -271,12 +274,13 @@ function MessageNotifications() {
|
|||||||
const noti = new window.Notification(roomName, {
|
const noti = new window.Notification(roomName, {
|
||||||
icon: roomAvatar,
|
icon: roomAvatar,
|
||||||
badge: roomAvatar,
|
badge: roomAvatar,
|
||||||
body: `New inbox notification from ${username}`,
|
body: body ? `${username}: ${body}`.slice(0, 120) : username,
|
||||||
silent: true,
|
silent: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
noti.onclick = () => {
|
noti.onclick = () => {
|
||||||
if (!window.closed) navigate(getInboxNotificationsPath());
|
window.focus();
|
||||||
|
navigate(roomPath);
|
||||||
noti.close();
|
noti.close();
|
||||||
notifRef.current = undefined;
|
notifRef.current = undefined;
|
||||||
};
|
};
|
||||||
@@ -400,8 +404,8 @@ function ReminderMonitor() {
|
|||||||
firedRef.current.add(key);
|
firedRef.current.add(key);
|
||||||
const room = mx.getRoom(r.roomId);
|
const room = mx.getRoom(r.roomId);
|
||||||
const hashPath = mDirects.has(r.roomId)
|
const hashPath = mDirects.has(r.roomId)
|
||||||
? getDirectRoomPath(r.roomId)
|
? getDirectRoomPath(r.roomId, r.eventId)
|
||||||
: getHomeRoomPath(r.roomId);
|
: getHomeRoomPath(r.roomId, r.eventId);
|
||||||
setToast({
|
setToast({
|
||||||
id: `reminder-${key}`,
|
id: `reminder-${key}`,
|
||||||
displayName: 'Reminder',
|
displayName: 'Reminder',
|
||||||
|
|||||||
Reference in New Issue
Block a user