From a41440857d1f8553ed11cb8ce184e727a64143a7 Mon Sep 17 00:00:00 2001 From: WhkSoft Date: Fri, 10 Mar 2023 12:49:55 +0800 Subject: [PATCH] update --- src/app/molecules/message/Message.scss | 1 + src/app/molecules/room-emojis/RoomEmojis.jsx | 3 +- .../molecules/room-timezone/RoomTimeZone.jsx | 66 ++++++++++++++ .../molecules/room-timezone/RoomTimeZone.scss | 84 +++++++++++++++++ src/app/organisms/navigation/Selector.jsx | 9 +- src/app/organisms/room/RoomBanner.jsx | 89 +++++++++++++++++++ src/app/organisms/room/RoomBanner.scss | 28 ++++++ src/app/organisms/room/RoomSettings.jsx | 5 ++ src/app/organisms/room/RoomView.jsx | 2 + src/app/organisms/room/RoomViewHeader.jsx | 12 ++- src/app/organisms/room/RoomViewHeader.scss | 7 ++ src/app/organisms/settings/Settings.jsx | 64 +++++++++++-- src/app/templates/client/Client.jsx | 15 +++- src/app/templates/client/Client.scss | 4 + src/client/action/navigation.js | 6 ++ src/client/action/notifications.js | 9 +- src/client/action/settings.js | 12 +++ src/client/state/Notifications.js | 22 ++--- src/client/state/cons.js | 9 ++ src/client/state/navigation.js | 3 + src/client/state/settings.js | 30 +++++++ src/util/markdown.js | 32 +++++-- 22 files changed, 477 insertions(+), 35 deletions(-) create mode 100644 src/app/molecules/room-timezone/RoomTimeZone.jsx create mode 100644 src/app/molecules/room-timezone/RoomTimeZone.scss create mode 100644 src/app/organisms/room/RoomBanner.jsx create mode 100644 src/app/organisms/room/RoomBanner.scss diff --git a/src/app/molecules/message/Message.scss b/src/app/molecules/message/Message.scss index 5dda9c9..8bbeeb1 100644 --- a/src/app/molecules/message/Message.scss +++ b/src/app/molecules/message/Message.scss @@ -400,6 +400,7 @@ & pre { width: fit-content; max-width: 100%; + direction: initial; @include scrollbar.scroll; @include scrollbar.scroll__h; @include scrollbar.scroll--auto-hide; diff --git a/src/app/molecules/room-emojis/RoomEmojis.jsx b/src/app/molecules/room-emojis/RoomEmojis.jsx index 81cee0a..94ae610 100644 --- a/src/app/molecules/room-emojis/RoomEmojis.jsx +++ b/src/app/molecules/room-emojis/RoomEmojis.jsx @@ -80,8 +80,7 @@ function RoomEmojis({ roomId }) { const { usablePacks, createPack, deletePack } = useRoomPacks(room); - const myPowerlevel = room.getMember(mx.getUserId())?.powerLevel || 0; - const canChange = room.currentState.hasSufficientPowerLevelFor('state_default', myPowerlevel); + const canChange = room.currentState.maySendStateEvent('im.ponies.room_emote', mx.getUserId()); const handlePackCreate = (e) => { e.preventDefault(); diff --git a/src/app/molecules/room-timezone/RoomTimeZone.jsx b/src/app/molecules/room-timezone/RoomTimeZone.jsx new file mode 100644 index 0000000..b50044e --- /dev/null +++ b/src/app/molecules/room-timezone/RoomTimeZone.jsx @@ -0,0 +1,66 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; + +import initMatrix from '../../../client/initMatrix'; +import Button from '../../atoms/button/Button'; +import Text from '../../atoms/text/Text'; + +import './RoomTimezone.scss'; + +function RoomTimezone({ roomId }) { + const mx = initMatrix.matrixClient; + const room = mx.getRoom(roomId); + const userId = mx.getUserId(); + + const timezoneEventId = room.currentState.getStateEvents('in.cinny.shared_timezone', userId)?.event?.content?.user_timezone_event; + const timezoneEvent = room.findEventById(timezoneEventId); + const timezoneContent = timezoneEvent?.getContent(); + + const [timezone, setTimezone] = useState(timezoneContent?.user_timezone); + + const clearTimezone = () => { + const eventKey = room.currentState.getStateEvents('in.cinny.shared_timezone', userId)?.event?.content?.user_timezone_event; + mx.redactEvent(roomId, eventKey); + mx.sendStateEvent(roomId, 'in.cinny.shared_timezone', { }, userId); + setTimezone(null); + }; + + const shareTimezone = () => { + const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + setTimezone(userTimezone); + const content = { + user_timezone: userTimezone, + msgtype: 'in.cinny.share_timezone', + }; + mx.sendEvent(roomId, 'in.cinny.share_timezone', content).then((event) => { + // Append the shared timezone event to the room state + mx.sendStateEvent(roomId, 'in.cinny.shared_timezone', { user_timezone_event: event.event_id }, userId); + }); + }; + + return ( +
+ Share your timezone + + Sharing your timezone will allow other users in this room to see your local time. + Keep in mind that sharing your timezone will share your approximate location. + + + {timezone ? `Currently shared timezone: ${timezone}` : 'You are not currently sharing a timezone'} + + + + +
+ ); +} + +RoomTimezone.propTypes = { + roomId: PropTypes.string.isRequired, +}; + +export default RoomTimezone; \ No newline at end of file diff --git a/src/app/molecules/room-timezone/RoomTimeZone.scss b/src/app/molecules/room-timezone/RoomTimeZone.scss new file mode 100644 index 0000000..9c84292 --- /dev/null +++ b/src/app/molecules/room-timezone/RoomTimeZone.scss @@ -0,0 +1,84 @@ +@use '../../partials/flex'; +@use '../../partials/dir'; +@use '../../partials/text'; + +.room-timezone { + &__message, + & .setting-tile { + margin: var(--sp-tight) var(--sp-normal); + } + & .setting-tile { + margin-bottom: var(--sp-loose); + } + + &__alias-item { + padding: var(--sp-extra-tight) var(--sp-normal); + @extend .cp-fx__row--s-c; + &.checkbox { + @include dir.side(margin, 0 , var(--sp-tight)); + } + & .text { + @extend .cp-fx__item-one; + @extend .cp-txt__ellipsis; + color: var(--tc-surface-high); + span { + margin: 0 var(--sp-extra-tight); + padding: 0 var(--sp-ultra-tight); + color: var(--bg-surface); + background-color: var(--tc-surface-low); + border-radius: 4px; + } + } + } + &__item-btns { + @include dir.side(margin, 48px, 0); + & button { + padding: var(--sp-ultra-tight) var(--sp-tight); + margin-bottom: var(--sp-tight); + @include dir.side(margin, 0, var(--sp-tight)); + } + } + + &__content { + margin-bottom: var(--sp-normal); + + & .checkbox { + @include dir.side(margin, 0, var(--sp-tight)); + min-width: 20px; + } + & > button { + margin: 0 var(--sp-normal); + } + } + + &__form { + padding: var(--sp-normal); + padding-top: 0; + display: flex; + &-label { + padding: var(--sp-normal) var(--sp-normal) var(--sp-ultra-tight); + } + } + + &__input-wrapper { + display: flex; + @extend .cp-fx__item-one; + @include dir.side(margin, 0, var(--sp-tight)); + + & .input-container { + @extend .cp-fx__item-one; + } + } + + &__input-status { + padding: 0 var(--sp-normal); + } + &__valid { + color: var(--tc-positive-high); + padding-bottom: var(--sp-normal); + } + &__invalid { + color: var(--tc-danger-high); + padding-bottom: var(--sp-normal); + } +} \ No newline at end of file diff --git a/src/app/organisms/navigation/Selector.jsx b/src/app/organisms/navigation/Selector.jsx index cb1086e..e84c063 100644 --- a/src/app/organisms/navigation/Selector.jsx +++ b/src/app/organisms/navigation/Selector.jsx @@ -25,8 +25,9 @@ function Selector({ const noti = initMatrix.notifications; const room = mx.getRoom(roomId); - let imageSrc = room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null; - if (imageSrc === null) imageSrc = room.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null; + const imageSrc = (isDM + ? room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') + : room.getAvatarUrl(mx.baseUrl, 24, 24, 'crop')) || null; const isMuted = noti.getNotiType(roomId) === cons.notifs.MUTE; @@ -57,8 +58,8 @@ function Selector({ key={roomId} name={room.name} roomId={roomId} - imageSrc={isDM ? imageSrc : null} - iconSrc={isDM ? null : joinRuleToIconSrc(room.getJoinRule(), room.isSpaceRoom())} + imageSrc={imageSrc} + iconSrc={imageSrc ? null : joinRuleToIconSrc(room.getJoinRule(), room.isSpaceRoom())} isSelected={navigation.selectedRoomId === roomId} isMuted={isMuted} isUnread={!isMuted && noti.hasNoti(roomId)} diff --git a/src/app/organisms/room/RoomBanner.jsx b/src/app/organisms/room/RoomBanner.jsx new file mode 100644 index 0000000..bf72924 --- /dev/null +++ b/src/app/organisms/room/RoomBanner.jsx @@ -0,0 +1,89 @@ +import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; + +import initMatrix from '../../../client/initMatrix'; +import Text from '../../atoms/text/Text'; + +import './RoomBanner.scss'; +import RawIcon from '../../atoms/system-icons/RawIcon'; + +import RecentClockIC from '../../../../public/res/ic/outlined/recent-clock.svg'; + +import { twemojify } from '../../../util/twemojify'; +import { getUsernameOfRoomMember } from '../../../util/matrixUtil'; + +function RoomBanner({ roomId }) { + const mx = initMatrix.matrixClient; + const isDM = initMatrix.roomList.directs.has(roomId); + const room = mx.getRoom(roomId); + + const [time, setTime] = useState(null); + + let partner = null; + + if (isDM) { + partner = room.getAvatarFallbackMember(); + } + + const timezoneEventId = room.currentState.getStateEvents('in.cinny.shared_timezone', partner?.userId)?.event?.content?.user_timezone_event; + const timezoneEvent = room.findEventById(timezoneEventId); + + let timezoneContent = null; + + const updateTime = () => { + if (isDM) { + const timezone = timezoneContent?.user_timezone; + const date = new Date(); + + try { + const partnerLocalTime = date.toLocaleTimeString([], { timeZone: timezone, hour: '2-digit', minute: '2-digit' }); + const userTime = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + // if the partner time and user time are the same, we wont show the time + if (userTime !== partnerLocalTime) { setTime(`${getUsernameOfRoomMember(partner)}'s local time is: ${partnerLocalTime}`); } + } catch { + setTime(null); + } + } + }; + + if (timezoneEvent) { + if (!timezoneEvent.getClearContent()) { + timezoneEvent.attemptDecryption(mx.crypto); + } + + timezoneContent = timezoneEvent.getContent(); + timezoneEvent.getDecryptionPromise()?.then(() => { + timezoneContent = timezoneEvent.getContent(); + updateTime(); + }); + } + + useEffect(() => { + updateTime(); + const interval = setInterval(() => updateTime(), 20000); + return () => { + setTime(null); + clearInterval(interval); + }; + }, [room]); + + return ( +
+ { time ? ( +
+ + + { twemojify(time) } + +
+ ) + : null } +
+ ); +} + +RoomBanner.propTypes = { + roomId: PropTypes.string.isRequired, +}; + +export default RoomBanner; \ No newline at end of file diff --git a/src/app/organisms/room/RoomBanner.scss b/src/app/organisms/room/RoomBanner.scss new file mode 100644 index 0000000..363ed68 --- /dev/null +++ b/src/app/organisms/room/RoomBanner.scss @@ -0,0 +1,28 @@ +@use '../../partials/flex'; +@use '../../partials/text'; +@use '../../partials/dir'; + +.room-view { + &__banner { + background-color: var(--bg-surface-low); + width: calc(100% - var(--sp-extra-loose)); + + padding: var(--sp-ultra-tight); + border-radius: var(--bo-radius); + align-items: center; + + position: relative; + @include dir.prop(left, var(--sp-normal), unset); + @include dir.prop(right, unset, var(--sp-normal)); + display: flex; + z-index: 999; + transform: translate(0%, 10%); + box-shadow: 0 0 2px 0 rgba(0, 0, 0, 20%); + + & .ic-raw { + opacity: 0.4; + margin: 0 var(--sp-extra-tight); + } + + } +} \ No newline at end of file diff --git a/src/app/organisms/room/RoomSettings.jsx b/src/app/organisms/room/RoomSettings.jsx index 6327734..49eab7d 100644 --- a/src/app/organisms/room/RoomSettings.jsx +++ b/src/app/organisms/room/RoomSettings.jsx @@ -39,6 +39,7 @@ import ChevronTopIC from '../../../../public/res/ic/outlined/chevron-top.svg'; import { useForceUpdate } from '../../hooks/useForceUpdate'; import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog'; +import RoomTimezone from '../../molecules/room-timezone/RoomTimeZone'; const tabText = { GENERAL: 'General', @@ -120,6 +121,10 @@ function GeneralSettings({ roomId }) { Room addresses +
+ Timezones + +
); } diff --git a/src/app/organisms/room/RoomView.jsx b/src/app/organisms/room/RoomView.jsx index b94c35c..abe8bb3 100644 --- a/src/app/organisms/room/RoomView.jsx +++ b/src/app/organisms/room/RoomView.jsx @@ -12,6 +12,7 @@ import RoomViewContent from './RoomViewContent'; import RoomViewFloating from './RoomViewFloating'; import RoomViewInput from './RoomViewInput'; import RoomViewCmdBar from './RoomViewCmdBar'; +import RoomBanner from './RoomBanner'; const viewEvent = new EventEmitter(); @@ -42,6 +43,7 @@ function RoomView({ roomTimeline, eventId }) { return (
+
openNavigation()} /> + toggleNavigation()} + /> @@ -404,9 +445,20 @@ function useWindowToggle(setSelectedTab) { return [isOpen, requestClose]; } +async function getCapabilities() { + const mx = initMatrix.matrixClient; + capabilities = { + privateReadReceipts: (await (Promise.all([ + mx.doesServerSupportUnstableFeature('org.matrix.msc2285.stable'), + mx.isVersionSupported('v1.4')]) + )).some((res) => res === true), + }; +} + function Settings() { const [selectedTab, setSelectedTab] = useState(tabItems[0]); const [isOpen, requestClose] = useWindowToggle(setSelectedTab); + useEffect(getCapabilities, []); const { t } = useTranslation(); const handleTabChange = (tabItem) => setSelectedTab(tabItem); @@ -420,7 +472,7 @@ function Settings() { {t("translation.Organisms.Settings.title")}} + title={{t("Settings")}} contentOptions={( <>