import twemoji from 'twemoji';
import initMatrix from '../../../client/initMatrix';
-import cons from '../../../client/state/cons';
import { toggleMarkdown } from '../../../client/action/settings';
import * as roomActions from '../../../client/action/room';
import {
- selectTab,
- selectRoom,
openCreateRoom,
openPublicRooms,
openInviteUser,
- openReadReceipts,
} from '../../../client/action/navigation';
import { emojis } from '../emoji-board/emoji';
import AsyncSearch from '../../../util/AsyncSearch';
import Text from '../../atoms/text/Text';
-import Button from '../../atoms/button/Button';
-import IconButton from '../../atoms/button/IconButton';
-import ContextMenu, { MenuHeader } from '../../atoms/context-menu/ContextMenu';
import ScrollView from '../../atoms/scroll/ScrollView';
-import SettingTile from '../../molecules/setting-tile/SettingTile';
-import TimelineChange from '../../molecules/message/TimelineChange';
-
-import CmdIC from '../../../../public/res/ic/outlined/cmd.svg';
-
-import { getUsersActionJsx } from './common';
+import FollowingMembers from '../../molecules/following-members/FollowingMembers';
const commands = [{
name: 'markdown',
exe: (roomId, searchTerm) => openInviteUser(roomId, searchTerm),
}];
-function CmdHelp() {
- return (
- <ContextMenu
- placement="top"
- content={(
- <>
- <MenuHeader>General command</MenuHeader>
- <Text variant="b2">/command_name</Text>
- <MenuHeader>Go-to commands</MenuHeader>
- <Text variant="b2">{'>*space_name'}</Text>
- <Text variant="b2">{'>#room_name'}</Text>
- <Text variant="b2">{'>@people_name'}</Text>
- <MenuHeader>Autofill commands</MenuHeader>
- <Text variant="b2">:emoji_name</Text>
- <Text variant="b2">@name</Text>
- </>
- )}
- render={(toggleMenu) => (
- <IconButton
- src={CmdIC}
- size="extra-small"
- onClick={toggleMenu}
- tooltip="Commands"
- />
- )}
- />
- );
-}
-
-function ViewCmd() {
- function renderAllCmds() {
- return commands.map((command) => (
- <SettingTile
- key={command.name}
- title={command.name}
- content={(<Text variant="b3">{command.description}</Text>)}
- />
- ));
- }
- return (
- <ContextMenu
- maxWidth={250}
- placement="top"
- content={(
- <>
- <MenuHeader>General commands</MenuHeader>
- {renderAllCmds()}
- </>
- )}
- render={(toggleMenu) => (
- <span>
- <Button onClick={toggleMenu}><span className="text text-b3">View all</span></Button>
- </span>
- )}
- />
- );
-}
-
-function FollowingMembers({ roomId, roomTimeline, viewEvent }) {
- const [followingMembers, setFollowingMembers] = useState([]);
- const mx = initMatrix.matrixClient;
- const myUserId = mx.getUserId();
-
- const handleOnMessageSent = () => setFollowingMembers([]);
-
- useEffect(() => {
- const updateFollowingMembers = () => {
- setFollowingMembers(roomTimeline.getLiveReaders());
- };
- updateFollowingMembers();
- roomTimeline.on(cons.events.roomTimeline.LIVE_RECEIPT, updateFollowingMembers);
- viewEvent.on('message_sent', handleOnMessageSent);
- return () => {
- roomTimeline.removeListener(cons.events.roomTimeline.LIVE_RECEIPT, updateFollowingMembers);
- viewEvent.removeListener('message_sent', handleOnMessageSent);
- };
- }, [roomTimeline]);
-
- const filteredM = followingMembers.filter((userId) => userId !== myUserId);
- return filteredM.length !== 0 && (
- <TimelineChange
- variant="follow"
- content={getUsersActionJsx(roomId, filteredM, 'following the conversation.')}
- time=""
- onClick={() => openReadReceipts(roomId, followingMembers)}
- />
- );
-}
-
-FollowingMembers.propTypes = {
- roomId: PropTypes.string.isRequired,
- roomTimeline: PropTypes.shape({}).isRequired,
- viewEvent: PropTypes.shape({}).isRequired,
-};
-
-function getCmdActivationMessage(prefix) {
- function genMessage(prime, secondary) {
- return (
- <>
- <span>{prime}</span>
- <span>{secondary}</span>
- </>
- );
- }
- const cmd = {
- '/': () => genMessage('General command mode activated. ', 'Type command name for suggestions.'),
- '>*': () => genMessage('Go-to command mode activated. ', 'Type space name for suggestions.'),
- '>#': () => genMessage('Go-to command mode activated. ', 'Type room name for suggestions.'),
- '>@': () => genMessage('Go-to command mode activated. ', 'Type people name for suggestions.'),
- ':': () => genMessage('Emoji autofill command mode activated. ', 'Type emoji shortcut for suggestions.'),
- '@': () => genMessage('Name autofill command mode activated. ', 'Type name for suggestions.'),
- };
- return cmd[prefix]?.();
-}
-
function CmdItem({ onClick, children }) {
return (
<button className="cmd-item" onClick={onClick} type="button">
children: PropTypes.node.isRequired,
};
-function getCmdSuggestions({ prefix, option, suggestions }, fireCmd) {
- function getGenCmdSuggestions(cmdPrefix, cmds) {
+function renderSuggestions({ prefix, option, suggestions }, fireCmd) {
+ function renderCmdSuggestions(cmdPrefix, cmds) {
const cmdOptString = (typeof option === 'string') ? `/${option}` : '/?';
return cmds.map((cmd) => (
<CmdItem
));
}
- function getRoomsSuggestion(cmdPrefix, rooms) {
- return rooms.map((room) => (
- <CmdItem
- key={room.roomId}
- onClick={() => {
- fireCmd({
- prefix: cmdPrefix,
- result: room,
- });
- }}
- >
- <Text variant="b2">{room.name}</Text>
- </CmdItem>
- ));
- }
-
- function getEmojiSuggestion(emPrefix, emos) {
+ function renderEmojiSuggestion(emPrefix, emos) {
return emos.map((emoji) => (
<CmdItem
key={emoji.hexcode}
));
}
- function getNameSuggestion(namePrefix, members) {
+ function renderNameSuggestion(namePrefix, members) {
return members.map((member) => (
<CmdItem
key={member.userId}
}
const cmd = {
- '/': (cmds) => getGenCmdSuggestions(prefix, cmds),
- '>*': (spaces) => getRoomsSuggestion(prefix, spaces),
- '>#': (rooms) => getRoomsSuggestion(prefix, rooms),
- '>@': (peoples) => getRoomsSuggestion(prefix, peoples),
- ':': (emos) => getEmojiSuggestion(prefix, emos),
- '@': (members) => getNameSuggestion(prefix, members),
+ '/': (cmds) => renderCmdSuggestions(prefix, cmds),
+ ':': (emos) => renderEmojiSuggestion(prefix, emos),
+ '@': (members) => renderNameSuggestion(prefix, members),
};
return cmd[prefix]?.(suggestions);
}
asyncSearch.search(searchTerm);
}
function activateCmd(prefix) {
- setCmd({ prefix });
cmdPrefix = prefix;
+ cmdPrefix = undefined;
- const { roomList, matrixClient } = initMatrix;
- function getRooms(roomIds) {
- return roomIds.map((rId) => {
- const room = matrixClient.getRoom(rId);
- return {
- name: room.name,
- roomId: room.roomId,
- };
- });
- }
+ const mx = initMatrix.matrixClient;
const setupSearch = {
- '/': () => asyncSearch.setup(commands, { keys: ['name'], isContain: true }),
- '>*': () => asyncSearch.setup(getRooms([...roomList.spaces]), { keys: ['name'], limit: 20 }),
- '>#': () => asyncSearch.setup(getRooms([...roomList.rooms]), { keys: ['name'], limit: 20 }),
- '>@': () => asyncSearch.setup(getRooms([...roomList.directs]), { keys: ['name'], limit: 20 }),
- ':': () => asyncSearch.setup(emojis, { keys: ['shortcode'], isContain: true, limit: 20 }),
- '@': () => asyncSearch.setup(matrixClient.getRoom(roomId).getJoinedMembers().map((member) => ({
- name: member.name,
- userId: member.userId.slice(1),
- })), { keys: ['name', 'userId'], limit: 20 }),
+ '/': () => {
+ asyncSearch.setup(commands, { keys: ['name'], isContain: true });
+ setCmd({ prefix, suggestions: commands });
+ },
+ ':': () => {
+ asyncSearch.setup(emojis, { keys: ['shortcode'], isContain: true, limit: 20 });
+ setCmd({ prefix, suggestions: emojis.slice(26, 46) });
+ },
+ '@': () => {
+ const members = mx.getRoom(roomId).getJoinedMembers().map((member) => ({
+ name: member.name,
+ userId: member.userId.slice(1),
+ }));
+ asyncSearch.setup(members, { keys: ['name', 'userId'], limit: 20 });
+ const endIndex = members.length > 20 ? 20 : members.length;
+ setCmd({ prefix, suggestions: members.slice(0, endIndex) });
+ },
};
setupSearch[prefix]?.();
}
cmdPrefix = undefined;
}
function fireCmd(myCmd) {
- if (myCmd.prefix.match(/^>[*#@]$/)) {
- if (cmd.prefix === '>*') selectTab(myCmd.result.roomId);
- else selectRoom(myCmd.result.roomId);
- viewEvent.emit('cmd_fired');
- }
if (myCmd.prefix === '/') {
myCmd.result.exe(roomId, myCmd.option);
viewEvent.emit('cmd_fired');
};
}, [cmd]);
- if (typeof cmd?.error === 'string') {
+ const isError = typeof cmd?.error === 'string';
+ if (cmd === null || isError) {
return (
<div className="cmd-bar">
- <div className="cmd-bar__info">
- <div className="cmd-bar__info-indicator--error" />
- </div>
- <div className="cmd-bar__content">
- <Text className="cmd-bar__content-error" variant="b2">{cmd.error}</Text>
- </div>
+ <FollowingMembers roomTimeline={roomTimeline} />
</div>
);
}
return (
<div className="cmd-bar">
<div className="cmd-bar__info">
- {cmd === null && <CmdHelp />}
- {cmd !== null && typeof cmd.suggestions === 'undefined' && <div className="cmd-bar__info-indicator" /> }
- {cmd !== null && typeof cmd.suggestions !== 'undefined' && <Text variant="b3">TAB</Text>}
+ <Text variant="b3">TAB</Text>
</div>
<div className="cmd-bar__content">
- {cmd === null && (
- <FollowingMembers
- roomId={roomId}
- roomTimeline={roomTimeline}
- viewEvent={viewEvent}
- />
- )}
- {cmd !== null && typeof cmd.suggestions === 'undefined' && <Text className="cmd-bar__content-help" variant="b2">{getCmdActivationMessage(cmd.prefix)}</Text>}
- {cmd !== null && typeof cmd.suggestions !== 'undefined' && (
- <ScrollView horizontal vertical={false} invisible>
- <div className="cmd-bar__content__suggestions">{getCmdSuggestions(cmd, fireCmd)}</div>
- </ScrollView>
- )}
- </div>
- <div className="cmd-bar__more">
- {cmd !== null && cmd.prefix === '/' && <ViewCmd />}
+ <ScrollView horizontal vertical={false} invisible>
+ <div className="cmd-bar__content-suggestions">
+ { renderSuggestions(cmd, fireCmd) }
+ </div>
+ </ScrollView>
</div>
</div>
);
import FileIC from '../../../../public/res/ic/outlined/file.svg';
import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
-const CMD_REGEX = /(^\/|^>[#*@]|:|@)(\S*)$/;
+const CMD_REGEX = /(^\/|:|@)(\S*)$/;
let isTyping = false;
let isCmdActivated = false;
let cmdCursorPos = null;
const inputBaseRef = useRef(null);
const uploadInputRef = useRef(null);
const uploadProgressRef = useRef(null);
- const rightOptionsRef = useRef(null);
- const escBtnRef = useRef(null);
const TYPING_TIMEOUT = 5000;
const mx = initMatrix.matrixClient;
uploadInputRef.current.value = null;
}
- function rightOptionsA11Y(A11Y) {
- const rightOptions = rightOptionsRef.current.children;
- for (let index = 0; index < rightOptions.length; index += 1) {
- rightOptions[index].disabled = !A11Y;
- }
- }
-
function activateCmd(prefix) {
isCmdActivated = true;
- requestAnimationFrame(() => {
- inputBaseRef.current.style.boxShadow = '0 0 0 1px var(--bg-positive)';
- escBtnRef.current.style.display = 'block';
- });
- rightOptionsA11Y(false);
viewEvent.emit('cmd_activate', prefix);
}
function deactivateCmd() {
- if (inputBaseRef.current !== null) {
- requestAnimationFrame(() => {
- inputBaseRef.current.style.boxShadow = 'var(--bs-surface-border)';
- escBtnRef.current.style.display = 'none';
- });
- rightOptionsA11Y(true);
- }
isCmdActivated = false;
cmdCursorPos = null;
}
deactivateCmd();
viewEvent.emit('cmd_deactivate');
}
- function errorCmd() {
- requestAnimationFrame(() => {
- inputBaseRef.current.style.boxShadow = '0 0 0 1px var(--bg-danger)';
- });
- }
function setCursorPosition(pos) {
setTimeout(() => {
textAreaRef.current.focus();
roomsInput.on(cons.events.roomsInput.UPLOAD_PROGRESS_CHANGES, uploadingProgress);
roomsInput.on(cons.events.roomsInput.ATTACHMENT_CANCELED, clearAttachment);
roomsInput.on(cons.events.roomsInput.FILE_UPLOADED, clearAttachment);
- viewEvent.on('cmd_error', errorCmd);
viewEvent.on('cmd_fired', firedCmd);
navigation.on(cons.events.navigation.REPLY_TO_CLICKED, setUpReply);
if (textAreaRef?.current !== null) {
roomsInput.removeListener(cons.events.roomsInput.UPLOAD_PROGRESS_CHANGES, uploadingProgress);
roomsInput.removeListener(cons.events.roomsInput.ATTACHMENT_CANCELED, clearAttachment);
roomsInput.removeListener(cons.events.roomsInput.FILE_UPLOADED, clearAttachment);
- viewEvent.removeListener('cmd_error', errorCmd);
viewEvent.removeListener('cmd_fired', firedCmd);
navigation.removeListener(cons.events.navigation.REPLY_TO_CLICKED, setUpReply);
if (isCmdActivated) deactivateCmd();
};
}, [roomId]);
- async function sendMessage() {
+ const sendMessage = async () => {
const msgBody = textAreaRef.current.value;
if (roomsInput.isSending(roomId)) return;
if (msgBody.trim() === '' && attachment === null) return;
viewEvent.emit('message_sent');
textAreaRef.current.style.height = 'unset';
if (replyTo !== null) setReplyTo(null);
- }
+ };
function processTyping(msg) {
const isEmptyMsg = msg === '';
return;
}
if (!isCmdActivated) activateCmd(cmdPrefix);
- requestAnimationFrame(() => {
- inputBaseRef.current.style.boxShadow = '0 0 0 1px var(--bg-caution)';
- });
viewEvent.emit('cmd_process', cmdPrefix, cmdSlug);
}
- function handleMsgTyping(e) {
+ const handleMsgTyping = (e) => {
const msg = e.target.value;
recognizeCmd(e.target.value);
if (!isCmdActivated) processTyping(msg);
- }
+ };
- function handleKeyDown(e) {
+ const handleKeyDown = (e) => {
if (e.keyCode === 13 && e.shiftKey === false) {
e.preventDefault();
viewEvent.emit('cmd_exe');
} else sendMessage();
}
- if (e.keyCode === 27 && isCmdActivated) {
- deactivateCmdAndEmit();
- e.preventDefault();
- }
- }
+ };
- function handlePaste(e) {
+ const handlePaste = (e) => {
if (e.clipboardData === false) {
return;
}
}
}
}
- }
+ };
function addEmoji(emoji) {
textAreaRef.current.value += emoji.unicode;
textAreaRef.current.focus();
}
- function handleUploadClick() {
+ const handleUploadClick = () => {
if (attachment === null) uploadInputRef.current.click();
else {
roomsInput.cancelAttachment(roomId);
}
- }
+ };
function uploadFileChange(e) {
const file = e.target.files.item(0);
setAttachment(file);
</Text>
</ScrollView>
{isMarkdown && <RawIcon size="extra-small" src={MarkdownIC} />}
- <button ref={escBtnRef} tabIndex="-1" onClick={deactivateCmdAndEmit} className="btn-cmd-esc" type="button"><Text variant="b3">ESC</Text></button>
</div>
- <div ref={rightOptionsRef} className="room-input__option-container">
+ <div className="room-input__option-container">
<IconButton
onClick={(e) => {
const cords = getEventCords(e);