Skip to content

Commit 198f805

Browse files
authored
feat(Color): add parseDOM rules to specs (#137)
1 parent ebf37d7 commit 198f805

File tree

5 files changed

+163
-12
lines changed

5 files changed

+163
-12
lines changed
Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
import {builders} from 'prosemirror-test-builder';
22
import {createMarkupChecker} from '../../../../tests/sameMarkup';
3+
import {parseDOM} from '../../../../tests/parse-dom';
34
import {ExtensionsManager} from '../../../core';
45
import {BaseNode, BaseSpecsPreset} from '../../base/specs';
56
import {colorMarkName, ColorSpecs} from './ColorSpecs';
67

78
const {schema, parser, serializer} = new ExtensionsManager({
8-
extensions: (builder) => builder.use(BaseSpecsPreset, {}).use(ColorSpecs),
9+
extensions: (builder) =>
10+
builder.use(BaseSpecsPreset, {}).use(ColorSpecs, {
11+
validateClassNameColorName: (color) => color !== 'something',
12+
parseStyleColorValue: (value: string) => (value === 'darkred' ? 'red' : null),
13+
}),
914
}).buildDeps();
1015

11-
const {doc, p, c1, c2} = builders(schema, {
16+
const {doc, p, color, c1, c2} = builders(schema, {
1217
doc: {nodeType: BaseNode.Doc},
1318
p: {nodeType: BaseNode.Paragraph},
19+
color: {markType: colorMarkName},
1420
c1: {nodeType: colorMarkName, [colorMarkName]: 'c1'},
1521
c2: {nodeType: colorMarkName, [colorMarkName]: 'c2'},
16-
}) as PMTestBuilderResult<'doc' | 'p', 'c1' | 'c2'>;
22+
}) as PMTestBuilderResult<'doc' | 'p', 'color' | 'c1' | 'c2'>;
1723

1824
const {same} = createMarkupChecker({parser, serializer});
1925

@@ -22,4 +28,44 @@ describe('Color extension', () => {
2228

2329
it('should parse code inside text', () =>
2430
same('he{c2}(llo wor)ld!', doc(p('he', c2('llo wor'), 'ld!'))));
31+
32+
it('should parse span with md-colorify--* classname', () => {
33+
parseDOM(
34+
schema,
35+
'<span class="md-colorify--mdcolor">text with md color</span>',
36+
doc(p(color({[colorMarkName]: 'mdcolor'}, 'text with md color'))),
37+
);
38+
});
39+
40+
it('should parse span with yfm-colorify--* classname', () => {
41+
parseDOM(
42+
schema,
43+
'<span class="yfm-colorify--yfmcolor">text with yfm color</span>',
44+
doc(p(color({[colorMarkName]: 'yfmcolor'}, 'text with yfm color'))),
45+
);
46+
});
47+
48+
it('should not parse span with yfm-colorify--something classname', () => {
49+
parseDOM(
50+
schema,
51+
'<span class="yfm-colorify--something">text with yfm color</span>',
52+
doc(p('text with yfm color')),
53+
);
54+
});
55+
56+
it('should parse span with style color darkred', () => {
57+
parseDOM(
58+
schema,
59+
'<span style="color:darkred">text with style color</span>',
60+
doc(p(color({[colorMarkName]: 'red'}, 'text with style color'))),
61+
);
62+
});
63+
64+
it('should not parse span with style color red', () => {
65+
parseDOM(
66+
schema,
67+
'<span style="color:red">text with style color</span>',
68+
doc(p('text with style color')),
69+
);
70+
});
2571
});

src/extensions/yfm/Color/ColorSpecs/index.ts

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,57 @@ import {colorMarkName, colorClassName, domColorAttr} from './const';
66
export {colorMarkName} from './const';
77
export const colorType = markTypeFactory(colorMarkName);
88

9-
export const ColorSpecs: ExtensionAuto = (builder) => {
9+
function getColorName(className: string) {
10+
const regexp = /^(yfm|md)-colorify--(\w+)$/;
11+
return className.match(regexp)?.[2];
12+
}
13+
14+
export type ColorSpecsOptions = {
15+
validateClassNameColorName?: (colorName: string) => boolean;
16+
parseStyleColorValue?: (color: string) => string | null;
17+
};
18+
19+
export const ColorSpecs: ExtensionAuto<ColorSpecsOptions> = (builder, opts) => {
20+
const {validateClassNameColorName, parseStyleColorValue} = opts;
21+
1022
builder
1123
.configureMd((md) => md.use(mdPlugin, {defaultClassName: colorClassName, inline: false}))
1224
.addMark(colorMarkName, () => ({
1325
spec: {
1426
attrs: {[colorMarkName]: {}},
1527
parseDOM: [
1628
{
17-
tag: `span[${domColorAttr}]`,
29+
tag: `span`,
30+
preserveWhitespace: false,
31+
getAttrs(node) {
32+
if (typeof node === 'string') return false;
33+
34+
for (const className of Array.from(node.classList)) {
35+
const color = getColorName(className);
36+
if (color && (validateClassNameColorName?.(color) ?? true)) {
37+
return {
38+
[colorMarkName]: color,
39+
};
40+
}
41+
}
42+
43+
return false;
44+
},
45+
},
46+
{
47+
style: 'color',
48+
preserveWhitespace: false,
1849
getAttrs(node) {
19-
return {
20-
[colorMarkName]: (node as HTMLElement).getAttribute(domColorAttr),
21-
};
50+
if (typeof node !== 'string') return false;
51+
52+
const color = parseStyleColorValue?.(node);
53+
if (color) {
54+
return {
55+
[colorMarkName]: color,
56+
};
57+
}
58+
59+
return false;
2260
},
2361
},
2462
],

src/extensions/yfm/Color/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type {Action, ExtensionAuto} from '../../../core';
33
import {isMarkActive} from '../../../utils/marks';
44
import {ColorSpecs, colorType} from './ColorSpecs';
55
import {colorAction, colorMarkName, Colors} from './const';
6-
import {chainAND} from './utils';
6+
import {chainAND, parseStyleColorValue, validateClassNameColorName} from './utils';
77

88
import './colors.scss';
99

@@ -15,7 +15,10 @@ export type ColorActionParams = {
1515
};
1616

1717
export const Color: ExtensionAuto = (builder) => {
18-
builder.use(ColorSpecs);
18+
builder.use(ColorSpecs, {
19+
validateClassNameColorName,
20+
parseStyleColorValue,
21+
});
1922

2023
builder.addAction(colorAction, ({schema}) => {
2124
const type = colorType(schema);

src/extensions/yfm/Color/utils.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {Command} from 'prosemirror-state';
2+
import {Colors} from './const';
23

34
export {chainCommands as chainOR} from 'prosemirror-commands';
45

@@ -39,3 +40,65 @@ export const chainAND = (...commands: Command[]): Command => {
3940
return true;
4041
};
4142
};
43+
44+
// see styles
45+
const COLORS: readonly string[] = [
46+
Colors.Black,
47+
Colors.Gray,
48+
Colors.Yellow,
49+
Colors.Orange,
50+
Colors.Red,
51+
Colors.Green,
52+
Colors.Blue,
53+
Colors.Violet,
54+
];
55+
56+
export const validateClassNameColorName = (color: string) => COLORS.includes(color);
57+
58+
// eslint-disable-next-line complexity
59+
export const parseStyleColorValue = (color: string) => {
60+
switch (color) {
61+
case 'black':
62+
return Colors.Black;
63+
64+
case 'gray':
65+
case 'grey':
66+
case 'lightgray':
67+
case 'lightgrey':
68+
case 'darkgray':
69+
case 'darkgrey':
70+
return Colors.Gray;
71+
72+
case 'yellow':
73+
case 'lightyellow':
74+
return Colors.Yellow;
75+
76+
case 'orange':
77+
case 'darkorange':
78+
return Colors.Orange;
79+
80+
case 'red':
81+
case 'darkred':
82+
return Colors.Red;
83+
84+
case 'green':
85+
case 'lightgreen':
86+
case 'darkgreen':
87+
return Colors.Green;
88+
89+
case 'blue':
90+
case 'lightblue':
91+
case 'mediumblue':
92+
case 'darkblue':
93+
return Colors.Blue;
94+
95+
case 'violet':
96+
case 'darkviolet':
97+
case 'purple':
98+
case 'mediumpurple':
99+
return Colors.Violet;
100+
101+
default:
102+
return null;
103+
}
104+
};

src/extensions/yfm/specs.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type {ExtensionAuto} from '../../core';
22

33
import {CheckboxSpecs, CheckboxSpecsOptions} from './Checkbox/CheckboxSpecs';
4-
import {ColorSpecs} from './Color/ColorSpecs';
4+
import {ColorSpecs, ColorSpecsOptions} from './Color/ColorSpecs';
55
import {MathSpecs} from './Math/MathSpecs';
66
import {MonospaceSpecs} from './Monospace/MonospaceSpecs';
77
import {ImgSizeSpecs, ImgSizeSpecsOptions} from './ImgSize/ImgSizeSpecs';
@@ -30,6 +30,7 @@ export * from './YfmTabs/YfmTabsSpecs';
3030

3131
export type YfmSpecsPresetOptions = {
3232
checkbox?: CheckboxSpecsOptions;
33+
color?: ColorSpecsOptions;
3334
video?: VideoSpecsOptions;
3435
imgSize?: ImgSizeSpecsOptions;
3536
yfmCut?: YfmCutSpecsOptions;
@@ -42,7 +43,7 @@ export type YfmSpecsPresetOptions = {
4243
export const YfmSpecsPreset: ExtensionAuto<YfmSpecsPresetOptions> = (builder, opts) => {
4344
builder
4445
.use(CheckboxSpecs, opts.checkbox ?? {})
45-
.use(ColorSpecs)
46+
.use(ColorSpecs, opts.color ?? {})
4647
.use(ImgSizeSpecs, opts.imgSize ?? {})
4748
.use(MathSpecs)
4849
.use(MonospaceSpecs)

0 commit comments

Comments
 (0)