umberto 9.0.0 → 9.1.0
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/CHANGELOG.md +14 -9
- package/package.json +8 -13
- package/scripts/filter/after-post-render/fix-code-samples.js +82 -18
- package/scripts/filter/after-post-render/gloria.js +27 -0
- package/scripts/filter/after-post-render/insert-error-codes.js +34 -26
- package/scripts/filter/after-post-render/validate-after-render.js +27 -6
- package/scripts/filter/after-render/process-svg.js +21 -0
- package/scripts/filter/before-post-render/gloria/render-post-render-pug-components.js +46 -18
- package/scripts/helper/u-extract-and-cache-title.js +27 -8
- package/scripts/helper/u-split-to-title-and-content.js +32 -8
- package/scripts/utils/gloria-after-post-render/append-copy-heading-buttons.js +119 -0
- package/scripts/utils/gloria-after-post-render/apply-design-doc-classes.js +157 -0
- package/scripts/utils/gloria-after-post-render/wrap-table-into-wrappers.js +25 -0
- package/scripts/utils/inline-svg.js +63 -94
- package/scripts/utils/spritesheet-svg.js +82 -102
- package/scripts/utils/toc.js +85 -31
- package/src/api-builder/api-builder.js +53 -40
- package/src/api-builder/build-page-worker.js +35 -0
- package/src/api-builder/classes/description-parser.js +77 -38
- package/src/data-converter/converters/jsduck2umberto.js +43 -15
- package/src/hexo/filter/project-locals.js +3 -0
- package/src/sdk-builder/get-sdk-sources.js +81 -44
- package/src/tasks/build-documentation.js +4 -0
- package/src/tasks/minify-html.js +1 -1
- package/src/tasks/validate-links-collect-worker.js +34 -0
- package/src/tasks/validate-links-worker.js +127 -0
- package/src/tasks/validate-links.js +61 -259
- package/themes/umberto/layout/gloria/_head/head.pug +3 -0
- package/themes/umberto/layout/gloria/_modules/index.pug +1 -0
- package/themes/umberto/layout/gloria/_modules/kapa/index.pug +0 -1
- package/themes/umberto/layout/gloria/_modules/sentry/index.pug +27 -0
- package/scripts/filter/after-post-render/gloria/append-copy-heading-buttons.js +0 -90
- package/scripts/filter/after-post-render/gloria/apply-design-doc-classes.js +0 -96
- package/scripts/filter/after-post-render/gloria/wrap-table-into-wrappers.js +0 -36
- package/scripts/filter/after-render/gloria/inline-svg.js +0 -14
- package/scripts/filter/after-render/gloria/spritesheet-svg.js +0 -14
- package/scripts/utils/apply-design-doc-classes.js +0 -82
- /package/src/tasks/{minify-worker.js → minify-html-worker.js} +0 -0
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
8
|
const { styleText } = require( 'util' );
|
|
9
|
-
const
|
|
9
|
+
const { parseDocument } = require( 'htmlparser2' );
|
|
10
|
+
const { default: render } = require( 'dom-serializer' );
|
|
11
|
+
const { selectAll, selectOne } = require( 'css-select' );
|
|
12
|
+
const { replaceElement, removeElement, appendChild } = require( 'domutils' );
|
|
10
13
|
const splitLongname = require( '../../helpers/split-longname' );
|
|
11
14
|
const macroReplacer = require( '../../tasks/macro-replacer' );
|
|
12
15
|
const findTargetDoclet = require( '../utils/findtargetdoclet' );
|
|
@@ -72,27 +75,29 @@ module.exports = class DescriptionParser {
|
|
|
72
75
|
* @returns {Object}.content Full description.
|
|
73
76
|
*/
|
|
74
77
|
_splitToExcerptAndContent( str, { withExcerpt = false } = {} ) {
|
|
75
|
-
if ( withExcerpt ) {
|
|
76
|
-
if ( !str.startsWith( '<p>' ) ) {
|
|
77
|
-
return {
|
|
78
|
-
excerpt: '',
|
|
79
|
-
content: str
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const $ = cheerio.load( str, null, false );
|
|
84
|
-
const excerpt = $( 'p' ).first().remove().html();
|
|
85
|
-
const content = $.html();
|
|
86
|
-
|
|
78
|
+
if ( !withExcerpt ) {
|
|
87
79
|
return {
|
|
88
|
-
|
|
89
|
-
content
|
|
80
|
+
content: str
|
|
90
81
|
};
|
|
91
|
-
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if ( !str.startsWith( '<p>' ) ) {
|
|
92
85
|
return {
|
|
86
|
+
excerpt: '',
|
|
93
87
|
content: str
|
|
94
88
|
};
|
|
95
89
|
}
|
|
90
|
+
|
|
91
|
+
const doc = parseDocument( str );
|
|
92
|
+
const firstParagraph = selectOne( 'p', doc );
|
|
93
|
+
const excerpt = render( firstParagraph.children || [] );
|
|
94
|
+
|
|
95
|
+
removeElement( firstParagraph );
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
excerpt,
|
|
99
|
+
content: render( doc )
|
|
100
|
+
};
|
|
96
101
|
}
|
|
97
102
|
|
|
98
103
|
/**
|
|
@@ -361,16 +366,16 @@ module.exports = class DescriptionParser {
|
|
|
361
366
|
return data;
|
|
362
367
|
}
|
|
363
368
|
|
|
364
|
-
const
|
|
369
|
+
const doc = parseDocument( data );
|
|
365
370
|
|
|
366
|
-
|
|
367
|
-
const headerLevel = Number(
|
|
371
|
+
for ( const header of selectAll( 'h1,h2,h3,h4,h5,h6', doc ) ) {
|
|
372
|
+
const headerLevel = Number( header.name.slice( -1 ) );
|
|
368
373
|
const newHeaderLevel = headerLevel + decreaseLevel <= 6 ? headerLevel + decreaseLevel : 6;
|
|
369
374
|
|
|
370
|
-
|
|
371
|
-
}
|
|
375
|
+
header.name = `h${ newHeaderLevel }`;
|
|
376
|
+
}
|
|
372
377
|
|
|
373
|
-
return
|
|
378
|
+
return render( doc );
|
|
374
379
|
}
|
|
375
380
|
|
|
376
381
|
/**
|
|
@@ -383,36 +388,38 @@ module.exports = class DescriptionParser {
|
|
|
383
388
|
return str;
|
|
384
389
|
}
|
|
385
390
|
|
|
386
|
-
const
|
|
391
|
+
const doc = parseDocument( str );
|
|
387
392
|
const theme = options.theme;
|
|
388
393
|
|
|
389
394
|
if ( theme === 'gloria' ) {
|
|
390
|
-
|
|
391
|
-
const
|
|
395
|
+
for ( const pre of selectAll( 'pre', doc ) ) {
|
|
396
|
+
const codeEl = selectOne( 'code', pre );
|
|
392
397
|
|
|
393
|
-
if (
|
|
394
|
-
|
|
398
|
+
if ( !codeEl ) {
|
|
399
|
+
continue;
|
|
395
400
|
}
|
|
396
401
|
|
|
397
402
|
// Get the language from the class (e.g., "language-js" -> "js")
|
|
398
|
-
const
|
|
399
|
-
|
|
403
|
+
const classAttr = codeEl.attribs && codeEl.attribs.class ? codeEl.attribs.class : null;
|
|
404
|
+
const language = classAttr ?
|
|
405
|
+
classAttr.replace( 'doc', '' ).replace( 'language-', '' ).replace( 'ts', 'typescript' ).trim() :
|
|
400
406
|
null;
|
|
401
407
|
|
|
402
408
|
// Get the code content
|
|
403
|
-
const code =
|
|
409
|
+
const code = render( codeEl.children || [] );
|
|
404
410
|
|
|
405
|
-
// Use the render-pug-component utility
|
|
411
|
+
// Use the render-pug-component utility.
|
|
406
412
|
const html = renderCodeBlockPug( {
|
|
407
413
|
language,
|
|
408
414
|
code
|
|
409
415
|
} );
|
|
410
416
|
|
|
411
|
-
|
|
412
|
-
}
|
|
417
|
+
replaceElement( pre, parseFirstElement( html ) );
|
|
418
|
+
}
|
|
413
419
|
} else {
|
|
414
|
-
|
|
415
|
-
let code =
|
|
420
|
+
for ( const pre of selectAll( 'pre.source', doc ) ) {
|
|
421
|
+
let code = render( pre.children || [] );
|
|
422
|
+
|
|
416
423
|
const begin = /<code> {4}/.exec( code );
|
|
417
424
|
|
|
418
425
|
if ( begin ) {
|
|
@@ -421,11 +428,11 @@ module.exports = class DescriptionParser {
|
|
|
421
428
|
} );
|
|
422
429
|
}
|
|
423
430
|
|
|
424
|
-
|
|
425
|
-
}
|
|
431
|
+
setInnerHTML( pre, code );
|
|
432
|
+
}
|
|
426
433
|
}
|
|
427
434
|
|
|
428
|
-
return
|
|
435
|
+
return render( doc );
|
|
429
436
|
}
|
|
430
437
|
|
|
431
438
|
_macrosReplacer( fullText ) {
|
|
@@ -442,3 +449,35 @@ function composeFunctions( ...fns ) {
|
|
|
442
449
|
return result;
|
|
443
450
|
};
|
|
444
451
|
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Helper: parses an HTML fragment and returns the first element node.
|
|
455
|
+
*/
|
|
456
|
+
function parseFirstElement( html ) {
|
|
457
|
+
const frag = parseDocument( html );
|
|
458
|
+
const body = selectOne( 'body', frag );
|
|
459
|
+
const nodes = body ? body.children : frag.children;
|
|
460
|
+
|
|
461
|
+
for ( const node of nodes || [] ) {
|
|
462
|
+
if ( node && node.type === 'tag' ) {
|
|
463
|
+
return node;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Helper: sets element's inner HTML using a fragment string.
|
|
472
|
+
*/
|
|
473
|
+
function setInnerHTML( element, html ) {
|
|
474
|
+
const frag = parseDocument( html );
|
|
475
|
+
const body = selectOne( 'body', frag );
|
|
476
|
+
const nodes = body ? body.children : frag.children;
|
|
477
|
+
|
|
478
|
+
element.children = [];
|
|
479
|
+
|
|
480
|
+
for ( const node of nodes || [] ) {
|
|
481
|
+
appendChild( element, node );
|
|
482
|
+
}
|
|
483
|
+
}
|
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const { parseDocument } = require( 'htmlparser2' );
|
|
9
|
+
const { default: render } = require( 'dom-serializer' );
|
|
10
|
+
const { selectAll, selectOne } = require( 'css-select' );
|
|
11
|
+
const { getAttributeValue, textContent, replaceElement } = require( 'domutils' );
|
|
12
|
+
|
|
9
13
|
const keyNameMap = {
|
|
10
14
|
tagname: {
|
|
11
15
|
name: 'kind',
|
|
@@ -161,16 +165,22 @@ function tagnameAdapter( item ) {
|
|
|
161
165
|
|
|
162
166
|
function docAdapter( item ) {
|
|
163
167
|
const description = item.doc;
|
|
164
|
-
const
|
|
168
|
+
const doc = parseDocument( description, { decodeEntities: false } );
|
|
165
169
|
|
|
166
|
-
|
|
167
|
-
const href =
|
|
168
|
-
const linkText =
|
|
170
|
+
for ( const a of selectAll( 'a', doc ) ) {
|
|
171
|
+
const href = getAttributeValue( a, 'href' );
|
|
172
|
+
const linkText = textContent( a );
|
|
173
|
+
const original = render( a, { encodeEntities: false } );
|
|
169
174
|
|
|
170
|
-
|
|
171
|
-
|
|
175
|
+
const replacementHtml = fixLink( href, linkText, original );
|
|
176
|
+
const replacementNode = parseFirstNode( replacementHtml );
|
|
172
177
|
|
|
173
|
-
|
|
178
|
+
if ( replacementNode ) {
|
|
179
|
+
replaceElement( a, replacementNode );
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return render( doc, { encodeEntities: false } );
|
|
174
184
|
}
|
|
175
185
|
|
|
176
186
|
function fixLink( href, linkText, original ) {
|
|
@@ -263,16 +273,19 @@ function overridesAdapter( item ) {
|
|
|
263
273
|
const overrides = item.overrides || [];
|
|
264
274
|
|
|
265
275
|
return overrides.map( o => {
|
|
266
|
-
const
|
|
276
|
+
const doc = parseDocument( o.link );
|
|
267
277
|
|
|
268
|
-
|
|
269
|
-
const
|
|
270
|
-
|
|
278
|
+
for ( const a of selectAll( 'a', doc ) ) {
|
|
279
|
+
const replacementHtml = fixLink(
|
|
280
|
+
getAttributeValue( a, 'href' ),
|
|
281
|
+
textContent( a ),
|
|
282
|
+
render( a )
|
|
283
|
+
);
|
|
271
284
|
|
|
272
|
-
|
|
273
|
-
}
|
|
285
|
+
replaceElement( a, parseFirstNode( replacementHtml ) );
|
|
286
|
+
}
|
|
274
287
|
|
|
275
|
-
o.link =
|
|
288
|
+
o.link = render( doc );
|
|
276
289
|
|
|
277
290
|
return o;
|
|
278
291
|
} );
|
|
@@ -300,3 +313,18 @@ function toArray( item ) {
|
|
|
300
313
|
|
|
301
314
|
return [];
|
|
302
315
|
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Parses an HTML fragment and returns the first node (element or text).
|
|
319
|
+
*/
|
|
320
|
+
function parseFirstNode( html ) {
|
|
321
|
+
const doc = parseDocument( html );
|
|
322
|
+
const body = selectOne( 'body', doc );
|
|
323
|
+
const nodes = body ? body.children : doc.children;
|
|
324
|
+
|
|
325
|
+
if ( nodes && nodes.length ) {
|
|
326
|
+
return nodes[ 0 ];
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
@@ -21,6 +21,7 @@ const umbertoVersion = require( '../../../package.json' ).version;
|
|
|
21
21
|
* @param {Object} googletagmanager Google Tag Manager config.
|
|
22
22
|
* @param {Object} googleanalytics Google Analytics config.
|
|
23
23
|
* @param {Object} kapa Kapa.ai config.
|
|
24
|
+
* @param {Object} sentry Sentry config.
|
|
24
25
|
* @param {Object} vwo Visual Website Optimizer config.
|
|
25
26
|
* @param {Object} feedbackWidget Feedback widget config
|
|
26
27
|
* @param {Array} extraStylePaths Paths to extra external css.
|
|
@@ -38,6 +39,7 @@ module.exports = ( ctx, {
|
|
|
38
39
|
googletagmanager,
|
|
39
40
|
googleanalytics,
|
|
40
41
|
kapa,
|
|
42
|
+
sentry,
|
|
41
43
|
promobar,
|
|
42
44
|
vwo,
|
|
43
45
|
feedbackWidget,
|
|
@@ -108,6 +110,7 @@ module.exports = ( ctx, {
|
|
|
108
110
|
locals.googletagmanager = googletagmanager;
|
|
109
111
|
locals.googleanalytics = googleanalytics;
|
|
110
112
|
locals.kapa = kapa;
|
|
113
|
+
locals.sentry = sentry;
|
|
111
114
|
locals.promobar = promobar;
|
|
112
115
|
locals.vwo = vwo;
|
|
113
116
|
locals.feedbackWidget = feedbackWidget;
|
|
@@ -8,7 +8,16 @@
|
|
|
8
8
|
const fs = require( 'fs' );
|
|
9
9
|
const upath = require( 'upath' );
|
|
10
10
|
const { globSync } = require( 'glob' );
|
|
11
|
-
const
|
|
11
|
+
const { parseDocument } = require( 'htmlparser2' );
|
|
12
|
+
const { cloneNode, Text } = require( 'domhandler' );
|
|
13
|
+
const { default: render } = require( 'dom-serializer' );
|
|
14
|
+
const { selectAll, selectOne } = require( 'css-select' );
|
|
15
|
+
const {
|
|
16
|
+
getAttributeValue,
|
|
17
|
+
hasAttrib,
|
|
18
|
+
textContent,
|
|
19
|
+
getName
|
|
20
|
+
} = require( 'domutils' );
|
|
12
21
|
|
|
13
22
|
const SDK_SELECTOR = 'meta[name="sdk-samples"]';
|
|
14
23
|
|
|
@@ -18,53 +27,66 @@ module.exports = sourcePath => {
|
|
|
18
27
|
|
|
19
28
|
for ( const filePath of filePaths ) {
|
|
20
29
|
const content = fs.readFileSync( filePath, { encoding: 'utf8' } );
|
|
21
|
-
const
|
|
22
|
-
const
|
|
30
|
+
const doc = parseDocument( content );
|
|
31
|
+
const sdkMeta = selectOne( SDK_SELECTOR, doc );
|
|
32
|
+
const samplesNames = sdkMeta && getAttributeValue( sdkMeta, 'content' ) ?
|
|
33
|
+
getAttributeValue( sdkMeta, 'content' ).split( '|' ) :
|
|
34
|
+
null;
|
|
35
|
+
|
|
23
36
|
const samples = samplesNames ? [] : null;
|
|
24
37
|
const meta = {};
|
|
25
38
|
|
|
26
39
|
if ( samplesNames ) {
|
|
27
|
-
for ( const
|
|
40
|
+
for ( const [ index, name ] of samplesNames.entries() ) {
|
|
28
41
|
const bodyItems = [];
|
|
29
42
|
const headItems = [];
|
|
30
43
|
const flags = {};
|
|
31
|
-
|
|
32
|
-
|
|
44
|
+
|
|
45
|
+
for ( const el of selectAll( `[data-sample*="${ index + 1 }"]`, doc ) ) {
|
|
46
|
+
const element = cloneNode( el, true );
|
|
33
47
|
const ret = {};
|
|
34
|
-
const outputArray =
|
|
48
|
+
const outputArray = isInsideHead( el ) ? headItems : bodyItems;
|
|
49
|
+
|
|
50
|
+
if ( hasAttrib( el, 'type' ) && getAttributeValue( el, 'type' ) === 'template' ) {
|
|
51
|
+
const source = textContent( el )
|
|
52
|
+
.replace( /</g, '<' )
|
|
53
|
+
.replace( />/g, '>' );
|
|
54
|
+
|
|
55
|
+
outputArray.push( { source } );
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if ( hasAttrib( element, 'data-sample-short' ) ) {
|
|
60
|
+
element.children = [ new Text( '{%SHORT_EDITOR_CONTENT%}' ) ];
|
|
61
|
+
ret.short = true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (
|
|
65
|
+
hasAttrib( element, 'data-sample-preservewhitespace' ) ||
|
|
66
|
+
hasAttrib( element, 'data-sample-preserve-whitespace' )
|
|
67
|
+
) {
|
|
68
|
+
ret.preserveWhitespace = true;
|
|
69
|
+
}
|
|
35
70
|
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
71
|
+
if ( hasAttrib( element, 'data-sample-template' ) && !flags.template ) {
|
|
72
|
+
flags.template = getAttributeValue( element, 'data-sample-template' );
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if ( hasAttrib( element, 'data-sample-highlighter' ) && !flags.highlighter ) {
|
|
76
|
+
flags.highlighter = getAttributeValue( element, 'data-sample-highlighter' );
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if ( hasAttrib( element, 'data-sample-strip-outer-tag' ) ) {
|
|
80
|
+
ret.source = render( element.children || [] ).replace( /^\n|\n$/g, '' );
|
|
41
81
|
} else {
|
|
42
|
-
|
|
43
|
-
element.text( '{%SHORT_EDITOR_CONTENT%}' );
|
|
44
|
-
ret.short = true;
|
|
45
|
-
}
|
|
46
|
-
if ( element.is( '[data-sample-preserveWhitespace]' ) || element.is( '[data-sample-preserve-whitespace]' ) ) {
|
|
47
|
-
ret.preserveWhitespace = true;
|
|
48
|
-
}
|
|
49
|
-
if ( element.is( '[data-sample-template]' ) && !flags.template ) {
|
|
50
|
-
flags.template = element.data( 'sample-template' );
|
|
51
|
-
}
|
|
52
|
-
if ( element.is( '[data-sample-highlighter]' ) && !flags.highlighter ) {
|
|
53
|
-
flags.highlighter = element.data( 'sample-highlighter' );
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if ( element.is( '[data-sample-strip-outer-tag]' ) ) {
|
|
57
|
-
ret.source = element.html().replace( /^\n|\n$/g, '' );
|
|
58
|
-
} else {
|
|
59
|
-
ret.source = $.html( element ).replace( /\s?data-sample(-[\w-]+)?="[^"]*?"/g, '' ); // Strip data attributes
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
outputArray.push( ret );
|
|
82
|
+
ret.source = render( element ).replace( /\s?data-sample(-[\w-]+)?="[^"]*?"/g, '' );
|
|
63
83
|
}
|
|
64
|
-
|
|
84
|
+
|
|
85
|
+
outputArray.push( ret );
|
|
86
|
+
}
|
|
65
87
|
|
|
66
88
|
samples.push( {
|
|
67
|
-
name
|
|
89
|
+
name,
|
|
68
90
|
bodyItems,
|
|
69
91
|
headItems,
|
|
70
92
|
flags
|
|
@@ -72,23 +94,38 @@ module.exports = sourcePath => {
|
|
|
72
94
|
}
|
|
73
95
|
}
|
|
74
96
|
|
|
75
|
-
|
|
76
|
-
const keyName =
|
|
97
|
+
for ( const el of selectAll( 'meta[name^="sdk-"]', doc ) ) {
|
|
98
|
+
const keyName = getAttributeValue( el, 'name' ).replace( 'sdk-', '' );
|
|
77
99
|
|
|
78
|
-
meta[ keyName ] =
|
|
79
|
-
}
|
|
100
|
+
meta[ keyName ] = getAttributeValue( el, 'content' );
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const sdkContents = selectOne( '.sdk-contents', doc );
|
|
104
|
+
const contentHtml = render( sdkContents.children || [] );
|
|
105
|
+
const presetVersion = getAttributeValue( sdkContents, 'data-cke-preset' );
|
|
80
106
|
|
|
81
107
|
files.push( {
|
|
82
108
|
name: upath.basename( filePath, '.html' ),
|
|
83
|
-
description:
|
|
84
|
-
title:
|
|
85
|
-
content:
|
|
86
|
-
source:
|
|
109
|
+
description: getAttributeValue( selectOne( 'meta[name="description"]', doc ), 'content' ),
|
|
110
|
+
title: textContent( selectOne( 'title', doc ) ),
|
|
111
|
+
content: contentHtml,
|
|
112
|
+
source: render( doc ),
|
|
87
113
|
path: upath.relative( sourcePath, filePath ),
|
|
88
|
-
presetVersion
|
|
114
|
+
presetVersion,
|
|
89
115
|
sdkSamples: samples,
|
|
90
116
|
meta
|
|
91
117
|
} );
|
|
92
118
|
}
|
|
119
|
+
|
|
93
120
|
return files;
|
|
94
121
|
};
|
|
122
|
+
|
|
123
|
+
function isInsideHead( node ) {
|
|
124
|
+
for ( let p = node && node.parent; p; p = p.parent ) {
|
|
125
|
+
if ( getName( p ) === 'head' ) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
@@ -146,6 +146,7 @@ module.exports = options => {
|
|
|
146
146
|
googletagmanager: mainConfig.googletagmanager,
|
|
147
147
|
googleanalytics: mainConfig.googleanalytics,
|
|
148
148
|
kapa: mainConfig.kapa,
|
|
149
|
+
sentry: mainConfig.sentry,
|
|
149
150
|
promobar: mainConfig.promobar,
|
|
150
151
|
sitemap: mainConfig.sitemap,
|
|
151
152
|
vwo: mainConfig.vwo,
|
|
@@ -291,6 +292,7 @@ async function buildProjects( rootPath, projectPaths, options = {} ) {
|
|
|
291
292
|
googletagmanager: options.googletagmanager,
|
|
292
293
|
googleanalytics: options.googleanalytics,
|
|
293
294
|
kapa: options.kapa,
|
|
295
|
+
sentry: options.sentry,
|
|
294
296
|
promobar: options.promobar,
|
|
295
297
|
sitemap: options.sitemap,
|
|
296
298
|
vwo: options.vwo,
|
|
@@ -370,6 +372,7 @@ async function buildProjects( rootPath, projectPaths, options = {} ) {
|
|
|
370
372
|
googletagmanager: options.googletagmanager,
|
|
371
373
|
googleanalytics: options.googleanalytics,
|
|
372
374
|
kapa: options.kapa,
|
|
375
|
+
sentry: options.sentry,
|
|
373
376
|
promobar: options.promobar,
|
|
374
377
|
quickNavigationProjects: options.quickNavigationProjects,
|
|
375
378
|
vwo: options.vwo,
|
|
@@ -477,6 +480,7 @@ async function buildApis( projectConfigs, options = {} ) {
|
|
|
477
480
|
googletagmanager: options.googletagmanager,
|
|
478
481
|
googleanalytics: options.googleanalytics,
|
|
479
482
|
kapa: options.kapa,
|
|
483
|
+
sentry: options.sentry,
|
|
480
484
|
promobar: options.promobar,
|
|
481
485
|
vwo: options.vwo,
|
|
482
486
|
feedbackWidget: options.feedbackWidget,
|
package/src/tasks/minify-html.js
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2017-2025, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const fs = require( 'fs-extra' );
|
|
9
|
+
const upath = require( 'upath' );
|
|
10
|
+
const { isMaskedID } = require( '../../scripts/utils/random-id' );
|
|
11
|
+
|
|
12
|
+
module.exports = function( { chunk } ) {
|
|
13
|
+
const links = [];
|
|
14
|
+
|
|
15
|
+
for ( const filePath of chunk ) {
|
|
16
|
+
links.push( upath.resolve( filePath ) );
|
|
17
|
+
links.push( upath.resolve( filePath + '#' ) );
|
|
18
|
+
|
|
19
|
+
const content = fs.readFileSync( filePath, 'utf-8' );
|
|
20
|
+
// Extract both quoted and unquoted "id" attributes.
|
|
21
|
+
const ids = [
|
|
22
|
+
...Array.from( content.matchAll( /id="([^"]+)"/g ), m => m[ 1 ] ),
|
|
23
|
+
...Array.from( content.matchAll( /id=([^\s"'/>]+)/g ), m => m[ 1 ] )
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
for ( const id of ids ) {
|
|
27
|
+
if ( !isMaskedID( id ) && !ids.includes( 'icons-' ) ) {
|
|
28
|
+
links.push( upath.resolve( `${ filePath }#${ id }` ) );
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return links;
|
|
34
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2017-2025, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const fs = require( 'fs-extra' );
|
|
9
|
+
const upath = require( 'upath' );
|
|
10
|
+
const { parseDocument } = require( 'htmlparser2' );
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Validates links in the given chunk of files.
|
|
14
|
+
* Checks if links point to existing files or fragments.
|
|
15
|
+
* Returns a list of invalid links.
|
|
16
|
+
*
|
|
17
|
+
* @param {Object} params
|
|
18
|
+
* @param {string} params.chunk Array of file paths to validate.
|
|
19
|
+
* @param {Set<string>} params.links Set of valid links.
|
|
20
|
+
* @param {Object} params.options Validation options.
|
|
21
|
+
* @returns {Array<Object>}
|
|
22
|
+
*/
|
|
23
|
+
module.exports = function( { chunk, links, options } ) {
|
|
24
|
+
const errors = [];
|
|
25
|
+
|
|
26
|
+
for ( const filePath of chunk ) {
|
|
27
|
+
const invalidHrefs = [];
|
|
28
|
+
const content = fs.readFileSync( filePath, 'utf-8' );
|
|
29
|
+
const linkElements = [];
|
|
30
|
+
|
|
31
|
+
processNode( parseDocument( content ), linkElements );
|
|
32
|
+
|
|
33
|
+
for ( let { href, text } of linkElements ) {
|
|
34
|
+
if ( href.endsWith( '/' ) && !href.includes( '#' ) ) {
|
|
35
|
+
href += 'index.html';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let resolvedPath = getResolvedPath( href, filePath, { publicDir: options.publicDir } );
|
|
39
|
+
|
|
40
|
+
if ( options.skipApi && resolvedPath.includes( '/api/' ) ) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if ( resolvedPath.includes( '/latest/' ) ) {
|
|
45
|
+
const projectInfo = options.projectsInfo ?
|
|
46
|
+
options.projectsInfo.find( i => resolvedPath.includes( `/${ i.slug }/` ) ) :
|
|
47
|
+
null;
|
|
48
|
+
const projectVersion = projectInfo ? projectInfo.version : 'latest';
|
|
49
|
+
resolvedPath = resolvedPath.replace( '/latest/', `/${ projectVersion }/` );
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if ( options.projectsInfo ) {
|
|
53
|
+
const isCorrectLocalPath = options.projectsInfo.some( info => {
|
|
54
|
+
return resolvedPath.includes( info.basePath );
|
|
55
|
+
} );
|
|
56
|
+
|
|
57
|
+
if ( !isCorrectLocalPath ) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if ( !links.has( resolvedPath ) ) {
|
|
63
|
+
invalidHrefs.push( { href, text, filePath } );
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if ( invalidHrefs.length ) {
|
|
68
|
+
errors.push( { filePath, invalidHrefs } );
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return errors;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Resolves the target path for the given href relative to the file and public directory.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} href
|
|
79
|
+
* @param {string} filePath
|
|
80
|
+
* @param {Object} options
|
|
81
|
+
* @param {string} options.publicDir
|
|
82
|
+
* @returns {string}
|
|
83
|
+
*/
|
|
84
|
+
function getResolvedPath( href, filePath, options ) {
|
|
85
|
+
if ( href.startsWith( '#' ) ) {
|
|
86
|
+
return upath.resolve( filePath + href );
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if ( href.startsWith( '/' ) ) {
|
|
90
|
+
const commonPoint = href.match( /^\/[^/]+/ )[ 0 ];
|
|
91
|
+
|
|
92
|
+
// The `commonPoint` should be removed from the end of the `options.publicDir` string.
|
|
93
|
+
// It may happen that `commonPoint` is present at the middle of that path and removing it produces invalid results.
|
|
94
|
+
// See: #909.
|
|
95
|
+
const regExpCommonPoint = new RegExp( commonPoint + '$' );
|
|
96
|
+
|
|
97
|
+
return upath.resolve( options.publicDir.replace( regExpCommonPoint, '' ) + href );
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return upath.resolve( filePath, '..', href );
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Recursively processes the DOM tree, collecting <a> elements with href.
|
|
105
|
+
*
|
|
106
|
+
* @param {Object} node
|
|
107
|
+
* @param {Array<Object>} linkElements
|
|
108
|
+
*/
|
|
109
|
+
function processNode( node, linkElements ) {
|
|
110
|
+
if ( node.type === 'tag' && node.name === 'a' ) {
|
|
111
|
+
const text = node.children.find( child => child.type === 'text' )?.data;
|
|
112
|
+
const href = node.attribs?.href;
|
|
113
|
+
const skipValidation = node.attribs?.[ 'data-skip-validation' ] !== undefined;
|
|
114
|
+
|
|
115
|
+
if ( !skipValidation && text && href && !href.match( /[a-z:]*\/\// ) && !href.match( /mailto:/ ) ) {
|
|
116
|
+
linkElements.push( { href, text } );
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if ( !node.childNodes ) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for ( const childNode of node.childNodes ) {
|
|
125
|
+
processNode( childNode, linkElements );
|
|
126
|
+
}
|
|
127
|
+
}
|