From 1bdb7f4e3ab71445fb740454f064bc20797dd658 Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Sat, 7 Oct 2023 18:19:01 +1100 Subject: [PATCH] Timeline-refactor-fixes (#1438) * fix type * fix missing member from reaction * stop context menu event propagation in msg modal * prevent encode blur hash from freezing app * replace roboto font with inter and fix weight * add recent emoji when selecting emoji * fix room latest evt hook * add option to drop typing status --- package-lock.json | 6 -- package.json | 1 - src/app/components/emoji-board/EmojiBoard.tsx | 6 +- src/app/hooks/useRoomLatestEvent.ts | 29 ---------- src/app/hooks/useRoomLatestRenderedEvent.ts | 57 +++++++++++++++++++ src/app/organisms/room/RoomTimeline.tsx | 2 +- src/app/organisms/room/RoomViewFollowing.tsx | 4 +- src/app/organisms/room/RoomViewTyping.css.ts | 3 + src/app/organisms/room/RoomViewTyping.tsx | 22 ++++++- .../organisms/room/message/FileContent.tsx | 4 +- .../organisms/room/message/ImageContent.tsx | 2 +- src/app/organisms/room/msgContent.ts | 9 ++- .../room/reaction-viewer/ReactionViewer.tsx | 11 ++-- src/client/state/settings.js | 4 ++ src/config.css.ts | 26 +++++++++ src/font.js | 5 -- src/index.jsx | 1 - src/index.scss | 10 ++-- 18 files changed, 138 insertions(+), 64 deletions(-) delete mode 100644 src/app/hooks/useRoomLatestEvent.ts create mode 100644 src/app/hooks/useRoomLatestRenderedEvent.ts create mode 100644 src/config.css.ts delete mode 100644 src/font.js diff --git a/package-lock.json b/package-lock.json index 6f00efe..6213a1d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,6 @@ "license": "AGPL-3.0-only", "dependencies": { "@fontsource/inter": "4.5.14", - "@fontsource/roboto": "4.5.8", "@khanacademy/simple-markdown": "0.8.6", "@matrix-org/olm": "3.2.14", "@tanstack/react-virtual": "3.0.0-beta.54", @@ -875,11 +874,6 @@ "resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-4.5.14.tgz", "integrity": "sha512-JDC9AocdPLuGsASkvWw9hS5gtHE7K9dOwL98XLrk5yjYqxy4uVnScG58NUvFMJDVJRl/7c8Wnap6PEs+7Zvj1Q==" }, - "node_modules/@fontsource/roboto": { - "version": "4.5.8", - "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.8.tgz", - "integrity": "sha512-CnD7zLItIzt86q4Sj3kZUiLcBk1dSk81qcqgMGaZe7SQ1P8hFNxhMl5AZthK1zrDM5m74VVhaOpuMGIL4gagaA==" - }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.7", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", diff --git a/package.json b/package.json index 83850a8..8ee5cc5 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "license": "AGPL-3.0-only", "dependencies": { "@fontsource/inter": "4.5.14", - "@fontsource/roboto": "4.5.8", "@khanacademy/simple-markdown": "0.8.6", "@matrix-org/olm": "3.2.14", "@tanstack/react-virtual": "3.0.0-beta.54", diff --git a/src/app/components/emoji-board/EmojiBoard.tsx b/src/app/components/emoji-board/EmojiBoard.tsx index a730983..81730e3 100644 --- a/src/app/components/emoji-board/EmojiBoard.tsx +++ b/src/app/components/emoji-board/EmojiBoard.tsx @@ -46,6 +46,7 @@ import { editableActiveElement, isIntersectingScrollView, targetFromEvent } from import { useAsyncSearch, UseAsyncSearchOptions } from '../../hooks/useAsyncSearch'; import { useDebounce } from '../../hooks/useDebounce'; import { useThrottle } from '../../hooks/useThrottle'; +import { addRecentEmoji } from '../../plugins/recent-emoji'; const RECENT_GROUP_ID = 'recent_group'; const SEARCH_GROUP_ID = 'search_group'; @@ -697,7 +698,10 @@ export function EmojiBoard({ if (!emojiInfo) return; if (emojiInfo.type === EmojiType.Emoji) { onEmojiSelect?.(emojiInfo.data, emojiInfo.shortcode); - if (!evt.altKey && !evt.shiftKey) requestClose(); + if (!evt.altKey && !evt.shiftKey) { + addRecentEmoji(mx, emojiInfo.data); + requestClose(); + } } if (emojiInfo.type === EmojiType.CustomEmoji) { onCustomEmojiSelect?.(emojiInfo.data, emojiInfo.shortcode); diff --git a/src/app/hooks/useRoomLatestEvent.ts b/src/app/hooks/useRoomLatestEvent.ts deleted file mode 100644 index 337438c..0000000 --- a/src/app/hooks/useRoomLatestEvent.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { MatrixEvent, Room, RoomEvent, RoomEventHandlerMap } from 'matrix-js-sdk'; -import { useEffect, useState } from 'react'; - -export const useRoomLatestEvent = (room: Room) => { - const [latestEvent, setLatestEvent] = useState(); - - useEffect(() => { - const getLatestEvent = (): MatrixEvent | undefined => { - const liveEvents = room.getLiveTimeline().getEvents(); - for (let i = liveEvents.length - 1; i >= 0; i -= 1) { - const evt = liveEvents[i]; - if (evt) return evt; - } - return undefined; - }; - - const handleTimelineEvent: RoomEventHandlerMap[RoomEvent.Timeline] = () => { - setLatestEvent(getLatestEvent()); - }; - setLatestEvent(getLatestEvent()); - - room.on(RoomEvent.Timeline, handleTimelineEvent); - return () => { - room.removeListener(RoomEvent.Timeline, handleTimelineEvent); - }; - }, [room]); - - return latestEvent; -}; diff --git a/src/app/hooks/useRoomLatestRenderedEvent.ts b/src/app/hooks/useRoomLatestRenderedEvent.ts new file mode 100644 index 0000000..295d103 --- /dev/null +++ b/src/app/hooks/useRoomLatestRenderedEvent.ts @@ -0,0 +1,57 @@ +/* eslint-disable no-continue */ +import { MatrixEvent, Room, RoomEvent, RoomEventHandlerMap } from 'matrix-js-sdk'; +import { useEffect, useState } from 'react'; +import { settingsAtom } from '../state/settings'; +import { useSetting } from '../state/hooks/settings'; +import { MessageEvent, StateEvent } from '../../types/matrix/room'; + +export const useRoomLatestRenderedEvent = (room: Room) => { + const [hideMembershipEvents] = useSetting(settingsAtom, 'hideMembershipEvents'); + const [hideNickAvatarEvents] = useSetting(settingsAtom, 'hideNickAvatarEvents'); + const [showHiddenEvents] = useSetting(settingsAtom, 'showHiddenEvents'); + const [latestEvent, setLatestEvent] = useState(); + + useEffect(() => { + const getLatestEvent = (): MatrixEvent | undefined => { + const liveEvents = room.getLiveTimeline().getEvents(); + for (let i = liveEvents.length - 1; i >= 0; i -= 1) { + const evt = liveEvents[i]; + + if (!evt) continue; + if (evt.isRelation()) continue; + if (evt.getType() === StateEvent.RoomMember) { + const membershipChanged = evt.getContent().membership !== evt.getPrevContent().membership; + if (membershipChanged && hideMembershipEvents) continue; + if (!membershipChanged && hideNickAvatarEvents) continue; + return evt; + } + + if ( + evt.getType() === MessageEvent.RoomMessage || + evt.getType() === MessageEvent.RoomMessageEncrypted || + evt.getType() === MessageEvent.Sticker || + evt.getType() === StateEvent.RoomName || + evt.getType() === StateEvent.RoomTopic || + evt.getType() === StateEvent.RoomAvatar + ) { + return evt; + } + + if (showHiddenEvents) return evt; + } + return undefined; + }; + + const handleTimelineEvent: RoomEventHandlerMap[RoomEvent.Timeline] = () => { + setLatestEvent(getLatestEvent()); + }; + setLatestEvent(getLatestEvent()); + + room.on(RoomEvent.Timeline, handleTimelineEvent); + return () => { + room.removeListener(RoomEvent.Timeline, handleTimelineEvent); + }; + }, [room, hideMembershipEvents, hideNickAvatarEvents, showHiddenEvents]); + + return latestEvent; +}; diff --git a/src/app/organisms/room/RoomTimeline.tsx b/src/app/organisms/room/RoomTimeline.tsx index 03f72a3..b3902d8 100644 --- a/src/app/organisms/room/RoomTimeline.tsx +++ b/src/app/organisms/room/RoomTimeline.tsx @@ -167,7 +167,7 @@ export const getFirstLinkedTimeline = ( export const getLinkedTimelines = (timeline: EventTimeline): EventTimeline[] => { const firstTimeline = getFirstLinkedTimeline(timeline, Direction.Backward); - const timelines = []; + const timelines: EventTimeline[] = []; for ( let nextTimeline: EventTimeline | null = firstTimeline; diff --git a/src/app/organisms/room/RoomViewFollowing.tsx b/src/app/organisms/room/RoomViewFollowing.tsx index cd62c42..a49f70d 100644 --- a/src/app/organisms/room/RoomViewFollowing.tsx +++ b/src/app/organisms/room/RoomViewFollowing.tsx @@ -19,7 +19,7 @@ import { getMemberDisplayName } from '../../utils/room'; import { getMxIdLocalPart } from '../../utils/matrix'; import * as css from './RoomViewFollowing.css'; import { useMatrixClient } from '../../hooks/useMatrixClient'; -import { useRoomLatestEvent } from '../../hooks/useRoomLatestEvent'; +import { useRoomLatestRenderedEvent } from '../../hooks/useRoomLatestRenderedEvent'; import { useRoomEventReaders } from '../../hooks/useRoomEventReaders'; import { EventReaders } from '../../components/event-readers'; @@ -30,7 +30,7 @@ export const RoomViewFollowing = as<'div', RoomViewFollowingProps>( ({ className, room, ...props }, ref) => { const mx = useMatrixClient(); const [open, setOpen] = useState(false); - const latestEvent = useRoomLatestEvent(room); + const latestEvent = useRoomLatestRenderedEvent(room); const latestEventReaders = useRoomEventReaders(room, latestEvent?.getId()); const followingMembers = latestEventReaders .map((readerId) => room.getMember(readerId)) diff --git a/src/app/organisms/room/RoomViewTyping.css.ts b/src/app/organisms/room/RoomViewTyping.css.ts index ef07316..9def1ae 100644 --- a/src/app/organisms/room/RoomViewTyping.css.ts +++ b/src/app/organisms/room/RoomViewTyping.css.ts @@ -22,3 +22,6 @@ export const RoomViewTyping = style([ animation: `${SlideUpAnime} 100ms ease-in-out`, }, ]); +export const TypingText = style({ + flexGrow: 1, +}); diff --git a/src/app/organisms/room/RoomViewTyping.tsx b/src/app/organisms/room/RoomViewTyping.tsx index c7c15ea..c393f3a 100644 --- a/src/app/organisms/room/RoomViewTyping.tsx +++ b/src/app/organisms/room/RoomViewTyping.tsx @@ -1,8 +1,8 @@ import React, { useMemo } from 'react'; -import { Box, Text, as } from 'folds'; +import { Box, Icon, IconButton, Icons, Text, as } from 'folds'; import { Room } from 'matrix-js-sdk'; import classNames from 'classnames'; -import { useAtomValue } from 'jotai'; +import { useAtomValue, useSetAtom } from 'jotai'; import { roomIdToTypingMembersAtom, selectRoomTypingMembersAtom } from '../../state/typingMembers'; import { TypingIndicator } from '../../components/typing-indicator'; import { getMemberDisplayName } from '../../utils/room'; @@ -15,6 +15,7 @@ export type RoomViewTypingProps = { }; export const RoomViewTyping = as<'div', RoomViewTypingProps>( ({ className, room, ...props }, ref) => { + const setTypingMembers = useSetAtom(roomIdToTypingMembersAtom); const mx = useMatrixClient(); const typingMembers = useAtomValue( useMemo(() => selectRoomTypingMembersAtom(room.roomId, roomIdToTypingMembersAtom), [room]) @@ -29,6 +30,18 @@ export const RoomViewTyping = as<'div', RoomViewTypingProps>( return null; } + const handleDropAll = () => { + // some homeserver does not timeout typing status + // we have given option so user can drop their typing status + typingMembers.forEach((member) => + setTypingMembers({ + type: 'DELETE', + roomId: room.roomId, + member, + }) + ); + }; + return ( ( ref={ref} > - + {typingNames.length === 1 && ( <> {typingNames[0]} @@ -96,6 +109,9 @@ export const RoomViewTyping = as<'div', RoomViewTypingProps>( )} + + + ); } diff --git a/src/app/organisms/room/message/FileContent.tsx b/src/app/organisms/room/message/FileContent.tsx index 8484d84..c6bd45d 100644 --- a/src/app/organisms/room/message/FileContent.tsx +++ b/src/app/organisms/room/message/FileContent.tsx @@ -94,7 +94,7 @@ function ReadTextFile({ body, mimeType, url, encInfo }: Omit - + evt.stopPropagation()}> - + evt.stopPropagation()}> ( clickOutsideDeactivates: true, }} > - + evt.stopPropagation()}> ( const senderId = mEvent.getSender(); if (!senderId) return null; const member = room.getMember(senderId); - if (!member) return null; - const name = getName(member); + const name = (member ? getName(member) : getMxIdLocalPart(senderId)) ?? senderId; - const avatarUrl = member.getAvatarUrl( + const avatarUrl = member?.getAvatarUrl( mx.baseUrl, 100, 100, @@ -113,12 +112,12 @@ export const ReactionViewer = as<'div', ReactionViewerProps>( return ( { requestClose(); - openProfileViewer(member.userId, room.roomId); + openProfileViewer(senderId, room.roomId); }} before={ @@ -127,7 +126,7 @@ export const ReactionViewer = as<'div', ReactionViewerProps>( ) : ( diff --git a/src/client/state/settings.js b/src/client/state/settings.js index cc1193c..d39b2ca 100644 --- a/src/client/state/settings.js +++ b/src/client/state/settings.js @@ -4,6 +4,7 @@ import appDispatcher from '../dispatcher'; import cons from './cons'; import { darkTheme, butterTheme, silverTheme } from '../../colors.css'; +import { onLightFontWeight, onDarkFontWeight } from '../../config.css'; function getSettings() { const settings = localStorage.getItem('settings'); @@ -23,6 +24,7 @@ class Settings extends EventEmitter { super(); this.themeClasses = [lightTheme, silverTheme, darkTheme, butterTheme]; + this.fontWeightClasses = [onLightFontWeight, onLightFontWeight, onDarkFontWeight, onDarkFontWeight] this.themes = ['', 'silver-theme', 'dark-theme', 'butter-theme']; this.themeIndex = this.getThemeIndex(); @@ -59,6 +61,7 @@ class Settings extends EventEmitter { this.themes.forEach((themeName, index) => { if (themeName !== '') document.body.classList.remove(themeName); document.body.classList.remove(this.themeClasses[index]); + document.body.classList.remove(this.fontWeightClasses[index]); document.body.classList.remove('prism-light') document.body.classList.remove('prism-dark') }); @@ -71,6 +74,7 @@ class Settings extends EventEmitter { if (this.themes[themeIndex] === undefined) return if (this.themes[themeIndex]) document.body.classList.add(this.themes[themeIndex]); document.body.classList.add(this.themeClasses[themeIndex]); + document.body.classList.add(this.fontWeightClasses[themeIndex]); document.body.classList.add(themeIndex < 2 ? 'prism-light' : 'prism-dark'); } diff --git a/src/config.css.ts b/src/config.css.ts new file mode 100644 index 0000000..df04b90 --- /dev/null +++ b/src/config.css.ts @@ -0,0 +1,26 @@ +import { createTheme } from '@vanilla-extract/css'; +import { config } from 'folds'; + +export const onLightFontWeight = createTheme(config.fontWeight, { + W100: '100', + W200: '200', + W300: '300', + W400: '420', + W500: '500', + W600: '600', + W700: '700', + W800: '800', + W900: '900', +}); + +export const onDarkFontWeight = createTheme(config.fontWeight, { + W100: '100', + W200: '200', + W300: '300', + W400: '350', + W500: '450', + W600: '550', + W700: '650', + W800: '750', + W900: '850', +}); diff --git a/src/font.js b/src/font.js deleted file mode 100644 index 94b1f47..0000000 --- a/src/font.js +++ /dev/null @@ -1,5 +0,0 @@ -import '@fontsource/roboto/300.css'; -import '@fontsource/roboto/400.css'; -import '@fontsource/roboto/500.css'; -import '@fontsource/roboto/700.css'; -import '@fontsource/inter/variable.css'; diff --git a/src/index.jsx b/src/index.jsx index e7256e2..a8a7657 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -8,7 +8,6 @@ import { configClass, varsClass } from 'folds'; enableMapSet(); -import './font'; import './index.scss'; import settings from './client/state/settings'; diff --git a/src/index.scss b/src/index.scss index 04125a1..90ce60a 100644 --- a/src/index.scss +++ b/src/index.scss @@ -158,7 +158,7 @@ /* font-weight */ --fw-light: 300; - --fw-normal: 400; + --fw-normal: 420; --fw-medium: 500; --fw-bold: 700; @@ -193,8 +193,8 @@ --fluid-slide-up: cubic-bezier(0.13, 0.56, 0.25, 0.99); --font-emoji: 'Twemoji'; - --font-primary: 'Roboto', var(--font-emoji), sans-serif; - --font-secondary: 'Roboto', var(--font-emoji), sans-serif; + --font-primary: 'InterVariable', var(--font-emoji), sans-serif; + --font-secondary: 'InterVariable', var(--font-emoji), sans-serif; } .silver-theme { @@ -291,8 +291,10 @@ /* override normal font weight for dark mode */ --fw-normal: 350; + --fw-medium: 450; + --fw-bold: 550; - --font-secondary: 'InterVariable', 'Roboto', var(--font-emoji), sans-serif; + --font-secondary: 'InterVariable', var(--font-emoji), sans-serif; } .butter-theme {