Skip to content

Commit

Permalink
feat: add editor presets (#265)
Browse files Browse the repository at this point in the history
Added `preset` prop to `useMarkdownEditor` hook.\
The preset specifies a set of plug-in extensions.\
Preset value can be `'zero' | 'commonmark' | 'default' | 'yfm' | 'full'`.
`'zero' | 'commonmark' | 'default'` – includes functionality similar to the markdown-it presets.\
`'yfm'` – YFM functionality
`'full'` – previously default functionality
Default value – `'full'`
  • Loading branch information
d3m1d0v authored Jun 6, 2024
1 parent 9ccf3b1 commit a2d8153
Show file tree
Hide file tree
Showing 13 changed files with 719 additions and 111 deletions.
141 changes: 141 additions & 0 deletions demo/PresetDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import React, {CSSProperties, useCallback, useEffect} from 'react';

import {toaster} from '@gravity-ui/uikit/toaster-singleton-react-18';

import {MarkupString, logger} from '../src';
import {
MarkdownEditorMode,
MarkdownEditorPreset,
MarkdownEditorView,
useMarkdownEditor,
} from '../src/bundle';
import type {RenderPreview} from '../src/bundle/Editor';
import type {FileUploadHandler} from '../src/utils/upload';
import {VERSION} from '../src/version';

import {WysiwygSelection} from './PMSelection';
import {WysiwygDevTools} from './ProseMirrorDevTools';
import {SplitModePreview} from './SplitModePreview';
import {block} from './cn';
import {randomDelay} from './delay';
import {plugins} from './md-plugins';

import './Playground.scss';

const b = block('playground');
const fileUploadHandler: FileUploadHandler = async (file) => {
console.info('[PresetDemo] Uploading file: ' + file.name);

Check warning on line 27 in demo/PresetDemo.tsx

View workflow job for this annotation

GitHub Actions / Verify Files

Unexpected console statement
await randomDelay(1000, 3000);
return {url: URL.createObjectURL(file)};
};

export type PresetDemoProps = {
preset: MarkdownEditorPreset;
allowHTML?: boolean;
settingsVisible?: boolean;
breaks?: boolean;
linkify?: boolean;
linkifyTlds?: string | string[];
splitModeOrientation?: 'horizontal' | 'vertical' | false;
stickyToolbar?: boolean;
height?: CSSProperties['height'];
};

logger.setLogger({
metrics: console.info,

Check warning on line 45 in demo/PresetDemo.tsx

View workflow job for this annotation

GitHub Actions / Verify Files

Unexpected console statement
action: (data) => console.info(`Action: ${data.action}`, data),
...console,
});

export const PresetDemo = React.memo<PresetDemoProps>((props) => {
const {
preset,
settingsVisible,
allowHTML,
breaks,
linkify,
linkifyTlds,
splitModeOrientation,
stickyToolbar,
height,
} = props;
const [editorMode, setEditorMode] = React.useState<MarkdownEditorMode>('wysiwyg');
const [mdRaw, setMdRaw] = React.useState<MarkupString>('');

const renderPreview = useCallback<RenderPreview>(
({getValue}) => (
<SplitModePreview
getValue={getValue}
allowHTML={allowHTML}
linkify={linkify}
linkifyTlds={linkifyTlds}
breaks={breaks}
needToSanitizeHtml
plugins={plugins}
/>
),
[allowHTML, breaks, linkify, linkifyTlds],
);

const mdEditor = useMarkdownEditor({
preset,
allowHTML,
linkify,
linkifyTlds,
breaks: breaks ?? true,
initialSplitModeEnabled: true,
initialToolbarVisible: true,
splitMode: splitModeOrientation,
renderPreview,
fileUploadHandler,
});

useEffect(() => {
function onChange() {
setMdRaw(mdEditor.getValue());
}
function onChangeEditorType({mode}: {mode: MarkdownEditorMode}) {
setEditorMode(mode);
}

mdEditor.on('change', onChange);
mdEditor.on('change-editor-mode', onChangeEditorType);

return () => {
mdEditor.off('change', onChange);
mdEditor.off('change-editor-mode', onChangeEditorType);
};
}, [mdEditor]);

return (
<div className={b()}>
<div className={b('header')}>
Markdown Editor ({preset} preset)
<span className={b('version')}>{VERSION}</span>
</div>
<hr />
<React.StrictMode>
<div className={b('editor')} style={{height: height ?? 'initial'}}>
<MarkdownEditorView
autofocus
toaster={toaster}
className={b('editor-view')}
stickyToolbar={Boolean(stickyToolbar)}
settingsVisible={settingsVisible}
editor={mdEditor}
/>
<WysiwygDevTools editor={mdEditor} />
<WysiwygSelection editor={mdEditor} className={b('pm-selection')} />
</div>
</React.StrictMode>

<hr />

<div className={b('preview')}>
{editorMode === 'wysiwyg' && <pre className={b('markup')}>{mdRaw}</pre>}
</div>
</div>
);
});

PresetDemo.displayName = 'PresetDemo';
58 changes: 58 additions & 0 deletions demo/Presets.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react';

// eslint-disable-next-line import/no-extraneous-dependencies
import type {Meta, StoryFn} from '@storybook/react';

import {PresetDemo, PresetDemoProps} from './PresetDemo';

export default {
title: 'Markdown Editor / Presets',
} as Meta;

type PlaygroundStoryProps = Pick<
PresetDemoProps,
| 'settingsVisible'
| 'breaks'
| 'allowHTML'
| 'linkify'
| 'linkifyTlds'
| 'splitModeOrientation'
| 'stickyToolbar'
| 'height'
>;

const args: Partial<PlaygroundStoryProps> = {
settingsVisible: true,
allowHTML: true,
breaks: true,
linkify: true,
linkifyTlds: [],
splitModeOrientation: 'horizontal',
stickyToolbar: true,
height: 'initial',
};

export const Zero: StoryFn<PlaygroundStoryProps> = (props) => (
<PresetDemo {...props} preset="zero" />
);

export const CommonMark: StoryFn<PlaygroundStoryProps> = (props) => (
<PresetDemo {...props} preset="commonmark" />
);

export const Default: StoryFn<PlaygroundStoryProps> = (props) => (
<PresetDemo {...props} preset="default" />
);

export const YFM: StoryFn<PlaygroundStoryProps> = (props) => <PresetDemo {...props} preset="yfm" />;

export const Full: StoryFn<PlaygroundStoryProps> = (props) => (
<PresetDemo {...props} preset="full" />
);

Zero.args = args;
CommonMark.args = args;
CommonMark.storyName = 'CommonMark';
Default.args = args;
YFM.args = args;
Full.args = args;
12 changes: 12 additions & 0 deletions src/bundle/Editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {FileUploadHandler} from '../utils/upload';

export type EditorMode = 'wysiwyg' | 'markup';
export type SplitMode = false | 'horizontal' | 'vertical';
export type EditorPreset = 'zero' | 'commonmark' | 'default' | 'yfm' | 'full';
export type RenderPreview = ({
getValue,
mode,
Expand Down Expand Up @@ -71,6 +72,7 @@ export interface EditorInt
readonly toolbarVisible: boolean;
readonly splitModeEnabled: boolean;
readonly splitMode: SplitMode;
readonly preset: EditorPreset;

/** @internal used in demo for dev-tools */
readonly _wysiwygView?: PMEditorView;
Expand Down Expand Up @@ -132,6 +134,7 @@ export type EditorOptions = Pick<
prepareRawMarkup?: (value: MarkupString) => MarkupString;
splitMode?: SplitMode;
renderPreview?: RenderPreview;
preset: EditorPreset;
};

/** @internal */
Expand All @@ -145,6 +148,7 @@ export class EditorImpl extends SafeEventEmitter<EventMapInt> implements EditorI
#wysiwygEditor?: WysiwygEditor;
#markupEditor?: MarkupEditor;

readonly #preset: EditorPreset;
#allowHTML?: boolean;
#linkify?: boolean;
#linkifyTlds?: string | string[];
Expand Down Expand Up @@ -213,6 +217,10 @@ export class EditorImpl extends SafeEventEmitter<EventMapInt> implements EditorI
return this.#splitMode;
}

get preset(): EditorPreset {
return this.#preset;
}

get renderPreview(): RenderPreview | undefined {
return this.#renderPreview;
}
Expand All @@ -231,7 +239,10 @@ export class EditorImpl extends SafeEventEmitter<EventMapInt> implements EditorI

get wysiwygEditor(): WysiwygEditor {
if (!this.#wysiwygEditor) {
const mdPreset: NonNullable<WysiwygEditorOptions['mdPreset']> =
this.#preset === 'zero' || this.#preset === 'commonmark' ? this.#preset : 'default';
this.#wysiwygEditor = new WysiwygEditor({
mdPreset,
initialContent: this.#markup,
extensions: this.#extensions,
allowHTML: this.#allowHTML,
Expand Down Expand Up @@ -286,6 +297,7 @@ export class EditorImpl extends SafeEventEmitter<EventMapInt> implements EditorI

this.#markup = opts.initialMarkup ?? '';

this.#preset = opts.preset ?? 'full';
this.#linkify = opts.linkify;
this.#linkifyTlds = opts.linkifyTlds;
this.#allowHTML = opts.allowHTML;
Expand Down
24 changes: 17 additions & 7 deletions src/bundle/MarkdownEditorView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,18 @@ 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 {
MToolbarData,
MToolbarItemData,
mHiddenDataByPreset,
mToolbarConfigByPreset,
} from './config/markup';
import {
WToolbarData,
WToolbarItemData,
wHiddenDataByPreset,
wToolbarConfigByPreset,
} from './config/wysiwyg';
import {useMarkdownEditorContext} from './context';
import {EditorSettings, EditorSettingsProps} from './settings';
import {stickyCn} from './sticky';
Expand All @@ -34,7 +44,7 @@ export type MarkdownEditorViewProps = ClassNameProps & {
autofocus?: boolean;
markupToolbarConfig?: MToolbarData;
wysiwygToolbarConfig?: WToolbarData;
markupHiddenActionsConfig?: MToolbarSingleItemData[];
markupHiddenActionsConfig?: MToolbarItemData[];
wysiwygHiddenActionsConfig?: WToolbarItemData[];
/** @default true */
settingsVisible?: boolean;
Expand Down Expand Up @@ -64,10 +74,10 @@ export const MarkdownEditorView = React.forwardRef<HTMLDivElement, MarkdownEdito
autofocus,
className,
settingsVisible = true,
markupToolbarConfig = mToolbarConfig,
wysiwygToolbarConfig = wToolbarConfig,
markupHiddenActionsConfig = mHiddenData,
wysiwygHiddenActionsConfig = wHiddenData,
markupToolbarConfig = mToolbarConfigByPreset[editor.preset],
wysiwygToolbarConfig = wToolbarConfigByPreset[editor.preset],
markupHiddenActionsConfig = mHiddenDataByPreset[editor.preset],
wysiwygHiddenActionsConfig = wHiddenDataByPreset[editor.preset],
toaster,
stickyToolbar,
} = props;
Expand Down
4 changes: 2 additions & 2 deletions src/bundle/MarkupEditorView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {useRenderTime} from '../react-utils/hooks';
import type {EditorInt, SplitMode} from './Editor';
import {MarkupEditorComponent} from './MarkupEditorComponent';
import {ToolbarView} from './ToolbarView';
import type {MToolbarData, MToolbarSingleItemData} from './config/markup';
import type {MToolbarData, MToolbarItemData} from './config/markup';
import {MarkupToolbarContextProvider} from './toolbar/markup/context';

import './MarkupEditorView.scss';
Expand All @@ -25,7 +25,7 @@ export type MarkupEditorViewProps = ClassNameProps & {
toolbarClassName?: string;
splitMode?: SplitMode;
splitModeEnabled: boolean;
hiddenActionsConfig?: MToolbarSingleItemData[];
hiddenActionsConfig?: MToolbarItemData[];
children?: React.ReactNode;
};

Expand Down
Loading

0 comments on commit a2d8153

Please sign in to comment.