Skip to content

Commit 3036e6d

Browse files
authored
feat(imsize): rework image styling to get more control (#549)
* feat(imsize): rework image styling to get more control * chore: add more tests with different size configs, refactor styling block * chore: hide inline size styling behind feature flag * chore: add forcedSanitizeWhiteList * chore: rename params
1 parent 36de97d commit 3036e6d

File tree

9 files changed

+168
-9
lines changed

9 files changed

+168
-9
lines changed

src/transform/md.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,9 @@ function initCompiler(md: MarkdownIt, options: OptionsType, env: EnvType) {
168168
const html = md.renderer.render(tokens, md.options, env);
169169

170170
// Sanitize the page
171-
return needToSanitizeHtml ? sanitizeHtml(html, sanitizeOptions) : html;
171+
return needToSanitizeHtml
172+
? sanitizeHtml(html, sanitizeOptions, {cssWhiteList: env.additionalOptionsCssWhiteList})
173+
: html;
172174
};
173175
}
174176

src/transform/plugins/imsize/const.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ export enum ImsizeAttr {
44
Title = 'title',
55
Width = 'width',
66
Height = 'height',
7+
Style = 'style',
78
}

src/transform/plugins/imsize/index.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import {PluginSimple} from 'markdown-it';
1+
import {PluginWithOptions} from 'markdown-it';
22

3-
import {imageWithSize} from './plugin';
3+
import {ImsizeOptions, imageWithSize} from './plugin';
44

55
/**
66
* Imsize plugin for markdown-it.
77
* This plugin overloads original image renderer.
88
* Forked from https://github.com/tatsy/markdown-it-imsize
99
*/
1010

11-
const imsize: PluginSimple = (md) => {
12-
md.inline.ruler.before('emphasis', 'image', imageWithSize(md));
11+
const imsize: PluginWithOptions<ImsizeOptions> = (md, opts) => {
12+
md.inline.ruler.before('emphasis', 'image', imageWithSize(md, opts));
1313
};
1414

1515
export = imsize;

src/transform/plugins/imsize/plugin.ts

+32-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ import type Token from 'markdown-it/lib/token';
55
import {ImsizeAttr} from './const';
66
import {parseImageSize} from './helpers';
77

8-
export const imageWithSize = (md: MarkdownIt): ParserInline.RuleInline => {
8+
export type ImsizeOptions = {
9+
enableInlineStyling?: boolean;
10+
};
11+
12+
export const imageWithSize = (md: MarkdownIt, opts?: ImsizeOptions): ParserInline.RuleInline => {
913
// eslint-disable-next-line complexity
1014
return (state, silent) => {
1115
if (state.src.charCodeAt(state.pos) !== 0x21 /* ! */) {
@@ -206,6 +210,33 @@ export const imageWithSize = (md: MarkdownIt): ParserInline.RuleInline => {
206210
if (height !== '') {
207211
token.attrs.push([ImsizeAttr.Height, height]);
208212
}
213+
214+
if (opts?.enableInlineStyling) {
215+
let style: string | undefined = '';
216+
217+
const widthWithPercent = width.includes('%');
218+
const heightWithPercent = height.includes('%');
219+
220+
if (width !== '') {
221+
const widthString = widthWithPercent ? width : `${width}px`;
222+
style += `width: ${widthString};`;
223+
}
224+
225+
if (height !== '') {
226+
if (width !== '' && !heightWithPercent && !widthWithPercent) {
227+
style += `aspect-ratio: ${width} / ${height};height: auto;`;
228+
state.env.additionalOptionsCssWhiteList ??= {};
229+
state.env.additionalOptionsCssWhiteList['aspect-ratio'] = true;
230+
} else {
231+
const heightString = heightWithPercent ? height : `${height}px`;
232+
style += `height: ${heightString};`;
233+
}
234+
}
235+
236+
if (style) {
237+
token.attrs.push([ImsizeAttr.Style, style]);
238+
}
239+
}
209240
}
210241

211242
state.pos = pos;

src/transform/sanitize.ts

+14-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import cssfilter from 'cssfilter';
44
import * as cheerio from 'cheerio';
55
import css from 'css';
66

7+
import {CssWhiteList} from './typings';
8+
79
const htmlTags = [
810
'a',
911
'abbr',
@@ -492,8 +494,6 @@ const allowedTags = Array.from(
492494
);
493495
const allowedAttributes = Array.from(new Set([...htmlAttrs, ...svgAttrs, ...yfmHtmlAttrs]));
494496

495-
export type CssWhiteList = {[property: string]: boolean};
496-
497497
export interface SanitizeOptions extends sanitizeHtml.IOptions {
498498
cssWhiteList?: CssWhiteList;
499499
disableStyleSanitizer?: boolean;
@@ -598,9 +598,20 @@ function sanitizeStyles(html: string, options: SanitizeOptions) {
598598
return styles + content;
599599
}
600600

601-
export default function sanitize(html: string, options?: SanitizeOptions) {
601+
export default function sanitize(
602+
html: string,
603+
options?: SanitizeOptions,
604+
additionalOptions?: SanitizeOptions,
605+
) {
602606
const sanitizeOptions = options || defaultOptions;
603607

608+
if (additionalOptions?.cssWhiteList) {
609+
sanitizeOptions.cssWhiteList = {
610+
...sanitizeOptions.cssWhiteList,
611+
...additionalOptions.cssWhiteList,
612+
};
613+
}
614+
604615
const needToSanitizeStyles = !(sanitizeOptions.disableStyleSanitizer ?? false);
605616

606617
const modifiedHtml = needToSanitizeStyles ? sanitizeStyles(html, sanitizeOptions) : html;

src/transform/typings.ts

+3
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export type EnvType<Extras extends {} = {}> = {
7474
assets?: unknown[];
7575
meta?: object;
7676
changelogs?: ChangelogItem[];
77+
additionalOptionsCssWhiteList?: CssWhiteList;
7778
} & Extras;
7879

7980
export interface MarkdownItPluginOpts {
@@ -98,3 +99,5 @@ export type MarkdownItPluginCb<T extends {} = {}> = {
9899
export type MarkdownItPreprocessorCb<T extends unknown = {}> = {
99100
(input: string, opts: T & Partial<MarkdownItPluginOpts>, md?: MarkdownIt): string;
100101
};
102+
103+
export type CssWhiteList = {[property: string]: boolean};

test/data/imsize/imsize-fixtures.txt

+20
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,26 @@ Coverage. Image
283283
.
284284
<p><img src="test" alt="test" width="100%"></p>
285285
.
286+
.
287+
![test](test =x100%)
288+
.
289+
<p><img src="test" alt="test" height="100%"></p>
290+
.
291+
.
292+
![test](test =100%x100%)
293+
.
294+
<p><img src="test" alt="test" width="100%" height="100%"></p>
295+
.
296+
.
297+
![test](test =100%x200)
298+
.
299+
<p><img src="test" alt="test" width="100%" height="200"></p>
300+
.
301+
.
302+
![test](test =100x100%)
303+
.
304+
<p><img src="test" alt="test" width="100" height="100%"></p>
305+
.
286306

287307
Coverage. Link
288308
.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
Coverage. Image with inlineStyling
2+
.
3+
![test]( x =100x200)
4+
.
5+
<p><img src="x" alt="test" width="100" height="200" style="width: 100px;aspect-ratio: 100 / 200;height: auto;"></p>
6+
.
7+
.
8+
![test]( x =x)
9+
.
10+
<p><img src="x" alt="test"></p>
11+
.
12+
.
13+
![test]( x =100x)
14+
.
15+
<p><img src="x" alt="test" width="100" style="width: 100px;"></p>
16+
.
17+
.
18+
![test]( x =x200)
19+
.
20+
<p><img src="x" alt="test" height="200" style="height: 200px;"></p>
21+
.
22+
.
23+
![test]( x "title" =100x200)
24+
.
25+
<p><img src="x" alt="test" title="title" width="100" height="200" style="width: 100px;aspect-ratio: 100 / 200;height: auto;"></p>
26+
.
27+
.
28+
![test]( x =WxH )
29+
.
30+
<p>![test]( x =WxH )</p>
31+
.
32+
.
33+
![test]( x = 100x200 )
34+
.
35+
<p>![test]( x = 100x200 )</p>
36+
.
37+
.
38+
![test]( x =aaaxbbb )
39+
.
40+
<p>![test]( x =aaaxbbb )</p>
41+
.
42+
.
43+
![test](http://this.is.test.jpg =100x200)
44+
.
45+
<p><img src="http://this.is.test.jpg" alt="test" width="100" height="200" style="width: 100px;aspect-ratio: 100 / 200;height: auto;"></p>
46+
.
47+
.
48+
![test](<x =100x200)
49+
.
50+
<p>![test](&lt;x =100x200)</p>
51+
.
52+
.
53+
![test](<x> =100x200)
54+
.
55+
<p><img src="x" alt="test" width="100" height="200" style="width: 100px;aspect-ratio: 100 / 200;height: auto;"></p>
56+
.
57+
.
58+
![test](test =100%x)
59+
.
60+
<p><img src="test" alt="test" width="100%" style="width: 100%;"></p>
61+
.
62+
.
63+
![test](test =x100%)
64+
.
65+
<p><img src="test" alt="test" height="100%" style="height: 100%;"></p>
66+
.
67+
.
68+
![test](test =100%x100%)
69+
.
70+
<p><img src="test" alt="test" width="100%" height="100%" style="width: 100%;height: 100%;"></p>
71+
.
72+
.
73+
![test](test =100%x200)
74+
.
75+
<p><img src="test" alt="test" width="100%" height="200" style="width: 100%;height: 200px;"></p>
76+
.
77+
.
78+
![test](test =100x100%)
79+
.
80+
<p><img src="test" alt="test" width="100" height="100%" style="width: 100px;height: 100%;"></p>
81+
.

test/imsize.test.ts

+10
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,13 @@ describe('imsize', () => {
1414

1515
generate(path.join(__dirname, 'data/imsize/imsize-fixtures.txt'), md);
1616
});
17+
18+
describe('imsize with inlineStyling', () => {
19+
const md = new MarkdownIt({
20+
html: true,
21+
linkify: false,
22+
typographer: false,
23+
}).use(imsize, {enableInlineStyling: true});
24+
25+
generate(path.join(__dirname, 'data/imsize/imsize-inlineSizeStyling-fixtures.txt'), md);
26+
});

0 commit comments

Comments
 (0)