Merge pull request #9410 from matrix-org/feat/add-formating-buttons-to-wysiwyg
Add formatting buttons to the WysiwygComposerpull/28788/head^2
commit
77543b32d4
|
@ -57,7 +57,7 @@
|
|||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@matrix-org/analytics-events": "^0.2.0",
|
||||
"@matrix-org/matrix-wysiwyg": "^0.0.2",
|
||||
"@matrix-org/matrix-wysiwyg": "^0.2.0",
|
||||
"@matrix-org/react-sdk-module-api": "^0.0.3",
|
||||
"@sentry/browser": "^6.11.0",
|
||||
"@sentry/tracing": "^6.11.0",
|
||||
|
|
|
@ -297,6 +297,7 @@
|
|||
@import "./views/rooms/_TopUnreadMessagesBar.pcss";
|
||||
@import "./views/rooms/_VoiceRecordComposerTile.pcss";
|
||||
@import "./views/rooms/_WhoIsTypingTile.pcss";
|
||||
@import "./views/rooms/wysiwyg_composer/_FormattingButtons.pcss";
|
||||
@import "./views/rooms/wysiwyg_composer/_WysiwygComposer.pcss";
|
||||
@import "./views/settings/_AvatarSetting.pcss";
|
||||
@import "./views/settings/_CrossSigningPanel.pcss";
|
||||
|
|
|
@ -233,6 +233,17 @@ limitations under the License.
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
The wysisyg composer increase the size of the MessageComposer. We temporary move the buttons
|
||||
Soon the dom structure of the MessageComposer will change with the next evolution of the wysiwyg composer
|
||||
and this workaround will disappear
|
||||
*/
|
||||
.mx_MessageComposer_wysiwyg {
|
||||
.mx_MessageComposer_e2eIcon.mx_E2EIcon,.mx_MessageComposer_button, .mx_MessageComposer_sendMessage {
|
||||
margin-top: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_MessageComposer_upload::before {
|
||||
mask-image: url('$(res)/img/element-icons/room/composer/attach.svg');
|
||||
}
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_FormattingButtons {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
|
||||
.mx_FormattingButtons_Button {
|
||||
--size: 28px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
height: var(--size);
|
||||
line-height: var(--size);
|
||||
width: auto;
|
||||
padding-left: 22px;
|
||||
margin-right: 8px;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 6px;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background-color: $icon-button-color;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
mask-position: center;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 0;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
background: rgba($secondary-content, 0.1);
|
||||
}
|
||||
|
||||
&::before {
|
||||
background-color: $secondary-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_FormattingButtons_active {
|
||||
&::after {
|
||||
background: rgba($accent, 0.1);
|
||||
}
|
||||
|
||||
&::before {
|
||||
background-color: $accent;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_FormattingButtons_Button_bold::before {
|
||||
mask-image: url('$(res)/img/element-icons/room/composer/bold.svg');
|
||||
}
|
||||
|
||||
.mx_FormattingButtons_Button_italic::before {
|
||||
mask-image: url('$(res)/img/element-icons/room/composer/italic.svg');
|
||||
}
|
||||
|
||||
.mx_FormattingButtons_Button_underline::before {
|
||||
mask-image: url('$(res)/img/element-icons/room/composer/underline.svg');
|
||||
}
|
||||
|
||||
.mx_FormattingButtons_Button_strikethrough::before {
|
||||
mask-image: url('$(res)/img/element-icons/room/composer/strikethrough.svg');
|
||||
}
|
||||
}
|
||||
|
||||
.mx_FormattingButtons_Tooltip {
|
||||
padding: 0 2px 0 2px;
|
||||
|
||||
.mx_FormattingButtons_Tooltip_KeyboardShortcut {
|
||||
color: $tertiary-content;
|
||||
|
||||
kbd {
|
||||
margin-top: 2px;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
text-transform: capitalize;
|
||||
font-size: 12px;
|
||||
font-family: Inter, sans-serif;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 3C4 2.44772 4.44772 2 5 2H8.19231C10.093 2 11.5 3.64388 11.5 5.5C11.5 6.25349 11.2681 6.97201 10.8655 7.55977C11.8435 8.1788 12.5 9.25485 12.5 10.5C12.5 12.4594 10.8743 14 8.92857 14H5C4.44772 14 4 13.5523 4 13V3ZM6 4V7H8.19231C8.84067 7 9.5 6.4053 9.5 5.5C9.5 4.5947 8.84067 4 8.19231 4H6ZM6 9V12H8.92857C9.82319 12 10.5 11.3021 10.5 10.5C10.5 9.69794 9.82319 9 8.92857 9H6Z" fill="#8D97A5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 549 B |
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.08596 3.60016L6.28068 12.4045H4.79997C4.35814 12.4045 3.99997 12.7627 3.99997 13.2045C3.99997 13.6464 4.35814 14.0045 4.79997 14.0045H6.92104C6.92979 14.0047 6.93852 14.0047 6.94722 14.0045H9.06664C9.50846 14.0045 9.86664 13.6464 9.86664 13.2045C9.86664 12.7627 9.50846 12.4045 9.06664 12.4045H7.91397L9.71924 3.60016H11.2C11.6418 3.60016 12 3.24199 12 2.80016C12 2.35833 11.6418 2.00016 11.2 2.00016H9.08168C9.07107 1.99995 9.06048 1.99995 9.04993 2.00016H6.9333C6.49147 2.00016 6.1333 2.35833 6.1333 2.80016C6.1333 3.24199 6.49147 3.60016 6.9333 3.60016H8.08596Z" fill="#8D97A5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 738 B |
|
@ -0,0 +1,4 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.93167 4.76958C9.48979 3.88101 8.58375 3.47362 7.58232 3.58148C6.03349 3.74829 5.62648 4.94831 5.81497 5.72822C6.02118 6.58143 6.69765 6.91317 7.89252 7.21717H13.28C13.6776 7.21717 14 7.56757 14 7.99981C14 8.43204 13.6776 8.78244 13.28 8.78244H2.72C2.32235 8.78244 2 8.43204 2 7.99981C2 7.56757 2.32235 7.21717 2.72 7.21717H4.90308C4.69392 6.91824 4.52701 6.55948 4.42223 6.12592C4.0021 4.38757 5.03605 2.28279 7.44033 2.02384C8.85635 1.87133 10.4103 2.44045 11.1984 4.02524C11.3875 4.40548 11.2572 4.88035 10.9074 5.08589C10.5576 5.29143 10.1208 5.14981 9.93167 4.76958Z" fill="#8D97A5"/>
|
||||
<path d="M10.2846 10.0868H11.7797C11.9254 10.8705 11.8142 11.7067 11.3665 12.4212C10.7426 13.4169 9.57984 14 7.98987 14C5.38435 14 4.18628 12.3895 3.94151 11.324C3.84516 10.9047 4.07981 10.4798 4.4656 10.3751C4.8514 10.2704 5.24225 10.5254 5.3386 10.9448C5.41285 11.268 6.00136 12.4347 7.98987 12.4347C9.27118 12.4347 9.90296 11.9764 10.1763 11.5401C10.4284 11.1378 10.472 10.6062 10.2846 10.0868Z" fill="#8D97A5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.19333 11.3C10.2133 11.04 11.6667 9.22667 11.6667 7.19333V2.83333C11.6667 2.37333 11.2933 2 10.8333 2C10.3733 2 10 2.37333 10 2.83333V7.26667C10 8.38 9.24667 9.39333 8.15333 9.61333C6.65333 9.92667 5.33333 8.78 5.33333 7.33333V2.83333C5.33333 2.37333 4.96 2 4.5 2C4.04 2 3.66667 2.37333 3.66667 2.83333V7.33333C3.66667 9.71333 5.75333 11.6133 8.19333 11.3ZM3 13.3333C3 13.7 3.3 14 3.66667 14H11.6667C12.0333 14 12.3333 13.7 12.3333 13.3333C12.3333 12.9667 12.0333 12.6667 11.6667 12.6667H3.66667C3.3 12.6667 3 12.9667 3 13.3333Z" fill="#8D97A5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 661 B |
|
@ -389,6 +389,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const isWysiwygComposerEnabled = SettingsStore.getValue("feature_wysiwyg_composer");
|
||||
const controls = [
|
||||
this.props.e2eStatus ?
|
||||
<E2EIcon key="e2eIcon" status={this.props.e2eStatus} className="mx_MessageComposer_e2eIcon" /> :
|
||||
|
@ -403,8 +404,6 @@ export default class MessageComposer extends React.Component<IProps, IState> {
|
|||
|
||||
const canSendMessages = this.context.canSendMessages && !this.context.tombstone;
|
||||
if (canSendMessages) {
|
||||
const isWysiwygComposerEnabled = SettingsStore.getValue("feature_wysiwyg_composer");
|
||||
|
||||
if (isWysiwygComposerEnabled) {
|
||||
controls.push(
|
||||
<WysiwygComposer key="controls_input"
|
||||
|
@ -503,6 +502,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
|
|||
"mx_MessageComposer": true,
|
||||
"mx_MessageComposer--compact": this.props.compact,
|
||||
"mx_MessageComposer_e2eStatus": this.props.e2eStatus != undefined,
|
||||
"mx_MessageComposer_wysiwyg": isWysiwygComposerEnabled,
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { forwardRef, memo } from 'react';
|
||||
|
||||
interface EditorProps {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
export const Editor = memo(
|
||||
forwardRef<HTMLDivElement, EditorProps>(
|
||||
function Editor({ disabled }: EditorProps, ref,
|
||||
) {
|
||||
return <div className="mx_WysiwygComposer_container">
|
||||
<div className="mx_WysiwygComposer_content"
|
||||
ref={ref!}
|
||||
contentEditable={!disabled}
|
||||
role="textbox"
|
||||
aria-multiline="true"
|
||||
aria-autocomplete="list"
|
||||
aria-haspopup="listbox"
|
||||
dir="auto"
|
||||
aria-disabled={disabled}
|
||||
/>
|
||||
</div>;
|
||||
},
|
||||
),
|
||||
);
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { MouseEventHandler } from "react";
|
||||
import { useWysiwyg } from "@matrix-org/matrix-wysiwyg";
|
||||
import classNames from "classnames";
|
||||
|
||||
import AccessibleTooltipButton from "../../elements/AccessibleTooltipButton";
|
||||
import { Alignment } from "../../elements/Tooltip";
|
||||
import { KeyboardShortcut } from "../../settings/KeyboardShortcut";
|
||||
import { KeyCombo } from "../../../../KeyBindingsManager";
|
||||
import { _td } from "../../../../languageHandler";
|
||||
|
||||
interface TooltipProps {
|
||||
label: string;
|
||||
keyCombo?: KeyCombo;
|
||||
}
|
||||
|
||||
function Tooltip({ label, keyCombo }: TooltipProps) {
|
||||
return <div className="mx_FormattingButtons_Tooltip">
|
||||
{ label }
|
||||
{ keyCombo && <KeyboardShortcut value={keyCombo} className="mx_FormattingButtons_Tooltip_KeyboardShortcut" /> }
|
||||
</div>;
|
||||
}
|
||||
|
||||
interface ButtonProps extends TooltipProps {
|
||||
className: string;
|
||||
isActive: boolean;
|
||||
onClick: MouseEventHandler<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
function Button({ label, keyCombo, onClick, isActive, className }: ButtonProps) {
|
||||
return <AccessibleTooltipButton
|
||||
element="button"
|
||||
onClick={onClick}
|
||||
title={label}
|
||||
className={
|
||||
classNames('mx_FormattingButtons_Button', className, { 'mx_FormattingButtons_active': isActive })}
|
||||
tooltip={keyCombo && <Tooltip label={label} keyCombo={keyCombo} />}
|
||||
alignment={Alignment.Top}
|
||||
/>;
|
||||
}
|
||||
|
||||
interface FormattingButtonsProps {
|
||||
composer: ReturnType<typeof useWysiwyg>['wysiwyg'];
|
||||
formattingStates: ReturnType<typeof useWysiwyg>['formattingStates'];
|
||||
}
|
||||
|
||||
export function FormattingButtons({ composer, formattingStates }: FormattingButtonsProps) {
|
||||
return <div className="mx_FormattingButtons">
|
||||
<Button isActive={formattingStates.bold === 'reversed'} label={_td("Bold")} keyCombo={{ ctrlOrCmdKey: true, key: 'b' }} onClick={() => composer.bold()} className="mx_FormattingButtons_Button_bold" />
|
||||
<Button isActive={formattingStates.italic === 'reversed'} label={_td('Italic')} keyCombo={{ ctrlOrCmdKey: true, key: 'i' }} onClick={() => composer.italic()} className="mx_FormattingButtons_Button_italic" />
|
||||
<Button isActive={formattingStates.underline === 'reversed'} label={_td('Underline')} keyCombo={{ ctrlOrCmdKey: true, key: 'u' }} onClick={() => composer.underline()} className="mx_FormattingButtons_Button_underline" />
|
||||
<Button isActive={formattingStates.strikeThrough === 'reversed'} label={_td('Strikethrough')} onClick={() => composer.strikeThrough()} className="mx_FormattingButtons_Button_strikethrough" />
|
||||
</div>;
|
||||
}
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useWysiwyg } from "@matrix-org/matrix-wysiwyg";
|
||||
import { IEventRelation, MatrixEvent } from 'matrix-js-sdk/src/models/event';
|
||||
|
||||
|
@ -22,6 +22,8 @@ import { useRoomContext } from '../../../../contexts/RoomContext';
|
|||
import { sendMessage } from './message';
|
||||
import { RoomPermalinkCreator } from '../../../../utils/permalinks/Permalinks';
|
||||
import { useMatrixClientContext } from '../../../../contexts/MatrixClientContext';
|
||||
import { FormattingButtons } from './FormattingButtons';
|
||||
import { Editor } from './Editor';
|
||||
|
||||
interface WysiwygProps {
|
||||
disabled?: boolean;
|
||||
|
@ -39,11 +41,13 @@ export function WysiwygComposer(
|
|||
const roomContext = useRoomContext();
|
||||
const mxClient = useMatrixClientContext();
|
||||
|
||||
const [content, setContent] = useState<string>();
|
||||
const { ref, isWysiwygReady, wysiwyg } = useWysiwyg({ onChange: (_content) => {
|
||||
setContent(_content);
|
||||
onChange(_content);
|
||||
} });
|
||||
const { ref, isWysiwygReady, content, formattingStates, wysiwyg } = useWysiwyg();
|
||||
|
||||
useEffect(() => {
|
||||
if (!disabled && content !== null) {
|
||||
onChange(content);
|
||||
}
|
||||
}, [onChange, content, disabled]);
|
||||
|
||||
const memoizedSendMessage = useCallback(() => {
|
||||
sendMessage(content, { mxClient, roomContext, ...props });
|
||||
|
@ -53,18 +57,8 @@ export function WysiwygComposer(
|
|||
|
||||
return (
|
||||
<div className="mx_WysiwygComposer">
|
||||
<div className="mx_WysiwygComposer_container">
|
||||
<div className="mx_WysiwygComposer_content"
|
||||
ref={ref}
|
||||
contentEditable={!disabled && isWysiwygReady}
|
||||
role="textbox"
|
||||
aria-multiline="true"
|
||||
aria-autocomplete="list"
|
||||
aria-haspopup="listbox"
|
||||
dir="auto"
|
||||
aria-disabled={disabled || !isWysiwygReady}
|
||||
/>
|
||||
</div>
|
||||
<FormattingButtons composer={wysiwyg} formattingStates={formattingStates} />
|
||||
<Editor ref={ref} disabled={!isWysiwygReady || disabled} />
|
||||
{ children?.(memoizedSendMessage) }
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -38,9 +38,10 @@ export const KeyboardKey: React.FC<IKeyboardKeyProps> = ({ name, last }) => {
|
|||
|
||||
interface IKeyboardShortcutProps {
|
||||
value: KeyCombo;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const KeyboardShortcut: React.FC<IKeyboardShortcutProps> = ({ value }) => {
|
||||
export const KeyboardShortcut: React.FC<IKeyboardShortcutProps> = ({ value, className = 'mx_KeyboardShortcut' }) => {
|
||||
if (!value) return null;
|
||||
|
||||
const modifiersElement = [];
|
||||
|
@ -58,7 +59,7 @@ export const KeyboardShortcut: React.FC<IKeyboardShortcutProps> = ({ value }) =>
|
|||
modifiersElement.push(<KeyboardKey key="shiftKey" name={Key.SHIFT} />);
|
||||
}
|
||||
|
||||
return <div className="mx_KeyboardShortcut">
|
||||
return <div className={className}>
|
||||
{ modifiersElement }
|
||||
<KeyboardKey name={value.key} last />
|
||||
</div>;
|
||||
|
|
|
@ -904,7 +904,7 @@
|
|||
"How can I leave the beta?": "How can I leave the beta?",
|
||||
"To leave, return to this page and use the “%(leaveTheBeta)s” button.": "To leave, return to this page and use the “%(leaveTheBeta)s” button.",
|
||||
"Leave the beta": "Leave the beta",
|
||||
"Wysiwyg composer (plain text mode coming soon) (under active development)": "Wysiwyg composer (plain text mode coming soon) (under active development)",
|
||||
"Try out the rich text editor (plain text mode coming soon)": "Try out the rich text editor (plain text mode coming soon)",
|
||||
"Render simple counters in room header": "Render simple counters in room header",
|
||||
"Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)",
|
||||
"Support adding custom themes": "Support adding custom themes",
|
||||
|
@ -2060,6 +2060,8 @@
|
|||
"No microphone found": "No microphone found",
|
||||
"We didn't find a microphone on your device. Please check your settings and try again.": "We didn't find a microphone on your device. Please check your settings and try again.",
|
||||
"Stop recording": "Stop recording",
|
||||
"Italic": "Italic",
|
||||
"Underline": "Underline",
|
||||
"Error updating main address": "Error updating main address",
|
||||
"There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.",
|
||||
"There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.",
|
||||
|
|
|
@ -306,7 +306,7 @@ export const SETTINGS: {[setting: string]: ISetting} = {
|
|||
"feature_wysiwyg_composer": {
|
||||
isFeature: true,
|
||||
labsGroup: LabGroup.Messaging,
|
||||
displayName: _td("Wysiwyg composer (plain text mode coming soon) (under active development)"),
|
||||
displayName: _td("Try out the rich text editor (plain text mode coming soon)"),
|
||||
supportedLevels: LEVELS_FEATURE,
|
||||
default: false,
|
||||
},
|
||||
|
|
|
@ -44,8 +44,9 @@ import { WysiwygComposer } from "../../../../src/components/views/rooms/wysiwyg_
|
|||
// The wysiwyg fetch wasm bytes and a specific workaround is needed to make it works in a node (jest) environnement
|
||||
// See https://github.com/matrix-org/matrix-wysiwyg/blob/main/platforms/web/test.setup.ts
|
||||
jest.mock("@matrix-org/matrix-wysiwyg", () => ({
|
||||
useWysiwyg: ({ onChange }) => {
|
||||
return { ref: { current: null }, isWysiwygReady: true, wysiwyg: { clear: () => void 0 } };
|
||||
useWysiwyg: () => {
|
||||
return { ref: { current: null }, isWysiwygReady: true, wysiwyg: { clear: () => void 0 },
|
||||
formattingStates: { bold: 'enabled', italic: 'enabled', underline: 'enabled', strikeThrough: 'enabled' } };
|
||||
},
|
||||
}));
|
||||
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import { FormattingButtons } from "../../../../../src/components/views/rooms/wysiwyg_composer/FormattingButtons";
|
||||
|
||||
describe('FormattingButtons', () => {
|
||||
const wysiwyg = {
|
||||
bold: jest.fn(),
|
||||
italic: jest.fn(),
|
||||
underline: jest.fn(),
|
||||
strikeThrough: jest.fn(),
|
||||
} as any;
|
||||
|
||||
const formattingStates = {
|
||||
bold: 'reversed',
|
||||
italic: 'reversed',
|
||||
underline: 'enabled',
|
||||
strikeThrough: 'enabled',
|
||||
} as any;
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('Should have the correspond CSS classes', () => {
|
||||
// When
|
||||
render(<FormattingButtons composer={wysiwyg} formattingStates={formattingStates} />);
|
||||
|
||||
// Then
|
||||
expect(screen.getByLabelText('Bold')).toHaveClass('mx_FormattingButtons_active');
|
||||
expect(screen.getByLabelText('Italic')).toHaveClass('mx_FormattingButtons_active');
|
||||
expect(screen.getByLabelText('Underline')).not.toHaveClass('mx_FormattingButtons_active');
|
||||
expect(screen.getByLabelText('Strikethrough')).not.toHaveClass('mx_FormattingButtons_active');
|
||||
});
|
||||
|
||||
it('Should call wysiwyg function on button click', () => {
|
||||
// When
|
||||
render(<FormattingButtons composer={wysiwyg} formattingStates={formattingStates} />);
|
||||
screen.getByLabelText('Bold').click();
|
||||
screen.getByLabelText('Italic').click();
|
||||
screen.getByLabelText('Underline').click();
|
||||
screen.getByLabelText('Strikethrough').click();
|
||||
|
||||
// Then
|
||||
expect(wysiwyg.bold).toHaveBeenCalledTimes(1);
|
||||
expect(wysiwyg.italic).toHaveBeenCalledTimes(1);
|
||||
expect(wysiwyg.underline).toHaveBeenCalledTimes(1);
|
||||
expect(wysiwyg.strikeThrough).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('Should display the tooltip on mouse over', async () => {
|
||||
// When
|
||||
const user = userEvent.setup();
|
||||
render(<FormattingButtons composer={wysiwyg} formattingStates={formattingStates} />);
|
||||
await user.hover(screen.getByLabelText('Bold'));
|
||||
|
||||
// Then
|
||||
expect(await screen.findByText('Bold')).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
|||
|
||||
import React from "react";
|
||||
import { act, render, screen } from "@testing-library/react";
|
||||
import "@testing-library/jest-dom";
|
||||
|
||||
import { IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import RoomContext, { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
|
@ -25,14 +24,12 @@ import { createTestClient, mkEvent, mkStubRoom } from "../../../../test-utils";
|
|||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import { WysiwygComposer } from "../../../../../src/components/views/rooms/wysiwyg_composer/WysiwygComposer";
|
||||
|
||||
let callOnChange: (content: string) => void;
|
||||
|
||||
// The wysiwyg fetch wasm bytes and a specific workaround is needed to make it works in a node (jest) environnement
|
||||
// See https://github.com/matrix-org/matrix-wysiwyg/blob/main/platforms/web/test.setup.ts
|
||||
jest.mock("@matrix-org/matrix-wysiwyg", () => ({
|
||||
useWysiwyg: ({ onChange }) => {
|
||||
callOnChange = onChange;
|
||||
return { ref: { current: null }, isWysiwygReady: true, wysiwyg: { clear: () => void 0 } };
|
||||
useWysiwyg: () => {
|
||||
return { ref: { current: null }, content: '<b>html</b>', isWysiwygReady: true, wysiwyg: { clear: () => void 0 },
|
||||
formattingStates: { bold: 'enabled', italic: 'enabled', underline: 'enabled', strikeThrough: 'enabled' } };
|
||||
},
|
||||
}));
|
||||
|
||||
|
@ -122,7 +119,7 @@ describe('WysiwygComposer', () => {
|
|||
expect(content).toBe((html));
|
||||
done();
|
||||
});
|
||||
act(() => callOnChange(html));
|
||||
// act(() => callOnChange(html));
|
||||
});
|
||||
|
||||
it('Should send message, call clear and focus the textbox', async () => {
|
||||
|
@ -130,7 +127,6 @@ describe('WysiwygComposer', () => {
|
|||
const html = '<b>html</b>';
|
||||
await new Promise((resolve) => {
|
||||
customRender(() => resolve(null));
|
||||
act(() => callOnChange(html));
|
||||
});
|
||||
act(() => sendMessage());
|
||||
|
||||
|
|
13
yarn.lock
13
yarn.lock
|
@ -1660,10 +1660,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@matrix-org/analytics-events/-/analytics-events-0.2.0.tgz#453925c939ecdd5ca6c797d293deb8cf0933f1b8"
|
||||
integrity sha512-+0/Sydm4MNOcqd8iySJmojVPB74Axba4BXlwTsiKmL5fgYqdUkwmqkO39K7Pn8i+a+8pg11oNvBPkpWs3O5Qww==
|
||||
|
||||
"@matrix-org/matrix-wysiwyg@^0.0.2":
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-wysiwyg/-/matrix-wysiwyg-0.0.2.tgz#c1a18f5f9ac061c4147a0fbbf9303a3c82e626e6"
|
||||
integrity sha512-AY4sbmgcaFZhNxJfn3Va1SiKH4/gIdvWV9c/iehcIi3/xFB7lKCIwe7NNxzPpFOp+b+fEIbdHf3fhS5vJBi7xg==
|
||||
"@matrix-org/matrix-wysiwyg@^0.2.0":
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-wysiwyg/-/matrix-wysiwyg-0.2.0.tgz#651002ad67be3004698d4a89806cf344283a4ca3"
|
||||
integrity sha512-m9R1NOd0ogkhrjqFNg159TMXL5dpME90G9RDrZrO106263Qtoj0TazyBaLhNjgvPkogbzbCJUULQWPFiLQfTjw==
|
||||
|
||||
"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz":
|
||||
version "3.2.8"
|
||||
|
@ -3198,6 +3198,11 @@ browser-process-hrtime@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626"
|
||||
integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==
|
||||
|
||||
browser-request@^0.3.3:
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/browser-request/-/browser-request-0.3.3.tgz#9ece5b5aca89a29932242e18bf933def9876cc17"
|
||||
integrity sha512-YyNI4qJJ+piQG6MMEuo7J3Bzaqssufx04zpEKYfSrl/1Op59HWali9zMtBpXnkmqMcOuWJPZvudrm9wISmnCbg==
|
||||
|
||||
browserslist@^4.20.2, browserslist@^4.21.3:
|
||||
version "4.21.3"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a"
|
||||
|
|
Loading…
Reference in New Issue