-
-
Notifications
You must be signed in to change notification settings - Fork 40
/
md-embed-image.js
90 lines (85 loc) · 3.2 KB
/
md-embed-image.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// Copyright 2021 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the “License”);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// <https://apache.org/licenses/LICENSE-2.0>.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
'use strict';
const imageSize = require('image-size');
const { existsSync, readFileSync } = require('fs');
const assert = require('assert');
module.exports = md => {
md.core.ruler.push('check_img_in_figure', state => {
let inFigure = false;
for (const t of state.tokens) {
switch (t.type) {
case 'figure_open':
case 'container_figure_open':
assert(!inFigure);
inFigure = true;
break;
case 'figure_close':
case 'container_figure_close':
assert(inFigure);
inFigure = false;
break;
case 'inline':
if (!inFigure) {
const image = t.children.find(t => t.type === 'image');
if (image) {
throw new Error(`Image ${image.attrGet('src')} is not in a separate block. Missing newlines around?`);
}
}
break;
}
}
assert(!inFigure);
});
// Add a post-process rule for inline items.
md.inline.ruler2.push('embed_image', state => {
for (const t of state.tokens) {
// Skip non-image tokens.
if (t.type !== 'image') continue;
let imgSrc = t.attrGet('src');
// We only embed self-hosted images.
if (imgSrc.startsWith('/_img/')) {
const { width, height } = imageSize('src' + imgSrc);
// Lazify image and embed its sizes to avoid layout jump.
t.attrs.push(['width', width], ['height', height], ['loading', 'lazy']);
// Check if `file@2x.ext` exists for `file.ext`.
const imgSrc2x = imgSrc.replace(/\.[^.]*$/, '@2x$&');
if (existsSync('src' + imgSrc2x)) {
// If it does, use it in `srcset` as an alternative variant.
t.attrs.push(['srcset', `${imgSrc2x} 2x`]);
}
} else if (imgSrc.startsWith('/_svg/')) {
// Ignore; we’ll fix this in the embed_svg pass.
} else {
throw new Error(`Image ${imgSrc} is not in the \`/_img/…\` directory.`);
}
}
});
// Add a post-process rule for inline SVGs. This has to be done after implicit
// <figure>s, else we’d lose the implicit figures for the image.
md.core.ruler.after('implicit_figures', 'embed_svg', state => {
for (const t of state.tokens) {
// Skip non-inline images tokens.
if (t.type !== 'inline') continue;
const image = t.children.find(t => t.type === 'image');
if (!image) continue;
const imgSrc = image.attrGet('src');
if (imgSrc.startsWith('/_svg/')) {
const svgContent = readFileSync(`src${imgSrc}`, 'utf8');
image.type = 'html_inline';
image.tag = '';
image.content = svgContent;
}
}
});
};