diff --git a/src/data/en/index.ts b/src/data/en/index.ts index 6512c9e57b..3eb4d394de 100644 --- a/src/data/en/index.ts +++ b/src/data/en/index.ts @@ -875,6 +875,7 @@ export const loggedInData = { eG: 'e.g.', functions: 'Functions', displayAsBlock: 'Display as block', + closeMathFormulaEditor: "Close math formula editor", }, }, video: { diff --git a/src/serlo-editor/editor-ui/editor-textarea.tsx b/src/serlo-editor/editor-ui/editor-textarea.tsx index e9833a8584..242923b1fc 100644 --- a/src/serlo-editor/editor-ui/editor-textarea.tsx +++ b/src/serlo-editor/editor-ui/editor-textarea.tsx @@ -5,13 +5,14 @@ type EditorTextareaProps = TextareaHTMLAttributes & { onMoveOutRight?(): void onMoveOutLeft?(): void className?: string + dataQa?: string } export const EditorTextarea = forwardRef< HTMLTextAreaElement, EditorTextareaProps >(function EditorTextarea( - { onMoveOutLeft, onMoveOutRight, className, ...props }, + { onMoveOutLeft, onMoveOutRight, className, dataQa, ...props }, ref ) { return ( @@ -22,6 +23,7 @@ export const EditorTextarea = forwardRef< )} {...props} ref={ref} + data-qa={dataQa} onKeyDown={(e) => { if (!ref || typeof ref === 'function' || !ref.current) return diff --git a/src/serlo-editor/math/editor.tsx b/src/serlo-editor/math/editor.tsx index 3df72a70ea..2eb2bcc8f1 100644 --- a/src/serlo-editor/math/editor.tsx +++ b/src/serlo-editor/math/editor.tsx @@ -1,9 +1,11 @@ import { faCheckCircle, faCircle } from '@fortawesome/free-regular-svg-icons' -import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons' +import { faQuestionCircle, faXmark } from '@fortawesome/free-solid-svg-icons' import clsx from 'clsx' import { useState, useCallback, createRef, useEffect } from 'react' import { createPortal } from 'react-dom' +import { useHotkeys } from 'react-hotkeys-hook' import Modal from 'react-modal' +import { Key } from 'ts-key-enum' import { MathRenderer } from './renderer' import { VisualEditor } from './visual-editor' @@ -49,7 +51,7 @@ const MathEditorTextArea = (props: MathEditorTextAreaProps) => { return ( { onMoveOutLeft={props.onMoveOutLeft} value={latex} ref={textareaRef} + dataQa="plugin-math-latex-editor" /> ) } @@ -74,8 +77,9 @@ export interface MathEditorProps { onEditorChange(visual: boolean): void onInlineChange?(inline: boolean): void onChange(state: string): void - onMoveOutRight?(): void - onMoveOutLeft?(): void + closeMathEditorOverlay: () => void + onMoveOutRight: () => void + onMoveOutLeft(): void onDeleteOutRight?(): void onDeleteOutLeft?(): void } @@ -89,7 +93,18 @@ export function MathEditor(props: MathEditorProps) { const { visual, readOnly, state, disableBlock } = props - const useVisualEditor = visual && !hasError + useHotkeys( + Key.Escape, + (event) => { + event.preventDefault() + props.closeMathEditorOverlay() + }, + { + enableOnFormTags: true, + } + ) + + const isVisualMode = visual && !hasError return ( <> @@ -173,7 +188,11 @@ export function MathEditor(props: MathEditorProps) { return state ? ( ) : ( - + {mathStrings.formula} ) @@ -181,7 +200,7 @@ export function MathEditor(props: MathEditorProps) { return ( <> - {useVisualEditor ? ( + {isVisualMode ? (
e.stopPropagation()} ref={anchorRef} @@ -221,7 +240,7 @@ export function MathEditor(props: MathEditorProps) { px-1 py-[2px] text-base text-almost-black transition-all hover:bg-editor-primary-200 focus:bg-editor-primary-200 focus:outline-none `} - value={useVisualEditor ? 'visual' : 'latex'} + value={isVisualMode ? 'visual' : 'latex'} onChange={(e) => { if (hasError) setHasError(false) props.onEditorChange(e.target.value === 'visual') @@ -241,7 +260,7 @@ export function MathEditor(props: MathEditorProps) { )} - {useVisualEditor && ( + {isVisualMode && (
)} - {hasError || !useVisualEditor ? renderOverlayPortal() : null} + {hasError || !isVisualMode ? renderOverlayPortal() : null} ) } @@ -267,19 +286,28 @@ export function MathEditor(props: MathEditorProps) { } function renderOverlayPortal() { - const children = ( + return (
e.stopPropagation()} // double/triple clicks close overlay otherwise (#2700) > -

- {hasError ? mathStrings.onlyLatex : mathStrings.latexEditorTitle} -

- {!useVisualEditor && ( +
+

+ {hasError ? mathStrings.onlyLatex : mathStrings.latexEditorTitle} +

+ +
+ {!isVisualMode && ( )}
) - return children } } diff --git a/src/serlo-editor/math/renderer.tsx b/src/serlo-editor/math/renderer.tsx index 793f08c9ff..4bca2b9dad 100644 --- a/src/serlo-editor/math/renderer.tsx +++ b/src/serlo-editor/math/renderer.tsx @@ -32,6 +32,7 @@ export const MathRenderer = React.memo( } } {...additionalContainerProps} + data-qa="plugin-math-renderer" /> ) diff --git a/src/serlo-editor/plugins/equations/editor/inline-math.tsx b/src/serlo-editor/plugins/equations/editor/inline-math.tsx index ee67b29990..3e475cab09 100644 --- a/src/serlo-editor/plugins/equations/editor/inline-math.tsx +++ b/src/serlo-editor/plugins/equations/editor/inline-math.tsx @@ -49,6 +49,7 @@ export function InlineMath(props: InlineMathProps) { onChange={onChange} onMoveOutRight={onFocusNext} onMoveOutLeft={onFocusPrevious} + closeMathEditorOverlay={onFocusNext} /> ) } diff --git a/src/serlo-editor/plugins/text/components/math-element.tsx b/src/serlo-editor/plugins/text/components/math-element.tsx index a4058654b0..5cbd200a0d 100644 --- a/src/serlo-editor/plugins/text/components/math-element.tsx +++ b/src/serlo-editor/plugins/text/components/math-element.tsx @@ -31,6 +31,7 @@ export function MathElement({ const editor = useSlate() const selected = useSelected() const preferences = useContext(PreferenceContext) + const isVisualMode = !!preferences.getKey(visualEditorPreferenceKey) const isInsideListElement = useMemo(() => { return isElementWithinList(element, editor) @@ -53,7 +54,38 @@ export function MathElement({ ) } - const isVisualMode = !!preferences.getKey(visualEditorPreferenceKey) + const VoidWrapper = element.inline ? 'span' : 'div' + return ( + // Slate void elements need to set attributes and contentEditable={false} + // See: https://docs.slatejs.org/api/nodes/element#rendering-void-elements + + updateElement({ src })} + closeMathEditorOverlay={transformOutOfElement} + onMoveOutRight={transformOutOfElement} + onMoveOutLeft={() => { + transformOutOfElement({ reverse: true }) + }} + onDeleteOutRight={() => { + transformOutOfElement({ shouldDelete: true }) + }} + onDeleteOutLeft={() => { + transformOutOfElement({ shouldDelete: true, reverse: true }) + }} + onEditorChange={(visual) => + preferences.setKey(visualEditorPreferenceKey, visual) + } + /> + {children} + + ) function updateElement(update: Partial) { const path = ReactEditor.findPath(editor, element) @@ -156,43 +188,10 @@ export function MathElement({ const unit = 'character' Transforms.move(editor, { unit, reverse }) - if (shouldDelete) { Transforms.delete(editor, { unit, reverse }) } ReactEditor.focus(editor) } - - const VoidWrapper = element.inline ? 'span' : 'div' - return ( - // Slate void elements need to set attributes and contentEditable={false} - // See: https://docs.slatejs.org/api/nodes/element#rendering-void-elements - - updateElement({ src })} - onMoveOutRight={transformOutOfElement} - onMoveOutLeft={() => { - transformOutOfElement({ reverse: true }) - }} - onDeleteOutRight={() => { - transformOutOfElement({ shouldDelete: true }) - }} - onDeleteOutLeft={() => { - transformOutOfElement({ shouldDelete: true, reverse: true }) - }} - onEditorChange={(visual) => - preferences.setKey(visualEditorPreferenceKey, visual) - } - /> - {children} - - ) }