diff --git a/demo/components/Playground.tsx b/demo/components/Playground.tsx index 3492b81f..b6c5708f 100644 --- a/demo/components/Playground.tsx +++ b/demo/components/Playground.tsx @@ -16,6 +16,7 @@ import { type RenderPreview, type ToolbarGroupData, type UseMarkdownEditorProps, + WysiwygPlaceholderOptions, logger, markupToolbarConfigs, useMarkdownEditor, @@ -79,6 +80,7 @@ export type PlaygroundProps = { breaks?: boolean; linkify?: boolean; linkifyTlds?: string | string[]; + placeholderOptions?: WysiwygPlaceholderOptions; sanitizeHtml?: boolean; prepareRawMarkup?: boolean; splitModeOrientation?: 'horizontal' | 'vertical' | false; @@ -139,6 +141,7 @@ export const Playground = React.memo((props) => { wysiwygCommandMenuConfig, markupConfigExtensions, markupToolbarConfig, + placeholderOptions, escapeConfig, enableSubmitInPreview, hidePreviewAfterSubmit, @@ -185,6 +188,9 @@ export const Playground = React.memo((props) => { needToSetDimensionsForUploadedImages, renderPreview: renderPreviewDefined ? renderPreview : undefined, fileUploadHandler, + wysiwygConfig: { + placeholderOptions: placeholderOptions, + }, experimental: { ...experimental, directiveSyntax, diff --git a/src/bundle/types.ts b/src/bundle/types.ts index 34d17136..b25ad335 100644 --- a/src/bundle/types.ts +++ b/src/bundle/types.ts @@ -26,6 +26,17 @@ export type RenderPreview = (params: RenderPreviewParams) => ReactNode; export type ParseInsertedUrlAsImage = (text: string) => {imageUrl: string; title?: string} | null; +export type WysiwygPlaceholderOptions = { + value?: string | (() => string); + /** Default – empty-doc + Values: + - 'empty-doc' – The placeholder will only be shown when the document is completely empty; + - 'empty-row-top-level' – The placeholder will be displayed in an empty line that is at the top level of the document structure; + - 'empty-row' – The placeholder will be shown in any empty line within the document, regardless of its nesting level. + */ + behavior?: 'empty-doc' | 'empty-row-top-level' | 'empty-row'; +}; + export type MarkdownEditorMdOptions = { html?: boolean; breaks?: boolean; @@ -148,6 +159,7 @@ export type MarkdownEditorWysiwygConfig = { extensions?: Extension; extensionOptions?: ExtensionsOptions; escapeConfig?: EscapeConfig; + placeholderOptions?: WysiwygPlaceholderOptions; }; // [major] TODO: remove generic type diff --git a/src/bundle/useMarkdownEditor.ts b/src/bundle/useMarkdownEditor.ts index b44cb463..6cdb1a7c 100644 --- a/src/bundle/useMarkdownEditor.ts +++ b/src/bundle/useMarkdownEditor.ts @@ -59,6 +59,7 @@ export function useMarkdownEditor( editor.emit('submit', null); return true; }, + placeholderOptions: wysiwygConfig.placeholderOptions, mdBreaks: breaks, fileUploadHandler: uploadFile, needToSetDimensionsForUploadedImages, diff --git a/src/bundle/wysiwyg-preset.ts b/src/bundle/wysiwyg-preset.ts index 42874148..af43bc4a 100644 --- a/src/bundle/wysiwyg-preset.ts +++ b/src/bundle/wysiwyg-preset.ts @@ -16,7 +16,7 @@ import type {FileUploadHandler} from '../utils/upload'; import {wCommandMenuConfigByPreset, wSelectionMenuConfigByPreset} from './config/wysiwyg'; import {emojiDefs} from './emoji'; -import type {MarkdownEditorPreset} from './types'; +import type {MarkdownEditorPreset, WysiwygPlaceholderOptions} from './types'; const DEFAULT_IGNORED_KEYS = ['Tab', 'Shift-Tab'] as const; @@ -27,6 +27,7 @@ export type BundlePresetOptions = ExtensionsOptions & preset: MarkdownEditorPreset; mdBreaks?: boolean; fileUploadHandler?: FileUploadHandler; + placeholderOptions?: WysiwygPlaceholderOptions; /** * If we need to set dimensions for uploaded images * @@ -63,9 +64,22 @@ export const BundlePreset: ExtensionAuto = (builder, opts) baseSchema: { paragraphKey: f.toPM(A.Text), paragraphPlaceholder: (node: Node, parent?: Node | null) => { - const isDocEmpty = - !node.text && parent?.type.name === BaseNode.Doc && parent.childCount === 1; - return isDocEmpty ? i18nPlaceholder('doc_empty') : null; + const {value, behavior} = opts.placeholderOptions || {}; + + const emptyEntries = { + 'empty-row': !node.text, + 'empty-row-top-level': !node.text && parent?.type.name === BaseNode.Doc, + 'empty-doc': + !node.text && parent?.type.name === BaseNode.Doc && parent.childCount === 1, + }; + + const showPlaceholder = emptyEntries[behavior || 'empty-doc']; + + if (!showPlaceholder) return null; + + return typeof value === 'function' + ? value() + : value ?? i18nPlaceholder('doc_empty'); }, ...opts.baseSchema, },