Editor Commands (#1450)

* add commands hook

* add commands in editor

* add command auto complete menu

* add commands in room input

* remove old reply code from room input

* fix video component css

* do not auto focus input on android or ios

* fix crash on enable block after selection

* fix circular deps in editor

* fix autocomplete return focus move editor cursor

* remove unwanted keydown from room input

* fix emoji alignment in editor

* test ipad user agent

* refactor isAndroidOrIOS to mobileOrTablet

* update slate & slate-react

* downgrade slate-react to 0.98.4
0.99.0 has breaking changes with ReactEditor.focus

* add sql to readable ext mimetype

* fix empty editor formatting gets saved as draft

* add option to use enter for newline

* remove empty msg draft from atom family

* prevent msg ctx menu from open on text selection
This commit is contained in:
Ajay Bura 2023-10-18 13:15:30 +11:00 committed by GitHub
parent 4d0b6b93bc
commit 613e6d6503
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 620 additions and 131 deletions

View file

@ -0,0 +1,109 @@
import React, { KeyboardEvent as ReactKeyboardEvent, useCallback, useEffect, useMemo } from 'react';
import { Editor } from 'slate';
import { Box, MenuItem, Text } from 'folds';
import { Room } from 'matrix-js-sdk';
import { Command, useCommands } from '../../hooks/useCommands';
import {
AutocompleteMenu,
AutocompleteQuery,
createCommandElement,
moveCursor,
replaceWithElement,
} from '../../components/editor';
import { UseAsyncSearchOptions, useAsyncSearch } from '../../hooks/useAsyncSearch';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useKeyDown } from '../../hooks/useKeyDown';
import { onTabPress } from '../../utils/keyboard';
type CommandAutoCompleteHandler = (commandName: string) => void;
type CommandAutocompleteProps = {
room: Room;
editor: Editor;
query: AutocompleteQuery<string>;
requestClose: () => void;
};
const SEARCH_OPTIONS: UseAsyncSearchOptions = {
matchOptions: {
contain: true,
},
};
export function CommandAutocomplete({
room,
editor,
query,
requestClose,
}: CommandAutocompleteProps) {
const mx = useMatrixClient();
const commands = useCommands(mx, room);
const commandNames = useMemo(() => Object.keys(commands) as Command[], [commands]);
const [result, search, resetSearch] = useAsyncSearch(
commandNames,
useCallback((commandName: string) => commandName, []),
SEARCH_OPTIONS
);
const autoCompleteNames = result ? result.items : commandNames;
useEffect(() => {
if (query.text) search(query.text);
else resetSearch();
}, [query.text, search, resetSearch]);
const handleAutocomplete: CommandAutoCompleteHandler = (commandName) => {
const cmdEl = createCommandElement(commandName);
replaceWithElement(editor, query.range, cmdEl);
moveCursor(editor, true);
requestClose();
};
useKeyDown(window, (evt: KeyboardEvent) => {
onTabPress(evt, () => {
if (autoCompleteNames.length === 0) {
return;
}
const cmdName = autoCompleteNames[0];
handleAutocomplete(cmdName);
});
});
return autoCompleteNames.length === 0 ? null : (
<AutocompleteMenu
headerContent={
<Box grow="Yes" direction="Row" gap="200" justifyContent="SpaceBetween">
<Text size="L400">Commands</Text>
<Text size="T200" priority="300" truncate>
Begin your message with command
</Text>
</Box>
}
requestClose={requestClose}
>
{autoCompleteNames.map((commandName) => (
<MenuItem
key={commandName}
as="button"
radii="300"
onKeyDown={(evt: ReactKeyboardEvent<HTMLButtonElement>) =>
onTabPress(evt, () => handleAutocomplete(commandName))
}
onClick={() => handleAutocomplete(commandName)}
>
<Box grow="Yes" direction="Row" gap="200" justifyContent="SpaceBetween">
<Box shrink="No">
<Text style={{ flexGrow: 1 }} size="B400" truncate>
{`/${commandName}`}
</Text>
</Box>
<Text truncate priority="300" size="T200">
{commands[commandName].description}
</Text>
</Box>
</MenuItem>
))}
</AutocompleteMenu>
);
}