Skip to content

Commit ed85f71

Browse files
committed
feat: add directiveSyntax experiment
1 parent 65054ee commit ed85f71

File tree

17 files changed

+218
-65
lines changed

17 files changed

+218
-65
lines changed

demo/components/Playground.tsx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import {Button, DropdownMenu} from '@gravity-ui/uikit';
55
import {toaster} from '@gravity-ui/uikit/toaster-singleton-react-18';
66

77
import {
8+
type DirectiveSyntaxValue,
89
type EscapeConfig,
9-
FileUploadHandler,
10+
type FileUploadHandler,
1011
type MarkdownEditorMode,
1112
MarkdownEditorView,
1213
type MarkdownEditorViewProps,
@@ -21,16 +22,16 @@ import {
2122
wysiwygToolbarConfigs,
2223
} from '../../src';
2324
import type {ToolbarActionData} from '../../src/bundle/Editor';
24-
import {Extension} from '../../src/cm/state';
25+
import type {Extension} from '../../src/cm/state';
2526
import {FoldingHeading} from '../../src/extensions/additional/FoldingHeading';
2627
import {Math} from '../../src/extensions/additional/Math';
2728
import {Mermaid} from '../../src/extensions/additional/Mermaid';
2829
import {YfmHtmlBlock} from '../../src/extensions/additional/YfmHtmlBlock';
2930
import {getSanitizeYfmHtmlBlock} from '../../src/extensions/additional/YfmHtmlBlock/utils';
3031
import {cloneDeep} from '../../src/lodash';
31-
import {CodeEditor} from '../../src/markup';
32+
import type {CodeEditor} from '../../src/markup';
3233
import {VERSION} from '../../src/version';
33-
import {plugins} from '../defaults/md-plugins';
34+
import {getPlugins} from '../defaults/md-plugins';
3435
import useYfmHtmlBlockStyles from '../hooks/useYfmHtmlBlockStyles';
3536
import {block} from '../utils/cn';
3637
import {randomDelay} from '../utils/delay';
@@ -91,6 +92,7 @@ export type PlaygroundProps = {
9192
markupToolbarConfig?: ToolbarGroupData<CodeEditor>[];
9293
onChangeEditorType?: (mode: MarkdownEditorMode) => void;
9394
onChangeSplitModeEnabled?: (splitModeEnabled: boolean) => void;
95+
directiveSyntax?: DirectiveSyntaxValue;
9496
} & Pick<
9597
UseMarkdownEditorProps,
9698
| 'needToSetDimensionsForUploadedImages'
@@ -142,6 +144,7 @@ export const Playground = React.memo<PlaygroundProps>((props) => {
142144
hidePreviewAfterSubmit,
143145
needToSetDimensionsForUploadedImages,
144146
experimental,
147+
directiveSyntax,
145148
} = props;
146149
const [editorMode, setEditorMode] = React.useState<MarkdownEditorMode>(
147150
initialEditor ?? 'wysiwyg',
@@ -153,15 +156,15 @@ export const Playground = React.memo<PlaygroundProps>((props) => {
153156
}, [mdRaw]);
154157

155158
const renderPreview = useCallback<RenderPreview>(
156-
({getValue, md}) => (
159+
({getValue, md, directiveSyntax}) => (
157160
<SplitModePreview
158161
getValue={getValue}
159162
allowHTML={md.html}
160163
linkify={md.linkify}
161164
linkifyTlds={md.linkifyTlds}
162165
breaks={md.breaks}
163166
needToSanitizeHtml={sanitizeHtml}
164-
plugins={plugins}
167+
plugins={getPlugins({directiveSyntax})}
165168
/>
166169
),
167170
[sanitizeHtml],
@@ -182,7 +185,10 @@ export const Playground = React.memo<PlaygroundProps>((props) => {
182185
needToSetDimensionsForUploadedImages,
183186
renderPreview: renderPreviewDefined ? renderPreview : undefined,
184187
fileUploadHandler,
185-
experimental,
188+
experimental: {
189+
...experimental,
190+
directiveSyntax,
191+
},
186192
prepareRawMarkup: prepareRawMarkup
187193
? (value) => '**prepare raw markup**\n\n' + value
188194
: undefined,
@@ -249,6 +255,7 @@ export const Playground = React.memo<PlaygroundProps>((props) => {
249255
experimental?.needToSetDimensionsForUploadedImages,
250256
experimental?.beforeEditorModeChange,
251257
experimental?.prepareRawMarkup,
258+
directiveSyntax,
252259
],
253260
);
254261

demo/components/PlaygroundMini.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export type PlaygroundMiniProps = Pick<
2323
| 'onChangeEditorType'
2424
| 'onChangeSplitModeEnabled'
2525
| 'escapeConfig'
26+
| 'directiveSyntax'
2627
> & {withDefaultInitialContent?: boolean};
2728

2829
export const PlaygroundMini = React.memo<PlaygroundMiniProps>(

demo/defaults/args.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ export const args: Meta<PlaygroundMiniProps>['args'] = {
1616
initialSplitModeEnabled: false,
1717
renderPreviewDefined: true,
1818
height: 'initial',
19+
directiveSyntax: 'disabled',
1920
};

demo/defaults/md-plugins.ts

Lines changed: 58 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import yfmTable from '@diplodoc/transform/lib/plugins/table';
1919
import video from '@diplodoc/transform/lib/plugins/video';
2020
import type {PluginWithParams} from 'markdown-it/lib';
2121

22+
import type {RenderPreviewParams} from '../../src';
2223
import {emojiDefs} from '../../src/bundle/emoji';
2324
import color from '../../src/markdown-it/color';
2425
import {bare as emoji} from '../../src/markdown-it/emoji';
@@ -30,54 +31,62 @@ export const LATEX_RUNTIME = 'extension:latex';
3031
export const MERMAID_RUNTIME = 'extension:mermaid';
3132
export const YFM_HTML_BLOCK_RUNTIME = 'extension:yfm-html-block';
3233

33-
const defaultPlugins: PluginWithParams[] = [
34-
anchors,
35-
code,
36-
yfmCut({bundle: false}) as PluginWithParams,
37-
deflist,
38-
file,
39-
(md) => md.use(imsize, {enableInlineStyling: true}),
40-
meta,
41-
monospace,
42-
notes,
43-
sup,
44-
yfmTabs({
45-
bundle: false,
46-
features: {
47-
enabledVariants: {
48-
regular: true,
49-
radio: true,
50-
dropdown: false,
51-
accordion: false,
34+
type GetPluginsOptions = {
35+
directiveSyntax?: RenderPreviewParams['directiveSyntax'];
36+
};
37+
38+
export function getPlugins(_opts: GetPluginsOptions = {}): markdownit.PluginWithParams[] {
39+
const defaultPlugins: PluginWithParams[] = [
40+
anchors,
41+
code,
42+
yfmCut({bundle: false}),
43+
deflist,
44+
file,
45+
(md) => md.use(imsize, {enableInlineStyling: true}),
46+
meta,
47+
monospace,
48+
notes,
49+
sup,
50+
yfmTabs({
51+
bundle: false,
52+
features: {
53+
enabledVariants: {
54+
regular: true,
55+
radio: true,
56+
dropdown: false,
57+
accordion: false,
58+
},
5259
},
53-
},
54-
}),
55-
video,
56-
yfmTable,
57-
];
58-
const extendedPlugins = defaultPlugins.concat(
59-
(md) => md.use(emoji, {defs: emojiDefs}),
60-
checkbox,
61-
color,
62-
ins,
63-
latex({bundle: false, validate: false, runtime: LATEX_RUNTIME}),
64-
mark,
65-
mermaid({bundle: false, runtime: MERMAID_RUNTIME}),
66-
sub,
67-
yfmHtmlBlock({
68-
bundle: false,
69-
runtimeJsPath: YFM_HTML_BLOCK_RUNTIME,
70-
head: `
71-
<base target="_blank" />
72-
<style>
73-
html, body {
74-
margin: 0;
75-
padding: 0;
76-
}
77-
</style
78-
`,
79-
}),
80-
foldingHeadings({bundle: false}),
81-
);
60+
}),
61+
video,
62+
yfmTable,
63+
];
64+
const extendedPlugins = defaultPlugins.concat(
65+
(md) => md.use(emoji, {defs: emojiDefs}),
66+
checkbox,
67+
color,
68+
ins,
69+
latex({bundle: false, validate: false, runtime: LATEX_RUNTIME}),
70+
mark,
71+
mermaid({bundle: false, runtime: MERMAID_RUNTIME}),
72+
sub,
73+
yfmHtmlBlock({
74+
bundle: false,
75+
runtimeJsPath: YFM_HTML_BLOCK_RUNTIME,
76+
head: `
77+
<base target="_blank" />
78+
<style>
79+
html, body {
80+
margin: 0;
81+
padding: 0;
82+
}
83+
</style
84+
`,
85+
}),
86+
foldingHeadings({bundle: false}),
87+
);
88+
89+
return extendedPlugins;
90+
}
8291

83-
export {extendedPlugins as plugins};
92+
export const plugins = getPlugins();

demo/stories/markdown/Markdown.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export default {
6161
args: args,
6262
parameters: {
6363
controls: {
64-
exclude: excludedControls,
64+
exclude: excludedControls.concat('directiveSyntax'),
6565
},
6666
},
6767
};

src/bundle/Editor.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {logger} from '../logger';
1717
import {createCodemirror} from '../markup';
1818
import {type CodeEditor, Editor as MarkupEditor} from '../markup/editor';
1919
import {type Emitter, FileUploadHandler, type Receiver, SafeEventEmitter} from '../utils';
20+
import type {DirectiveSyntaxContext} from '../utils/directive';
2021

2122
import type {
2223
MarkdownEditorMode as EditorMode,
@@ -79,6 +80,7 @@ export interface EditorInt
7980
readonly splitMode: SplitMode;
8081
readonly preset: EditorPreset;
8182
readonly mdOptions: Readonly<MarkdownEditorMdOptions>;
83+
readonly directiveSyntax: DirectiveSyntaxContext;
8284

8385
/** @internal used in demo for dev-tools */
8486
readonly _wysiwygView?: PMEditorView;
@@ -120,6 +122,7 @@ export type EditorOptions = Pick<
120122
> & {
121123
renderStorage: ReactRenderStorage;
122124
preset: EditorPreset;
125+
directiveSyntax: DirectiveSyntaxContext;
123126
};
124127

125128
/** @internal */
@@ -143,6 +146,7 @@ export class EditorImpl extends SafeEventEmitter<EventMapInt> implements EditorI
143146
#parseInsertedUrlAsImage?: ParseInsertedUrlAsImage;
144147
#needToSetDimensionsForUploadedImages: boolean;
145148
#enableNewImageSizeCalculation: boolean;
149+
#directiveSyntax: DirectiveSyntaxContext;
146150
#prepareRawMarkup?: (value: MarkupString) => MarkupString;
147151
#beforeEditorModeChange?: (
148152
options: Pick<ChangeEditorModeOptions, 'mode' | 'reason'>,
@@ -215,6 +219,10 @@ export class EditorImpl extends SafeEventEmitter<EventMapInt> implements EditorI
215219
return this.#mdOptions;
216220
}
217221

222+
get directiveSyntax(): DirectiveSyntaxContext {
223+
return this.#directiveSyntax;
224+
}
225+
218226
get renderPreview(): RenderPreview | undefined {
219227
return this.#renderPreview;
220228
}
@@ -271,6 +279,7 @@ export class EditorImpl extends SafeEventEmitter<EventMapInt> implements EditorI
271279
keymaps: this.#markupConfig.keymaps,
272280
yfmLangOptions: {languageData: this.#markupConfig.languageData},
273281
autocompletion: this.#markupConfig.autocompletion,
282+
directiveSyntax: this.directiveSyntax,
274283
receiver: this,
275284
}),
276285
);
@@ -329,6 +338,7 @@ export class EditorImpl extends SafeEventEmitter<EventMapInt> implements EditorI
329338
this.#needToSetDimensionsForUploadedImages = Boolean(
330339
experimental.needToSetDimensionsForUploadedImages,
331340
);
341+
this.#directiveSyntax = opts.directiveSyntax;
332342
this.#enableNewImageSizeCalculation = Boolean(experimental.enableNewImageSizeCalculation);
333343
this.#prepareRawMarkup = experimental.prepareRawMarkup;
334344
this.#escapeConfig = wysiwygConfig.escapeConfig;

src/bundle/MarkdownEditorView.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ export const MarkdownEditorView = React.forwardRef<HTMLDivElement, MarkdownEdito
240240
getValue: editor.getValue,
241241
mode: 'preview',
242242
md: editor.mdOptions,
243+
directiveSyntax: editor.directiveSyntax,
243244
})}
244245
</div>
245246
{settings}

src/bundle/SplitModeView.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ const SplitModeView = React.forwardRef<HTMLDivElement, SplitModeProps>(({editor}
110110
getValue: editor.getValue,
111111
mode: 'split',
112112
md: editor.mdOptions,
113+
directiveSyntax: editor.directiveSyntax,
113114
})}
114115
</div>
115116
);

src/bundle/types.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {MarkupString} from '../common';
66
import type {EscapeConfig, Extension} from '../core';
77
import type {CreateCodemirrorParams, YfmLangOptions} from '../markup';
88
import type {FileUploadHandler} from '../utils';
9+
import type {DirectiveSyntaxContext, DirectiveSyntaxOption} from '../utils/directive';
910

1011
import type {ChangeEditorModeOptions} from './Editor';
1112
import type {ExtensionsOptions as WysiwygPresetExtensionsOptions} from './wysiwyg-preset';
@@ -19,6 +20,7 @@ export type RenderPreviewParams = {
1920
getValue: () => MarkupString;
2021
mode: 'preview' | 'split';
2122
md: Readonly<MarkdownEditorMdOptions>;
23+
directiveSyntax: Pick<DirectiveSyntaxContext, 'option' | 'valueFor' | 'mdPluginValueFor'>;
2224
};
2325
export type RenderPreview = (params: RenderPreviewParams) => ReactNode;
2426

@@ -71,6 +73,26 @@ export type MarkdownEditorExperimentalOptions = {
7173
beforeEditorModeChange?: (
7274
options: Pick<ChangeEditorModeOptions, 'mode' | 'reason'>,
7375
) => boolean | undefined;
76+
/**
77+
* Enables support of directive syntax for diplodoc (YFM) extensions.
78+
*
79+
* **Note:** This setting affects parsing of markdown markup and serializing to markdown markup.
80+
* Be careful with it and use it in consistency with diplodoc/transform and diplodoc-extensions.
81+
*
82+
* Before enabling this option, make sure that appropriate versions of diplodoc/transform and diplodoc-extensions are installed.
83+
*
84+
* You can pass an object in `key:value` format to provide different behaviour for each extension individually.
85+
*
86+
* Values:
87+
* - 'disabled' – directive syntax is disabled;
88+
* - 'enabled' – directive syntax is enabled. Syntax of existing blocks is preserved. New blocks will be serialized using old syntax;
89+
* - 'preserve' – directive syntax is enabled. Syntax of existing blocks is preserved. New blocks will be serialized using directive syntax;
90+
* - 'overwrite' – existing blocks will be overwritten using directive syntax through serialization;
91+
* - 'only' – old syntax is disabled, only directive syntax available. Blocks in old syntax will not be parsed.
92+
*
93+
* Default value is 'disabled'.
94+
*/
95+
directiveSyntax?: DirectiveSyntaxOption;
7496
};
7597

7698
export type MarkdownEditorMarkupConfig = {

src/bundle/useMarkdownEditor.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {useLayoutEffect, useMemo} from 'react';
33
import type {Extension} from '../core';
44
import {ReactRenderStorage} from '../extensions';
55
import {logger} from '../logger';
6+
import {DirectiveSyntaxContext} from '../utils/directive';
67

78
import {EditorImpl, type EditorInt} from './Editor';
89
import type {
@@ -40,11 +41,14 @@ export function useMarkdownEditor<T extends object = {}>(
4041
props.needToSetDimensionsForUploadedImages;
4142
const enableNewImageSizeCalculation = experimental.enableNewImageSizeCalculation;
4243

44+
const directiveSyntax = new DirectiveSyntaxContext(experimental.directiveSyntax);
45+
4346
const extensions: Extension = (builder) => {
4447
const extensionOptions = wysiwygConfig.extensionOptions ?? props.extensionOptions;
4548

4649
builder.use(BundlePreset, {
4750
...extensionOptions,
51+
directiveSyntax,
4852
preset,
4953
reactRenderer: renderStorage,
5054
onCancel: () => {
@@ -71,6 +75,7 @@ export function useMarkdownEditor<T extends object = {}>(
7175
...props,
7276
preset,
7377
renderStorage,
78+
directiveSyntax,
7479
md: {
7580
...md,
7681
breaks,

0 commit comments

Comments
 (0)