update
This commit is contained in:
22 files changed
+477
-35
No files matched your search
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 (
|
||||
<div className="room-timezone__content">
|
||||
<Text className="room-timezone__message">Share your timezone</Text>
|
||||
<Text className="room-timezone__message" variant="b3">
|
||||
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.
|
||||
</Text>
|
||||
<Text className="room-timezone__message" variant="b2">
|
||||
{timezone ? `Currently shared timezone: ${timezone}` : 'You are not currently sharing a timezone'}
|
||||
</Text>
|
||||
<Button onClick={() => shareTimezone()} variant={timezone ? 'surface' : 'danger'}>
|
||||
{timezone ? 'Update Timezone' : 'Share Timezone'}
|
||||
</Button>
|
||||
|
||||
<Button onClick={() => clearTimezone()} disabled={!timezone}>
|
||||
Clear Shared Timezone
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
RoomTimezone.propTypes = {
|
||||
roomId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default RoomTimezone;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)}
|
||||
|
||||
@@ -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 (
|
||||
<div>
|
||||
{ time ? (
|
||||
<div className="room-view__banner">
|
||||
<RawIcon src={RecentClockIC} size="small" />
|
||||
<Text>
|
||||
{ twemojify(time) }
|
||||
</Text>
|
||||
</div>
|
||||
)
|
||||
: null }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
RoomBanner.propTypes = {
|
||||
roomId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default RoomBanner;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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 }) {
|
||||
<MenuHeader>Room addresses</MenuHeader>
|
||||
<RoomAliases roomId={roomId} />
|
||||
</div>
|
||||
<div className="room-settings__card">
|
||||
<MenuHeader>Timezones</MenuHeader>
|
||||
<RoomTimezone roomId={roomId} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<div className="room-view" ref={roomViewRef}>
|
||||
<RoomViewHeader roomId={roomId} />
|
||||
<RoomBanner roomId={roomId} />
|
||||
<div className="room-view__content-wrapper">
|
||||
<div className="room-view__scrollable">
|
||||
<RoomViewContent
|
||||
|
||||
@@ -8,8 +8,7 @@ import { blurOnBubbling } from '../../atoms/button/script';
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import cons from '../../../client/state/cons';
|
||||
import navigation from '../../../client/state/navigation';
|
||||
import { toggleRoomSettings, openReusableContextMenu, openNavigation } from '../../../client/action/navigation';
|
||||
import { togglePeopleDrawer } from '../../../client/action/settings';
|
||||
import { toggleNavigation, toggleRoomSettings, openReusableContextMenu, openNavigation } from '../../../client/action/navigation';import { togglePeopleDrawer } from '../../../client/action/settings';
|
||||
import colorMXID from '../../../util/colorMXID';
|
||||
import { getEventCords } from '../../../util/common';
|
||||
|
||||
@@ -24,6 +23,7 @@ import RoomOptions from '../../molecules/room-options/RoomOptions';
|
||||
import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg';
|
||||
import SearchIC from '../../../../public/res/ic/outlined/search.svg';
|
||||
import UserIC from '../../../../public/res/ic/outlined/user.svg';
|
||||
import HomeIC from '../../../../public/res/ic/outlined/home.svg';
|
||||
import VerticalMenuIC from '../../../../public/res/ic/outlined/vertical-menu.svg';
|
||||
import BackArrowIC from '../../../../public/res/ic/outlined/chevron-left.svg';
|
||||
|
||||
@@ -76,11 +76,17 @@ function RoomViewHeader({ roomId }) {
|
||||
return (
|
||||
<Header>
|
||||
<IconButton
|
||||
src={BackArrowIC}
|
||||
src={HomeIC}
|
||||
className="room-header__back-btn"
|
||||
tooltip="Return to navigation"
|
||||
onClick={() => openNavigation()}
|
||||
/>
|
||||
<IconButton
|
||||
src={BackArrowIC}
|
||||
className="room-header__hide-navigation-btn"
|
||||
tooltip="Hide Navigation"
|
||||
onClick={() => toggleNavigation()}
|
||||
/>
|
||||
<button
|
||||
ref={roomHeaderBtnRef}
|
||||
className="room-header__btn"
|
||||
|
||||
@@ -45,3 +45,10 @@
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.room-header__hide-navigation-btn {
|
||||
@include dir.side(margin, 0, var(--sp-tight));
|
||||
@include screen.smallerThan(mobileBreakpoint) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import settings from '../../../client/state/settings';
|
||||
import navigation from '../../../client/state/navigation';
|
||||
import {
|
||||
toggleSystemTheme, toggleMarkdown, toggleMembershipEvents, toggleNickAvatarEvents,
|
||||
toggleNotifications, toggleNotificationSounds, toggleSelfTranslate,
|
||||
toggleNotifications, toggleNotificationSounds, toggleSelfTranslate, toggleReadReceipts, toggleHideNavigation
|
||||
} from '../../../client/action/settings';
|
||||
import { usePermission } from '../../hooks/usePermission';
|
||||
|
||||
@@ -48,7 +48,9 @@ import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
|
||||
|
||||
import '../../i18n.jsx'
|
||||
|
||||
|
||||
let capabilities = {
|
||||
privateReadReceipts: false,
|
||||
}
|
||||
|
||||
|
||||
function AppearanceSection() {
|
||||
@@ -78,7 +80,7 @@ function AppearanceSection() {
|
||||
return (
|
||||
<div className="settings-appearance">
|
||||
<div className="settings-appearance__card">
|
||||
<MenuHeader>{t("translation.Organisms.Settings.theme.title")}</MenuHeader>
|
||||
<MenuHeader>{t("Theme")}</MenuHeader>
|
||||
<SettingTile
|
||||
title="Follow system theme"
|
||||
options={(
|
||||
@@ -131,6 +133,16 @@ function AppearanceSection() {
|
||||
)}
|
||||
content={<Text variant="b3">Hide membership change messages from room timeline. (Join, Leave, Invite, Kick and Ban)</Text>}
|
||||
/>
|
||||
<SettingTile
|
||||
title="Hide navigation by default"
|
||||
options={(
|
||||
<Toggle
|
||||
isActive={settings.hideNavigation}
|
||||
onToggle={() => { toggleHideNavigation(); updateState({}); }}
|
||||
/>
|
||||
)}
|
||||
content={<Text variant="b3">Hide the navigation bar by default, when cinny is opened.</Text>}
|
||||
/>
|
||||
<SettingTile
|
||||
title="Hide nick/avatar events"
|
||||
options={(
|
||||
@@ -272,6 +284,7 @@ function EmojiSection() {
|
||||
}
|
||||
|
||||
function SecuritySection() {
|
||||
const [, updateState] = useState({});
|
||||
return (
|
||||
<div className="settings-security">
|
||||
<div className="settings-security__card">
|
||||
@@ -301,6 +314,33 @@ function SecuritySection() {
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="settings-security__card">
|
||||
<MenuHeader>Presence</MenuHeader>
|
||||
<SettingTile
|
||||
title="Send read receipts"
|
||||
options={(
|
||||
<Toggle
|
||||
/** Always allow to switch receipts on. */
|
||||
disabled={!capabilities.privateReadReceipts && settings.sendReadReceipts}
|
||||
isActive={settings.sendReadReceipts}
|
||||
onToggle={() => { toggleReadReceipts(); updateState({}); }}
|
||||
/>
|
||||
)}
|
||||
content={(
|
||||
<>
|
||||
<Text variant="b3">
|
||||
Let other people know what messages you read.
|
||||
</Text>
|
||||
{!capabilities.privateReadReceipts
|
||||
&& (
|
||||
<Text variant="b3">
|
||||
Making your read receipts private requires a compatible homeserver.
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -314,10 +354,11 @@ function AboutSection() {
|
||||
<img width="60" height="60" src={CinnySVG} alt="Cinny logo" />
|
||||
<div>
|
||||
<Text variant="h2" weight="medium">
|
||||
Cinny
|
||||
<span className="text text-b3" style={{ margin: '0 var(--sp-extra-tight)' }}>{`v${cons.version}`}</span>
|
||||
Cinny Mod
|
||||
<span className="text text-b3" style={{ margin: '0 var(--sp-extra-tight)' }}>{`v${cons.version}.52`}</span>
|
||||
</Text>
|
||||
<Text>Yet another matrix client</Text>
|
||||
<Text>Modded By WHK with magic & love</Text>
|
||||
|
||||
<div className="settings-about__btns">
|
||||
<Button onClick={() => window.open('https://github.com/ajbura/cinny')}>Source code</Button>
|
||||
@@ -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() {
|
||||
<PopupWindow
|
||||
isOpen={isOpen}
|
||||
className="settings-window"
|
||||
title={<Text variant="s1" weight="medium" primary>{t("translation.Organisms.Settings.title")}</Text>}
|
||||
title={<Text variant="s1" weight="medium" primary>{t("Settings")}</Text>}
|
||||
contentOptions={(
|
||||
<>
|
||||
<Button variant="danger" iconSrc={PowerIC} onClick={handleLogout}>
|
||||
|
||||
@@ -15,6 +15,7 @@ import Windows from '../../organisms/pw/Windows';
|
||||
import Dialogs from '../../organisms/pw/Dialogs';
|
||||
import EmojiBoardOpener from '../../organisms/emoji-board/EmojiBoardOpener';
|
||||
|
||||
import settings from '../../../client/state/settings';
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import navigation from '../../../client/state/navigation';
|
||||
import cons from '../../../client/state/cons';
|
||||
@@ -24,9 +25,11 @@ import VerticalMenuIC from '../../../../public/res/ic/outlined/vertical-menu.svg
|
||||
|
||||
function Client() {
|
||||
const [isLoading, changeLoading] = useState(true);
|
||||
const [roomSelected, selectRoom] = useState(false);
|
||||
const [loadingMsg, setLoadingMsg] = useState('Heating up');
|
||||
const [dragCounter, setDragCounter] = useState(0);
|
||||
const classNameHidden = 'client__item-hidden';
|
||||
const toggleHidden = 'toggle-hidden';
|
||||
|
||||
const navWrapperRef = useRef(null);
|
||||
const roomWrapperRef = useRef(null);
|
||||
@@ -34,19 +37,29 @@ function Client() {
|
||||
function onRoomSelected() {
|
||||
navWrapperRef.current?.classList.add(classNameHidden);
|
||||
roomWrapperRef.current?.classList.remove(classNameHidden);
|
||||
if (settings.hideNavigation) {
|
||||
selectRoom(navigation.selectedRoomId);
|
||||
navWrapperRef.current?.classList.add(toggleHidden);
|
||||
}
|
||||
}
|
||||
function onNavigationSelected() {
|
||||
navWrapperRef.current?.classList.remove(classNameHidden);
|
||||
navWrapperRef.current?.classList.remove(toggleHidden);
|
||||
roomWrapperRef.current?.classList.add(classNameHidden);
|
||||
}
|
||||
function onNavigationToggled() {
|
||||
navWrapperRef.current?.classList.toggle(toggleHidden);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
navigation.on(cons.events.navigation.ROOM_SELECTED, onRoomSelected);
|
||||
navigation.on(cons.events.navigation.NAVIGATION_OPENED, onNavigationSelected);
|
||||
navigation.on(cons.events.navigation.NAVIGATION_TOGGLED, onNavigationToggled);
|
||||
|
||||
return (() => {
|
||||
navigation.removeListener(cons.events.navigation.ROOM_SELECTED, onRoomSelected);
|
||||
navigation.removeListener(cons.events.navigation.NAVIGATION_OPENED, onNavigationSelected);
|
||||
navigation.removeListener(cons.events.navigation.NAVIGATION_TOGGLED, onNavigationToggled);
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -165,7 +178,7 @@ function Client() {
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
>
|
||||
<div className="navigation__wrapper" ref={navWrapperRef}>
|
||||
<div className={`navigation__wrapper ${settings.hideNavigation && roomSelected ? toggleHidden : ''}`} ref={navWrapperRef}>
|
||||
<Navigation />
|
||||
</div>
|
||||
<div className={`room__wrapper ${classNameHidden}`} ref={roomWrapperRef}>
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.room__wrapper {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
@@ -30,6 +30,12 @@ export function openNavigation() {
|
||||
});
|
||||
}
|
||||
|
||||
export function toggleNavigation() {
|
||||
appDispatcher.dispatch({
|
||||
type: cons.actions.navigation.TOGGLE_NAVIGATION,
|
||||
});
|
||||
}
|
||||
|
||||
export function openSpaceSettings(roomId, tabText) {
|
||||
appDispatcher.dispatch({
|
||||
type: cons.actions.navigation.OPEN_SPACE_SETTINGS,
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import initMatrix from '../initMatrix';
|
||||
import settings from '../state/settings';
|
||||
|
||||
const ReceiptType = {
|
||||
Read: 'm.read',
|
||||
ReadPrivate: 'm.read.private',
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export async function markAsRead(roomId) {
|
||||
@@ -22,5 +28,6 @@ export async function markAsRead(roomId) {
|
||||
const latestEvent = getLatestValidEvent();
|
||||
if (latestEvent === null) return;
|
||||
|
||||
await mx.sendReadReceipt(latestEvent);
|
||||
const receiptType = settings.sendReadReceipts ? ReceiptType.Read : ReceiptType.ReadPrivate;
|
||||
await mx.sendReadReceipt(latestEvent, receiptType);
|
||||
}
|
||||
@@ -25,6 +25,12 @@ export function toggleMembershipEvents() {
|
||||
});
|
||||
}
|
||||
|
||||
export function toggleHideNavigation() {
|
||||
appDispatcher.dispatch({
|
||||
type: cons.actions.settings.TOGGLE_HIDE_NAVIGATION,
|
||||
});
|
||||
}
|
||||
|
||||
export function toggleNickAvatarEvents() {
|
||||
appDispatcher.dispatch({
|
||||
type: cons.actions.settings.TOGGLE_NICKAVATAR_EVENT,
|
||||
@@ -43,6 +49,12 @@ export function toggleNotifications() {
|
||||
});
|
||||
}
|
||||
|
||||
export function toggleReadReceipts() {
|
||||
appDispatcher.dispatch({
|
||||
type: cons.actions.settings.TOGGLE_READ_RECEIPTS,
|
||||
});
|
||||
}
|
||||
|
||||
export function toggleNotificationSounds() {
|
||||
appDispatcher.dispatch({
|
||||
type: cons.actions.settings.TOGGLE_NOTIFICATION_SOUNDS,
|
||||
|
||||
@@ -383,17 +383,19 @@ class Notifications extends EventEmitter {
|
||||
});
|
||||
|
||||
this.matrixClient.on('Room.receipt', (mEvent, room) => {
|
||||
if (mEvent.getType() === 'm.receipt') {
|
||||
if (room.isSpaceRoom()) return;
|
||||
const content = mEvent.getContent();
|
||||
const readedEventId = Object.keys(content)[0];
|
||||
const readerUserId = Object.keys(content[readedEventId]['m.read'])[0];
|
||||
if (readerUserId !== this.matrixClient.getUserId()) return;
|
||||
if (mEvent.getType() !== 'm.receipt' || room.isSpaceRoom()) return;
|
||||
const content = mEvent.getContent();
|
||||
const userId = this.matrixClient.getUserId();
|
||||
|
||||
this.deleteNoti(room.roomId);
|
||||
|
||||
this._deletePopupRoomNotis(room.roomId);
|
||||
}
|
||||
Object.keys(content).forEach((eventId) => {
|
||||
Object.entries(content[eventId]).forEach(([receiptType, receipt]) => {
|
||||
if (!cons.supportReceiptTypes.includes(receiptType)) return;
|
||||
if (Object.keys(receipt || {}).includes(userId)) {
|
||||
this.deleteNoti(room.roomId);
|
||||
this._deletePopupRoomNotis(room.roomId);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.matrixClient.on('Room.myMembership', (room, membership) => {
|
||||
|
||||
@@ -19,6 +19,10 @@ const cons = {
|
||||
'm.room.member',
|
||||
'm.sticker',
|
||||
],
|
||||
supportReceiptTypes: [
|
||||
'm.read',
|
||||
'm.read.private',
|
||||
],
|
||||
notifs: {
|
||||
DEFAULT: 'default',
|
||||
ALL_MESSAGES: 'all_messages',
|
||||
@@ -55,6 +59,7 @@ const cons = {
|
||||
CLICK_REPLY_TO: 'CLICK_REPLY_TO',
|
||||
OPEN_SEARCH: 'OPEN_SEARCH',
|
||||
OPEN_REUSABLE_CONTEXT_MENU: 'OPEN_REUSABLE_CONTEXT_MENU',
|
||||
TOGGLE_NAVIGATION: 'TOGGLE_NAVIGATION',
|
||||
OPEN_NAVIGATION: 'OPEN_NAVIGATION',
|
||||
OPEN_REUSABLE_DIALOG: 'OPEN_REUSABLE_DIALOG',
|
||||
OPEN_EMOJI_VERIFICATION: 'OPEN_EMOJI_VERIFICATION',
|
||||
@@ -76,10 +81,12 @@ const cons = {
|
||||
TOGGLE_MARKDOWN: 'TOGGLE_MARKDOWN',
|
||||
TOGGLE_PEOPLE_DRAWER: 'TOGGLE_PEOPLE_DRAWER',
|
||||
TOGGLE_MEMBERSHIP_EVENT: 'TOGGLE_MEMBERSHIP_EVENT',
|
||||
TOGGLE_HIDE_NAVIGATION: 'TOGGLE_HIDE_NAVIGATION',
|
||||
TOGGLE_NICKAVATAR_EVENT: 'TOGGLE_NICKAVATAR_EVENT',
|
||||
TOGGLE_SELF_TRANSLATE: 'TOGGLE_SELF_TRANSLATE',
|
||||
TOGGLE_NOTIFICATIONS: 'TOGGLE_NOTIFICATIONS',
|
||||
TOGGLE_NOTIFICATION_SOUNDS: 'TOGGLE_NOTIFICATION_SOUNDS',
|
||||
TOGGLE_READ_RECEIPTS: 'TOGGLE_READ_RECEIPTS',
|
||||
},
|
||||
},
|
||||
events: {
|
||||
@@ -91,6 +98,7 @@ const cons = {
|
||||
SPACE_MANAGE_OPENED: 'SPACE_MANAGE_OPENED',
|
||||
SPACE_ADDEXISTING_OPENED: 'SPACE_ADDEXISTING_OPENED',
|
||||
ROOM_SETTINGS_TOGGLED: 'ROOM_SETTINGS_TOGGLED',
|
||||
NAVIGATION_TOGGLED: 'NAVIGATIONS_TOGGLED',
|
||||
SHORTCUT_SPACES_OPENED: 'SHORTCUT_SPACES_OPENED',
|
||||
INVITE_LIST_OPENED: 'INVITE_LIST_OPENED',
|
||||
PUBLIC_ROOMS_OPENED: 'PUBLIC_ROOMS_OPENED',
|
||||
@@ -153,6 +161,7 @@ const cons = {
|
||||
NICKAVATAR_EVENTS_TOGGLED: 'NICKAVATAR_EVENTS_TOGGLED',
|
||||
NOTIFICATIONS_TOGGLED: 'NOTIFICATIONS_TOGGLED',
|
||||
NOTIFICATION_SOUNDS_TOGGLED: 'NOTIFICATION_SOUNDS_TOGGLED',
|
||||
READ_RECEIPTS_TOGGLED: 'READ_RECEIPTS_TOGGLED',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -349,6 +349,9 @@ class Navigation extends EventEmitter {
|
||||
[cons.actions.navigation.OPEN_NAVIGATION]: () => {
|
||||
this.emit(cons.events.navigation.NAVIGATION_OPENED);
|
||||
},
|
||||
[cons.actions.navigation.TOGGLE_NAVIGATION]: () => {
|
||||
this.emit(cons.events.navigation.NAVIGATION_TOGGLED, this.navigationVisible);
|
||||
},
|
||||
[cons.actions.navigation.OPEN_EMOJIBOARD]: () => {
|
||||
this.emit(
|
||||
cons.events.navigation.EMOJIBOARD_OPENED,
|
||||
|
||||
@@ -28,12 +28,14 @@ class Settings extends EventEmitter {
|
||||
|
||||
this.useSystemTheme = this.getUseSystemTheme();
|
||||
this.isMarkdown = this.getIsMarkdown();
|
||||
this.hideNavigation = this.getHideNavigation();
|
||||
this.isPeopleDrawer = this.getIsPeopleDrawer();
|
||||
this.hideMembershipEvents = this.getHideMembershipEvents();
|
||||
this.hideNickAvatarEvents = this.getHideNickAvatarEvents();
|
||||
this.enableSelfTranslate = this.getEnableSelfTranslate();
|
||||
this._showNotifications = this.getShowNotifications();
|
||||
this.isNotificationSounds = this.getIsNotificationSounds();
|
||||
this.sendReadReceipts = this.getSendReadReceipts();
|
||||
|
||||
this.isTouchScreenDevice = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0);
|
||||
}
|
||||
@@ -135,6 +137,15 @@ class Settings extends EventEmitter {
|
||||
return settings.useSystemTheme;
|
||||
}
|
||||
|
||||
getHideNavigation() {
|
||||
if (typeof this.hideNavigation === 'boolean') return this.hideNavigation;
|
||||
|
||||
const settings = getSettings();
|
||||
if (settings === null) return false;
|
||||
if (typeof settings.hideNavigation === 'undefined') return false;
|
||||
return settings.hideNavigation;
|
||||
}
|
||||
|
||||
getIsMarkdown() {
|
||||
if (typeof this.isMarkdown === 'boolean') return this.isMarkdown;
|
||||
|
||||
@@ -203,6 +214,15 @@ class Settings extends EventEmitter {
|
||||
return settings.isNotificationSounds;
|
||||
}
|
||||
|
||||
getSendReadReceipts() {
|
||||
if (typeof this.sendReadReceipts === 'boolean') return this.sendReadReceipts;
|
||||
|
||||
const settings = getSettings();
|
||||
if (settings === null) return true;
|
||||
if (typeof settings.sendReadReceipts === 'undefined') return true;
|
||||
return settings.sendReadReceipts;
|
||||
}
|
||||
|
||||
setter(action) {
|
||||
const actions = {
|
||||
[cons.actions.settings.TOGGLE_SYSTEM_THEME]: () => {
|
||||
@@ -223,6 +243,11 @@ class Settings extends EventEmitter {
|
||||
setSettings('hideMembershipEvents', this.hideMembershipEvents);
|
||||
this.emit(cons.events.settings.MEMBERSHIP_EVENTS_TOGGLED, this.hideMembershipEvents);
|
||||
},
|
||||
[cons.actions.settings.TOGGLE_HIDE_NAVIGATION]: () => {
|
||||
this.hideNavigation = !this.hideNavigation;
|
||||
setSettings('hideNavigation', this.hideNavigation);
|
||||
this.emit(cons.events.settings.HIDE_NAVIGATION_TOGGLED, this.hideNavigation);
|
||||
},
|
||||
[cons.actions.settings.TOGGLE_NICKAVATAR_EVENT]: () => {
|
||||
this.hideNickAvatarEvents = !this.hideNickAvatarEvents;
|
||||
setSettings('hideNickAvatarEvents', this.hideNickAvatarEvents);
|
||||
@@ -247,6 +272,11 @@ class Settings extends EventEmitter {
|
||||
setSettings('isNotificationSounds', this.isNotificationSounds);
|
||||
this.emit(cons.events.settings.NOTIFICATION_SOUNDS_TOGGLED, this.isNotificationSounds);
|
||||
},
|
||||
[cons.actions.settings.TOGGLE_READ_RECEIPTS]: () => {
|
||||
this.sendReadReceipts = !this.sendReadReceipts;
|
||||
setSettings('sendReadReceipts', this.sendReadReceipts);
|
||||
this.emit(cons.events.settings.READ_RECEIPTS_TOGGLED, this.sendReadReceipts);
|
||||
},
|
||||
};
|
||||
|
||||
actions[action.type]?.();
|
||||
|
||||
+24
-8
@@ -82,12 +82,22 @@ const plainRules = {
|
||||
},
|
||||
newline: {
|
||||
...defaultRules.newline,
|
||||
match: blockRegex(/^ *\n/),
|
||||
plain: () => '\n',
|
||||
html: () => '<br>',
|
||||
},
|
||||
paragraph: {
|
||||
...defaultRules.paragraph,
|
||||
plain: (node, output, state) => `${output(node.content, state)}\n\n`,
|
||||
html: (node, output, state) => htmlTag('p', output(node.content, state)),
|
||||
match: (source, state) => {
|
||||
const endMatch = blockRegex(/^([^\n]*)\n*$/);
|
||||
if (endMatch) {
|
||||
state.end = true;
|
||||
return endMatch(source, state);
|
||||
}
|
||||
return blockRegex(/^([^\n]*)\n?/)(source, state);
|
||||
},
|
||||
plain: (node, output, state) => `${output(node.content, state)}${state.end ? '' : '\n'}`,
|
||||
html: (node, output, state) => `${output(node.content, state)}${state.end ? '' : '<br>'}`,
|
||||
},
|
||||
escape: {
|
||||
...defaultRules.escape,
|
||||
@@ -101,9 +111,7 @@ const plainRules = {
|
||||
text: {
|
||||
...defaultRules.text,
|
||||
match: anyScopeRegex(/^[\s\S]+?(?=[^0-9A-Za-z\s\u00c0-\uffff]| *\n|\w+:\S|$)/),
|
||||
plain: (node, _, state) => (state.kind === 'edit'
|
||||
? node.content.replace(/(\*|_|!\[|\[|\|\||\$\$?)/g, '\\$1')
|
||||
: node.content),
|
||||
plain: (node) => node.content,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -138,7 +146,15 @@ const markdownRules = {
|
||||
},
|
||||
blockQuote: {
|
||||
...defaultRules.blockQuote,
|
||||
plain: (node, output, state) => `> ${output(node.content, state).trim().replace(/\n/g, '\n> ')}\n\n`,
|
||||
match: blockRegex(/^( *>[^\n]+(\n *>+[^\n]+)*\n?)+/),
|
||||
parse: (capture, parse, state) => {
|
||||
const content = capture[0].replace(/^ *> ?/gm, '');
|
||||
return {
|
||||
content: parse(`${content}`, state),
|
||||
};
|
||||
},
|
||||
plain: (node, output, state) => `> ${output(node.content, state).trim().replace(/\n/g, '\n> ')}\n`,
|
||||
html: (node, output, state) => htmlTag('blockquote', output(node.content, state)),
|
||||
},
|
||||
list: {
|
||||
...defaultRules.list,
|
||||
@@ -492,11 +508,11 @@ function render(content, state, plainOut, htmlOut) {
|
||||
};
|
||||
}
|
||||
|
||||
const plainParser = parserFor(plainRules);
|
||||
const plainParser = parserFor(plainRules, { disableAutoBlockNewlines: true });
|
||||
const plainPlainOut = outputFor(plainRules, 'plain');
|
||||
const plainHtmlOut = outputFor(plainRules, 'html');
|
||||
|
||||
const mdParser = parserFor(markdownRules);
|
||||
const mdParser = parserFor(markdownRules, { disableAutoBlockNewlines: true });
|
||||
const mdPlainOut = outputFor(markdownRules, 'plain');
|
||||
const mdHtmlOut = outputFor(markdownRules, 'html');
|
||||
|
||||
|
||||
Reference in new issue
Block a user