Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Add keyboard UP behaviour for RTE
Browse files Browse the repository at this point in the history
  • Loading branch information
florianduros committed Jan 24, 2023
1 parent 156b45b commit b350d3c
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,18 @@ limitations under the License.
import { createContext, useContext } from "react";

import { SubSelection } from "./types";
import EditorStateTransfer from "../../../../utils/EditorStateTransfer";

export function getDefaultContextValue(): { selection: SubSelection } {
export function getDefaultContextValue(defaultValue?: Partial<ComposerContextState>): { selection: SubSelection } {
return {
selection: { anchorNode: null, anchorOffset: 0, focusNode: null, focusOffset: 0 },
...defaultValue,
};
}

export interface ComposerContextState {
selection: SubSelection;
editorStateTransfer?: EditorStateTransfer;
}

export const ComposerContext = createContext<ComposerContextState>(getDefaultContextValue());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export default function EditWysiwygComposer({
className,
...props
}: EditWysiwygComposerProps): JSX.Element {
const defaultContextValue = useRef(getDefaultContextValue());
const defaultContextValue = useRef(getDefaultContextValue({ editorStateTransfer }));
const initialContent = useInitialContent(editorStateTransfer);
const isReady = !editorStateTransfer || initialContent !== undefined;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const WysiwygComposer = memo(function WysiwygComposer({
rightComponent,
children,
}: WysiwygComposerProps) {
const inputEventProcessor = useInputEventProcessor(onSend);
const inputEventProcessor = useInputEventProcessor(onSend, initialContent);

const { ref, isWysiwygReady, content, actionStates, wysiwyg } = useWysiwyg({ initialContent, inputEventProcessor });

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,110 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { WysiwygEvent } from "@matrix-org/matrix-wysiwyg";
import { Wysiwyg, WysiwygEvent } from "@matrix-org/matrix-wysiwyg";
import { useCallback } from "react";
import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";

import { useSettingValue } from "../../../../../hooks/useSettings";
import { getKeyBindingsManager } from "../../../../../KeyBindingsManager";
import { KeyBindingAction } from "../../../../../accessibility/KeyboardShortcuts";
import { findEditableEvent } from "../../../../../utils/EventUtils";
import dis from "../../../../../dispatcher/dispatcher";
import { Action } from "../../../../../dispatcher/actions";
import { useRoomContext } from "../../../../../contexts/RoomContext";
import { IRoomState } from "../../../../structures/RoomView";
import { ComposerContextState, useComposerContext } from "../ComposerContext";
import EditorStateTransfer from "../../../../../utils/EditorStateTransfer";
import { useMatrixClientContext } from "../../../../../contexts/MatrixClientContext";
import { isCaretAtStart } from "../utils/selection";

export function useInputEventProcessor(
onSend: () => void,
initialContent?: string,
): (event: WysiwygEvent, composer: Wysiwyg, editor: HTMLElement) => WysiwygEvent | null {
const roomContext = useRoomContext();
const composerContext = useComposerContext();
const mxClient = useMatrixClientContext();
const isCtrlEnter = useSettingValue<boolean>("MessageComposerInput.ctrlEnterToSend");

return useCallback(
(event: WysiwygEvent, composer: Wysiwyg, editor: HTMLElement) => {
if (event instanceof ClipboardEvent) {
return event;
}

const send = (): void => {
event.stopPropagation?.();
event.preventDefault?.();
onSend();
};

const isKeyboardEvent = event instanceof KeyboardEvent;
if (isKeyboardEvent) {
return handleKeyboardEvent(
event,
send,
initialContent,
composer,
editor,
roomContext,
composerContext,
mxClient,
);
} else {
return handleInputEvent(event, send, isCtrlEnter);
}
},
[isCtrlEnter, onSend, initialContent, roomContext, composerContext, mxClient],
);
}

type Send = () => void;

function handleKeyboardEvent(event: KeyboardEvent, send: Send): KeyboardEvent | null {
function handleKeyboardEvent(
event: KeyboardEvent,
send: Send,
initialContent: string,
composer: Wysiwyg,
editor: HTMLElement,
roomContext: IRoomState,
composerContext: ComposerContextState,
mxClient: MatrixClient,
): KeyboardEvent | null {
const action = getKeyBindingsManager().getMessageComposerAction(event);

switch (action) {
case KeyBindingAction.SendMessage:
send();
return null;
case KeyBindingAction.EditPrevMessage: {
const { editorStateTransfer } = composerContext;

const isEditorModified = initialContent !== composer.content();

// If not in edition
// Or if the caret is not at the beginning of the editor
// Or the editor is modified
if (!editorStateTransfer || !isCaretAtStart(editor) || isEditorModified) {
break;
}

const previousEvent = findEditableEvent({
events: getEventsFromEditorStateTransfer(editorStateTransfer, roomContext, mxClient),
isForward: false,
fromEventId: editorStateTransfer.getEvent().getId(),
});
if (previousEvent) {
dis.dispatch({
action: Action.EditEvent,
event: previousEvent,
timelineRenderingType: roomContext.timelineRenderingType,
});
event.stopPropagation();
event.preventDefault();
}
return null;
}
}

return event;
Expand All @@ -54,27 +142,14 @@ function handleInputEvent(event: InputEvent, send: Send, isCtrlEnter: boolean):
return event;
}

export function useInputEventProcessor(onSend: () => void): (event: WysiwygEvent) => WysiwygEvent | null {
const isCtrlEnter = useSettingValue<boolean>("MessageComposerInput.ctrlEnterToSend");
return useCallback(
(event: WysiwygEvent) => {
if (event instanceof ClipboardEvent) {
return event;
}

const send = (): void => {
event.stopPropagation?.();
event.preventDefault?.();
onSend();
};

const isKeyboardEvent = event instanceof KeyboardEvent;
if (isKeyboardEvent) {
return handleKeyboardEvent(event, send);
} else {
return handleInputEvent(event, send, isCtrlEnter);
}
},
[isCtrlEnter, onSend],
);
// From EditMessageComposer private get events(): MatrixEvent[]
function getEventsFromEditorStateTransfer(
editorStateTransfer: EditorStateTransfer,
roomContext: IRoomState,
mxClient: MatrixClient,
): MatrixEvent[] {
const liveTimelineEvents = roomContext.liveTimeline.getEvents();
const pendingEvents = mxClient.getRoom(editorStateTransfer.getEvent().getRoomId()).getPendingEvents();
const isInThread = Boolean(editorStateTransfer.getEvent().getThread());
return liveTimelineEvents.concat(isInThread ? [] : pendingEvents);
}
18 changes: 18 additions & 0 deletions src/components/views/rooms/wysiwyg_composer/utils/selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,21 @@ export function isSelectionEmpty(): boolean {
const selection = document.getSelection();
return Boolean(selection?.isCollapsed);
}

export function isCaretAtStart(editor: HTMLElement): Boolean {
const selection = document.getSelection();

if (!selection || selection.anchorOffset !== 0) {
return false;
}

// In case of nested html elements (list, code blocks), we are going through all the first child
let child = editor.firstChild;
do {
if (child === selection.anchorNode) {
return true;
}
} while ((child = child.firstChild));

return false;
}

0 comments on commit b350d3c

Please sign in to comment.