Compare commits

..
Author SHA1 Message Date
Ajay Bura d2a9e179f0 Fix bug
Signed-off-by: Ajay Bura <ajbura@gmail.com>
2022-03-21 08:48:36 +05:30
Ajay Bura 321ee96620 Update string
Signed-off-by: Ajay Bura <ajbura@gmail.com>
2022-03-20 22:03:55 +05:30
Ajay Bura ea98418e50 Redesign user profile in settings
Signed-off-by: Ajay Bura <ajbura@gmail.com>
2022-03-20 21:59:54 +05:30
Ajay Bura 4265b97419 Redesign app settings
Signed-off-by: Ajay Bura <ajbura@gmail.com>
2022-03-20 19:10:06 +05:30
44 changed files with 1029 additions and 1091 deletions

No files matched your search

+3 -1
View File
@@ -1,5 +1,7 @@
name: 🐞 Bug Report
title: '[bug] '
description: Report a bug
labels: 'type: bug'
body:
- type: markdown
@@ -49,7 +51,7 @@ body:
render: shell
validations:
required: true
- type: textarea
id: context
attributes:
+3 -1
View File
@@ -1,5 +1,7 @@
name: 💡 Feature Request
title: '[feat] '
description: Suggest an idea
labels: 'type: feature'
body:
- type: textarea
@@ -25,7 +27,7 @@ body:
attributes:
label: Alternatives considered
description: "Any alternative solutions you've considered"
- type: textarea
id: context
attributes:
+11 -13
View File
@@ -1,30 +1,28 @@
# Docs: <https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/customizing-dependency-updates>
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
day: "tuesday"
time: "01:00"
timezone: "Asia/Kolkata"
open-pull-requests-limit: 15
updates:
- package-ecosystem: github-actions
directory: /
schedule:
schedule:
interval: weekly
day: "tuesday"
time: "01:00"
timezone: "Asia/Kolkata"
open-pull-requests-limit: 5
- package-ecosystem: docker
directory: /
schedule:
schedule:
interval: weekly
day: "tuesday"
time: "01:00"
timezone: "Asia/Kolkata"
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
day: "tuesday"
time: "01:00"
timezone: "Asia/Kolkata"
open-pull-requests-limit: 5
+6 -1
View File
@@ -10,7 +10,7 @@ jobs:
env:
PR_NUMBER: ${{github.event.number}}
steps:
- name: Checkout repository
- name: Check out the repo
uses: actions/checkout@v3.0.0
- name: Build app
run: npm ci && npm run build
@@ -32,3 +32,8 @@ jobs:
name: pr.json
path: pr.json
retention-days: 1
- name: Build Docker image
uses: docker/build-push-action@v2.10.0
with:
context: .
push: false
+2 -2
View File
@@ -13,7 +13,7 @@ jobs:
# There's a 'download artifact' action but it hasn't been updated for the
# workflow_run action (https://github.com/actions/download-artifact/issues/60)
# so instead we get this mess:
- name: Download artifact
- name: 'Download artifact'
uses: actions/github-script@v6.0.0
with:
script: |
@@ -46,7 +46,7 @@ jobs:
fs.writeFileSync('${{github.workspace}}/pr.json.zip', Buffer.from(download.data));
- name: Extract Artifacts
run: unzip -d dist previewbuild.zip && rm previewbuild.zip && unzip pr.json.zip && rm pr.json.zip
- name: Read PR Info
- name: 'Read PR Info'
id: readctx
uses: actions/github-script@v6.0.0
with:
-20
View File
@@ -1,20 +0,0 @@
name: 'Docker check'
on:
pull_request:
paths:
- 'Dockerfile'
jobs:
docker-build:
runs-on: ubuntu-latest
env:
PR_NUMBER: ${{github.event.number}}
steps:
- name: Checkout repository
uses: actions/checkout@v3.0.0
- name: Build Docker image
uses: docker/build-push-action@v2.10.0
with:
context: .
push: false
@@ -6,15 +6,13 @@ on:
- dev
jobs:
deploy-to-netlify:
deploy:
name: 'Deploy'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3.0.0
- name: Build and deploy to Netlify
uses: jsmrcaga/action-netlify-deploy@v1.7.2
- uses: actions/checkout@v3.0.0
- uses: jsmrcaga/action-netlify-deploy@v1.7.2
with:
install_command: "npm ci"
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
@@ -9,7 +9,7 @@ jobs:
name: 'Deploy to Netlify'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
- name: Check out the repo
uses: actions/checkout@v3.0.0
- name: Build and deploy to Netlify
uses: jsmrcaga/action-netlify-deploy@v1.7.2
@@ -26,7 +26,7 @@ jobs:
- name: Create tar.gz
run: tar -czvf cinny-${{ steps.vars.outputs.tag }}.tar.gz dist
- name: Upload tagged release
uses: softprops/action-gh-release@v0.1.14
uses: softprops/action-gh-release@v1
with:
files: |
cinny-${{ steps.vars.outputs.tag }}.tar.gz
@@ -35,7 +35,7 @@ jobs:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
steps:
- name: Checkout repository
- name: Check out the repo
uses: actions/checkout@v3.0.0
- name: Login to Docker Hub
uses: docker/login-action@v1.14.1
@@ -44,7 +44,7 @@ jobs:
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v3.7.0
uses: docker/metadata-action@v3.6.2
with:
images: ajbura/cinny
- name: Build and push Docker image
+1 -1
View File
@@ -1,5 +1,5 @@
## Builder
FROM node:17.8.0-alpine3.15 as builder
FROM node:17.7.1-alpine3.15 as builder
WORKDIR /src
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021 Ajay Bura (ajbura) and contributors
Copyright (c) 2021 Ajay Bura (ajbura) and other contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+937 -646
View File
File diff suppressed because it is too large. Load diff
+14 -14
View File
@@ -15,8 +15,8 @@
"author": "Ajay Bura",
"license": "MIT",
"dependencies": {
"@fontsource/inter": "^4.5.7",
"@fontsource/roboto": "^4.5.5",
"@fontsource/inter": "^4.5.5",
"@fontsource/roboto": "^4.5.3",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz",
"@tippyjs/react": "^4.2.6",
"babel-polyfill": "^6.26.0",
@@ -26,9 +26,9 @@
"file-saver": "^2.0.5",
"flux": "^4.0.3",
"formik": "^2.2.9",
"html-react-parser": "^1.4.9",
"html-react-parser": "^1.4.8",
"linkifyjs": "^2.1.9",
"matrix-js-sdk": "^17.0.0",
"matrix-js-sdk": "^15.6.0",
"micromark": "^3.0.10",
"micromark-extension-gfm": "^2.0.1",
"micromark-util-chunked": "^1.0.0",
@@ -37,21 +37,21 @@
"prop-types": "^15.8.1",
"react": "^17.0.2",
"react-autosize-textarea": "^7.1.0",
"react-dnd": "^15.1.2",
"react-dnd-html5-backend": "^15.1.3",
"react-dnd": "^15.1.1",
"react-dnd-html5-backend": "^15.1.2",
"react-dom": "^17.0.2",
"react-google-recaptcha": "^2.1.0",
"react-modal": "^3.14.4",
"sanitize-html": "^2.7.0",
"tippy.js": "^6.3.7",
"twemoji": "^14.0.2"
"twemoji": "^14.0.1"
},
"devDependencies": {
"@babel/core": "^7.17.9",
"@babel/core": "^7.17.7",
"@babel/preset-env": "^7.16.11",
"@babel/preset-react": "^7.16.7",
"assert": "^2.0.0",
"babel-loader": "^8.2.4",
"babel-loader": "^8.2.2",
"browserify-fs": "^1.0.0",
"buffer": "^6.0.3",
"clean-webpack-plugin": "^4.0.0",
@@ -59,9 +59,9 @@
"crypto-browserify": "^3.12.0",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^3.4.1",
"eslint": "^8.13.0",
"eslint": "^8.11.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.2.0",
@@ -71,15 +71,15 @@
"html-webpack-plugin": "^5.3.1",
"mini-css-extract-plugin": "^2.6.0",
"path-browserify": "^1.0.1",
"sass": "^1.50.0",
"sass": "^1.49.9",
"sass-loader": "^12.6.0",
"stream-browserify": "^3.0.0",
"style-loader": "^3.3.1",
"url": "^0.11.0",
"util": "^0.12.4",
"webpack": "^5.72.0",
"webpack": "^5.70.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.8.1",
"webpack-dev-server": "^4.7.4",
"webpack-merge": "^5.7.3"
}
}
+1 -4
View File
@@ -12,7 +12,6 @@ import RawModal from '../../atoms/modal/RawModal';
function Dialog({
className, isOpen, title, onAfterOpen, onAfterClose,
contentOptions, onRequestClose, closeFromOutside, children,
invisibleScroll,
}) {
return (
<RawModal
@@ -37,7 +36,7 @@ function Dialog({
{contentOptions}
</Header>
<div className="dialog__content__wrapper">
<ScrollView autoHide invisible={invisibleScroll}>
<ScrollView autoHide>
<div className="dialog__content-container">
{children}
</div>
@@ -56,7 +55,6 @@ Dialog.defaultProps = {
onAfterClose: null,
onRequestClose: null,
closeFromOutside: true,
invisibleScroll: false,
};
Dialog.propTypes = {
@@ -69,7 +67,6 @@ Dialog.propTypes = {
onRequestClose: PropTypes.func,
closeFromOutside: PropTypes.bool,
children: PropTypes.node.isRequired,
invisibleScroll: PropTypes.bool,
};
export default Dialog;
+5
View File
@@ -21,3 +21,8 @@
flex-direction: column;
}
}
.dialog__content-container {
padding-top: var(--sp-extra-tight);
padding-bottom: var(--sp-extra-loose);
}
@@ -1,49 +0,0 @@
import React, { useState, useEffect } from 'react';
import cons from '../../../client/state/cons';
import navigation from '../../../client/state/navigation';
import IconButton from '../../atoms/button/IconButton';
import Dialog from './Dialog';
import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
function ReusableDialog() {
const [isOpen, setIsOpen] = useState(false);
const [data, setData] = useState(null);
useEffect(() => {
const handleOpen = (title, render, afterClose) => {
setIsOpen(true);
setData({ title, render, afterClose });
};
navigation.on(cons.events.navigation.REUSABLE_DIALOG_OPENED, handleOpen);
return () => {
navigation.removeListener(cons.events.navigation.REUSABLE_DIALOG_OPENED, handleOpen);
};
}, []);
const handleAfterClose = () => {
data.afterClose();
setData(null);
};
const handleRequestClose = () => {
setIsOpen(false);
};
return (
<Dialog
isOpen={isOpen}
title={data?.title || ''}
onAfterClose={handleAfterClose}
onRequestClose={handleRequestClose}
contentOptions={<IconButton src={CrossIC} onClick={handleRequestClose} tooltip="Close" />}
invisibleScroll
>
{data?.render(handleRequestClose) || <div />}
</Dialog>
);
}
export default ReusableDialog;
+4 -12
View File
@@ -186,17 +186,9 @@ const MessageBody = React.memo(({
// if body is not string it is a React element.
if (typeof body !== 'string') return <div className="message__body">{body}</div>;
let content = null;
if (isCustomHTML) {
try {
content = twemojify(sanitizeCustomHtml(body), undefined, true, false);
} catch {
console.error('Malformed custom html: ', body);
content = twemojify(body, undefined);
}
} else {
content = twemojify(body, undefined, true);
}
let content = isCustomHTML
? twemojify(sanitizeCustomHtml(body), undefined, true, false)
: twemojify(body, undefined, true);
// Determine if this message should render with large emojis
// Criteria:
@@ -547,7 +539,7 @@ const MessageOptions = React.memo(({
variant="danger"
iconSrc={BinIC}
onClick={() => {
if (window.confirm('Are you sure that you want to delete this event?')) {
if (window.confirm('Are you sure you want to delete this event')) {
redactEvent(roomId, mEvent.getId());
}
}}
@@ -1,7 +1,7 @@
@use '../../partials/dir';
.pw-model {
--modal-height: 774px;
--modal-height: 656px;
max-height: var(--modal-height) !important;
height: 100%;
}
@@ -17,19 +17,19 @@ const visibility = {
const items = [{
iconSrc: null,
text: 'Anyone (including guests)',
text: 'World readable (anyone can read)',
type: visibility.WORLD_READABLE,
}, {
iconSrc: null,
text: 'Members (all messages)',
text: 'Member shared (since the point in time of selecting this option)',
type: visibility.SHARED,
}, {
iconSrc: null,
text: 'Members (messages after invite)',
text: 'Member invited (since they were invited)',
type: visibility.INVITED,
}, {
iconSrc: null,
text: 'Members (messages after join)',
text: 'Member joined (since they joined)',
type: visibility.JOINED,
}];
@@ -87,7 +87,7 @@ function RoomHistoryVisibility({ roomId }) {
</MenuItem>
))
}
<Text variant="b3">Changes to history visibility will only apply to future messages. The visibility of existing history will have no effect.</Text>
<Text variant="b3">Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.</Text>
</div>
);
}
@@ -35,12 +35,7 @@ const items = [{
function setRoomNotifType(roomId, newType) {
const mx = initMatrix.matrixClient;
const { notifications } = initMatrix;
let roomPushRule;
try {
roomPushRule = mx.getRoomPushRule('global', roomId);
} catch {
roomPushRule = undefined;
}
const roomPushRule = mx.getRoomPushRule('global', roomId);
const promises = [];
if (newType === cons.notifs.MUTE) {
@@ -30,7 +30,7 @@ function RoomOptions({ roomId, afterOptionSelect }) {
afterOptionSelect();
};
const handleLeaveClick = () => {
if (confirm('Are you sure that you want to leave this room?')) {
if (confirm('Are you really want to leave this room?')) {
roomActions.leave(roomId);
afterOptionSelect();
}
@@ -179,7 +179,6 @@ function RoomPermissions({ roomId }) {
const pLEvent = room.currentState.getStateEvents('m.room.power_levels')[0];
const permissions = pLEvent.getContent();
const canChangePermission = room.currentState.maySendStateEvent('m.room.power_levels', mx.getUserId());
const myPowerLevel = room.getMember(mx.getUserId())?.powerLevel ?? 100;
const handlePowerSelector = (e, permKey, parentKey, powerLevel) => {
const handlePowerLevelChange = (newPowerLevel) => {
@@ -209,7 +208,7 @@ function RoomPermissions({ roomId }) {
(closeMenu) => (
<PowerLevelSelector
value={powerLevel}
max={myPowerLevel}
max={100}
onSelect={(pl) => {
closeMenu();
handlePowerLevelChange(pl);
@@ -117,7 +117,7 @@ function RoomProfile({ roomId }) {
const handleAvatarUpload = async (url) => {
if (url === null) {
if (confirm('Are you sure that you want to remove room avatar?')) {
if (confirm('Are you sure you want to remove avatar?')) {
await mx.sendStateEvent(roomId, 'm.room.avatar', { url }, '');
}
} else await mx.sendStateEvent(roomId, 'm.room.avatar', { url }, '');
@@ -9,11 +9,7 @@ function SettingTile({ title, options, content }) {
<div className="setting-tile">
<div className="setting-tile__content">
<div className="setting-tile__title">
{
typeof title === 'string'
? <Text variant="b1">{title}</Text>
: title
}
<Text variant="b1">{title}</Text>
</div>
{content}
</div>
@@ -28,7 +24,7 @@ SettingTile.defaultProps = {
};
SettingTile.propTypes = {
title: PropTypes.node.isRequired,
title: PropTypes.string.isRequired,
options: PropTypes.node,
content: PropTypes.node,
};
@@ -55,7 +55,7 @@ function SpaceOptions({ roomId, afterOptionSelect }) {
};
const handleLeaveClick = () => {
if (confirm('Are you sure that you want to leave this space?')) {
if (confirm('Are you really want to leave this space?')) {
leave(roomId);
afterOptionSelect();
}
+1 -1
View File
@@ -210,7 +210,7 @@ function CreateRoomContent({ isSpace, parentId, onRequestClose }) {
/>
)}
content={(
<Text variant="b3">Selecting Admin sets 100 power level whereas Founder sets 101.</Text>
<Text variant="b3">Override the default (100) power level.</Text>
)}
/>
<Input name="topic" minHeight={174} resizable label="Topic (optional)" />
@@ -2,7 +2,6 @@
@use '../../partials/dir';
.create-room {
margin: var(--sp-normal);
@include dir.side(margin, var(--sp-normal), var(--sp-extra-tight));
&__form > * {
@@ -40,7 +40,7 @@ function ProfileEditor({ userId }) {
const handleAvatarUpload = (url) => {
if (url === null) {
if (confirm('Are you sure that you want to remove avatar?')) {
if (confirm('Are you sure you want to remove avatar?')) {
mx.setAvatarUrl('');
setAvatarSrc(null);
}
@@ -4,7 +4,7 @@ import './PublicRooms.scss';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
import { selectRoom, selectTab } from '../../../client/action/navigation';
import { selectRoom } from '../../../client/action/navigation';
import * as roomActions from '../../../client/action/room';
import Text from '../../atoms/text/Text';
@@ -179,9 +179,7 @@ function PublicRooms({ isOpen, searchTerm, onRequestClose }) {
}, [joiningRooms]);
function handleViewRoom(roomId) {
const room = initMatrix.matrixClient.getRoom(roomId);
if (room.isSpaceRoom()) selectTab(roomId);
else selectRoom(roomId);
selectRoom(roomId);
onRequestClose();
}
@@ -195,7 +193,7 @@ function PublicRooms({ isOpen, searchTerm, onRequestClose }) {
return rooms.map((room) => {
const alias = typeof room.canonical_alias === 'string' ? room.canonical_alias : room.room_id;
const name = typeof room.name === 'string' ? room.name : alias;
const isJoined = initMatrix.matrixClient.getRoom(room.room_id) !== null;
const isJoined = initMatrix.roomList.rooms.has(room.room_id);
return (
<RoomTile
key={room.room_id}
-4
View File
@@ -8,8 +8,6 @@ import Search from '../search/Search';
import ViewSource from '../view-source/ViewSource';
import CreateRoom from '../create-room/CreateRoom';
import ReusableDialog from '../../molecules/dialog/ReusableDialog';
function Dialogs() {
return (
<>
@@ -20,8 +18,6 @@ function Dialogs() {
<CreateRoom />
<SpaceAddExisting />
<Search />
<ReusableDialog />
</>
);
}
@@ -64,11 +64,9 @@ function ReadReceipts() {
onRequestClose={() => setIsOpen(false)}
contentOptions={<IconButton src={CrossIC} onClick={() => setIsOpen(false)} tooltip="Close" />}
>
<div style={{ marginTop: 'var(--sp-tight)', marginBottom: 'var(--sp-extra-loose)' }}>
{
readers.map(renderPeople)
}
</div>
{
readers.map(renderPeople)
}
</Dialog>
);
}
+2 -2
View File
@@ -86,7 +86,7 @@ function GeneralSettings({ roomId }) {
<MenuItem
variant="danger"
onClick={() => {
if (confirm('Are you sure that you want to leave this room?')) {
if (confirm('Are you really want to leave this room?')) {
roomActions.leave(roomId);
}
}}
@@ -123,7 +123,7 @@ function SecuritySettings({ roomId }) {
<RoomEncryption roomId={roomId} />
</div>
<div className="room-settings__card">
<MenuHeader>Message history visibility</MenuHeader>
<MenuHeader>Message history visibility (Who can read history)</MenuHeader>
<RoomHistoryVisibility roomId={roomId} />
</div>
</>
+1 -1
View File
@@ -10,7 +10,7 @@
&__info {
display: flex;
width: 40px;
@include dir.side(margin, 14px, 10px);
@include dir.side(margin, 10px, 14px);
& > * {
margin: auto;
+1 -1
View File
@@ -63,7 +63,7 @@ function genRoomIntro(mEvent, roomTimeline) {
avatarSrc={avatarSrc}
name={roomTimeline.room.name}
heading={`Welcome to ${roomTimeline.room.name}`}
desc={`This is the beginning of the ${roomTimeline.room.name} room.${typeof roomTopic !== 'undefined' ? (` Topic: ${roomTopic}`) : ''}`}
desc={`This is the beginning of ${roomTimeline.room.name} room.${typeof roomTopic !== 'undefined' ? (` Topic: ${roomTopic}`) : ''}`}
time={mEvent ? `Created at ${dateFormat(mEvent.getDate(), 'dd mmmm yyyy, hh:MM TT')}` : null}
/>
);
-219
View File
@@ -1,219 +0,0 @@
import React, { useState, useEffect } from 'react';
import './DeviceManage.scss';
import dateFormat from 'dateformat';
import initMatrix from '../../../client/initMatrix';
import Text from '../../atoms/text/Text';
import Button from '../../atoms/button/Button';
import IconButton from '../../atoms/button/IconButton';
import { MenuHeader } from '../../atoms/context-menu/ContextMenu';
import Spinner from '../../atoms/spinner/Spinner';
import SettingTile from '../../molecules/setting-tile/SettingTile';
import PencilIC from '../../../../public/res/ic/outlined/pencil.svg';
import BinIC from '../../../../public/res/ic/outlined/bin.svg';
import { useStore } from '../../hooks/useStore';
function useDeviceList() {
const mx = initMatrix.matrixClient;
const [deviceList, setDeviceList] = useState(null);
useEffect(() => {
let isMounted = true;
const updateDevices = () => mx.getDevices().then((data) => {
if (!isMounted) return;
setDeviceList(data.devices || []);
});
updateDevices();
const handleDevicesUpdate = (users) => {
if (users.includes(mx.getUserId())) {
updateDevices();
}
};
mx.on('crypto.devicesUpdated', handleDevicesUpdate);
return () => {
mx.removeListener('crypto.devicesUpdated', handleDevicesUpdate);
isMounted = false;
};
}, []);
return deviceList;
}
function isCrossVerified(deviceId) {
try {
const mx = initMatrix.matrixClient;
const crossSignInfo = mx.getStoredCrossSigningForUser(mx.getUserId());
const deviceInfo = mx.getStoredDevice(mx.getUserId(), deviceId);
const deviceTrust = crossSignInfo.checkDeviceTrust(crossSignInfo, deviceInfo, false, true);
return deviceTrust.isCrossSigningVerified();
} catch {
return false;
}
}
function DeviceManage() {
const TRUNCATED_COUNT = 4;
const mx = initMatrix.matrixClient;
const deviceList = useDeviceList();
const [processing, setProcessing] = useState([]);
const [truncated, setTruncated] = useState(true);
const mountStore = useStore();
mountStore.setItem(true);
useEffect(() => {
setProcessing([]);
}, [deviceList]);
const addToProcessing = (device) => {
const old = [...processing];
old.push(device.device_id);
setProcessing(old);
};
const removeFromProcessing = () => {
setProcessing([]);
};
if (deviceList === null) {
return (
<div className="device-manage">
<div className="device-manage__loading">
<Spinner size="small" />
<Text>Loading devices...</Text>
</div>
</div>
);
}
const handleRename = async (device) => {
const newName = window.prompt('Edit session name', device.display_name);
if (newName === null || newName.trim() === '') return;
if (newName.trim() === device.display_name) return;
addToProcessing(device);
try {
await mx.setDeviceDetails(device.device_id, {
display_name: newName,
});
} catch {
if (!mountStore.getItem()) return;
removeFromProcessing(device);
}
};
const handleRemove = async (device, auth = undefined) => {
if (auth === undefined
? window.confirm(`You are about to logout "${device.display_name}" session.`)
: true
) {
addToProcessing(device);
try {
await mx.deleteDevice(device.device_id, auth);
} catch (e) {
if (e.httpStatus === 401 && e.data?.flows) {
const { flows } = e.data;
const flow = flows.find((f) => f.stages.includes('m.login.password'));
if (flow) {
const password = window.prompt('Please enter account password', '');
if (password && password.trim() !== '') {
handleRemove(device, {
session: e.data.session,
type: 'm.login.password',
password,
identifier: {
type: 'm.id.user',
user: mx.getUserId(),
},
});
return;
}
}
}
window.alert('Failed to remove session!');
if (!mountStore.getItem()) return;
removeFromProcessing(device);
}
}
};
const renderDevice = (device, isVerified) => {
const deviceId = device.device_id;
const displayName = device.display_name;
const lastIP = device.last_seen_ip;
const lastTS = device.last_seen_ts;
return (
<SettingTile
key={deviceId}
title={(
<Text style={{ color: isVerified ? '' : 'var(--tc-danger-high)' }}>
{displayName}
<Text variant="b3" span>{` — ${deviceId}${mx.deviceId === deviceId ? ' (current)' : ''}`}</Text>
</Text>
)}
options={
processing.includes(deviceId)
? <Spinner size="small" />
: (
<>
<IconButton size="small" onClick={() => handleRename(device)} src={PencilIC} tooltip="Rename" />
<IconButton size="small" onClick={() => handleRemove(device)} src={BinIC} tooltip="Remove session" />
</>
)
}
content={(
<Text variant="b3">
Last activity
<span style={{ color: 'var(--tc-surface-normal)' }}>
{dateFormat(new Date(lastTS), ' hh:MM TT, dd/mm/yyyy')}
</span>
{lastIP ? ` at ${lastIP}` : ''}
</Text>
)}
/>
);
};
const unverified = [];
const verified = [];
deviceList.sort((a, b) => b.last_seen_ts - a.last_seen_ts).forEach((device) => {
if (isCrossVerified(device.device_id)) verified.push(device);
else unverified.push(device);
});
return (
<div className="device-manage">
<div>
<MenuHeader>Unverified sessions</MenuHeader>
{
unverified.length > 0
? unverified.map((device) => renderDevice(device, false))
: <Text className="device-manage__info">No unverified session</Text>
}
</div>
<div>
<MenuHeader>Verified sessions</MenuHeader>
{
verified.length > 0
? verified.map((device, index) => {
if (truncated && index >= TRUNCATED_COUNT) return null;
return renderDevice(device, true);
})
: <Text className="device-manage__info">No verified session</Text>
}
{ verified.length > TRUNCATED_COUNT && (
<Button className="device-manage__info" onClick={() => setTruncated(!truncated)}>
{truncated ? `View ${verified.length - 4} more` : 'View less'}
</Button>
)}
{ deviceList.length > 0 && (
<Text className="device-manage__info" variant="b3">Session names are visible to everyone, so do not put any private info here.</Text>
)}
</div>
</div>
);
}
export default DeviceManage;
@@ -1,18 +0,0 @@
@use '../../partials/flex';
.device-manage {
&__loading {
@extend .cp-fx__row--c-c;
padding: var(--sp-extra-loose) var(--sp-normal);
.text {
margin: 0 var(--sp-normal);
}
}
&__info {
margin: var(--sp-normal);
}
& .setting-tile:last-of-type {
border-bottom: none;
}
}
+4 -6
View File
@@ -26,7 +26,6 @@ import ImportE2ERoomKeys from '../../molecules/import-export-e2e-room-keys/Impor
import ExportE2ERoomKeys from '../../molecules/import-export-e2e-room-keys/ExportE2ERoomKeys';
import ProfileEditor from '../profile-editor/ProfileEditor';
import DeviceManage from './DeviceManage';
import SunIC from '../../../../public/res/ic/outlined/sun.svg';
import LockIC from '../../../../public/res/ic/outlined/lock.svg';
@@ -168,16 +167,15 @@ function SecuritySection() {
return (
<div className="settings-security">
<div className="settings-security__card">
<MenuHeader>Session Info</MenuHeader>
<MenuHeader>Device Info</MenuHeader>
<SettingTile
title={`Session ID: ${initMatrix.matrixClient.getDeviceId()}`}
title={`Device ID: ${initMatrix.matrixClient.getDeviceId()}`}
/>
<SettingTile
title={`Session key: ${initMatrix.matrixClient.getDeviceEd25519Key().match(/.{1,4}/g).join(' ')}`}
content={<Text variant="b3">Use this session ID-key combo to verify or manage this session.</Text>}
title={`Device key: ${initMatrix.matrixClient.getDeviceEd25519Key().match(/.{1,4}/g).join(' ')}`}
content={<Text variant="b3">Use this device ID-key combo to verify or manage this session from Element client.</Text>}
/>
</div>
<DeviceManage />
<div className="settings-security__card">
<MenuHeader>Encryption</MenuHeader>
<SettingTile
-1
View File
@@ -38,7 +38,6 @@
.settings-appearance__card,
.settings-notifications,
.settings-security__card,
.settings-security .device-manage,
.settings-about__card {
@extend .settings-window__card;
}
@@ -90,7 +90,7 @@ function GeneralSettings({ roomId }) {
<MenuItem
variant="danger"
onClick={() => {
if (confirm('Are you sure that you want to leave this space?')) {
if (confirm('Are you really want to leave this space?')) {
leave(roomId);
}
}}
-9
View File
@@ -150,12 +150,3 @@ export function openReusableContextMenu(placement, cords, render, afterClose) {
afterClose,
});
}
export function openReusableDialog(title, render, afterClose) {
appDispatcher.dispatch({
type: cons.actions.navigation.OPEN_REUSABLE_DIALOG,
title,
render,
afterClose,
});
}
+1 -6
View File
@@ -83,12 +83,7 @@ class Notifications extends EventEmitter {
getNotiType(roomId) {
const mx = this.matrixClient;
let pushRule;
try {
pushRule = mx.getRoomPushRule('global', roomId);
} catch {
pushRule = undefined;
}
const pushRule = mx.getRoomPushRule('global', roomId);
if (pushRule === undefined) {
const overrideRules = mx.getAccountData('m.push_rules')?.getContent()?.global?.override;
+1 -1
View File
@@ -206,7 +206,7 @@ class RoomList extends EventEmitter {
this.matrixClient.getRooms().forEach((room) => {
const { roomId } = room;
const tombstone = room.currentState.events.get('m.room.tombstone');
if (tombstone?.get('') !== undefined) {
if (typeof tombstone !== 'undefined') {
const repRoomId = tombstone.get('').getContent().replacement_room;
const repRoomMembership = this.matrixClient.getRoom(repRoomId)?.getMyMembership();
if (repRoomMembership === 'join') return;
-2
View File
@@ -47,7 +47,6 @@ const cons = {
CLICK_REPLY_TO: 'CLICK_REPLY_TO',
OPEN_SEARCH: 'OPEN_SEARCH',
OPEN_REUSABLE_CONTEXT_MENU: 'OPEN_REUSABLE_CONTEXT_MENU',
OPEN_REUSABLE_DIALOG: 'OPEN_REUSABLE_DIALOG',
},
room: {
JOIN: 'JOIN',
@@ -93,7 +92,6 @@ const cons = {
REPLY_TO_CLICKED: 'REPLY_TO_CLICKED',
SEARCH_OPENED: 'SEARCH_OPENED',
REUSABLE_CONTEXT_MENU_OPENED: 'REUSABLE_CONTEXT_MENU_OPENED',
REUSABLE_DIALOG_OPENED: 'REUSABLE_DIALOG_OPENED',
},
roomList: {
ROOMLIST_UPDATED: 'ROOMLIST_UPDATED',
-8
View File
@@ -174,14 +174,6 @@ class Navigation extends EventEmitter {
action.afterClose,
);
},
[cons.actions.navigation.OPEN_REUSABLE_DIALOG]: () => {
this.emit(
cons.events.navigation.REUSABLE_DIALOG_OPENED,
action.title,
action.render,
action.afterClose,
);
},
};
actions[action.type]?.();
}
+2 -2
View File
@@ -13,8 +13,8 @@ export function roomIdByAtoZ(aId, bId) {
// remove "#" from the room name
// To ignore it in sorting
aName = aName.replace(/#/g, '');
bName = bName.replace(/#/g, '');
aName = aName.replaceAll('#', '');
bName = bName.replaceAll('#', '');
if (aName.toLowerCase() < bName.toLowerCase()) {
return -1;