Add formatting buttons for WysisygComposer
This commit is contained in:
parent
b336e18eae
commit
01858354f8
14 changed files with 271 additions and 24 deletions
|
@ -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 (
|
||||
|
|
41
src/components/views/rooms/wysiwyg_composer/Editor.tsx
Normal file
41
src/components/views/rooms/wysiwyg_composer/Editor.tsx
Normal file
|
@ -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 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: () => void;
|
||||
}
|
||||
|
||||
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 {
|
||||
wysiwyg: ReturnType<typeof useWysiwyg>['wysiwyg'];
|
||||
formattingStates: ReturnType<typeof useWysiwyg>['formattingStates'];
|
||||
}
|
||||
|
||||
export function FormattingButtons({ wysiwyg, formattingStates }: FormattingButtonsProps) {
|
||||
return <div className="mx_FormattingButtons">
|
||||
<Button isActive={formattingStates.bold === 'reversed'} label={_td("Bold")} keyCombo={{ ctrlOrCmdKey: true, key: 'b' }} onClick={() => wysiwyg.bold()} className="mx_FormattingButtons_Button_bold" />
|
||||
<Button isActive={formattingStates.italic === 'reversed'} label={_td('Italic')} keyCombo={{ ctrlOrCmdKey: true, key: 'i' }} onClick={() => wysiwyg.italic()} className="mx_FormattingButtons_Button_italic" />
|
||||
<Button isActive={formattingStates.underline === 'reversed'} label={_td('Underline')} keyCombo={{ ctrlOrCmdKey: true, key: 'u' }} onClick={() => wysiwyg.underline()} className="mx_FormattingButtons_Button_underline" />
|
||||
<Button isActive={formattingStates.strikeThrough === 'reversed'} label={_td('Strike through')} onClick={() => wysiwyg.strikeThrough()} className="mx_FormattingButtons_Button_strike-through" />
|
||||
</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 (content !== null) {
|
||||
onChange(content);
|
||||
}
|
||||
}, [onChange, content]);
|
||||
|
||||
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 wysiwyg={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>;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue