Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Close math editor through ESC and new button #2697

Merged
merged 7 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/data/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,7 @@ export const loggedInData = {
eG: 'e.g.',
functions: 'Functions',
displayAsBlock: 'Display as block',
closeMathFormulaEditor: "Close math formula editor",
},
},
video: {
Expand Down
4 changes: 3 additions & 1 deletion src/serlo-editor/editor-ui/editor-textarea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ type EditorTextareaProps = TextareaHTMLAttributes<HTMLTextAreaElement> & {
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 (
Expand All @@ -22,6 +23,7 @@ export const EditorTextarea = forwardRef<
)}
{...props}
ref={ref}
data-qa={dataQa}
onKeyDown={(e) => {
if (!ref || typeof ref === 'function' || !ref.current) return

Expand Down
60 changes: 44 additions & 16 deletions src/serlo-editor/math/editor.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -49,7 +51,7 @@ const MathEditorTextArea = (props: MathEditorTextAreaProps) => {
return (
<EditorTextarea
className={tw`
!m-0.5 h-24 !w-[80vw] !max-w-[600px] rounded-md !border-2
mx-0 my-1 h-24 !w-[80vw] !max-w-[600px] rounded-md !border-2
border-transparent text-black !shadow-none focus:border-editor-primary
`}
onChange={parentOnChange}
Expand All @@ -59,6 +61,7 @@ const MathEditorTextArea = (props: MathEditorTextAreaProps) => {
onMoveOutLeft={props.onMoveOutLeft}
value={latex}
ref={textareaRef}
dataQa="plugin-math-latex-editor"
/>
)
}
Expand All @@ -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
}
Expand All @@ -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 (
<>
Expand Down Expand Up @@ -173,15 +188,19 @@ export function MathEditor(props: MathEditorProps) {
return state ? (
<MathRenderer {...props} />
) : (
<span className="bg-gray-300" {...props.additionalContainerProps}>
<span
className="bg-gray-300"
{...props.additionalContainerProps}
data-qa="plugin-math-renderer"
>
{mathStrings.formula}
</span>
)
}

return (
<>
{useVisualEditor ? (
{isVisualMode ? (
<div
onClick={(e) => e.stopPropagation()}
ref={anchorRef}
Expand Down Expand Up @@ -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')
Expand All @@ -241,7 +260,7 @@ export function MathEditor(props: MathEditorProps) {
<FaIcon icon={props.inline ? faCircle : faCheckCircle} />
</button>
)}
{useVisualEditor && (
{isVisualMode && (
<button
onMouseDown={() => setHelpOpen(true)}
className="mx-2 text-almost-black hover:text-editor-primary"
Expand All @@ -252,7 +271,7 @@ export function MathEditor(props: MathEditorProps) {
</div>
)}

{hasError || !useVisualEditor ? renderOverlayPortal() : null}
{hasError || !isVisualMode ? renderOverlayPortal() : null}
</>
)
}
Expand All @@ -267,19 +286,28 @@ export function MathEditor(props: MathEditorProps) {
}

function renderOverlayPortal() {
const children = (
return (
<div
className="fixed bottom-0 z-50 rounded-t-xl bg-editor-primary-100 p-3 shadow-menu"
onClick={(e) => e.stopPropagation()} // double/triple clicks close overlay otherwise (#2700)
>
<p className="mr-0.5 mt-1 text-right text-sm font-bold text-gray-600">
{hasError ? mathStrings.onlyLatex : mathStrings.latexEditorTitle}
</p>
{!useVisualEditor && (
<div className="flex items-center justify-between">
<p className="mr-0.5 mt-1 text-right text-sm font-bold text-gray-600">
{hasError ? mathStrings.onlyLatex : mathStrings.latexEditorTitle}
</p>
<button
onClick={props.closeMathEditorOverlay}
className="serlo-button-editor-secondary py-0"
aria-label={mathStrings.closeMathFormulaEditor}
data-qa="plugin-math-close-formula-editor"
>
<FaIcon icon={faXmark} />
</button>
</div>
{!isVisualMode && (
<MathEditorTextArea {...props} defaultValue={state} />
)}
</div>
)
return children
}
}
1 change: 1 addition & 0 deletions src/serlo-editor/math/renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const MathRenderer = React.memo(
}
}
{...additionalContainerProps}
data-qa="plugin-math-renderer"
/>
)

Expand Down
1 change: 1 addition & 0 deletions src/serlo-editor/plugins/equations/editor/inline-math.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export function InlineMath(props: InlineMathProps) {
onChange={onChange}
onMoveOutRight={onFocusNext}
onMoveOutLeft={onFocusPrevious}
closeMathEditorOverlay={onFocusNext}
/>
)
}
67 changes: 33 additions & 34 deletions src/serlo-editor/plugins/text/components/math-element.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
<VoidWrapper {...attributes} tabIndex={-1} contentEditable={false}>
<MathEditor
autofocus
state={element.src}
inline={element.inline}
readOnly={false}
visual={isVisualMode}
disableBlock={isInsideListElement}
onInlineChange={handleInlineChange}
onChange={(src) => 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}
</VoidWrapper>
)

function updateElement(update: Partial<MathElementType>) {
const path = ReactEditor.findPath(editor, element)
Expand Down Expand Up @@ -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
<VoidWrapper {...attributes} tabIndex={-1} contentEditable={false}>
<MathEditor
autofocus
state={element.src}
inline={element.inline}
readOnly={false}
visual={isVisualMode}
disableBlock={isInsideListElement}
onInlineChange={handleInlineChange}
onChange={(src) => 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}
</VoidWrapper>
)
}