@zeropress/build-core 0.6.11 → 0.6.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/api/build-site.js +22 -0
- package/src/render/content-renderer.js +189 -22
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeropress/build-core",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.12",
|
|
4
4
|
"description": "Shared ZeroPress build core",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@zeropress/slug-policy": "0.1.0",
|
|
37
|
-
"@zeropress/preview-data-validator": "0.6.
|
|
37
|
+
"@zeropress/preview-data-validator": "0.6.12",
|
|
38
38
|
"@zeropress/theme-validator": "0.6.5",
|
|
39
39
|
"highlight.js": "11.11.1",
|
|
40
40
|
"markdown-it": "14.2.0",
|
package/src/api/build-site.js
CHANGED
|
@@ -449,6 +449,7 @@ function normalizePreviewData(previewData, options = {}) {
|
|
|
449
449
|
? normalizeSiteFavicon(previewData.site.favicon, media_base_url)
|
|
450
450
|
: normalizeSiteFavicon(options.favicon, ''),
|
|
451
451
|
logo: normalizeSiteLogo(previewData.site.logo, media_base_url),
|
|
452
|
+
newsletter: normalizeSiteNewsletter(previewData.site.newsletter),
|
|
452
453
|
posts_per_page: Number.isInteger(previewData.site.posts_per_page) && previewData.site.posts_per_page > 0
|
|
453
454
|
? previewData.site.posts_per_page
|
|
454
455
|
: DEFAULT_POSTS_PER_PAGE,
|
|
@@ -773,6 +774,27 @@ function normalizeSiteLogo(logo, media_base_url) {
|
|
|
773
774
|
};
|
|
774
775
|
}
|
|
775
776
|
|
|
777
|
+
function normalizeSiteNewsletter(newsletter) {
|
|
778
|
+
if (!newsletter || typeof newsletter !== 'object' || Array.isArray(newsletter)) {
|
|
779
|
+
return undefined;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
const title = normalizeOptionalString(newsletter.title);
|
|
783
|
+
const description = normalizeOptionalString(newsletter.description);
|
|
784
|
+
const buttonLabel = normalizeOptionalString(newsletter.button_label);
|
|
785
|
+
const signupUrl = normalizeOptionalString(newsletter.signup_url);
|
|
786
|
+
const embedUrl = normalizeOptionalString(newsletter.embed_url);
|
|
787
|
+
|
|
788
|
+
return {
|
|
789
|
+
enabled: newsletter.enabled === true,
|
|
790
|
+
...(title ? { title } : {}),
|
|
791
|
+
...(description ? { description } : {}),
|
|
792
|
+
...(buttonLabel ? { button_label: buttonLabel } : {}),
|
|
793
|
+
...(signupUrl ? { signup_url: signupUrl } : {}),
|
|
794
|
+
...(embedUrl ? { embed_url: embedUrl } : {}),
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
|
|
776
798
|
function normalizePermalinks(permalinks) {
|
|
777
799
|
const source = permalinks && typeof permalinks === 'object' ? permalinks : {};
|
|
778
800
|
const outputStyle = typeof source.output_style === 'string' && PERMALINK_OUTPUT_STYLES.has(source.output_style)
|
|
@@ -10,6 +10,15 @@ const ALERT_TYPES = new Map([
|
|
|
10
10
|
['WARNING', { className: 'warning', title: 'Warning' }],
|
|
11
11
|
['CAUTION', { className: 'caution', title: 'Caution' }],
|
|
12
12
|
]);
|
|
13
|
+
const ADMONITION_CONTAINER_TYPES = new Map([
|
|
14
|
+
['NOTE', { className: 'note', title: 'Note' }],
|
|
15
|
+
['INFO', { className: 'note', title: 'Info' }],
|
|
16
|
+
['TIP', { className: 'tip', title: 'Tip' }],
|
|
17
|
+
['IMPORTANT', { className: 'important', title: 'Important' }],
|
|
18
|
+
['WARNING', { className: 'warning', title: 'Warning' }],
|
|
19
|
+
['CAUTION', { className: 'caution', title: 'Caution' }],
|
|
20
|
+
['DANGER', { className: 'caution', title: 'Danger' }],
|
|
21
|
+
]);
|
|
13
22
|
|
|
14
23
|
export function renderDocumentContent(content, documentType = 'markdown') {
|
|
15
24
|
return renderDocument(content, documentType).html;
|
|
@@ -51,7 +60,7 @@ function createMarkdownRenderer(toc) {
|
|
|
51
60
|
html: true,
|
|
52
61
|
linkify: false,
|
|
53
62
|
typographer: true,
|
|
54
|
-
breaks:
|
|
63
|
+
breaks: false,
|
|
55
64
|
highlight(value, language) {
|
|
56
65
|
if (language && hljs.getLanguage(language)) {
|
|
57
66
|
try {
|
|
@@ -69,6 +78,7 @@ function createMarkdownRenderer(toc) {
|
|
|
69
78
|
},
|
|
70
79
|
});
|
|
71
80
|
|
|
81
|
+
markdown.use(markdownAdmonitionContainers);
|
|
72
82
|
markdown.use(markdownTaskLists);
|
|
73
83
|
markdown.use(markdownAlerts);
|
|
74
84
|
markdown.use(markdownTableAlignmentClasses);
|
|
@@ -92,6 +102,107 @@ function createMarkdownRenderer(toc) {
|
|
|
92
102
|
return markdown;
|
|
93
103
|
}
|
|
94
104
|
|
|
105
|
+
function markdownAdmonitionContainers(markdown) {
|
|
106
|
+
markdown.block.ruler.before('fence', 'zeropress_admonition_containers', (state, startLine, endLine, silent) => {
|
|
107
|
+
if (state.sCount[startLine] - state.blkIndent >= 4) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const opener = parseAdmonitionContainerOpener(getLineText(state, startLine));
|
|
112
|
+
if (!opener) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const alert = ADMONITION_CONTAINER_TYPES.get(opener.type.toUpperCase());
|
|
117
|
+
if (!alert) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const closeLine = findAdmonitionContainerClose(state, startLine + 1, endLine, opener.markerLength);
|
|
122
|
+
if (closeLine === -1) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (silent) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const openToken = state.push('admonition_container_open', 'aside', 1);
|
|
131
|
+
openToken.block = true;
|
|
132
|
+
openToken.markup = opener.markup;
|
|
133
|
+
openToken.map = [startLine, closeLine + 1];
|
|
134
|
+
openToken.attrSet('class', `zp-alert zp-alert--${alert.className}`);
|
|
135
|
+
openToken.attrSet('role', 'note');
|
|
136
|
+
|
|
137
|
+
state.tokens.push(...createAlertTitleTokens(state, alert.title));
|
|
138
|
+
state.md.block.tokenize(state, startLine + 1, closeLine);
|
|
139
|
+
|
|
140
|
+
const closeToken = state.push('admonition_container_close', 'aside', -1);
|
|
141
|
+
closeToken.block = true;
|
|
142
|
+
closeToken.markup = opener.markup;
|
|
143
|
+
|
|
144
|
+
state.line = closeLine + 1;
|
|
145
|
+
return true;
|
|
146
|
+
}, {
|
|
147
|
+
alt: ['paragraph', 'reference', 'blockquote', 'list'],
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function getLineText(state, line) {
|
|
152
|
+
const start = state.bMarks[line] + state.tShift[line];
|
|
153
|
+
const end = state.eMarks[line];
|
|
154
|
+
return state.src.slice(start, end);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function parseAdmonitionContainerOpener(lineText) {
|
|
158
|
+
const match = /^(:{3,})[ \t]*([A-Za-z][A-Za-z0-9_-]*)(?=$|[ \t[\{])/.exec(lineText);
|
|
159
|
+
if (!match) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
markup: match[1],
|
|
165
|
+
markerLength: match[1].length,
|
|
166
|
+
type: match[2],
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function findAdmonitionContainerClose(state, startLine, endLine, markerLength) {
|
|
171
|
+
let fenceMarker = '';
|
|
172
|
+
let fenceLength = 0;
|
|
173
|
+
|
|
174
|
+
for (let line = startLine; line < endLine; line += 1) {
|
|
175
|
+
const lineText = getLineText(state, line);
|
|
176
|
+
|
|
177
|
+
if (fenceMarker) {
|
|
178
|
+
const fenceClose = new RegExp(`^${escapeRegExp(fenceMarker)}{${fenceLength},}[ \\t]*$`).exec(lineText);
|
|
179
|
+
if (fenceClose) {
|
|
180
|
+
fenceMarker = '';
|
|
181
|
+
fenceLength = 0;
|
|
182
|
+
}
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const fenceOpen = /^(`{3,}|~{3,})/.exec(lineText);
|
|
187
|
+
if (fenceOpen) {
|
|
188
|
+
fenceMarker = fenceOpen[1][0];
|
|
189
|
+
fenceLength = fenceOpen[1].length;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const closeMatch = /^(:{3,})[ \t]*$/.exec(lineText);
|
|
194
|
+
if (closeMatch && closeMatch[1].length >= markerLength) {
|
|
195
|
+
return line;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return -1;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function escapeRegExp(value) {
|
|
203
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
204
|
+
}
|
|
205
|
+
|
|
95
206
|
function markdownTableAlignmentClasses(markdown) {
|
|
96
207
|
for (const tokenName of ['th_open', 'td_open']) {
|
|
97
208
|
markdown.renderer.rules[tokenName] = (tokens, index, options, env, self) => {
|
|
@@ -209,8 +320,9 @@ function markdownAlerts(markdown) {
|
|
|
209
320
|
state.tokens.splice(firstParagraph.openIndex, 3);
|
|
210
321
|
}
|
|
211
322
|
|
|
212
|
-
|
|
213
|
-
index
|
|
323
|
+
const titleTokens = createAlertTitleTokens(state, alert.title);
|
|
324
|
+
state.tokens.splice(index + 1, 0, ...titleTokens);
|
|
325
|
+
index += titleTokens.length;
|
|
214
326
|
}
|
|
215
327
|
});
|
|
216
328
|
}
|
|
@@ -272,18 +384,10 @@ function stripAlertMarker(inlineToken, state) {
|
|
|
272
384
|
}
|
|
273
385
|
|
|
274
386
|
function createAlertTitleTokens(state, title) {
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
inlineToken.content = title;
|
|
280
|
-
const textToken = new state.Token('text', '', 0);
|
|
281
|
-
textToken.content = title;
|
|
282
|
-
inlineToken.children = [textToken];
|
|
283
|
-
|
|
284
|
-
const closeToken = new state.Token('paragraph_close', 'p', -1);
|
|
285
|
-
|
|
286
|
-
return [openToken, inlineToken, closeToken];
|
|
387
|
+
const token = new state.Token('html_block', '', 0);
|
|
388
|
+
token.block = true;
|
|
389
|
+
token.content = `<p class="zp-alert__title">${escapeHtml(title)}</p>`;
|
|
390
|
+
return [token];
|
|
287
391
|
}
|
|
288
392
|
|
|
289
393
|
function addTokenClass(token, className) {
|
|
@@ -301,12 +405,7 @@ function normalizeDocumentType(value) {
|
|
|
301
405
|
}
|
|
302
406
|
|
|
303
407
|
function transformPlaintext(content) {
|
|
304
|
-
const escaped = content
|
|
305
|
-
.replace(/&/g, '&')
|
|
306
|
-
.replace(/</g, '<')
|
|
307
|
-
.replace(/>/g, '>')
|
|
308
|
-
.replace(/"/g, '"')
|
|
309
|
-
.replace(/'/g, ''');
|
|
408
|
+
const escaped = escapeHtml(content);
|
|
310
409
|
|
|
311
410
|
const paragraphs = escaped
|
|
312
411
|
.split(/\n\s*\n/)
|
|
@@ -320,11 +419,20 @@ function transformPlaintext(content) {
|
|
|
320
419
|
return paragraphs.map((entry) => `<p>${entry}</p>`).join('\n');
|
|
321
420
|
}
|
|
322
421
|
|
|
422
|
+
function escapeHtml(value) {
|
|
423
|
+
return String(value)
|
|
424
|
+
.replace(/&/g, '&')
|
|
425
|
+
.replace(/</g, '<')
|
|
426
|
+
.replace(/>/g, '>')
|
|
427
|
+
.replace(/"/g, '"')
|
|
428
|
+
.replace(/'/g, ''');
|
|
429
|
+
}
|
|
430
|
+
|
|
323
431
|
function sanitizeHtml(html) {
|
|
324
432
|
const allowedTags = new Set([
|
|
325
433
|
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
|
326
434
|
'p', 'br', 'hr',
|
|
327
|
-
'strong', 'em', 'u', 's', 'code', 'pre',
|
|
435
|
+
'strong', 'b', 'em', 'u', 's', 'sup', 'sub', 'code', 'pre',
|
|
328
436
|
'a', 'img',
|
|
329
437
|
'ul', 'ol', 'li',
|
|
330
438
|
'blockquote', 'aside',
|
|
@@ -342,6 +450,8 @@ function sanitizeHtml(html) {
|
|
|
342
450
|
iframe: new Set(['src', 'width', 'height', 'frameborder', 'allowfullscreen', 'class', 'title']),
|
|
343
451
|
input: new Set(['type', 'checked', 'disabled', 'class', 'id', 'aria-label']),
|
|
344
452
|
source: new Set(['src', 'srcset', 'sizes', 'type', 'media', 'width', 'height', 'class', 'id']),
|
|
453
|
+
th: new Set(['rowspan', 'colspan', 'align', 'class', 'id']),
|
|
454
|
+
td: new Set(['rowspan', 'colspan', 'align', 'class', 'id']),
|
|
345
455
|
video: new Set(['src', 'controls', 'controlslist', 'autoplay', 'loop', 'muted', 'playsinline', 'poster', 'preload', 'width', 'height', 'class', 'id', 'title']),
|
|
346
456
|
audio: new Set(['src', 'controls', 'controlslist', 'autoplay', 'loop', 'muted', 'preload', 'class', 'id', 'title']),
|
|
347
457
|
track: new Set(['src', 'kind', 'srclang', 'label', 'default', 'class', 'id']),
|
|
@@ -370,6 +480,7 @@ function sanitizeHtml(html) {
|
|
|
370
480
|
const filteredAttributes = [];
|
|
371
481
|
let anchorTarget = '';
|
|
372
482
|
let anchorRel = '';
|
|
483
|
+
let tableCellAlignClass = '';
|
|
373
484
|
|
|
374
485
|
if (attributeString) {
|
|
375
486
|
const attributePattern = /([a-zA-Z][a-zA-Z0-9-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
|
|
@@ -418,6 +529,24 @@ function sanitizeHtml(html) {
|
|
|
418
529
|
continue;
|
|
419
530
|
}
|
|
420
531
|
|
|
532
|
+
if ((normalizedTag === 'th' || normalizedTag === 'td') && (attributeName === 'rowspan' || attributeName === 'colspan')) {
|
|
533
|
+
const span = sanitizePositiveIntegerAttribute(attributeValue);
|
|
534
|
+
if (!span) {
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
filteredAttributes.push(`${attributeName}="${span}"`);
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if ((normalizedTag === 'th' || normalizedTag === 'td') && attributeName === 'align') {
|
|
542
|
+
const alignment = sanitizeTableAlignment(attributeValue);
|
|
543
|
+
if (!alignment) {
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
tableCellAlignClass = `zp-align-${alignment}`;
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
|
|
421
550
|
if (normalizedTag === 'input' && attributeName === 'type' && attributeValue !== 'checkbox') {
|
|
422
551
|
continue;
|
|
423
552
|
}
|
|
@@ -426,6 +555,10 @@ function sanitizeHtml(html) {
|
|
|
426
555
|
}
|
|
427
556
|
}
|
|
428
557
|
|
|
558
|
+
if (tableCellAlignClass) {
|
|
559
|
+
appendClassAttribute(filteredAttributes, tableCellAlignClass);
|
|
560
|
+
}
|
|
561
|
+
|
|
429
562
|
if (normalizedTag === 'a') {
|
|
430
563
|
if (anchorTarget) {
|
|
431
564
|
filteredAttributes.push(`target="${anchorTarget}"`);
|
|
@@ -455,6 +588,40 @@ function sanitizeHtml(html) {
|
|
|
455
588
|
.join('');
|
|
456
589
|
}
|
|
457
590
|
|
|
591
|
+
function appendClassAttribute(attributes, className) {
|
|
592
|
+
const classIndex = attributes.findIndex((attribute) => attribute.startsWith('class="'));
|
|
593
|
+
if (classIndex === -1) {
|
|
594
|
+
attributes.push(`class="${className}"`);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const existingValue = attributes[classIndex].slice('class="'.length, -1);
|
|
599
|
+
const classes = existingValue.split(/\s+/).filter(Boolean);
|
|
600
|
+
if (!classes.includes(className)) {
|
|
601
|
+
classes.push(className);
|
|
602
|
+
}
|
|
603
|
+
attributes[classIndex] = `class="${classes.join(' ')}"`;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function sanitizePositiveIntegerAttribute(value) {
|
|
607
|
+
const normalized = String(value).trim();
|
|
608
|
+
if (!/^\d+$/.test(normalized)) {
|
|
609
|
+
return '';
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const numericValue = Number(normalized);
|
|
613
|
+
if (!Number.isSafeInteger(numericValue) || numericValue < 1) {
|
|
614
|
+
return '';
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
return String(numericValue);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function sanitizeTableAlignment(value) {
|
|
621
|
+
const normalized = String(value).trim().toLowerCase();
|
|
622
|
+
return ['left', 'center', 'right'].includes(normalized) ? normalized : '';
|
|
623
|
+
}
|
|
624
|
+
|
|
458
625
|
function sanitizeAnchorTarget(value) {
|
|
459
626
|
const normalized = String(value).trim().toLowerCase();
|
|
460
627
|
return normalized === '_blank' ? '_blank' : '';
|