Integration work for rich text editor 2.3.1 (#11172)

* accompanying changes

* switch to set innerHTML

* bump rte to 2.3.1

* update types for dynamic import

* add comment

* Add comments to dynamic imports

* update comments
This commit is contained in:
alunturner 2023-07-13 08:26:33 +01:00 committed by GitHub
parent 38d24f164a
commit f6ee109f9e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 34 additions and 27 deletions

View file

@ -372,7 +372,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
const { isRichTextEnabled, composerContent } = this.state;
const convertedContent = isRichTextEnabled
? await richToPlain(composerContent)
: await plainToRich(composerContent);
: await plainToRich(composerContent, false);
this.setState({
isRichTextEnabled: !isRichTextEnabled,

View file

@ -39,8 +39,27 @@ export const dynamicImportSendMessage = async (
};
export const dynamicImportConversionFunctions = async (): Promise<{
/**
* Creates a rust model from rich text input (html) and uses it to generate the plain text equivalent (which may
* contain markdown). The return value must be used to set `.innerHTML` (rather than `.innerText`) to
* ensure that HTML entities are correctly interpreted, and to prevent newline characters being turned into `<br>`.
*
* @param rich - html to convert
* @returns a string of plain text that may contain markdown
*/
richToPlain(rich: string): Promise<string>;
plainToRich(plain: string): Promise<string>;
/**
* Creates a rust model from plain text input (interpreted as markdown) and uses it to generate the rich text
* equivalent. Output can be formatted for display in the composer or for sending in a Matrix message.
*
* @param plain - plain text to convert. Note: when reading the plain text from the editor element, be sure to
* use `.innerHTML` (rather than `.innerText`) to ensure that punctuation characters are correctly HTML-encoded.
* @param inMessageFormat - `true` to format the return value for use as a message `formatted_body`.
* `false` to format it for writing to an editor element.
* @returns a string of html
*/
plainToRich(plain: string, inMessageFormat: boolean): Promise<string>;
}> => {
const { richToPlain, plainToRich } = await retry(() => import("@matrix-org/matrix-wysiwyg"), RETRY_COUNT);

View file

@ -18,8 +18,9 @@ import { RefObject, useEffect } from "react";
export function usePlainTextInitialization(initialContent = "", ref: RefObject<HTMLElement>): void {
useEffect(() => {
// always read and write the ref.current using .innerHTML for consistency in linebreak and HTML entity handling
if (ref.current) {
ref.current.innerText = initialContent;
ref.current.innerHTML = initialContent;
}
}, [ref, initialContent]);
}

View file

@ -31,16 +31,6 @@ function isDivElement(target: EventTarget): target is HTMLDivElement {
return target instanceof HTMLDivElement;
}
// Hitting enter inside the editor inserts an editable div, initially containing a <br />
// For correct display, first replace this pattern with a newline character and then remove divs
// noting that they are used to delimit paragraphs
function amendInnerHtml(text: string): string {
return text
.replace(/<div><br><\/div>/g, "\n") // this is pressing enter then not typing
.replace(/<div>/g, "\n") // this is from pressing enter, then typing inside the div
.replace(/<\/div>/g, "");
}
/**
* React hook which generates all of the listeners and the ref to be attached to the editor.
*
@ -100,9 +90,8 @@ export function usePlainTextListeners(
} else if (isNotNull(ref) && isNotNull(ref.current)) {
// if called with no argument, read the current innerHTML from the ref and amend it as per `onInput`
const currentRefContent = ref.current.innerHTML;
const amendedContent = amendInnerHtml(currentRefContent);
setContent(amendedContent);
onChange?.(amendedContent);
setContent(currentRefContent);
onChange?.(currentRefContent);
}
},
[onChange, ref],
@ -113,16 +102,13 @@ export function usePlainTextListeners(
// when a user selects a suggestion from the autocomplete menu
const { suggestion, onSelect, handleCommand, handleMention, handleAtRoomMention } = useSuggestion(ref, setText);
const enterShouldSend = !useSettingValue<boolean>("MessageComposerInput.ctrlEnterToSend");
const onInput = useCallback(
(event: SyntheticEvent<HTMLDivElement, InputEvent | ClipboardEvent>) => {
if (isDivElement(event.target)) {
// if enterShouldSend, we do not need to amend the html before setting text
const newInnerHTML = enterShouldSend ? event.target.innerHTML : amendInnerHtml(event.target.innerHTML);
setText(newInnerHTML);
setText(event.target.innerHTML);
}
},
[setText, enterShouldSend],
[setText],
);
const onPaste = useCallback(
@ -146,6 +132,7 @@ export function usePlainTextListeners(
[eventRelation, mxClient, onInput, roomContext],
);
const enterShouldSend = !useSettingValue<boolean>("MessageComposerInput.ctrlEnterToSend");
const onKeyDown = useCallback(
(event: KeyboardEvent<HTMLDivElement>) => {
// we need autocomplete to take priority when it is open for using enter to select

View file

@ -108,7 +108,7 @@ export async function createMessageContent(
// TODO markdown support
const isMarkdownEnabled = SettingsStore.getValue<boolean>("MessageComposerInput.useMarkdown");
const formattedBody = isHTML ? message : isMarkdownEnabled ? await plainToRich(message) : null;
const formattedBody = isHTML ? message : isMarkdownEnabled ? await plainToRich(message, true) : null;
if (formattedBody) {
content.format = "org.matrix.custom.html";