From 3388ddc3b935e6aaa1961783f54a6f602bb0dc79 Mon Sep 17 00:00:00 2001 From: Mikey Stengel Date: Fri, 4 Aug 2023 21:01:31 +0200 Subject: [PATCH 1/6] WIP: Close math editor through ESC and new button --- .../editor-ui/editor-textarea.tsx | 4 +- src/serlo-editor/math/editor.tsx | 56 ++++++-- src/serlo-editor/math/renderer.tsx | 1 + .../plugins/text/components/math-element.tsx | 136 +++++++++++++----- 4 files changed, 146 insertions(+), 51 deletions(-) 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 0eb00b31b9..a7cd5556e0 100644 --- a/src/serlo-editor/math/editor.tsx +++ b/src/serlo-editor/math/editor.tsx @@ -3,7 +3,9 @@ import { faQuestionCircle } 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' @@ -59,6 +61,7 @@ const MathEditorTextArea = (props: MathEditorTextAreaProps) => { 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 + + onMoveOutRight: (options?: { closeThroughModal?: boolean }) => void + onMoveOutLeft(): void onDeleteOutRight?(): void onDeleteOutLeft?(): void } @@ -89,7 +93,19 @@ export function MathEditor(props: MathEditorProps) { const { visual, readOnly, state, disableBlock } = props - const useVisualEditor = visual && !hasError + useHotkeys( + Key.Escape, + (event) => { + event.preventDefault() + // close overlay + props.onMoveOutRight() + }, + { + enableOnFormTags: true, + } + ) + + const isVisualMode = visual && !hasError return ( <> @@ -173,7 +189,11 @@ export function MathEditor(props: MathEditorProps) { return state ? ( ) : ( - + {mathStrings.formula} ) @@ -181,7 +201,7 @@ export function MathEditor(props: MathEditorProps) { return ( <> - {useVisualEditor ? ( + {isVisualMode ? (
e.stopPropagation()} ref={anchorRef} @@ -214,7 +234,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') @@ -234,7 +254,7 @@ export function MathEditor(props: MathEditorProps) { )} - {useVisualEditor && ( + {isVisualMode && (
)} - {hasError || !useVisualEditor ? renderOverlayPortal() : null} + {hasError || !isVisualMode ? renderOverlayPortal() : null} ) } @@ -260,16 +280,24 @@ export function MathEditor(props: MathEditorProps) { } function renderOverlayPortal() { - const children = ( + return (
-

- {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/text/components/math-element.tsx b/src/serlo-editor/plugins/text/components/math-element.tsx index a4058654b0..4337634d0d 100644 --- a/src/serlo-editor/plugins/text/components/math-element.tsx +++ b/src/serlo-editor/plugins/text/components/math-element.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useMemo } from 'react' +import React, { useContext, useEffect, useMemo, useState } from 'react' import { Editor, Node, Path, Range, Transforms } from 'slate' import { ReactEditor, @@ -31,6 +31,12 @@ export function MathElement({ const editor = useSlate() const selected = useSelected() const preferences = useContext(PreferenceContext) + const visualModePreferences = !!preferences.getKey(visualEditorPreferenceKey) + const [isVisualMode, setIsVisualMode] = useState(visualModePreferences) + + useEffect(() => { + setIsVisualMode(visualModePreferences) + }, [visualModePreferences]) const isInsideListElement = useMemo(() => { return isElementWithinList(element, editor) @@ -43,6 +49,13 @@ export function MathElement({ Range.isCollapsed(editor.selection) if (!shouldShowMathEditor) { + console.log('Should not show math editor. Rendering math formula', { + focused, + selected, + editorSelection: editor.selection, + isCollapsed: editor.selection && Range.isCollapsed(editor?.selection), + }) + return ( // Slate void elements need to set attributes and contentEditable={false} // See: https://docs.slatejs.org/api/nodes/element#rendering-void-elements @@ -53,7 +66,43 @@ export function MathElement({ ) } - const isVisualMode = !!preferences.getKey(visualEditorPreferenceKey) + const VoidWrapper = element.inline ? 'span' : 'div' + console.log('Rendering math editor', { + focused, + selected, + editorSelection: editor.selection, + isCollapsed: editor.selection && Range.isCollapsed(editor?.selection), + }) + 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} + + ) function updateElement(update: Partial) { const path = ReactEditor.findPath(editor, element) @@ -149,50 +198,65 @@ export function MathElement({ function transformOutOfElement({ reverse = false, shouldDelete = false, + closeThroughModal, }: { reverse?: boolean shouldDelete?: boolean + closeThroughModal?: boolean } = {}) { const unit = 'character' Transforms.move(editor, { unit, reverse }) - if (shouldDelete) { Transforms.delete(editor, { unit, reverse }) } - ReactEditor.focus(editor) - } + // if (editor.selection) { + // console.log('Setting selection to the end: ', { editor }) + // // move cursor to the end of line + // // setTimeout(() => { + // const endOfNode = Editor.end(editor, editor.selection.focus.path) + // // const endOfNode = Editor.end(editor, editor.selection) - 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} - - ) + // Transforms.setSelection(editor, { anchor: endOfNode, focus: endOfNode }) + + // Transforms.move(editor, { + // edge: 'end', + // unit: 'line', + // }) + // // ReactEditor.focus(editor) + + // // Transforms.setSelection(editor, { + // // anchor: endOfNode, + // // focus: endOfNode, + // // }) + // // }) + // // setTimeout(() => { + // // // Transforms.setSelection(editor, { anchor: end, focus: end }) + // // ReactEditor.focus(editor) + // // }) + // } else { + // console.log( + // 'We have no selection, simply ensure that the editor is focused.' + // ) + // setTimeout(() => { + // }) + // } + + // When calling this function from within the 'x' button to close the + // popup-modal, a small timeout is needed to reset the selection. + // Transforms.deselect() was not needed + if (closeThroughModal) { + setTimeout(() => { + ReactEditor.focus(editor) + // move cursor to the end + // Transforms.move(editor, { + // edge: 'end', + // unit: 'line', + // }) + }) + } else { + ReactEditor.focus(editor) + } + } } From 8347a2d6e97c653222a4545b5f2797261d8ba2b9 Mon Sep 17 00:00:00 2001 From: Mikey Stengel Date: Sun, 6 Aug 2023 11:30:46 +0200 Subject: [PATCH 2/6] Clean up code --- .../plugins/text/components/math-element.tsx | 56 +------------------ 1 file changed, 3 insertions(+), 53 deletions(-) diff --git a/src/serlo-editor/plugins/text/components/math-element.tsx b/src/serlo-editor/plugins/text/components/math-element.tsx index 4337634d0d..f6e4eac849 100644 --- a/src/serlo-editor/plugins/text/components/math-element.tsx +++ b/src/serlo-editor/plugins/text/components/math-element.tsx @@ -49,13 +49,6 @@ export function MathElement({ Range.isCollapsed(editor.selection) if (!shouldShowMathEditor) { - console.log('Should not show math editor. Rendering math formula', { - focused, - selected, - editorSelection: editor.selection, - isCollapsed: editor.selection && Range.isCollapsed(editor?.selection), - }) - return ( // Slate void elements need to set attributes and contentEditable={false} // See: https://docs.slatejs.org/api/nodes/element#rendering-void-elements @@ -67,12 +60,6 @@ export function MathElement({ } const VoidWrapper = element.inline ? 'span' : 'div' - console.log('Rendering math editor', { - focused, - selected, - editorSelection: editor.selection, - isCollapsed: editor.selection && Range.isCollapsed(editor?.selection), - }) return ( // Slate void elements need to set attributes and contentEditable={false} // See: https://docs.slatejs.org/api/nodes/element#rendering-void-elements @@ -211,49 +198,12 @@ export function MathElement({ Transforms.delete(editor, { unit, reverse }) } - // if (editor.selection) { - // console.log('Setting selection to the end: ', { editor }) - // // move cursor to the end of line - // // setTimeout(() => { - // const endOfNode = Editor.end(editor, editor.selection.focus.path) - // // const endOfNode = Editor.end(editor, editor.selection) - - // Transforms.setSelection(editor, { anchor: endOfNode, focus: endOfNode }) - - // Transforms.move(editor, { - // edge: 'end', - // unit: 'line', - // }) - // // ReactEditor.focus(editor) - - // // Transforms.setSelection(editor, { - // // anchor: endOfNode, - // // focus: endOfNode, - // // }) - // // }) - // // setTimeout(() => { - // // // Transforms.setSelection(editor, { anchor: end, focus: end }) - // // ReactEditor.focus(editor) - // // }) - // } else { - // console.log( - // 'We have no selection, simply ensure that the editor is focused.' - // ) - // setTimeout(() => { - // }) - // } - - // When calling this function from within the 'x' button to close the - // popup-modal, a small timeout is needed to reset the selection. - // Transforms.deselect() was not needed + // When calling this function when the 'x' button of the modal is clicked, + // a small timeout is needed to reset the selection. Transforms.deselect() + // was not needed if (closeThroughModal) { setTimeout(() => { ReactEditor.focus(editor) - // move cursor to the end - // Transforms.move(editor, { - // edge: 'end', - // unit: 'line', - // }) }) } else { ReactEditor.focus(editor) From 5ec6b8b51b70603efc9c866d2ba79911d62724b4 Mon Sep 17 00:00:00 2001 From: Mikey Stengel Date: Sun, 6 Aug 2023 11:38:28 +0200 Subject: [PATCH 3/6] Remove horizontal margin of EditorTextArea --- src/serlo-editor/math/editor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/serlo-editor/math/editor.tsx b/src/serlo-editor/math/editor.tsx index a7cd5556e0..7dd613fefd 100644 --- a/src/serlo-editor/math/editor.tsx +++ b/src/serlo-editor/math/editor.tsx @@ -51,7 +51,7 @@ const MathEditorTextArea = (props: MathEditorTextAreaProps) => { return ( Date: Mon, 7 Aug 2023 22:06:21 +0200 Subject: [PATCH 4/6] Implement feedback of Vito and Botho - Hover/focus state & a11y - Remove unneeded code - Define extra function to close modal --- src/serlo-editor/math/editor.tsx | 15 ++++++------ .../plugins/equations/editor/inline-math.tsx | 1 + .../plugins/text/components/math-element.tsx | 23 ++++--------------- 3 files changed, 13 insertions(+), 26 deletions(-) diff --git a/src/serlo-editor/math/editor.tsx b/src/serlo-editor/math/editor.tsx index b847210bad..630461344e 100644 --- a/src/serlo-editor/math/editor.tsx +++ b/src/serlo-editor/math/editor.tsx @@ -1,5 +1,5 @@ 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' @@ -77,8 +77,8 @@ export interface MathEditorProps { onEditorChange(visual: boolean): void onInlineChange?(inline: boolean): void onChange(state: string): void - - onMoveOutRight: (options?: { closeThroughModal?: boolean }) => void + closeMathEditorOverlay: () => void + onMoveOutRight: () => void onMoveOutLeft(): void onDeleteOutRight?(): void onDeleteOutLeft?(): void @@ -98,7 +98,7 @@ export function MathEditor(props: MathEditorProps) { (event) => { event.preventDefault() // close overlay - props.onMoveOutRight() + props.closeMathEditorOverlay() }, { enableOnFormTags: true, @@ -297,11 +297,12 @@ export function MathEditor(props: MathEditorProps) { {hasError ? mathStrings.onlyLatex : mathStrings.latexEditorTitle}

{!isVisualMode && ( 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 f6e4eac849..5cbd200a0d 100644 --- a/src/serlo-editor/plugins/text/components/math-element.tsx +++ b/src/serlo-editor/plugins/text/components/math-element.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useMemo, useState } from 'react' +import React, { useContext, useMemo } from 'react' import { Editor, Node, Path, Range, Transforms } from 'slate' import { ReactEditor, @@ -31,12 +31,7 @@ export function MathElement({ const editor = useSlate() const selected = useSelected() const preferences = useContext(PreferenceContext) - const visualModePreferences = !!preferences.getKey(visualEditorPreferenceKey) - const [isVisualMode, setIsVisualMode] = useState(visualModePreferences) - - useEffect(() => { - setIsVisualMode(visualModePreferences) - }, [visualModePreferences]) + const isVisualMode = !!preferences.getKey(visualEditorPreferenceKey) const isInsideListElement = useMemo(() => { return isElementWithinList(element, editor) @@ -73,6 +68,7 @@ export function MathElement({ disableBlock={isInsideListElement} onInlineChange={handleInlineChange} onChange={(src) => updateElement({ src })} + closeMathEditorOverlay={transformOutOfElement} onMoveOutRight={transformOutOfElement} onMoveOutLeft={() => { transformOutOfElement({ reverse: true }) @@ -185,11 +181,9 @@ export function MathElement({ function transformOutOfElement({ reverse = false, shouldDelete = false, - closeThroughModal, }: { reverse?: boolean shouldDelete?: boolean - closeThroughModal?: boolean } = {}) { const unit = 'character' @@ -198,15 +192,6 @@ export function MathElement({ Transforms.delete(editor, { unit, reverse }) } - // When calling this function when the 'x' button of the modal is clicked, - // a small timeout is needed to reset the selection. Transforms.deselect() - // was not needed - if (closeThroughModal) { - setTimeout(() => { - ReactEditor.focus(editor) - }) - } else { - ReactEditor.focus(editor) - } + ReactEditor.focus(editor) } } From 125b8e39b839044114c6516733a0951410c99d90 Mon Sep 17 00:00:00 2001 From: Mikey Stengel Date: Mon, 7 Aug 2023 23:37:09 +0200 Subject: [PATCH 5/6] Remove comment and let event handler call fn --- src/serlo-editor/math/editor.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/serlo-editor/math/editor.tsx b/src/serlo-editor/math/editor.tsx index 630461344e..32fe11279c 100644 --- a/src/serlo-editor/math/editor.tsx +++ b/src/serlo-editor/math/editor.tsx @@ -97,7 +97,6 @@ export function MathEditor(props: MathEditorProps) { Key.Escape, (event) => { event.preventDefault() - // close overlay props.closeMathEditorOverlay() }, { @@ -297,7 +296,7 @@ export function MathEditor(props: MathEditorProps) { {hasError ? mathStrings.onlyLatex : mathStrings.latexEditorTitle}