diff --git a/packages/core/dev-test/config.yml b/packages/core/dev-test/config.yml index b1112c6b..ae845d87 100644 --- a/packages/core/dev-test/config.yml +++ b/packages/core/dev-test/config.yml @@ -694,6 +694,10 @@ collections: label: Required With Default widget: markdown default: Default **markdown** value + - name: raw + label: Raw Editor + widget: markdown + show_raw: true - name: pattern label: Pattern Validation widget: markdown diff --git a/packages/core/src/components/common/text-field/TextArea.tsx b/packages/core/src/components/common/text-field/TextArea.tsx index ba837731..2f188e40 100644 --- a/packages/core/src/components/common/text-field/TextArea.tsx +++ b/packages/core/src/components/common/text-field/TextArea.tsx @@ -6,6 +6,8 @@ import type { ChangeEventHandler, RefObject } from 'react'; export interface TextAreaProps { value: string; disabled?: boolean; + placeholder?: string; + className?: string; 'data-testid'?: string; onChange: ChangeEventHandler; } @@ -18,7 +20,7 @@ function getHeight(rawHeight: string): number { } const TextArea = forwardRef( - ({ value, disabled, 'data-testid': dataTestId, onChange }, ref) => { + ({ value, disabled, placeholder, className, 'data-testid': dataTestId, onChange }, ref) => { const [lastAutogrowHeight, setLastAutogrowHeight] = useState(MIN_TEXT_AREA_HEIGHT); const autoGrow = useCallback(() => { @@ -63,10 +65,12 @@ const TextArea = forwardRef( className: ` flex w-full + ${className} `, }, input: { ref, + placeholder, className: ` w-full min-h-[80px] diff --git a/packages/core/src/interface.ts b/packages/core/src/interface.ts index 00f81f86..f982ed0a 100644 --- a/packages/core/src/interface.ts +++ b/packages/core/src/interface.ts @@ -769,6 +769,7 @@ export interface MarkdownField extends MediaField { widget: 'markdown'; toolbar_buttons?: MarkdownFieldToolbarButtons; default?: string; + show_raw?: string; } export interface NumberField extends BaseField { diff --git a/packages/core/src/locales/en/index.ts b/packages/core/src/locales/en/index.ts index 0332cd76..3cd5b64f 100644 --- a/packages/core/src/locales/en/index.ts +++ b/packages/core/src/locales/en/index.ts @@ -144,6 +144,7 @@ const en: LocalePhrasesRoot = { addComponent: 'Add Component', richText: 'Rich Text', markdown: 'Markdown', + type: 'Type...', }, image: { choose: 'Choose an image', diff --git a/packages/core/src/widgets/markdown/plate/PlateEditor.tsx b/packages/core/src/widgets/markdown/plate/PlateEditor.tsx index 4ec7d077..37fdfb68 100644 --- a/packages/core/src/widgets/markdown/plate/PlateEditor.tsx +++ b/packages/core/src/widgets/markdown/plate/PlateEditor.tsx @@ -53,6 +53,7 @@ import { StyledLeaf } from '@udecode/plate-styled-components'; import React, { useMemo, useRef } from 'react'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; +import { useTranslate } from 'react-polyglot'; import useUUID from '@staticcms/core/lib/hooks/useUUID'; import { CodeBlockElement, withShortcodeElement } from './components'; @@ -104,6 +105,7 @@ import type { } from '@staticcms/core/interface'; import type { AnyObject, AutoformatPlugin, PlatePlugin } from '@udecode/plate'; import type { FC } from 'react'; +import type { t as T } from 'react-polyglot'; import type { MdEditor, MdValue } from './plateTypes'; export interface PlateEditorProps { @@ -129,6 +131,8 @@ const PlateEditor: FC = ({ onFocus, onBlur, }) => { + const t = useTranslate() as T; + const editorContainerRef = useRef(null); const innerEditorContainerRef = useRef(null); @@ -278,6 +282,7 @@ const PlateEditor: FC = ({ id={id} editableProps={{ ...editableProps, + placeholder: t('editor.editorWidgets.markdown.type'), onFocus, onBlur, className: '!outline-none', diff --git a/packages/core/src/widgets/markdown/plate/editableProps.ts b/packages/core/src/widgets/markdown/plate/editableProps.ts index 3a6979c8..3978f621 100644 --- a/packages/core/src/widgets/markdown/plate/editableProps.ts +++ b/packages/core/src/widgets/markdown/plate/editableProps.ts @@ -5,7 +5,6 @@ const editableProps: TEditableProps = { spellCheck: false, autoFocus: false, readOnly: false, - placeholder: 'Type…', }; export default editableProps; diff --git a/packages/core/src/widgets/markdown/plate/hooks/useMarkdownToSlate.ts b/packages/core/src/widgets/markdown/plate/hooks/useMarkdownToSlate.ts index 980bffbd..ad71782c 100644 --- a/packages/core/src/widgets/markdown/plate/hooks/useMarkdownToSlate.ts +++ b/packages/core/src/widgets/markdown/plate/hooks/useMarkdownToSlate.ts @@ -14,6 +14,7 @@ import type { MdValue } from '../plateTypes'; export interface UseMarkdownToSlateOptions { shortcodeConfigs?: Record; useMdx: boolean; + unload?: boolean; } export const markdownToSlate = async ( @@ -45,12 +46,17 @@ const useMarkdownToSlate = ( const [slateValue, setSlateValue] = useState([]); useEffect(() => { + if (loaded && !options.unload) { + return; + } + markdownToSlate(markdownValue, options).then(value => { setSlateValue(value); + setLoaded(true); }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [markdownValue]); return [ slateValue.length > 0 ? slateValue : [{ type: ELEMENT_PARAGRAPH, children: [{ text: '' }] }], diff --git a/packages/core/src/widgets/markdown/schema.ts b/packages/core/src/widgets/markdown/schema.ts index 21b257ae..f8cd7b24 100644 --- a/packages/core/src/widgets/markdown/schema.ts +++ b/packages/core/src/widgets/markdown/schema.ts @@ -106,6 +106,7 @@ export default { public_folder: { type: 'string' }, choose_url: { type: 'boolean' }, multiple: { type: 'boolean' }, + show_raw: { type: 'boolean' }, toolbar_buttons: { type: 'object', properties: { diff --git a/packages/core/src/widgets/markdown/withMarkdownControl.tsx b/packages/core/src/widgets/markdown/withMarkdownControl.tsx index abca5269..3a83a12b 100644 --- a/packages/core/src/widgets/markdown/withMarkdownControl.tsx +++ b/packages/core/src/widgets/markdown/withMarkdownControl.tsx @@ -1,13 +1,15 @@ import React, { useCallback, useMemo, useState } from 'react'; +import Button from '@staticcms/core/components/common/button/Button'; import Field from '@staticcms/core/components/common/field/Field'; +import TextArea from '@staticcms/core/components/common/text-field/TextArea'; import useDebounce from '../../lib/hooks/useDebounce'; -import useMarkdownToSlate from './plate/hooks/useMarkdownToSlate'; import PlateEditor from './plate/PlateEditor'; +import useMarkdownToSlate from './plate/hooks/useMarkdownToSlate'; import serializeMarkdown from './plate/serialization/serializeMarkdown'; import type { MarkdownField, WidgetControlProps } from '@staticcms/core/interface'; -import type { FC } from 'react'; +import type { ChangeEvent, FC } from 'react'; import type { MdValue } from './plate/plateTypes'; export interface WithMarkdownControlProps { @@ -28,6 +30,7 @@ const withMarkdownControl = ({ useMdx }: WithMarkdownControlProps) => { errors, forSingleList, disabled, + t, } = controlProps; const [internalRawValue, setInternalValue] = useState(value ?? ''); @@ -38,6 +41,8 @@ const withMarkdownControl = ({ useMdx }: WithMarkdownControlProps) => { const [hasFocus, setHasFocus] = useState(false); const debouncedFocus = useDebounce(hasFocus, 150); + const [showRaw, setShowRaw] = useState(false); + const handleOnFocus = useCallback(() => { setHasFocus(true); }, []); @@ -57,37 +62,51 @@ const withMarkdownControl = ({ useMdx }: WithMarkdownControlProps) => { [internalValue, onChange], ); + const handleRawOnChange = useCallback( + (event: ChangeEvent) => { + const rawValue = event.target.value; + if (rawValue !== internalValue) { + setInternalValue(rawValue); + onChange(rawValue); + } + }, + [internalValue, onChange], + ); + const handleLabelClick = useCallback(() => { // editorRef.current?.getInstance().focus(); }, []); - const [slateValue, loaded] = useMarkdownToSlate(internalValue, { useMdx }); + const handleShowRaw = useCallback(() => { + if (!field.show_raw) { + return; + } - return useMemo( - () => ( - - {loaded ? ( - - ) : null} - - ), + setShowRaw(true); + }, [field.show_raw]); + + const handleShowRich = useCallback(() => { + setShowRaw(false); + }, []); + + const [slateValue, loaded] = useMarkdownToSlate(internalValue, { useMdx, unload: showRaw }); + + const richEditor = useMemo( + () => + loaded ? ( + + ) : null, // eslint-disable-next-line react-hooks/exhaustive-deps [ collection, @@ -100,11 +119,57 @@ const withMarkdownControl = ({ useMdx }: WithMarkdownControlProps) => { handleOnFocus, hasErrors, hasFocus, - label, loaded, slateValue, + showRaw, ], ); + + return ( + + {showRaw ? ( +