Skip to content

Commit da1c209

Browse files
authored
feat(wysiwyg): allow to disable markdown-it-attrs in yfm preset (#690)
1 parent 6f54ee7 commit da1c209

File tree

15 files changed

+188
-38
lines changed

15 files changed

+188
-38
lines changed

demo/components/Playground.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export type PlaygroundProps = {
7979
onChangeSplitModeEnabled?: (splitModeEnabled: boolean) => void;
8080
directiveSyntax?: DirectiveSyntaxValue;
8181
disabledHTMLBlockModes?: EmbeddingMode[];
82+
disableMarkdownItAttrs?: boolean;
8283
} & Pick<UseMarkdownEditorProps, 'experimental' | 'wysiwygConfig'> &
8384
Pick<
8485
MarkdownEditorViewProps,
@@ -128,6 +129,7 @@ export const Playground = memo<PlaygroundProps>((props) => {
128129
experimental,
129130
directiveSyntax,
130131
disabledHTMLBlockModes,
132+
disableMarkdownItAttrs,
131133
} = props;
132134
const [editorMode, setEditorMode] = useState<MarkdownEditorMode>(initialEditor ?? 'wysiwyg');
133135
const [mdRaw, setMdRaw] = useState<MarkupString>(initial || '');
@@ -146,10 +148,11 @@ export const Playground = memo<PlaygroundProps>((props) => {
146148
breaks={md.breaks}
147149
needToSanitizeHtml={sanitizeHtml}
148150
plugins={getPlugins({directiveSyntax})}
151+
disableMarkdownItAttrs={disableMarkdownItAttrs}
149152
htmlRuntimeConfig={{disabledModes: disabledHTMLBlockModes}}
150153
/>
151154
),
152-
[sanitizeHtml, disabledHTMLBlockModes],
155+
[sanitizeHtml, disabledHTMLBlockModes, disableMarkdownItAttrs],
153156
);
154157

155158
const logger = useMemo(() => new Logger2().nested({env: 'playground'}), []);
@@ -161,6 +164,7 @@ export const Playground = memo<PlaygroundProps>((props) => {
161164
preset: 'full',
162165
wysiwygConfig: {
163166
placeholderOptions: placeholderOptions,
167+
disableMarkdownAttrs: disableMarkdownItAttrs,
164168
extensions: (builder) => {
165169
builder
166170
.use(Math, {
@@ -250,6 +254,7 @@ export const Playground = memo<PlaygroundProps>((props) => {
250254
experimental?.beforeEditorModeChange,
251255
experimental?.prepareRawMarkup,
252256
directiveSyntax,
257+
disableMarkdownItAttrs,
253258
],
254259
);
255260

demo/components/PlaygroundMini.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export type PlaygroundMiniProps = Pick<
2424
| 'onChangeSplitModeEnabled'
2525
| 'directiveSyntax'
2626
| 'disabledHTMLBlockModes'
27+
| 'disableMarkdownItAttrs'
2728
> & {withDefaultInitialContent?: boolean};
2829

2930
export const PlaygroundMini = memo<PlaygroundMiniProps>(

demo/components/SplitModePreview.tsx

+9-2
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,15 @@ const Preview = withMermaid({runtime: MERMAID_RUNTIME})(
2525
);
2626

2727
export type SplitModePreviewProps = {
28-
plugins?: MarkdownIt.PluginSimple[];
28+
plugins: MarkdownIt.PluginSimple[];
2929
getValue: () => MarkupString;
3030
allowHTML?: boolean;
3131
breaks?: boolean;
3232
linkify?: boolean;
3333
linkifyTlds?: string | string[];
3434
needToSanitizeHtml?: boolean;
3535
htmlRuntimeConfig?: HTMLRuntimeConfig;
36+
disableMarkdownItAttrs?: boolean;
3637
};
3738

3839
export const SplitModePreview: React.FC<SplitModePreviewProps> = (props) => {
@@ -45,6 +46,7 @@ export const SplitModePreview: React.FC<SplitModePreviewProps> = (props) => {
4546
linkifyTlds,
4647
needToSanitizeHtml,
4748
htmlRuntimeConfig,
49+
disableMarkdownItAttrs,
4850
} = props;
4951
const [html, setHtml] = useState('');
5052
const [meta, setMeta] = useState<object | undefined>({});
@@ -58,12 +60,17 @@ export const SplitModePreview: React.FC<SplitModePreviewProps> = (props) => {
5860
const res = transform(getValue(), {
5961
allowHTML,
6062
breaks,
61-
plugins,
6263
linkify,
6364
linkifyTlds,
6465
needToSanitizeHtml,
6566
linkAttrs: [[ML_ATTR, true]],
6667
defaultClassName: colorClassName,
68+
plugins: [
69+
...plugins,
70+
...(disableMarkdownItAttrs
71+
? [(md: MarkdownIt) => md.core.ruler.disable('curly_attributes')]
72+
: []),
73+
],
6774
}).result;
6875
setHtml(res.html);
6976
setMeta(res.meta);

demo/defaults/args.ts

+1
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ export const args: Meta<PlaygroundMiniProps>['args'] = {
1818
height: 'initial',
1919
directiveSyntax: 'disabled',
2020
disabledHTMLBlockModes: [],
21+
disableMarkdownItAttrs: true,
2122
};

demo/stories/playground/Playground.stories.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import {type PlaygroundProps, Playground as component} from '../../components/Pl
44
import {args} from '../../defaults/args';
55
import {getInitialMd} from '../../utils/getInitialMd';
66

7-
export const Story: StoryObj<typeof component> = {};
7+
export const Story: StoryObj<typeof component> = {
8+
args: {
9+
disableMarkdownItAttrs: true,
10+
},
11+
};
812
Story.storyName = 'Playground';
913

1014
const meta: Meta<PlaygroundProps> = {

demo/stories/presets/Preset.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export const Preset = memo<PresetDemoProps>((props) => {
9090
splitModeEnabled: true,
9191
},
9292
wysiwygConfig: {
93+
disableMarkdownAttrs: true,
9394
extensionOptions: {
9495
imgSize: {
9596
parseInsertedUrlAsImage,

package-lock.json

+26-18
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@
181181
"@codemirror/search": "~6.5.8",
182182
"@codemirror/state": "~6.5.1",
183183
"@codemirror/view": "~6.36.2",
184+
"@diplodoc/utils": "^2.1.0",
184185
"@gravity-ui/i18n": "^1.7.0",
185186
"@gravity-ui/icons": "^2.12.0",
186187
"@lezer/highlight": "~1.2.1",

src/bundle/types.ts

+7
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,13 @@ export type MarkdownEditorWysiwygConfig = {
178178
extensionOptions?: ExtensionsOptions;
179179
escapeConfig?: EscapeConfig;
180180
placeholderOptions?: WysiwygPlaceholderOptions;
181+
// MAJOR: remove markdown-it-attrs
182+
/**
183+
* Disable the markdown-it-attrs plugin in the markup parser.
184+
*
185+
* Note: The use of the markdown-it-attrs plugin will be removed in the next major version.
186+
*/
187+
disableMarkdownAttrs?: boolean;
181188
};
182189

183190
export type MarkdownEditorOptions = {

src/bundle/useMarkdownEditor.ts

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export function useMarkdownEditor(
5757
editor.emit('submit', null);
5858
return true;
5959
},
60+
disableMdAttrs: wysiwygConfig.disableMarkdownAttrs,
6061
preserveEmptyRows: experimental.preserveEmptyRows,
6162
placeholderOptions: wysiwygConfig.placeholderOptions,
6263
mdBreaks: md.breaks,

src/bundle/wysiwyg-preset.ts

+3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ export type BundlePresetOptions = ExtensionsOptions &
4040
needToSetDimensionsForUploadedImages?: boolean;
4141
enableNewImageSizeCalculation?: boolean;
4242
directiveSyntax: DirectiveSyntaxContext;
43+
// MAJOR: remove markdown-it-attrs
44+
disableMdAttrs?: boolean;
4345
};
4446

4547
declare global {
@@ -136,6 +138,7 @@ export const BundlePreset: ExtensionAuto<BundlePresetOptions> = (builder, opts)
136138
};
137139
const yfmOptions: BehaviorPresetOptions & YfmPresetOptions = {
138140
...defaultOptions,
141+
yfmConfigs: {disableAttrs: opts.disableMdAttrs, ...opts.yfmConfigs},
139142
selectionContext: {config: wSelectionMenuConfigByPreset.yfm, ...opts.selectionContext},
140143
commandMenu: {actions: wCommandMenuConfigByPreset.yfm, ...opts.commandMenu},
141144
underline: {underlineKey: f.toPM(A.Underline), ...opts.underline},

src/extensions/yfm/YfmConfigs/YfmConfigsSpecs/index.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import attrsPlugin, {type AttrsOptions} from 'markdown-it-attrs'; // eslint-disable-line import/no-extraneous-dependencies
1+
import attrsPlugin, {type AttrsOptions} from 'markdown-it-attrs';
22

3-
import type {ExtensionAuto} from '../../../../core';
4-
import {noop} from '../../../../lodash';
3+
import type {ExtensionAuto} from '#core';
4+
import {noop} from 'src/lodash';
55

66
const defaultAttrsOpts: AttrsOptions = {
77
allowedAttributes: ['id'],
@@ -10,12 +10,17 @@ const defaultAttrsOpts: AttrsOptions = {
1010
export type YfmConfigsSpecsOptions = {
1111
/** markdown-it-attrs options */
1212
attrs?: AttrsOptions;
13+
/** Disable markdown-it-attrs plugin */
14+
disableAttrs?: boolean;
1315
};
1416

1517
export const YfmConfigsSpecs: ExtensionAuto<YfmConfigsSpecsOptions> = (builder, opts) => {
1618
const attrsOpts = {...defaultAttrsOpts, ...opts.attrs};
1719

18-
builder.configureMd((md) => md.use<AttrsOptions>(attrsPlugin, attrsOpts), {text: false});
20+
// MAJOR: remove markdown-it-attrs
21+
if (!opts.disableAttrs) {
22+
builder.configureMd((md) => md.use<AttrsOptions>(attrsPlugin, attrsOpts), {text: false});
23+
}
1924

2025
// ignore yfm lint token
2126
builder.addNode('__yfm_lint', () => ({

src/extensions/yfm/YfmHeading/YfmHeading.test.ts

+63-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {builders} from 'prosemirror-test-builder';
2+
import dd from 'ts-dedent';
23

34
import {parseDOM} from '../../../../tests/parse-dom';
45
import {createMarkupChecker} from '../../../../tests/sameMarkup';
@@ -73,21 +74,21 @@ describe('Heading extension', () => {
7374
});
7475

7576
it('should parse few headings', () => {
76-
const markup = `
77-
# h1 {#one}
77+
const markup = dd`
78+
# h1 {#one}
7879
79-
## h2 {#two}
80+
## h2 {#two}
8081
81-
### h3 {#three}
82+
### h3 {#three}
8283
83-
#### h4 {#four}
84+
#### h4 {#four}
8485
85-
##### h5 {#five}
86+
##### h5 {#five}
8687
87-
###### h6 {#six}
88+
###### h6 {#six}
8889
89-
para
90-
`.trim();
90+
para
91+
`.trim();
9192

9293
same(
9394
markup,
@@ -103,6 +104,59 @@ para
103104
);
104105
});
105106

107+
it('should parse headings with id without markdown-it-attrs', () => {
108+
const markup = dd`
109+
# h1 {#one}
110+
111+
## h2 {#two}
112+
113+
### h3 {#three}
114+
115+
#### h4 {#four}
116+
117+
##### h5 {#five}
118+
119+
###### h6 {#six}
120+
121+
para
122+
`.trim();
123+
124+
const {
125+
schema,
126+
markupParser: parser,
127+
serializer,
128+
} = new ExtensionsManager({
129+
extensions: (builder) =>
130+
builder
131+
.use(BaseSchemaSpecs, {})
132+
.use(YfmConfigsSpecs, {disableAttrs: true})
133+
.use(YfmHeadingSpecs, {}),
134+
}).buildDeps();
135+
const {same} = createMarkupChecker({parser, serializer});
136+
137+
const {doc, p, h} = builders<
138+
'doc' | 'p' | 'h' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6',
139+
'b'
140+
>(schema, {
141+
doc: {nodeType: BaseNode.Doc},
142+
p: {nodeType: BaseNode.Paragraph},
143+
h: {nodeType: headingNodeName},
144+
});
145+
146+
same(
147+
markup,
148+
doc(
149+
h({[YfmHeadingAttr.Level]: 1, [YfmHeadingAttr.Id]: 'one'}, 'h1'),
150+
h({[YfmHeadingAttr.Level]: 2, [YfmHeadingAttr.Id]: 'two'}, 'h2'),
151+
h({[YfmHeadingAttr.Level]: 3, [YfmHeadingAttr.Id]: 'three'}, 'h3'),
152+
h({[YfmHeadingAttr.Level]: 4, [YfmHeadingAttr.Id]: 'four'}, 'h4'),
153+
h({[YfmHeadingAttr.Level]: 5, [YfmHeadingAttr.Id]: 'five'}, 'h5'),
154+
h({[YfmHeadingAttr.Level]: 6, [YfmHeadingAttr.Id]: 'six'}, 'h6'),
155+
p('para'),
156+
),
157+
);
158+
});
159+
106160
it.each([1, 2, 3, 4, 5, 6])('should parse html - h%s tag', (lvl) => {
107161
parseDOM(
108162
schema,

src/extensions/yfm/YfmHeading/YfmHeadingSpecs/index.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import type {Node, NodeSpec} from 'prosemirror-model';
2-
3-
import type {ExtensionAuto} from '../../../../core';
1+
import type {ExtensionAuto} from '#core';
2+
import type {Node, NodeSpec} from '#pm/model';
43

54
import {YfmHeadingAttr, headingNodeName} from './const';
5+
import {headingAttrsPlugin} from './markdown/heading-attrs';
66
import {getNodeAttrs} from './utils';
77

88
const DEFAULT_PLACEHOLDER = (node: Node) => 'Heading ' + node.attrs[YfmHeadingAttr.Level];
@@ -18,6 +18,7 @@ export type YfmHeadingSpecsOptions = {
1818

1919
/** YfmHeading extension needs markdown-it-attrs plugin */
2020
export const YfmHeadingSpecs: ExtensionAuto<YfmHeadingSpecsOptions> = (builder, opts) => {
21+
builder.configureMd((md) => md.use(headingAttrsPlugin));
2122
builder.addNode(headingNodeName, () => ({
2223
spec: {
2324
attrs: {

0 commit comments

Comments
 (0)