Skip to content

Commit 0996def

Browse files
committed
merge dev
2 parents 099ca05 + 000e9ba commit 0996def

16 files changed

Lines changed: 438 additions & 72 deletions

.changeset/fix-blockquote-md.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
default: patch
3+
---
4+
5+
Fixed blockquotes needing a double backslash to escape and require a space after the `>` in order to form a blockquote.

.changeset/fix-jump-to-events.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
default: patch
3+
---
4+
5+
Fixed jumpting to arbitrary events (e.g. reactions, edits, pins, leaves/joins).

.changeset/fix-latex-codeblock.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
default: patch
3+
---
4+
5+
Fix latex in codeblocks getting parsed.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
default: patch
3+
---
4+
5+
Fixed tweak automatic favoriting behavior when entering/leaving the catalog.

src/app/components/url-preview/TweakPreviewUrlCard.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,6 @@ export function TweakPreviewUrlCard({ url }: { url: string }) {
198198
const nextEnabled = enabledTweakFullUrls.filter((u) => u !== url);
199199
patchSettings({
200200
themeRemoteEnabledTweakFullUrls: nextEnabled,
201-
themeRemoteTweakFavorites: pruneTweakFavorites(tweakFavorites, nextEnabled),
202201
});
203202
}
204203
},

src/app/features/room/RoomTimeline.tsx

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
factoryRenderLinkifyWithMention,
4646
} from '$plugins/react-custom-html-parser';
4747
import { today, yesterday, timeDayMonthYear } from '$utils/time';
48+
import { unwrapRelationJumpTarget } from '$utils/room';
4849
import { useMemberEventParser } from '$hooks/useMemberEventParser';
4950
import { usePowerLevelsContext } from '$hooks/usePowerLevels';
5051
import { useRoomCreators } from '$hooks/useRoomCreators';
@@ -77,8 +78,11 @@ import {
7778
} from '$utils/timeline';
7879
import { useTimelineSync } from '$hooks/timeline/useTimelineSync';
7980
import { useTimelineActions } from '$hooks/timeline/useTimelineActions';
80-
import type { ProcessedEvent } from '$hooks/timeline/useProcessedTimeline';
81-
import { useProcessedTimeline } from '$hooks/timeline/useProcessedTimeline';
81+
import {
82+
useProcessedTimeline,
83+
getProcessedRowIndexForRawTimelineIndex,
84+
type ProcessedEvent,
85+
} from '$hooks/timeline/useProcessedTimeline';
8286
import { useTimelineEventRenderer } from '$hooks/timeline/useTimelineEventRenderer';
8387
import * as css from './RoomTimeline.css';
8488

@@ -494,19 +498,40 @@ export function RoomTimeline({
494498
setOpenThread: setOpenThread as unknown as (threadId: string | undefined) => void,
495499
handleEdit,
496500
handleOpenEvent: (id) => {
497-
const evtTimeline = getEventTimeline(room, id);
501+
const anchorId = unwrapRelationJumpTarget(room, id);
502+
let evtTimeline = getEventTimeline(room, anchorId);
503+
let resolvedForIndex = anchorId;
504+
if (!evtTimeline && anchorId !== id) {
505+
evtTimeline = getEventTimeline(room, id);
506+
resolvedForIndex = id;
507+
}
498508
const absoluteIndex = evtTimeline
499-
? getEventIdAbsoluteIndex(timelineSync.timeline.linkedTimelines, evtTimeline, id)
509+
? getEventIdAbsoluteIndex(
510+
timelineSync.timeline.linkedTimelines,
511+
evtTimeline,
512+
resolvedForIndex
513+
)
500514
: undefined;
501515

502516
if (typeof absoluteIndex === 'number') {
503-
const processedIndex = getRawIndexToProcessedIndex(absoluteIndex);
517+
let processedIndex = getRawIndexToProcessedIndex(absoluteIndex);
518+
let focusRawIndex = absoluteIndex;
519+
if (processedIndex === undefined) {
520+
const nearest = getProcessedRowIndexForRawTimelineIndex(
521+
processedEventsRef.current,
522+
absoluteIndex
523+
);
524+
if (nearest) {
525+
processedIndex = nearest.rowIndex;
526+
focusRawIndex = nearest.focusRawIndex;
527+
}
528+
}
504529
if (vListRef.current && processedIndex !== undefined) {
505530
vListRef.current.scrollToIndex(processedIndex, { align: 'center' });
506531
}
507-
timelineSync.setFocusItem({ index: absoluteIndex, scrollTo: false, highlight: true });
532+
timelineSync.setFocusItem({ index: focusRawIndex, scrollTo: false, highlight: true });
508533
} else {
509-
timelineSync.loadEventTimeline(id);
534+
timelineSync.loadEventTimeline(anchorId);
510535
}
511536
},
512537
});

src/app/features/room/ThreadDrawer.tsx

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,12 @@ import {
2323
makeMentionCustomProps,
2424
renderMatrixMention,
2525
} from '$plugins/react-custom-html-parser';
26-
import { getEditedEvent, getMemberDisplayName, reactionOrEditEvent } from '$utils/room';
26+
import {
27+
getEditedEvent,
28+
getMemberDisplayName,
29+
reactionOrEditEvent,
30+
unwrapRelationJumpTarget,
31+
} from '$utils/room';
2732
import { getMxIdLocalPart, toggleReaction } from '$utils/matrix';
2833
import { useMatrixClient } from '$hooks/useMatrixClient';
2934
import { useMediaAuthentication } from '$hooks/useMediaAuthentication';
@@ -49,8 +54,11 @@ import { useIgnoredUsers } from '$hooks/useIgnoredUsers';
4954
import { useGetMemberPowerTag } from '$hooks/useMemberPowerTag';
5055
import { useMemberEventParser } from '$hooks/useMemberEventParser';
5156
import { useMessageEdit } from '$hooks/useMessageEdit';
52-
import type { ProcessedEvent } from '$hooks/timeline/useProcessedTimeline';
53-
import { useProcessedTimeline } from '$hooks/timeline/useProcessedTimeline';
57+
import {
58+
useProcessedTimeline,
59+
getProcessedRowIndexForRawTimelineIndex,
60+
type ProcessedEvent,
61+
} from '$hooks/timeline/useProcessedTimeline';
5462
import { useTimelineEventRenderer } from '$hooks/timeline/useTimelineEventRenderer';
5563
import { RoomInput } from './RoomInput';
5664
import { RoomViewFollowing, RoomViewFollowingPlaceholder } from './RoomViewFollowing';
@@ -627,18 +635,32 @@ export function ThreadDrawer({ room, threadRootId, onClose, overlay }: ThreadDra
627635
(evt) => {
628636
const targetId = evt.currentTarget.getAttribute('data-event-id');
629637
if (!targetId) return;
630-
const isRoot = targetId === threadRootId;
631-
const isInReplies = processedEventsRef.current.some((e) => e.id === targetId);
638+
let anchorId = unwrapRelationJumpTarget(room, targetId);
639+
const threadLive = thread?.timelineSet.getLiveTimeline();
640+
const threadEvents = threadLive?.getEvents();
641+
const rawIndex = threadEvents?.findIndex((e) => e.getId() === anchorId) ?? -1;
642+
if (rawIndex >= 0) {
643+
const nearest = getProcessedRowIndexForRawTimelineIndex(
644+
processedEventsRef.current,
645+
rawIndex
646+
);
647+
if (nearest) {
648+
const rowEv = processedEventsRef.current[nearest.rowIndex];
649+
if (rowEv) anchorId = rowEv.id;
650+
}
651+
}
652+
const isRoot = anchorId === threadRootId;
653+
const isInReplies = processedEventsRef.current.some((e) => e.id === anchorId);
632654
if (!isRoot && !isInReplies) return;
633-
setJumpToEventId(targetId);
655+
setJumpToEventId(anchorId);
634656
setTimeout(() => setJumpToEventId(undefined), 2500);
635657
const el = drawerRef.current;
636658
if (el) {
637-
const target = el.querySelector(`[data-message-id="${targetId}"]`);
659+
const target = el.querySelector(`[data-message-id="${anchorId}"]`);
638660
target?.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
639661
}
640662
},
641-
[threadRootId]
663+
[threadRootId, room, thread]
642664
);
643665

644666
// Map jumpToEventId to a focusItem index for useTimelineEventRenderer highlighting

src/app/features/settings/cosmetics/ThemeCatalogSettings.tsx

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type ChangeEventHandler, useCallback, useEffect, useMemo, useState } from 'react';
1+
import { type ChangeEventHandler, useCallback, useEffect, useMemo, useRef, useState } from 'react';
22
import { useTimeoutToggle } from '$hooks/useTimeoutToggle';
33
import { copyToClipboard, downloadTextFile } from '$utils/dom';
44
import { useQuery, useQueryClient } from '@tanstack/react-query';
@@ -214,6 +214,9 @@ export function ThemeCatalogSettings({
214214
const [browseOpen, setBrowseOpen] = useState(false);
215215
const [importModalOpen, setImportModalOpen] = useState(false);
216216

217+
const appearanceCatalogBrowseWasOpenRef = useRef(false);
218+
const tweakFavoritesSnapshotAtAppearanceCatalogOpenRef = useRef<Set<string>>(new Set());
219+
217220
useEffect(() => {
218221
if (isAppearanceMode) {
219222
onBrowseOpenChange?.(browseOpen);
@@ -248,6 +251,19 @@ export function ThemeCatalogSettings({
248251
'themeChatAutoPreviewAnyUrl'
249252
);
250253

254+
useEffect(() => {
255+
if (!isAppearanceMode) {
256+
appearanceCatalogBrowseWasOpenRef.current = false;
257+
return;
258+
}
259+
if (browseOpen && !appearanceCatalogBrowseWasOpenRef.current) {
260+
tweakFavoritesSnapshotAtAppearanceCatalogOpenRef.current = new Set(
261+
tweakFavorites.map((f) => f.fullUrl.trim()).filter(Boolean)
262+
);
263+
}
264+
appearanceCatalogBrowseWasOpenRef.current = browseOpen;
265+
}, [browseOpen, isAppearanceMode, tweakFavorites]);
266+
251267
const [themeSearch, setThemeSearch] = useState('');
252268
const [tweakSearch, setTweakSearch] = useState('');
253269
const [kindFilter, setKindFilter] = useState<'all' | 'light' | 'dark'>('all');
@@ -779,7 +795,15 @@ export function ThemeCatalogSettings({
779795
}, [patchSettings]);
780796

781797
const setTweakApplied = useCallback(
782-
async (fullUrl: string, apply: boolean, hint?: { displayName?: string; basename?: string }) => {
798+
async (
799+
fullUrl: string,
800+
apply: boolean,
801+
hint?: {
802+
displayName?: string;
803+
basename?: string;
804+
pruneUnpinnedFavoriteOnDisable?: boolean;
805+
}
806+
) => {
783807
const trimmed = fullUrl.trim();
784808
if (!trimmed) return;
785809

@@ -811,10 +835,25 @@ export function ThemeCatalogSettings({
811835
});
812836
} else {
813837
const nextEnabled = enabledTweakFullUrls.filter((u) => u !== trimmed);
814-
patchSettings({
815-
themeRemoteEnabledTweakFullUrls: nextEnabled,
816-
themeRemoteTweakFavorites: pruneTweakFavorites(tweakFavorites, nextEnabled),
817-
});
838+
if (hint?.pruneUnpinnedFavoriteOnDisable) {
839+
const enabledSet = new Set(nextEnabled);
840+
const inLibraryBeforeThisCatalogVisit =
841+
tweakFavoritesSnapshotAtAppearanceCatalogOpenRef.current;
842+
const nextTweakFavs = tweakFavorites.filter(
843+
(f) =>
844+
f.pinned === true ||
845+
enabledSet.has(f.fullUrl) ||
846+
inLibraryBeforeThisCatalogVisit.has(f.fullUrl.trim())
847+
);
848+
patchSettings({
849+
themeRemoteEnabledTweakFullUrls: nextEnabled,
850+
themeRemoteTweakFavorites: nextTweakFavs,
851+
});
852+
} else {
853+
patchSettings({
854+
themeRemoteEnabledTweakFullUrls: nextEnabled,
855+
});
856+
}
818857
}
819858
},
820859
[enabledTweakFullUrls, patchSettings, prefetchFull, pruneTweakFavorites, tweakFavorites]
@@ -1468,6 +1507,7 @@ export function ThemeCatalogSettings({
14681507
setTweakApplied(row.fullUrl, v, {
14691508
displayName: row.displayName,
14701509
basename: row.basename,
1510+
pruneUnpinnedFavoriteOnDisable: true,
14711511
})
14721512
}
14731513
/>

src/app/hooks/timeline/useProcessedTimeline.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,19 @@ export interface ProcessedEvent {
3939
willRenderDayDivider: boolean;
4040
}
4141

42+
/** Raw timeline indices for skipped events (reactions, edits, …) have no row; walk backward to a visible one. */
43+
export function getProcessedRowIndexForRawTimelineIndex(
44+
processedEvents: ProcessedEvent[],
45+
startRawIndex: number
46+
): { rowIndex: number; focusRawIndex: number } | undefined {
47+
if (startRawIndex < 0) return undefined;
48+
for (let i = startRawIndex; i >= 0; i -= 1) {
49+
const rowIndex = processedEvents.findIndex((e) => e.itemIndex === i);
50+
if (rowIndex >= 0) return { rowIndex, focusRawIndex: i };
51+
}
52+
return undefined;
53+
}
54+
4255
const MESSAGE_EVENT_TYPES = new Set([
4356
'm.room.message',
4457
'm.room.message.encrypted',

0 commit comments

Comments
 (0)