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
|
@@ -0,0 +1,119 @@
|
|
|
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 { parseDocument } = require( 'htmlparser2' );
|
|
9
|
+
const { Element } = require( 'domhandler' );
|
|
10
|
+
const { selectAll, selectOne } = require( 'css-select' );
|
|
11
|
+
const {
|
|
12
|
+
getAttributeValue,
|
|
13
|
+
appendChild,
|
|
14
|
+
removeElement,
|
|
15
|
+
replaceElement,
|
|
16
|
+
textContent
|
|
17
|
+
} = require( 'domutils' );
|
|
18
|
+
|
|
19
|
+
const createPrerenderPugTemplate = require( '../pug-renderer/create-prerender-pug-template' );
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Renders the button using Pug template.
|
|
23
|
+
*/
|
|
24
|
+
const renderButton = createPrerenderPugTemplate( {
|
|
25
|
+
requires: [
|
|
26
|
+
'_components/svg/index',
|
|
27
|
+
'_components/icon/index',
|
|
28
|
+
'_components/tooltip-popover/index',
|
|
29
|
+
'_components/heading-link/index'
|
|
30
|
+
],
|
|
31
|
+
component: 'heading-link',
|
|
32
|
+
componentAttrs: {
|
|
33
|
+
mask: [ 'headingId' ]
|
|
34
|
+
}
|
|
35
|
+
} );
|
|
36
|
+
|
|
37
|
+
module.exports = function appendCopyHeadingButtons( doc ) {
|
|
38
|
+
// Do not process h1 tags as it's processed further by Umberto and may be removed.
|
|
39
|
+
for ( const heading of selectAll( 'h2, h3, h4, h5, h6', doc ) ) {
|
|
40
|
+
if ( hasClassOrParentWithClass( heading, 'no-transform' ) ) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
removeAllEmptyAnchors( heading );
|
|
45
|
+
|
|
46
|
+
const headingId = getAttributeValue( heading, 'id' );
|
|
47
|
+
const container = new Element( 'div', { class: 'doc b-heading' } );
|
|
48
|
+
|
|
49
|
+
// Create the copy button from the Pug template and parse it into nodes.
|
|
50
|
+
const buttonHtml = renderButton( { headingId } );
|
|
51
|
+
const buttonNodes = parseFragmentElements( buttonHtml );
|
|
52
|
+
|
|
53
|
+
// Put the container where the heading was, then move heading and append the button.
|
|
54
|
+
replaceElement( heading, container );
|
|
55
|
+
appendChild( container, heading );
|
|
56
|
+
for ( const n of buttonNodes ) {
|
|
57
|
+
appendChild( container, n );
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return doc;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Removes all empty anchors that were created to hold the heading links buttons.
|
|
66
|
+
* "Empty" = no element children AND text content (after trimming and removing a single '#') is empty.
|
|
67
|
+
*/
|
|
68
|
+
function removeAllEmptyAnchors( heading ) {
|
|
69
|
+
for ( const a of selectAll( 'a', heading ) ) {
|
|
70
|
+
const hasElementChildren = ( a.children || [] ).some( c => c && c.type === 'tag' );
|
|
71
|
+
const text = ( textContent ? textContent( a ) : getTextFallback( a ) ).trim().replace( '#', '' );
|
|
72
|
+
|
|
73
|
+
if ( !hasElementChildren && !text ) {
|
|
74
|
+
removeElement( a );
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Checks if the element itself or any of its parents has the specified class.
|
|
81
|
+
*/
|
|
82
|
+
function hasClassOrParentWithClass( el, className ) {
|
|
83
|
+
for ( let n = el; n; n = n.parent ) {
|
|
84
|
+
const cls = getAttributeValue( n, 'class' );
|
|
85
|
+
if ( cls && cls.split( /\s+/ ).includes( className ) ) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Parses an HTML fragment into an array of element nodes (skips stray text nodes/whitespace).
|
|
94
|
+
*/
|
|
95
|
+
function parseFragmentElements( html ) {
|
|
96
|
+
const frag = parseDocument( html );
|
|
97
|
+
const body = selectOne( 'body', frag );
|
|
98
|
+
const nodes = body ? body.children : frag.children;
|
|
99
|
+
|
|
100
|
+
return ( nodes || [] ).filter( n => n && n.type === 'tag' );
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Small fallback in case domutils.textContent isn't available.
|
|
105
|
+
*/
|
|
106
|
+
function getTextFallback( node ) {
|
|
107
|
+
let out = '';
|
|
108
|
+
if ( !node || !node.children ) {
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
for ( const ch of node.children ) {
|
|
112
|
+
if ( ch.type === 'text' && typeof ch.data === 'string' ) {
|
|
113
|
+
out += ch.data;
|
|
114
|
+
} else if ( ch.children && ch.children.length ) {
|
|
115
|
+
out += getTextFallback( ch );
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
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 { selectAll } = require( 'css-select' );
|
|
9
|
+
const { getAttributeValue } = require( 'domutils' );
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Global mapping of HTML elements to their corresponding design system classes.
|
|
13
|
+
*/
|
|
14
|
+
const ELEMENTS_CLASSES_MAPPINGS = {
|
|
15
|
+
'h1': [ 'b-h1' ],
|
|
16
|
+
'h2': [ 'b-h2' ],
|
|
17
|
+
'h3': [ 'b-h3' ],
|
|
18
|
+
'h4': [ 'b-h4' ],
|
|
19
|
+
'h5': [ 'b-h5' ],
|
|
20
|
+
'h6': [ 'b-h6' ],
|
|
21
|
+
'a': [ 'b-link' ],
|
|
22
|
+
|
|
23
|
+
// Text formatting elements
|
|
24
|
+
'p': [ 'b-paragraph' ],
|
|
25
|
+
'strong': [ 'b-bold' ],
|
|
26
|
+
'b': [ 'b-bold' ],
|
|
27
|
+
'em': [ 'b-italic' ],
|
|
28
|
+
'i': [ 'b-italic' ],
|
|
29
|
+
'code': [ 'b-inline-code' ],
|
|
30
|
+
'pre': [ 'b-code-block' ],
|
|
31
|
+
'blockquote': [ 'b-quote' ],
|
|
32
|
+
'hr': [ 'b-separator' ],
|
|
33
|
+
'span': [ 'b-text' ],
|
|
34
|
+
'small': [ 'b-small-text' ],
|
|
35
|
+
'sub': [ 'b-subscript' ],
|
|
36
|
+
'sup': [ 'b-superscript' ],
|
|
37
|
+
|
|
38
|
+
// List elements
|
|
39
|
+
'ul': [ 'b-list', 'b-list--unordered' ],
|
|
40
|
+
'ol': [ 'b-list', 'b-list--ordered' ],
|
|
41
|
+
'li': [ 'b-list__item' ],
|
|
42
|
+
'dl': [ 'b-description' ],
|
|
43
|
+
'dt': [ 'b-description__term' ],
|
|
44
|
+
'dd': [ 'b-description__details' ],
|
|
45
|
+
|
|
46
|
+
// Details
|
|
47
|
+
'details': [ 'b-details' ],
|
|
48
|
+
'summary': [ 'b-reset-button', 'b-details__summary' ],
|
|
49
|
+
|
|
50
|
+
// Table elements
|
|
51
|
+
'table': node => {
|
|
52
|
+
const variant = getAttributeValue( node, 'data-variant' ) || 'striped';
|
|
53
|
+
|
|
54
|
+
return [ 'b-table', `b-table--${ variant }` ];
|
|
55
|
+
},
|
|
56
|
+
'thead': [ 'b-table__header' ],
|
|
57
|
+
'tbody': [ 'b-table__body' ],
|
|
58
|
+
'tr': [ 'b-table__row' ],
|
|
59
|
+
'td': [ 'b-table__cell' ],
|
|
60
|
+
'th': [ 'b-table__cell', 'b-table__cell-header' ],
|
|
61
|
+
|
|
62
|
+
// Image and media elements
|
|
63
|
+
'img': [ 'b-image' ],
|
|
64
|
+
'figure': [ 'b-figure' ],
|
|
65
|
+
'figcaption': [ 'b-figure-caption' ],
|
|
66
|
+
'iframe': [ 'b-iframe' ],
|
|
67
|
+
|
|
68
|
+
// Accessibility elements
|
|
69
|
+
'kbd': [ 'c-keyboard-shortcut', 'c-keyboard-shortcut--raised' ],
|
|
70
|
+
'mark': [ 'b-highlight' ]
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Classes that are allowed to be added to elements with existing classes.
|
|
75
|
+
*/
|
|
76
|
+
const ELEMENTS_WITH_WHITELIST_CLASSES = [
|
|
77
|
+
'headerlink'
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Add design system classes to the parsed document elements. If element has 0 CSS classes
|
|
82
|
+
* it'll be considered as not styled and the design system classes will be applied.
|
|
83
|
+
*
|
|
84
|
+
* @param doc - The parsed document to apply design classes to.
|
|
85
|
+
*/
|
|
86
|
+
module.exports = function applyDesignDocClasses( doc ) {
|
|
87
|
+
// Apply classes based on the global mapping.
|
|
88
|
+
for ( const [ selector, classesOrCallback ] of Object.entries( ELEMENTS_CLASSES_MAPPINGS ) ) {
|
|
89
|
+
for ( const element of selectAll( selector, doc ) ) {
|
|
90
|
+
if ( hasClassOrParentWithClass( element, 'no-transform' ) ) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Add the 'doc' class to each element that gets styled.
|
|
95
|
+
addClass( element, 'doc' );
|
|
96
|
+
|
|
97
|
+
const classList = getClasses( element ).filter( className => !ELEMENTS_WITH_WHITELIST_CLASSES.includes( className ) );
|
|
98
|
+
|
|
99
|
+
// Avoid applying classes to elements that already have one non-whitelisted class
|
|
100
|
+
// (besides 'doc' which we just added).
|
|
101
|
+
if ( classList.length > 1 ) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const classesToAdd = typeof classesOrCallback === 'function' ?
|
|
106
|
+
classesOrCallback( element ) :
|
|
107
|
+
classesOrCallback;
|
|
108
|
+
|
|
109
|
+
addClass( element, classesToAdd.join( ' ' ) );
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return doc;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Checks if the element itself or any of its parents has the specified class.
|
|
118
|
+
*
|
|
119
|
+
* @param element - The DOM element to check.
|
|
120
|
+
* @param className - The class name to look for.
|
|
121
|
+
* @returns True if the element or any parent has the class, false otherwise.
|
|
122
|
+
*/
|
|
123
|
+
function hasClassOrParentWithClass( element, className ) {
|
|
124
|
+
for ( let node = element; node; node = node.parent ) {
|
|
125
|
+
if ( node.attribs?.class?.includes( className ) ) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Adds class to a DOM element.
|
|
135
|
+
*
|
|
136
|
+
* @param element - The DOM element.
|
|
137
|
+
* @param className - Class name to add.
|
|
138
|
+
*/
|
|
139
|
+
function addClass( element, className ) {
|
|
140
|
+
const existing = getClasses( element );
|
|
141
|
+
|
|
142
|
+
if ( !existing.includes( className ) ) {
|
|
143
|
+
existing.push( className );
|
|
144
|
+
element.attribs.class = existing.join( ' ' );
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Gets classes of a DOM element.
|
|
150
|
+
*
|
|
151
|
+
* @param element - The DOM element.
|
|
152
|
+
* @returns Array of class names.
|
|
153
|
+
*/
|
|
154
|
+
function getClasses( element ) {
|
|
155
|
+
const existing = getAttributeValue( element, 'class' ) || '';
|
|
156
|
+
return existing.split( /\s+/ ).filter( Boolean );
|
|
157
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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 { Element } = require( 'domhandler' );
|
|
9
|
+
const { selectAll } = require( 'css-select' );
|
|
10
|
+
const { replaceElement, appendChild, isTag, getName } = require( 'domutils' );
|
|
11
|
+
|
|
12
|
+
module.exports = function wrapTableIntoWrappers( doc ) {
|
|
13
|
+
for ( const table of selectAll( 'table', doc ) ) {
|
|
14
|
+
const parent = table.parent;
|
|
15
|
+
const parentIsFigure = isTag( parent ) && getName( parent ).toUpperCase() === 'FIGURE';
|
|
16
|
+
|
|
17
|
+
if ( !parentIsFigure ) {
|
|
18
|
+
const figure = new Element( 'figure', { class: 'doc b-table-wrapper' } );
|
|
19
|
+
replaceElement( table, figure );
|
|
20
|
+
appendChild( figure, table );
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return doc;
|
|
25
|
+
};
|
|
@@ -5,148 +5,117 @@
|
|
|
5
5
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
|
-
const cheerio = require( 'cheerio' );
|
|
9
8
|
const fs = require( 'fs' );
|
|
10
9
|
const path = require( 'path' );
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// Find all elements with data-inline-svg-image attribute.
|
|
23
|
-
$( '[data-inline-svg-image]' ).each( function() {
|
|
24
|
-
const element = $( this );
|
|
25
|
-
const svgPath = element.attr( 'data-inline-svg-image' );
|
|
26
|
-
const fillColor = element.attr( 'data-inline-svg-fill-color' );
|
|
27
|
-
const trimDimensions = element.attr( 'data-inline-svg-trim-dimensions' ) !== undefined;
|
|
28
|
-
const title = element.attr( 'data-inline-svg-title' );
|
|
10
|
+
const { parseDocument } = require( 'htmlparser2' );
|
|
11
|
+
const { Element, Text } = require( 'domhandler' );
|
|
12
|
+
const { selectAll, selectOne } = require( 'css-select' );
|
|
13
|
+
const { getAttributeValue, hasAttrib, replaceElement, appendChild, prependChild } = require( 'domutils' );
|
|
14
|
+
|
|
15
|
+
module.exports = function inlineSvg( doc, themeDir ) {
|
|
16
|
+
for ( const el of selectAll( '[data-inline-svg-image]', doc ) ) {
|
|
17
|
+
const svgPath = getAttributeValue( el, 'data-inline-svg-image' );
|
|
18
|
+
const fillColor = getAttributeValue( el, 'data-inline-svg-fill-color' );
|
|
19
|
+
const trimDimensions = hasAttrib( el, 'data-inline-svg-trim-dimensions' );
|
|
20
|
+
const title = getAttributeValue( el, 'data-inline-svg-title' );
|
|
29
21
|
|
|
30
22
|
// Construct the absolute path to the SVG file.
|
|
31
23
|
// Assuming the path is relative to the Hexo root.
|
|
32
|
-
const absolutePath = path.join(
|
|
24
|
+
const absolutePath = path.join( themeDir, svgPath );
|
|
33
25
|
|
|
34
26
|
// Read the SVG file content
|
|
35
27
|
if ( !fs.existsSync( absolutePath ) ) {
|
|
36
28
|
console.warn( `SVG file not found: ${ absolutePath }` );
|
|
37
|
-
|
|
29
|
+
continue;
|
|
38
30
|
}
|
|
39
31
|
|
|
40
|
-
//
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
trimDimensions,
|
|
45
|
-
title: title || null
|
|
46
|
-
} );
|
|
32
|
+
// Parse the SVG.
|
|
33
|
+
const svgContent = fs.readFileSync( absolutePath, 'utf8' );
|
|
34
|
+
const svgDoc = parseDocument( svgContent, { xmlMode: true } );
|
|
35
|
+
const svgRoot = selectOne( 'svg', svgDoc );
|
|
47
36
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
$svg = SVG_CACHE.get( cacheKey );
|
|
53
|
-
} else {
|
|
54
|
-
// Parse the SVG.
|
|
55
|
-
const svgContent = fs.readFileSync( absolutePath, 'utf8' );
|
|
56
|
-
|
|
57
|
-
$svg = cheerio.load( svgContent, { xmlMode: true } )( 'svg' );
|
|
58
|
-
|
|
59
|
-
// Apply fill color if specified.
|
|
60
|
-
if ( fillColor ) {
|
|
61
|
-
applyFillColor( $, $svg, fillColor );
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Remove width and height if trimDimensions is true
|
|
65
|
-
if ( trimDimensions ) {
|
|
66
|
-
trimSvgDimensions( $, $svg );
|
|
67
|
-
}
|
|
37
|
+
// Apply fill color if specified.
|
|
38
|
+
if ( fillColor ) {
|
|
39
|
+
applyFillColor( svgRoot, fillColor );
|
|
40
|
+
}
|
|
68
41
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
42
|
+
// Remove width and height if trimDimensions is true
|
|
43
|
+
if ( trimDimensions ) {
|
|
44
|
+
trimSvgDimensions( svgRoot );
|
|
45
|
+
}
|
|
73
46
|
|
|
74
|
-
|
|
75
|
-
|
|
47
|
+
// Add title if specified
|
|
48
|
+
if ( title ) {
|
|
49
|
+
addTitle( svgRoot, title );
|
|
76
50
|
}
|
|
77
51
|
|
|
78
52
|
// Copy original element's attributes to SVG.
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
for ( const attr in elementAttributes ) {
|
|
83
|
-
if ( attr.startsWith( 'data-inline-svg-' ) ) {
|
|
53
|
+
for ( const [ name, value ] of Object.entries( el.attribs ) ) {
|
|
54
|
+
if ( name.startsWith( 'data-inline-svg-' ) ) {
|
|
84
55
|
continue;
|
|
85
56
|
}
|
|
86
57
|
|
|
87
|
-
|
|
88
|
-
$svg = $svg.clone();
|
|
89
|
-
svgCloned = true;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
$svg.attr( attr, elementAttributes[ attr ] );
|
|
58
|
+
svgRoot.attribs[ name ] = value;
|
|
93
59
|
}
|
|
94
60
|
|
|
95
|
-
// Replace the original element with the
|
|
96
|
-
|
|
97
|
-
}
|
|
61
|
+
// Replace the original element with the prepared <svg>.
|
|
62
|
+
replaceElement( el, svgRoot );
|
|
63
|
+
};
|
|
98
64
|
|
|
99
|
-
|
|
65
|
+
for ( const el of selectAll( '[data-inline-svg-image]', doc ) ) {
|
|
66
|
+
delete el.attribs[ 'data-spritesheet-svg' ];
|
|
67
|
+
}
|
|
100
68
|
|
|
101
|
-
return
|
|
69
|
+
return doc;
|
|
102
70
|
};
|
|
103
71
|
|
|
104
72
|
/**
|
|
105
|
-
* Applies fill color to an SVG element.
|
|
73
|
+
* Applies fill color to an SVG element (root and any descendant with [fill]).
|
|
74
|
+
* Uses css-select for querying and direct attrib updates for speed.
|
|
106
75
|
*/
|
|
107
|
-
function applyFillColor(
|
|
76
|
+
function applyFillColor( svg, fillColor ) {
|
|
108
77
|
// Replace all fill attributes with the specified color.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
78
|
+
for ( const node of selectAll( '[fill]', svg ) ) {
|
|
79
|
+
node.attribs.fill = fillColor;
|
|
80
|
+
}
|
|
112
81
|
|
|
113
82
|
// Also check the root SVG for fill attribute.
|
|
114
|
-
if (
|
|
115
|
-
|
|
83
|
+
if ( hasAttrib( svg, 'fill' ) ) {
|
|
84
|
+
svg.attribs.fill = fillColor;
|
|
116
85
|
}
|
|
117
|
-
|
|
118
|
-
return $svg;
|
|
119
86
|
}
|
|
120
87
|
|
|
121
88
|
/**
|
|
122
89
|
* Removes width and height attributes from SVG elements.
|
|
123
90
|
*/
|
|
124
|
-
function trimSvgDimensions(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return $svg;
|
|
91
|
+
function trimSvgDimensions( svg ) {
|
|
92
|
+
delete svg.attribs.width;
|
|
93
|
+
delete svg.attribs.height;
|
|
128
94
|
}
|
|
129
95
|
|
|
130
96
|
/**
|
|
131
|
-
* Adds or updates a title element in the SVG.
|
|
97
|
+
* Adds or updates a <title> element in the SVG.
|
|
98
|
+
* - If any <title> exists anywhere under the root, update all of them (mirrors Cheerio .find('title').text()).
|
|
99
|
+
* - Otherwise, prepend a new <title> as the first child of the root <svg>.
|
|
132
100
|
*/
|
|
133
|
-
function addTitle(
|
|
101
|
+
function addTitle( svg, title ) {
|
|
134
102
|
if ( [ '', 'none', 'false', 'null' ].includes( title ) ) {
|
|
135
|
-
return
|
|
103
|
+
return;
|
|
136
104
|
}
|
|
137
105
|
|
|
138
106
|
// Check if SVG already has a title
|
|
139
|
-
const
|
|
107
|
+
const titles = selectAll( 'title', svg );
|
|
140
108
|
|
|
141
|
-
if (
|
|
109
|
+
if ( titles.length ) {
|
|
110
|
+
for ( const t of titles ) {
|
|
142
111
|
// Update existing title
|
|
143
|
-
|
|
112
|
+
t.children = [];
|
|
113
|
+
appendChild( t, new Text( title ) );
|
|
114
|
+
}
|
|
144
115
|
} else {
|
|
145
116
|
// Create new title and add it as the first child
|
|
146
|
-
const
|
|
147
|
-
|
|
117
|
+
const t = new Element( 'title', {} );
|
|
118
|
+
appendChild( t, new Text( title ) );
|
|
119
|
+
prependChild( svg, t );
|
|
148
120
|
}
|
|
149
|
-
|
|
150
|
-
return $svg;
|
|
151
121
|
}
|
|
152
|
-
|