mirror of
https://github.com/element-hq/element-web
synced 2024-11-26 11:15:53 +03:00
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:
parent
38d24f164a
commit
f6ee109f9e
7 changed files with 34 additions and 27 deletions
|
@ -61,7 +61,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.12.5",
|
"@babel/runtime": "^7.12.5",
|
||||||
"@matrix-org/analytics-events": "^0.5.0",
|
"@matrix-org/analytics-events": "^0.5.0",
|
||||||
"@matrix-org/matrix-wysiwyg": "^2.3.0",
|
"@matrix-org/matrix-wysiwyg": "^2.3.1",
|
||||||
"@matrix-org/react-sdk-module-api": "^1.0.0",
|
"@matrix-org/react-sdk-module-api": "^1.0.0",
|
||||||
"@sentry/browser": "^7.0.0",
|
"@sentry/browser": "^7.0.0",
|
||||||
"@sentry/tracing": "^7.0.0",
|
"@sentry/tracing": "^7.0.0",
|
||||||
|
|
|
@ -372,7 +372,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
||||||
const { isRichTextEnabled, composerContent } = this.state;
|
const { isRichTextEnabled, composerContent } = this.state;
|
||||||
const convertedContent = isRichTextEnabled
|
const convertedContent = isRichTextEnabled
|
||||||
? await richToPlain(composerContent)
|
? await richToPlain(composerContent)
|
||||||
: await plainToRich(composerContent);
|
: await plainToRich(composerContent, false);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
isRichTextEnabled: !isRichTextEnabled,
|
isRichTextEnabled: !isRichTextEnabled,
|
||||||
|
|
|
@ -39,8 +39,27 @@ export const dynamicImportSendMessage = async (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dynamicImportConversionFunctions = async (): Promise<{
|
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>;
|
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);
|
const { richToPlain, plainToRich } = await retry(() => import("@matrix-org/matrix-wysiwyg"), RETRY_COUNT);
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,9 @@ import { RefObject, useEffect } from "react";
|
||||||
|
|
||||||
export function usePlainTextInitialization(initialContent = "", ref: RefObject<HTMLElement>): void {
|
export function usePlainTextInitialization(initialContent = "", ref: RefObject<HTMLElement>): void {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// always read and write the ref.current using .innerHTML for consistency in linebreak and HTML entity handling
|
||||||
if (ref.current) {
|
if (ref.current) {
|
||||||
ref.current.innerText = initialContent;
|
ref.current.innerHTML = initialContent;
|
||||||
}
|
}
|
||||||
}, [ref, initialContent]);
|
}, [ref, initialContent]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,16 +31,6 @@ function isDivElement(target: EventTarget): target is HTMLDivElement {
|
||||||
return target instanceof 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.
|
* 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)) {
|
} 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`
|
// if called with no argument, read the current innerHTML from the ref and amend it as per `onInput`
|
||||||
const currentRefContent = ref.current.innerHTML;
|
const currentRefContent = ref.current.innerHTML;
|
||||||
const amendedContent = amendInnerHtml(currentRefContent);
|
setContent(currentRefContent);
|
||||||
setContent(amendedContent);
|
onChange?.(currentRefContent);
|
||||||
onChange?.(amendedContent);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[onChange, ref],
|
[onChange, ref],
|
||||||
|
@ -113,16 +102,13 @@ export function usePlainTextListeners(
|
||||||
// when a user selects a suggestion from the autocomplete menu
|
// when a user selects a suggestion from the autocomplete menu
|
||||||
const { suggestion, onSelect, handleCommand, handleMention, handleAtRoomMention } = useSuggestion(ref, setText);
|
const { suggestion, onSelect, handleCommand, handleMention, handleAtRoomMention } = useSuggestion(ref, setText);
|
||||||
|
|
||||||
const enterShouldSend = !useSettingValue<boolean>("MessageComposerInput.ctrlEnterToSend");
|
|
||||||
const onInput = useCallback(
|
const onInput = useCallback(
|
||||||
(event: SyntheticEvent<HTMLDivElement, InputEvent | ClipboardEvent>) => {
|
(event: SyntheticEvent<HTMLDivElement, InputEvent | ClipboardEvent>) => {
|
||||||
if (isDivElement(event.target)) {
|
if (isDivElement(event.target)) {
|
||||||
// if enterShouldSend, we do not need to amend the html before setting text
|
setText(event.target.innerHTML);
|
||||||
const newInnerHTML = enterShouldSend ? event.target.innerHTML : amendInnerHtml(event.target.innerHTML);
|
|
||||||
setText(newInnerHTML);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[setText, enterShouldSend],
|
[setText],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onPaste = useCallback(
|
const onPaste = useCallback(
|
||||||
|
@ -146,6 +132,7 @@ export function usePlainTextListeners(
|
||||||
[eventRelation, mxClient, onInput, roomContext],
|
[eventRelation, mxClient, onInput, roomContext],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const enterShouldSend = !useSettingValue<boolean>("MessageComposerInput.ctrlEnterToSend");
|
||||||
const onKeyDown = useCallback(
|
const onKeyDown = useCallback(
|
||||||
(event: KeyboardEvent<HTMLDivElement>) => {
|
(event: KeyboardEvent<HTMLDivElement>) => {
|
||||||
// we need autocomplete to take priority when it is open for using enter to select
|
// we need autocomplete to take priority when it is open for using enter to select
|
||||||
|
|
|
@ -108,7 +108,7 @@ export async function createMessageContent(
|
||||||
// TODO markdown support
|
// TODO markdown support
|
||||||
|
|
||||||
const isMarkdownEnabled = SettingsStore.getValue<boolean>("MessageComposerInput.useMarkdown");
|
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) {
|
if (formattedBody) {
|
||||||
content.format = "org.matrix.custom.html";
|
content.format = "org.matrix.custom.html";
|
||||||
|
|
|
@ -1809,10 +1809,10 @@
|
||||||
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0.tgz#766580036d4df12120ded223e13b5640e77db136"
|
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0.tgz#766580036d4df12120ded223e13b5640e77db136"
|
||||||
integrity sha512-ra/bcFdleC1iRNms2I96UXA0NvQYWpMsHrV5EfJRS7qV1PtnQNvgsvMfjMbkx8QT2ErEmIhsvB5fPCpfp8BSuw==
|
integrity sha512-ra/bcFdleC1iRNms2I96UXA0NvQYWpMsHrV5EfJRS7qV1PtnQNvgsvMfjMbkx8QT2ErEmIhsvB5fPCpfp8BSuw==
|
||||||
|
|
||||||
"@matrix-org/matrix-wysiwyg@^2.3.0":
|
"@matrix-org/matrix-wysiwyg@^2.3.1":
|
||||||
version "2.3.0"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-wysiwyg/-/matrix-wysiwyg-2.3.0.tgz#7a815fb90600342cc74c03a3cc7c9908a1d15dd1"
|
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-wysiwyg/-/matrix-wysiwyg-2.3.1.tgz#4b607323f3ffd8c332abeba7226010ecc031ed12"
|
||||||
integrity sha512-VtA+Bti2IdqpnpCNaTFHMjbpKXe4xHR+OWWJl/gjuYgn4NJO9lfeeEIv34ftC6dBh7R280JEiMxQ9mDcH0J54g==
|
integrity sha512-OxJvA+pSGdP2f55foZGEDmU2qvILFLLjV53MOgPw1F6zDAp8nDL1rPPIzFv1qgDj5W7d4Rzq7FnN25vINnAu+A==
|
||||||
|
|
||||||
"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz":
|
"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz":
|
||||||
version "3.2.14"
|
version "3.2.14"
|
||||||
|
|
Loading…
Reference in a new issue