From 77f976392f9567fa3016bdd6487500f91641909a Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Sat, 27 Aug 2022 20:48:53 +0530 Subject: [PATCH] Fix commands and added more --- .../profile-viewer/ProfileViewer.jsx | 17 +- src/app/organisms/room/RoomViewCmdBar.jsx | 54 +----- src/app/organisms/room/RoomViewInput.jsx | 21 ++- src/app/organisms/room/commands.js | 174 ++++++++++++++++++ src/client/action/room.js | 61 +++++- src/client/state/RoomList.js | 15 +- 6 files changed, 282 insertions(+), 60 deletions(-) create mode 100644 src/app/organisms/room/commands.js diff --git a/src/app/organisms/profile-viewer/ProfileViewer.jsx b/src/app/organisms/profile-viewer/ProfileViewer.jsx index 26e39682..fec9189a 100644 --- a/src/app/organisms/profile-viewer/ProfileViewer.jsx +++ b/src/app/organisms/profile-viewer/ProfileViewer.jsx @@ -11,7 +11,7 @@ import { selectRoom, openReusableContextMenu } from '../../../client/action/navi import * as roomActions from '../../../client/action/room'; import { - getUsername, getUsernameOfRoomMember, getPowerLabel, hasDMWith, hasDevices + getUsername, getUsernameOfRoomMember, getPowerLabel, hasDMWith, hasDevices, } from '../../../util/matrixUtil'; import { getEventCords } from '../../../util/common'; import colorMXID from '../../../util/colorMXID'; @@ -209,19 +209,18 @@ function ProfileFooter({ roomId, userId, onRequestClose }) { }; const toggleIgnore = async () => { - const ignoredUsers = mx.getIgnoredUsers(); - const uIndex = ignoredUsers.indexOf(userId); - if (uIndex >= 0) { - if (uIndex === -1) return; - ignoredUsers.splice(uIndex, 1); - } else ignoredUsers.push(userId); + const isIgnored = mx.getIgnoredUsers().includes(userId); try { setIsIgnoring(true); - await mx.setIgnoredUsers(ignoredUsers); + if (isIgnored) { + await roomActions.unignore([userId]); + } else { + await roomActions.ignore([userId]); + } if (isMountedRef.current === false) return; - setIsUserIgnored(uIndex < 0); + setIsUserIgnored(!isIgnored); setIsIgnoring(false); } catch { setIsIgnoring(false); diff --git a/src/app/organisms/room/RoomViewCmdBar.jsx b/src/app/organisms/room/RoomViewCmdBar.jsx index 68919aac..8c390a06 100644 --- a/src/app/organisms/room/RoomViewCmdBar.jsx +++ b/src/app/organisms/room/RoomViewCmdBar.jsx @@ -8,13 +8,6 @@ import twemoji from 'twemoji'; import { twemojify } from '../../../util/twemojify'; import initMatrix from '../../../client/initMatrix'; -import { toggleMarkdown } from '../../../client/action/settings'; -import * as roomActions from '../../../client/action/room'; -import { - openCreateRoom, - openPublicRooms, - openInviteUser, -} from '../../../client/action/navigation'; import { getEmojiForCompletion } from '../emoji-board/custom-emoji'; import AsyncSearch from '../../../util/AsyncSearch'; @@ -22,37 +15,7 @@ import Text from '../../atoms/text/Text'; import ScrollView from '../../atoms/scroll/ScrollView'; import FollowingMembers from '../../molecules/following-members/FollowingMembers'; import { addRecentEmoji, getRecentEmojis } from '../emoji-board/recent'; - -const commands = [{ - name: 'markdown', - description: 'Toggle markdown for messages.', - exe: () => toggleMarkdown(), -}, { - name: 'startDM', - isOptions: true, - description: 'Start direct message with user. Example: /startDM/@johndoe.matrix.org', - exe: (roomId, searchTerm) => openInviteUser(undefined, searchTerm), -}, { - name: 'createRoom', - description: 'Create new room', - exe: () => openCreateRoom(), -}, { - name: 'join', - isOptions: true, - description: 'Join room with alias. Example: /join/#cinny:matrix.org', - exe: (roomId, searchTerm) => openPublicRooms(searchTerm), -}, { - name: 'leave', - description: 'Leave current room', - exe: (roomId) => { - roomActions.leave(roomId); - }, -}, { - name: 'invite', - isOptions: true, - description: 'Invite user to room. Example: /invite/@johndoe:matrix.org', - exe: (roomId, searchTerm) => openInviteUser(roomId, searchTerm), -}]; +import commands from './commands'; function CmdItem({ onClick, children }) { return ( @@ -71,16 +34,16 @@ function renderSuggestions({ prefix, option, suggestions }, fireCmd) { const cmdOptString = (typeof option === 'string') ? `/${option}` : '/?'; return cmds.map((cmd) => ( { fireCmd({ prefix: cmdPrefix, option, - result: cmd, + result: commands[cmd], }); }} > - {`${cmd.name}${cmd.isOptions ? cmdOptString : ''}`} + {`${cmd}${cmd.isOptions ? cmdOptString : ''}`} )); } @@ -209,8 +172,8 @@ function RoomViewCmdBar({ roomId, roomTimeline, viewEvent }) { const mx = initMatrix.matrixClient; const setupSearch = { '/': () => { - asyncSearch.setup(commands, { keys: ['name'], isContain: true }); - setCmd({ prefix, suggestions: commands }); + asyncSearch.setup(Object.keys(commands), { isContain: true }); + setCmd({ prefix, suggestions: Object.keys(commands) }); }, ':': () => { const parentIds = initMatrix.roomList.getAllParentSpaces(roomId); @@ -242,8 +205,9 @@ function RoomViewCmdBar({ roomId, roomTimeline, viewEvent }) { } function fireCmd(myCmd) { if (myCmd.prefix === '/') { - myCmd.result.exe(roomId, myCmd.option); - viewEvent.emit('cmd_fired'); + viewEvent.emit('cmd_fired', { + replace: `/${myCmd.result.name}`, + }); } if (myCmd.prefix === ':') { if (!myCmd.result.mxc) addRecentEmoji(myCmd.result.unicode); diff --git a/src/app/organisms/room/RoomViewInput.jsx b/src/app/organisms/room/RoomViewInput.jsx index 930eae10..38e20ad6 100644 --- a/src/app/organisms/room/RoomViewInput.jsx +++ b/src/app/organisms/room/RoomViewInput.jsx @@ -33,6 +33,8 @@ import MarkdownIC from '../../../../public/res/ic/outlined/markdown.svg'; import FileIC from '../../../../public/res/ic/outlined/file.svg'; import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; +import commands from './commands'; + const CMD_REGEX = /(^\/|:|@)(\S*)$/; let isTyping = false; let isCmdActivated = false; @@ -182,9 +184,27 @@ function RoomViewInput({ }; }, [roomId]); + const processCommand = (cmdBody) => { + const spaceIndex = cmdBody.indexOf(' '); + const cmdName = cmdBody.slice(1, spaceIndex > -1 ? spaceIndex : undefined); + const cmdData = spaceIndex > -1 ? cmdBody.slice(spaceIndex + 1) : ''; + if (!commands[cmdName]) { + console.log('Invalid command'); + return; + } + commands[cmdName].exe(roomId, cmdData); + }; + const sendMessage = async () => { requestAnimationFrame(() => deactivateCmdAndEmit()); const msgBody = textAreaRef.current.value; + if (msgBody.startsWith('/')) { + processCommand(msgBody.trim()); + + textAreaRef.current.value = ''; + textAreaRef.current.style.height = 'unset'; + return; + } if (roomsInput.isSending(roomId)) return; if (msgBody.trim() === '' && attachment === null) return; sendIsTyping(false); @@ -201,7 +221,6 @@ function RoomViewInput({ focusInput(); textAreaRef.current.value = roomsInput.getMessage(roomId); - viewEvent.emit('message_sent'); textAreaRef.current.style.height = 'unset'; if (replyTo !== null) setReplyTo(null); }; diff --git a/src/app/organisms/room/commands.js b/src/app/organisms/room/commands.js new file mode 100644 index 00000000..1c2e614b --- /dev/null +++ b/src/app/organisms/room/commands.js @@ -0,0 +1,174 @@ +import initMatrix from '../../../client/initMatrix'; +import { toggleMarkdown } from '../../../client/action/settings'; +import * as roomActions from '../../../client/action/room'; +import { hasDMWith, hasDevices } from '../../../util/matrixUtil'; +import { selectRoom } from '../../../client/action/navigation'; + +const MXID_REG = /^@\S+:\S+$/; +const ROOM_ID_ALIAS_REG = /^(#|!)\S+:\S+$/; +const ROOM_ID_REG = /^!\S+:\S+$/; +const MXC_REG = /^mxc:\/\/\S+$/; + +const commands = { + me: { + name: 'me', + description: 'Display action', + exe: (roomId, data) => { + const body = data.trim(); + if (body === '') return undefined; + return body; + }, + }, + shrug: { + name: 'shrug', + description: 'Send ¯\\_(ツ)_/¯ as message', + exe: (roomId, data) => ( + `¯\\_(ツ)_/¯${data.trim() !== '' ? ` ${data}` : ''}` + ), + }, + markdown: { + name: 'markdown', + description: 'Toggle markdown for messages', + exe: () => toggleMarkdown(), + }, + startdm: { + name: 'startdm', + description: 'Start DM with user id. Example: /startdm @johndoe.matrix.org', + exe: async (roomId, data) => { + const mx = initMatrix.matrixClient; + const rawIds = data.split(' '); + const userIds = rawIds.filter((id) => id.match(MXID_REG) && id !== mx.getUserId()); + if (userIds.length === 0) return; + if (userIds.length === 1) { + const dmRoomId = hasDMWith(userIds[0]); + if (dmRoomId) { + selectRoom(dmRoomId); + return; + } + } + const devices = await Promise.all(userIds.map(hasDevices)); + const isEncrypt = devices.every((hasDevice) => hasDevice); + const result = await roomActions.createDM(userIds, isEncrypt); + selectRoom(result.room_id); + }, + }, + join: { + name: 'join', + description: 'Join room with alias. Example: /join #cinny:matrix.org', + exe: (roomId, data) => { + const rawIds = data.split(' '); + const roomIds = rawIds.filter((id) => id.match(ROOM_ID_ALIAS_REG)); + roomIds.map((id) => roomActions.join(id)); + }, + }, + leave: { + name: 'leave', + description: 'Leave room', + exe: (roomId, data) => { + if (data.trim() === '') { + roomActions.leave(roomId); + return; + } + const rawIds = data.split(' '); + const roomIds = rawIds.filter((id) => id.match(ROOM_ID_REG)); + roomIds.map((id) => roomActions.leave(id)); + }, + }, + invite: { + name: 'invite', + description: 'Invite user to room. Example: /invite @johndoe:matrix.org', + exe: (roomId, data) => { + const rawIds = data.split(' '); + const userIds = rawIds.filter((id) => id.match(MXID_REG)); + userIds.map((id) => roomActions.invite(roomId, id)); + }, + }, + disinvite: { + name: 'disinvite', + description: 'Disinvite user to room. Example: /disinvite @johndoe:matrix.org', + exe: (roomId, data) => { + const rawIds = data.split(' '); + const userIds = rawIds.filter((id) => id.match(MXID_REG)); + userIds.map((id) => roomActions.kick(roomId, id)); + }, + }, + kick: { + name: 'kick', + description: 'Kick user from room. Example: /kick @johndoe:matrix.org', + exe: (roomId, data) => { + const rawIds = data.split(' '); + const userIds = rawIds.filter((id) => id.match(MXID_REG)); + userIds.map((id) => roomActions.kick(roomId, id)); + }, + }, + ban: { + name: 'ban', + description: 'Ban user from room. Example: /ban @johndoe:matrix.org', + exe: (roomId, data) => { + const rawIds = data.split(' '); + const userIds = rawIds.filter((id) => id.match(MXID_REG)); + userIds.map((id) => roomActions.ban(roomId, id)); + }, + }, + unban: { + name: 'unban', + description: 'Unban user from room. Example: /unban @johndoe:matrix.org', + exe: (roomId, data) => { + const rawIds = data.split(' '); + const userIds = rawIds.filter((id) => id.match(MXID_REG)); + userIds.map((id) => roomActions.unban(roomId, id)); + }, + }, + ignore: { + name: 'ignore', + description: 'Ignore user. Example: /ignore @johndoe:matrix.org', + exe: (roomId, data) => { + const rawIds = data.split(' '); + const userIds = rawIds.filter((id) => id.match(MXID_REG)); + if (userIds.length > 0) roomActions.ignore(userIds); + }, + }, + unignore: { + name: 'unignore', + description: 'Unignore user. Example: /unignore @johndoe:matrix.org', + exe: (roomId, data) => { + const rawIds = data.split(' '); + const userIds = rawIds.filter((id) => id.match(MXID_REG)); + if (userIds.length > 0) roomActions.unignore(userIds); + }, + }, + myroomnick: { + name: 'myroomnick', + description: 'Change my room nick', + exe: (roomId, data) => { + const nick = data.trim(); + if (nick === '') return; + roomActions.setMyRoomNick(roomId, nick); + }, + }, + myroomavatar: { + name: 'myroomavatar', + description: 'Change my room avatar. Example /myroomavatar mxc://xyzabc', + exe: (roomId, data) => { + if (data.match(MXC_REG)) { + roomActions.setMyRoomAvatar(roomId, data); + } + }, + }, + converttodm: { + name: 'converttodm', + description: 'Convert room to direct message', + exe: (roomId) => { + roomActions.convertToDm(roomId); + }, + }, + converttoroom: { + name: 'converttoroom', + description: 'Convert direct message to room', + exe: (roomId) => { + roomActions.convertToRoom(roomId); + }, + }, +}; + +export default commands; diff --git a/src/client/action/room.js b/src/client/action/room.js index 1fe721de..c047d698 100644 --- a/src/client/action/room.js +++ b/src/client/action/room.js @@ -6,7 +6,7 @@ import { getIdServer } from '../../util/matrixUtil'; /** * https://github.com/matrix-org/matrix-react-sdk/blob/1e6c6e9d800890c732d60429449bc280de01a647/src/Rooms.js#L73 * @param {string} roomId Id of room to add - * @param {string} userId User id to which dm + * @param {string} userId User id to which dm || undefined to remove * @returns {Promise} A promise */ function addRoomToMDirect(roomId, userId) { @@ -79,13 +79,23 @@ function guessDMRoomTargetId(room, myUserId) { return oldestMember.userId; } +function convertToDm(roomId) { + const mx = initMatrix.matrixClient; + const room = mx.getRoom(roomId); + return addRoomToMDirect(roomId, guessDMRoomTargetId(room, mx.getUserId())); +} + +function convertToRoom(roomId) { + return addRoomToMDirect(roomId, undefined); +} + /** * * @param {string} roomId * @param {boolean} isDM * @param {string[]} via */ -async function join(roomIdOrAlias, isDM, via) { +async function join(roomIdOrAlias, isDM = false, via = undefined) { const mx = initMatrix.matrixClient; const roomIdParts = roomIdOrAlias.split(':'); const viaServers = via || [roomIdParts[1]]; @@ -150,10 +160,10 @@ async function create(options, isDM = false) { } } -async function createDM(userId, isEncrypted = true) { +async function createDM(userIdOrIds, isEncrypted = true) { const options = { is_direct: true, - invite: [userId], + invite: Array.isArray(userIdOrIds) ? userIdOrIds : [userIdOrIds], visibility: 'private', preset: 'trusted_private_chat', initial_state: [], @@ -290,6 +300,21 @@ async function unban(roomId, userId) { return result; } +async function ignore(userIds) { + const mx = initMatrix.matrixClient; + + let ignoredUsers = mx.getIgnoredUsers().concat(userIds); + ignoredUsers = [...new Set(ignoredUsers)]; + await mx.setIgnoredUsers(ignoredUsers); +} + +async function unignore(userIds) { + const mx = initMatrix.matrixClient; + + const ignoredUsers = mx.getIgnoredUsers(); + await mx.setIgnoredUsers(ignoredUsers.filter((id) => !userIds.includes(id))); +} + async function setPowerLevel(roomId, userId, powerLevel) { const mx = initMatrix.matrixClient; const room = mx.getRoom(roomId); @@ -300,9 +325,37 @@ async function setPowerLevel(roomId, userId, powerLevel) { return result; } +async function setMyRoomNick(roomId, nick) { + const mx = initMatrix.matrixClient; + const room = mx.getRoom(roomId); + const mEvent = room.currentState.getStateEvents('m.room.member', mx.getUserId()); + const content = mEvent?.getContent(); + if (!content) return; + await mx.sendStateEvent(roomId, 'm.room.member', { + ...content, + displayname: nick, + }, mx.getUserId()); +} + +async function setMyRoomAvatar(roomId, mxc) { + const mx = initMatrix.matrixClient; + const room = mx.getRoom(roomId); + const mEvent = room.currentState.getStateEvents('m.room.member', mx.getUserId()); + const content = mEvent?.getContent(); + if (!content) return; + await mx.sendStateEvent(roomId, 'm.room.member', { + ...content, + avatar_url: mxc, + }, mx.getUserId()); +} + export { + convertToDm, + convertToRoom, join, leave, createDM, createRoom, invite, kick, ban, unban, + ignore, unignore, setPowerLevel, + setMyRoomNick, setMyRoomAvatar, }; diff --git a/src/client/state/RoomList.js b/src/client/state/RoomList.js index b24379b0..a1570480 100644 --- a/src/client/state/RoomList.js +++ b/src/client/state/RoomList.js @@ -257,10 +257,10 @@ class RoomList extends EventEmitter { const latestMDirects = this.getMDirects(); latestMDirects.forEach((directId) => { - const myRoom = this.matrixClient.getRoom(directId); if (this.mDirects.has(directId)) return; this.mDirects.add(directId); + const myRoom = this.matrixClient.getRoom(directId); if (myRoom === null) return; if (myRoom.getMyMembership() === 'join') { this.directs.add(directId); @@ -268,6 +268,19 @@ class RoomList extends EventEmitter { this.emit(cons.events.roomList.ROOMLIST_UPDATED); } }); + + [...this.directs].forEach((directId) => { + if (latestMDirects.has(directId)) return; + this.mDirects.delete(directId); + + const myRoom = this.matrixClient.getRoom(directId); + if (myRoom === null) return; + if (myRoom.getMyMembership() === 'join') { + this.directs.delete(directId); + this.rooms.add(directId); + this.emit(cons.events.roomList.ROOMLIST_UPDATED); + } + }); }); this.matrixClient.on('Room.name', (room) => {