diff --git a/demo/Playground.tsx b/demo/Playground.tsx index cab4a17c..23499b0d 100644 --- a/demo/Playground.tsx +++ b/demo/Playground.tsx @@ -5,10 +5,10 @@ import {toaster} from '@gravity-ui/uikit/toaster-singleton-react-18'; import {MarkupString, logger} from '../src'; import { - YfmEditorType, - YfmEditorView, + MarkdownEditorType, + MarkdownEditorView, markupToolbarConfigs, - useYfmEditor, + useMarkdownEditor, wysiwygToolbarConfigs, } from '../src/bundle'; import {RenderPreview, ToolbarActionData} from '../src/bundle/Editor'; @@ -54,7 +54,7 @@ export type PlaygroundProps = { initial?: MarkupString; allowHTML?: boolean; settingsVisible?: boolean; - initialEditor?: YfmEditorType; + initialEditor?: MarkdownEditorType; breaks?: boolean; linkify?: boolean; linkifyTlds?: string | string[]; @@ -90,7 +90,9 @@ export const Playground = React.memo((props) => { renderPreviewDefined, height, } = props; - const [editorType, setEditorType] = React.useState(initialEditor ?? 'wysiwyg'); + const [editorType, setEditorType] = React.useState( + initialEditor ?? 'wysiwyg', + ); const [mdRaw, setMdRaw] = React.useState(initial || ''); React.useEffect(() => { @@ -112,7 +114,7 @@ export const Playground = React.memo((props) => { [allowHTML, breaks, linkify, linkifyTlds, sanitizeHtml], ); - const mdEditor = useYfmEditor({ + const mdEditor = useMarkdownEditor({ allowHTML, linkify, linkifyTlds, @@ -164,7 +166,7 @@ export const Playground = React.memo((props) => { function onChange() { setMdRaw(mdEditor.getValue()); } - function onChangeEditorType({type}: {type: YfmEditorType}) { + function onChangeEditorType({type}: {type: MarkdownEditorType}) { setEditorType(type); } const onToolbarAction = ({id, editorType: type}: ToolbarActionData) => { @@ -263,7 +265,7 @@ export const Playground = React.memo((props) => {
- { - const mdEditor = useYfmEditor({ + const mdEditor = useMarkdownEditor({ initialEditorType: 'wysiwyg', initialToolbarVisible: true, allowHTML: false, @@ -52,7 +52,7 @@ export const PlaygroundEditorInEditor: React.FC = () => {

- ( + (props, ref) => { + const divRef = useEnsuredForwardedRef(ref as MutableRefObject); + + const [isMounted, setIsMounted] = useState(false); + useEffect(() => { + setIsMounted(true); + }, []); + + const [showPreview, , unsetShowPreview, toggleShowPreview] = useBooleanState(false); + + const context = useMarkdownEditorContext(); + const editor = (props.editor ?? context) as EditorInt; + if (!editor) + throw new Error( + '[MarkdownEditorView]: an instance of the editor must be passed through the props or context', + ); + + const { + autofocus, + className, + settingsVisible = true, + markupToolbarConfig = mToolbarConfig, + wysiwygToolbarConfig = wToolbarConfig, + markupHiddenActionsConfig = mHiddenData, + wysiwygHiddenActionsConfig = wHiddenData, + toaster, + stickyToolbar, + } = props; + + const rerender = useUpdate(); + React.useLayoutEffect(() => { + editor.on('rerender', rerender); + return () => { + editor.off('rerender', rerender); + }; + }, [editor, rerender]); + + const onModeChange = React.useCallback( + (type: EditorType) => { + editor.changeEditorType({type, reason: 'settings'}); + unsetShowPreview(); + }, + [editor, unsetShowPreview], + ); + const onToolbarVisibilityChange = React.useCallback( + (visible: boolean) => { + editor.changeToolbarVisibility({visible}); + }, + [editor], + ); + const onSplitModeChange = React.useCallback( + (splitModeEnabled: boolean) => { + unsetShowPreview(); + editor.changeSplitModeEnabled({splitModeEnabled}); + }, + [editor, unsetShowPreview], + ); + + const onShowPreviewChange = React.useCallback( + (showPreviewValue: boolean) => { + editor.changeSplitModeEnabled({splitModeEnabled: false}); + if (showPreviewValue !== showPreview) toggleShowPreview(); + }, + [editor, showPreview, toggleShowPreview], + ); + + const editorType = editor.currentType; + const markupSplitMode = + editor.splitModeEnabled && editor.splitMode && editorType === 'markup'; + const canRenderPreview = Boolean( + editor.renderPreview && editorType === 'markup' && !editor.splitModeEnabled, + ); + + useKey( + (e) => canRenderPreview && isPreviewKeyDown(e), + () => onShowPreviewChange(!showPreview), + {event: 'keydown'}, + [showPreview, editorType, onShowPreviewChange, canRenderPreview], + ); + + const editorWrapperRef = useRef(null); + const splitModeViewWrapperRef = useRef(null); + + const settings = useMemo( + () => ( + + ), + [ + canRenderPreview, + stickyToolbar, + editor.splitMode, + editor.splitModeEnabled, + editor.toolbarVisible, + editorType, + onModeChange, + onShowPreviewChange, + onSplitModeChange, + onToolbarVisibilityChange, + showPreview, + ], + ); + + return ( + { + logger.error(e); + }} + fallbackRender={({error, resetErrorBoundary}) => { + toaster.add({ + theme: 'danger', + name: 'g-md-editor-error', + title: i18n('error-title'), + content: error.message, + }); + setTimeout(() => { + resetErrorBoundary(); + editor.changeEditorType({type: 'markup', reason: 'error-boundary'}); + }); + return null; + }} + > + +
+
+ {showPreview ? ( + <> +
+ {editor.renderPreview?.({ + getValue: editor.getValue, + mode: 'preview', + })} +
+ {settings} + + ) : ( + <> + {editorType === 'wysiwyg' && ( + + {editor.toolbarVisible && settingsVisible && settings} + + )} + {editorType === 'markup' && ( + + {editor.toolbarVisible && settingsVisible && settings} + + )} + {!editor.toolbarVisible && settingsVisible && settings} + + )} +
+ + {markupSplitMode && ( + <> + {editor.splitMode === 'horizontal' ? ( + + ) : ( +
+ )} + + + )} +
+ + + ); + }, +); +MarkdownEditorView.displayName = 'MarkdownEditorView'; + +export function Settings(props: EditorSettingsProps & {stickyToolbar: boolean}) { + const wrapperRef = useRef(null); + const isSticky = useSticky(wrapperRef) && props.toolbarVisibility && props.stickyToolbar; + return ( +
+
+ +
+
+ ); +} + +function isPreviewKeyDown(e: KeyboardEvent) { + const modKey = isMac() ? e.metaKey : e.ctrlKey; + return modKey && e.shiftKey && e.code === 'KeyP'; +} diff --git a/src/bundle/MarkupEditorView.tsx b/src/bundle/MarkupEditorView.tsx index 0a15d1ed..0bc452ab 100644 --- a/src/bundle/MarkupEditorView.tsx +++ b/src/bundle/MarkupEditorView.tsx @@ -85,4 +85,4 @@ export const MarkupEditorView = React.memo((props) => {
); }); -MarkupEditorView.displayName = 'YfmMarkupEditorView'; +MarkupEditorView.displayName = 'MarkdownMarkupEditorView'; diff --git a/src/bundle/WysiwygEditorView.tsx b/src/bundle/WysiwygEditorView.tsx index ebf55537..5ad6958d 100644 --- a/src/bundle/WysiwygEditorView.tsx +++ b/src/bundle/WysiwygEditorView.tsx @@ -69,4 +69,4 @@ export const WysiwygEditorView = React.memo((props) => {
); }); -WysiwygEditorView.displayName = 'YfmWysiwgEditorView'; +WysiwygEditorView.displayName = 'MarkdownWysiwgEditorView'; diff --git a/src/bundle/YfmEditorView.tsx b/src/bundle/YfmEditorView.tsx deleted file mode 100644 index df5e4985..00000000 --- a/src/bundle/YfmEditorView.tsx +++ /dev/null @@ -1,282 +0,0 @@ -import React, {MutableRefObject, useEffect, useMemo, useRef, useState} from 'react'; - -import type {ToasterPublicMethods} from '@gravity-ui/uikit'; -import {ErrorBoundary} from 'react-error-boundary'; -import {useEnsuredForwardedRef, useKey, useUpdate} from 'react-use'; - -import {ClassNameProps, cn} from '../classname'; -import {i18n} from '../i18n/bundle'; -import {logger} from '../logger'; -import {useBooleanState} from '../react-utils/hooks'; -import {ToasterContext} from '../react-utils/toaster'; -import {useSticky} from '../react-utils/useSticky'; -import {isMac} from '../utils/platform'; - -import type {Editor, EditorInt, EditorType} from './Editor'; -import {HorizontalDrag} from './HorizontalDrag'; -import {MarkupEditorView} from './MarkupEditorView'; -import {SplitModeView} from './SplitModeView'; -import {WysiwygEditorView} from './WysiwygEditorView'; -import {MToolbarData, MToolbarSingleItemData, mHiddenData, mToolbarConfig} from './config/markup'; -import {WToolbarData, WToolbarItemData, wHiddenData, wToolbarConfig} from './config/wysiwyg'; -import {useYfmEditorContext} from './context'; -import {EditorSettings, EditorSettingsProps} from './settings'; -import {stickyCn} from './sticky'; - -import '../styles/styles.scss'; -import './YfmEditorView.scss'; // eslint-disable-line import/order - -export const cnEditorComponent = cn('editor-component'); -const b = cnEditorComponent; - -export type YfmEditorViewProps = ClassNameProps & { - editor?: Editor; - autofocus?: boolean; - markupToolbarConfig?: MToolbarData; - wysiwygToolbarConfig?: WToolbarData; - markupHiddenActionsConfig?: MToolbarSingleItemData[]; - wysiwygHiddenActionsConfig?: WToolbarItemData[]; - /** @default true */ - settingsVisible?: boolean; - toaster: ToasterPublicMethods; - stickyToolbar: boolean; -}; - -export const YfmEditorView = React.forwardRef((props, ref) => { - const divRef = useEnsuredForwardedRef(ref as MutableRefObject); - - const [isMounted, setIsMounted] = useState(false); - useEffect(() => { - setIsMounted(true); - }, []); - - const [showPreview, , unsetShowPreview, toggleShowPreview] = useBooleanState(false); - - const context = useYfmEditorContext(); - const editor = (props.editor ?? context) as EditorInt; - if (!editor) - throw new Error( - '[YfmEditorView]: an instance of the editor must be passed through the props or context', - ); - - const { - autofocus, - className, - settingsVisible = true, - markupToolbarConfig = mToolbarConfig, - wysiwygToolbarConfig = wToolbarConfig, - markupHiddenActionsConfig = mHiddenData, - wysiwygHiddenActionsConfig = wHiddenData, - toaster, - stickyToolbar, - } = props; - - const rerender = useUpdate(); - React.useLayoutEffect(() => { - editor.on('rerender', rerender); - return () => { - editor.off('rerender', rerender); - }; - }, [editor, rerender]); - - const onModeChange = React.useCallback( - (type: EditorType) => { - editor.changeEditorType({type, reason: 'settings'}); - unsetShowPreview(); - }, - [editor, unsetShowPreview], - ); - const onToolbarVisibilityChange = React.useCallback( - (visible: boolean) => { - editor.changeToolbarVisibility({visible}); - }, - [editor], - ); - const onSplitModeChange = React.useCallback( - (splitModeEnabled: boolean) => { - unsetShowPreview(); - editor.changeSplitModeEnabled({splitModeEnabled}); - }, - [editor, unsetShowPreview], - ); - - const onShowPreviewChange = React.useCallback( - (showPreviewValue: boolean) => { - editor.changeSplitModeEnabled({splitModeEnabled: false}); - if (showPreviewValue !== showPreview) toggleShowPreview(); - }, - [editor, showPreview, toggleShowPreview], - ); - - const editorType = editor.currentType; - const markupSplitMode = editor.splitModeEnabled && editor.splitMode && editorType === 'markup'; - const canRenderPreview = Boolean( - editor.renderPreview && editorType === 'markup' && !editor.splitModeEnabled, - ); - - useKey( - (e) => canRenderPreview && isPreviewKeyDown(e), - () => onShowPreviewChange(!showPreview), - {event: 'keydown'}, - [showPreview, editorType, onShowPreviewChange, canRenderPreview], - ); - - const editorWrapperRef = useRef(null); - const splitModeViewWrapperRef = useRef(null); - - const settings = useMemo( - () => ( - - ), - [ - canRenderPreview, - stickyToolbar, - editor.splitMode, - editor.splitModeEnabled, - editor.toolbarVisible, - editorType, - onModeChange, - onShowPreviewChange, - onSplitModeChange, - onToolbarVisibilityChange, - showPreview, - ], - ); - - return ( - { - logger.error(e); - }} - fallbackRender={({error, resetErrorBoundary}) => { - toaster.add({ - theme: 'danger', - name: 'yfm-editor', - title: i18n('error-title'), - content: error.message, - }); - setTimeout(() => { - resetErrorBoundary(); - editor.changeEditorType({type: 'markup', reason: 'error-boundary'}); - }); - return null; - }} - > - -
-
- {showPreview ? ( - <> -
- {editor.renderPreview?.({ - getValue: editor.getValue, - mode: 'preview', - })} -
- {settings} - - ) : ( - <> - {editorType === 'wysiwyg' && ( - - {editor.toolbarVisible && settingsVisible && settings} - - )} - {editorType === 'markup' && ( - - {editor.toolbarVisible && settingsVisible && settings} - - )} - {!editor.toolbarVisible && settingsVisible && settings} - - )} -
- - {markupSplitMode && ( - <> - {editor.splitMode === 'horizontal' ? ( - - ) : ( -
- )} - - - )} -
- - - ); -}); -YfmEditorView.displayName = 'YfmEditorView'; - -export function Settings(props: EditorSettingsProps & {stickyToolbar: boolean}) { - const wrapperRef = useRef(null); - const isSticky = useSticky(wrapperRef) && props.toolbarVisibility && props.stickyToolbar; - return ( -
-
- -
-
- ); -} - -function isPreviewKeyDown(e: KeyboardEvent) { - const modKey = isMac() ? e.metaKey : e.ctrlKey; - return modKey && e.shiftKey && e.code === 'KeyP'; -} diff --git a/src/bundle/context.ts b/src/bundle/context.ts index 844c3f7d..ddfacab7 100644 --- a/src/bundle/context.ts +++ b/src/bundle/context.ts @@ -4,7 +4,7 @@ import {Editor} from './Editor'; const EditorContext = createContext(null); -export const YfmEditorProvider = EditorContext.Provider; -export function useYfmEditorContext() { +export const MarkdownEditorProvider = EditorContext.Provider; +export function useMarkdownEditorContext() { return useContext(EditorContext); } diff --git a/src/bundle/index.ts b/src/bundle/index.ts index 9191dfa2..5fb86a74 100644 --- a/src/bundle/index.ts +++ b/src/bundle/index.ts @@ -1,8 +1,8 @@ export type {ExtensionsOptions} from './wysiwyg-preset'; -export type {Editor, EditorType as YfmEditorType, RenderPreview, SplitMode} from './Editor'; +export type {Editor, EditorType as MarkdownEditorType, RenderPreview, SplitMode} from './Editor'; export * from './context'; -export * from './useYfmEditor'; -export * from './YfmEditorView'; +export * from './useMarkdownEditor'; +export * from './MarkdownEditorView'; // For project that do not support star export (export * as something from '...') import * as markupToolbarConfigs from './config/markup'; diff --git a/src/bundle/useYfmEditor.ts b/src/bundle/useMarkdownEditor.ts similarity index 93% rename from src/bundle/useYfmEditor.ts rename to src/bundle/useMarkdownEditor.ts index 09aad3d4..1109da0c 100644 --- a/src/bundle/useYfmEditor.ts +++ b/src/bundle/useMarkdownEditor.ts @@ -7,7 +7,7 @@ import {logger} from '../logger'; import {Editor, EditorImpl, EditorInt, EditorOptions, EditorType} from './Editor'; import {BundlePreset, ExtensionsOptions} from './wysiwyg-preset'; -export type UseYfmEditorProps = Omit< +export type UseMarkdownEditorProps = Omit< EditorOptions, 'extensions' | 'renderStorage' > & { @@ -18,8 +18,8 @@ export type UseYfmEditorProps = Omit< extraExtensions?: Extension; }; -export function useYfmEditor( - props: UseYfmEditorProps, +export function useMarkdownEditor( + props: UseMarkdownEditorProps, deps: React.DependencyList = [], ): Editor { const editor = useMemo(