umberto 9.0.0 → 9.1.1
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 +21 -16
- 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 +28 -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
|
@@ -12,130 +12,66 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
const { styleText } = require( 'util' );
|
|
15
|
-
const upath = require( 'upath' );
|
|
16
15
|
const fs = require( 'fs-extra' );
|
|
17
|
-
const
|
|
18
|
-
const { Worker, isMainThread, parentPort, workerData } = require( 'worker_threads' );
|
|
16
|
+
const upath = require( 'upath' );
|
|
19
17
|
const { globSync } = require( 'glob' );
|
|
20
|
-
const {
|
|
21
|
-
const { isMaskedID } = require( '../../scripts/utils/random-id' );
|
|
18
|
+
const { default: TinyPool } = require( 'tinypool' );
|
|
22
19
|
|
|
23
20
|
/**
|
|
24
21
|
* Collects all links from the provided HTML files.
|
|
25
22
|
* Skips files from the vendors directory and (optionally) api.
|
|
26
23
|
* Adds links to files and their fragments (#id).
|
|
27
24
|
*
|
|
28
|
-
* @param {string
|
|
25
|
+
* @param {string} pattern Glob pattern for file paths to process.
|
|
29
26
|
* @param {Object} options Options for filtering files.
|
|
30
27
|
* @returns {{links: Set<string>, pathsToFiles: string[]}}
|
|
31
28
|
*/
|
|
32
|
-
function collectLinks(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
29
|
+
async function collectLinks( pattern, options ) {
|
|
30
|
+
let links = new Set();
|
|
31
|
+
|
|
32
|
+
const pathsToFiles = globSync( pattern )
|
|
33
|
+
.map( path => upath.normalize( path ) )
|
|
34
|
+
.filter( p => {
|
|
35
|
+
if ( p.includes( '/vendors/' ) ) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
if ( options.skipApi && p.includes( '/api/' ) ) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if ( p.endsWith( '.html' ) ) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
42
46
|
|
|
43
|
-
if ( !p.endsWith( '.html' ) ) {
|
|
44
47
|
const resolvePath = upath.resolve( p );
|
|
48
|
+
|
|
45
49
|
if ( fs.statSync( resolvePath ).isFile() ) {
|
|
46
50
|
links.add( upath.resolve( resolvePath ) );
|
|
47
51
|
}
|
|
52
|
+
|
|
48
53
|
return false;
|
|
49
|
-
}
|
|
50
|
-
return true;
|
|
51
|
-
} );
|
|
54
|
+
} );
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
const pool = new TinyPool( {
|
|
57
|
+
filename: require.resolve( './validate-links-collect-worker.js' )
|
|
58
|
+
} );
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
...Array.from( content.matchAll( /id="([^"]+)"/g ), m => m[ 1 ] ),
|
|
61
|
-
...Array.from( content.matchAll( /id=([^\s"'/>]+)/g ), m => m[ 1 ] )
|
|
62
|
-
];
|
|
60
|
+
try {
|
|
61
|
+
const tasks = splitArrayBalanced( pathsToFiles, pool.options.maxThreads ).map( chunk => pool.run( { chunk } ) );
|
|
62
|
+
const results = await Promise.all( tasks );
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
}
|
|
64
|
+
links = new Set( [ ...links, ...results.flat() ] );
|
|
65
|
+
} finally {
|
|
66
|
+
await pool.destroy();
|
|
69
67
|
}
|
|
70
68
|
|
|
71
69
|
return {
|
|
72
|
-
links
|
|
70
|
+
links,
|
|
73
71
|
pathsToFiles
|
|
74
72
|
};
|
|
75
73
|
}
|
|
76
74
|
|
|
77
|
-
/**
|
|
78
|
-
* Validates links in the given chunk of files.
|
|
79
|
-
* Checks if links point to existing files or fragments.
|
|
80
|
-
* Returns a list of invalid links.
|
|
81
|
-
*
|
|
82
|
-
* @param {Object} params
|
|
83
|
-
* @param {string[]} params.chunk Array of file paths to validate.
|
|
84
|
-
* @param {Set<string>} params.links Set of valid links.
|
|
85
|
-
* @param {Object} params.options Validation options.
|
|
86
|
-
* @returns {Array<Object>}
|
|
87
|
-
*/
|
|
88
|
-
function validateChunk( { chunk, links, options } ) {
|
|
89
|
-
const errors = [];
|
|
90
|
-
|
|
91
|
-
for ( const filePath of chunk ) {
|
|
92
|
-
const invalidHrefs = [];
|
|
93
|
-
const content = fs.readFileSync( filePath, 'utf-8' );
|
|
94
|
-
const linkElements = [];
|
|
95
|
-
|
|
96
|
-
processNode( parseDocument( content ), linkElements );
|
|
97
|
-
|
|
98
|
-
for ( let { href, text } of linkElements ) {
|
|
99
|
-
if ( href.endsWith( '/' ) && !href.includes( '#' ) ) {
|
|
100
|
-
href += 'index.html';
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
let resolvedPath = getResolvedPath( href, filePath, { publicDir: options.publicDir } );
|
|
104
|
-
|
|
105
|
-
if ( options.skipApi && resolvedPath.includes( '/api/' ) ) {
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if ( resolvedPath.includes( '/latest/' ) ) {
|
|
110
|
-
const projectInfo = options.projectsInfo ?
|
|
111
|
-
options.projectsInfo.find( i => resolvedPath.includes( `/${ i.slug }/` ) ) :
|
|
112
|
-
null;
|
|
113
|
-
const projectVersion = projectInfo ? projectInfo.version : 'latest';
|
|
114
|
-
resolvedPath = resolvedPath.replace( '/latest/', `/${ projectVersion }/` );
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if ( options.projectsInfo ) {
|
|
118
|
-
const isCorrectLocalPath = options.projectsInfo.some( info => {
|
|
119
|
-
return resolvedPath.includes( info.basePath );
|
|
120
|
-
} );
|
|
121
|
-
|
|
122
|
-
if ( !isCorrectLocalPath ) {
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
if ( !links.has( resolvedPath ) ) {
|
|
127
|
-
invalidHrefs.push( { href, text, filePath } );
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if ( invalidHrefs.length ) {
|
|
132
|
-
errors.push( { filePath, invalidHrefs } );
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return errors;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
75
|
/**
|
|
140
76
|
* Main function executed in the main thread.
|
|
141
77
|
* Splits files into chunks and runs worker threads for each chunk.
|
|
@@ -144,88 +80,32 @@ function validateChunk( { chunk, links, options } ) {
|
|
|
144
80
|
* @param {string} buildPath Path to the build directory.
|
|
145
81
|
* @param {Object} [options={}] Validation options.
|
|
146
82
|
*/
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const pattern = upath.join( buildPath, '**', '*' );
|
|
150
|
-
const pathsToFiles = globSync( pattern ).map( path => upath.normalize( path ) );
|
|
151
|
-
|
|
152
|
-
console.info( `Validating links in ${ styleText( 'magenta', buildPath ) }...` );
|
|
153
|
-
|
|
154
|
-
const start = Date.now();
|
|
155
|
-
const { links, pathsToFiles: filteredPaths } = collectLinks( pathsToFiles, options );
|
|
156
|
-
|
|
157
|
-
const cpuCount = Math.max( 1, os.cpus().length );
|
|
158
|
-
const maxWorkers = 8;
|
|
159
|
-
const workerCount = Math.min( cpuCount, maxWorkers );
|
|
160
|
-
const chunks = chunkBasedOnMemory( workerCount, filteredPaths );
|
|
161
|
-
|
|
162
|
-
let finished = 0;
|
|
163
|
-
let allErrors = [];
|
|
164
|
-
let nextChunkIdx = 0;
|
|
165
|
-
const totalChunks = chunks.length;
|
|
83
|
+
module.exports = async function validateLinks( buildPath, options = {} ) {
|
|
84
|
+
console.info( `Gathering links in ${ styleText( 'magenta', buildPath ) } to validate...` );
|
|
166
85
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
const chunkIdx = nextChunkIdx++;
|
|
175
|
-
const worker = new Worker( __filename, {
|
|
176
|
-
workerData: {
|
|
177
|
-
chunk: chunks[ chunkIdx ],
|
|
178
|
-
links,
|
|
179
|
-
buildPath,
|
|
180
|
-
options
|
|
181
|
-
}
|
|
182
|
-
} );
|
|
183
|
-
|
|
184
|
-
worker.on( 'message', errors => {
|
|
185
|
-
allErrors = allErrors.concat( errors );
|
|
186
|
-
finished++;
|
|
187
|
-
console.info( `Processed ${ finished } of ${ totalChunks } links chunks.` );
|
|
188
|
-
|
|
189
|
-
if ( nextChunkIdx < totalChunks ) {
|
|
190
|
-
startWorker( workerId ); // Start next chunk on this worker
|
|
191
|
-
} else if ( finished === totalChunks ) {
|
|
192
|
-
printErrors( allErrors, buildPath );
|
|
193
|
-
console.info( `Links validation finished in ${ styleText( 'green', ( Date.now() - start ) + 'ms' ) }.` );
|
|
194
|
-
|
|
195
|
-
if ( allErrors.length ) {
|
|
196
|
-
reject( new Error( `Found ${ allErrors.length } invalid links.` ) );
|
|
197
|
-
} else {
|
|
198
|
-
resolve();
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
} );
|
|
86
|
+
const start = Date.now();
|
|
87
|
+
const { links, pathsToFiles } = await collectLinks( upath.join( buildPath, '**', '*' ), options );
|
|
88
|
+
const pool = new TinyPool( {
|
|
89
|
+
filename: require.resolve( './validate-links-worker.js' )
|
|
90
|
+
} );
|
|
202
91
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
console.error( styleText( 'red', `Worker error: ${ err }` ) );
|
|
206
|
-
if ( nextChunkIdx < totalChunks ) {
|
|
207
|
-
startWorker( workerId );
|
|
208
|
-
} else if ( finished === totalChunks ) {
|
|
209
|
-
reject( new Error( `Worker error: ${ err }` ) );
|
|
210
|
-
}
|
|
211
|
-
} );
|
|
212
|
-
workers[ workerId ] = worker;
|
|
213
|
-
}
|
|
92
|
+
try {
|
|
93
|
+
console.info( `Validating links in ${ styleText( 'magenta', buildPath ) }...` );
|
|
214
94
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
} );
|
|
220
|
-
};
|
|
95
|
+
const tasks = splitArrayBalanced( pathsToFiles, pool.options.maxThreads ).map( chunk => pool.run( { chunk, links, options } ) );
|
|
96
|
+
const results = await Promise.all( tasks );
|
|
97
|
+
const errors = results.flat();
|
|
221
98
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
99
|
+
if ( errors.length ) {
|
|
100
|
+
printErrors( errors, buildPath );
|
|
101
|
+
throw new Error( `Found ${ errors.length } invalid links.` );
|
|
102
|
+
}
|
|
226
103
|
|
|
227
|
-
|
|
228
|
-
}
|
|
104
|
+
console.info( `Links validation finished in ${ styleText( 'green', ( Date.now() - start ) + 'ms' ) }.` );
|
|
105
|
+
} finally {
|
|
106
|
+
await pool.destroy();
|
|
107
|
+
}
|
|
108
|
+
};
|
|
229
109
|
|
|
230
110
|
/**
|
|
231
111
|
* Prints errors found during link validation.
|
|
@@ -250,94 +130,16 @@ function printErrors( allErrors, buildPath ) {
|
|
|
250
130
|
}
|
|
251
131
|
|
|
252
132
|
/**
|
|
253
|
-
*
|
|
133
|
+
* Splits an array into smaller chunks, each containing approximately equal number of elements.
|
|
254
134
|
*
|
|
255
|
-
* @param {
|
|
256
|
-
* @param {
|
|
257
|
-
* @
|
|
258
|
-
* @param {string} options.publicDir
|
|
259
|
-
* @returns {string}
|
|
135
|
+
* @param {Array<any>} arr Array to split.
|
|
136
|
+
* @param {number} numParts Number of parts to split into.
|
|
137
|
+
* @returns {Array<Array<any>>}
|
|
260
138
|
*/
|
|
261
|
-
function
|
|
262
|
-
|
|
263
|
-
return upath.resolve( filePath + href );
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if ( href.startsWith( '/' ) ) {
|
|
267
|
-
const commonPoint = href.match( /^\/[^/]+/ )[ 0 ];
|
|
268
|
-
|
|
269
|
-
// The `commonPoint` should be removed from the end of the `options.publicDir` string.
|
|
270
|
-
// It may happen that `commonPoint` is present at the middle of that path and removing it produces invalid results.
|
|
271
|
-
// See: #909.
|
|
272
|
-
const regExpCommonPoint = new RegExp( commonPoint + '$' );
|
|
273
|
-
|
|
274
|
-
return upath.resolve( options.publicDir.replace( regExpCommonPoint, '' ) + href );
|
|
275
|
-
}
|
|
139
|
+
function splitArrayBalanced( arr, numParts ) {
|
|
140
|
+
const result = Array.from( { length: numParts }, () => [] );
|
|
276
141
|
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Recursively processes the DOM tree, collecting <a> elements with href.
|
|
282
|
-
*
|
|
283
|
-
* @param {Object} node
|
|
284
|
-
* @param {Array<Object>} linkElements
|
|
285
|
-
*/
|
|
286
|
-
function processNode( node, linkElements ) {
|
|
287
|
-
if ( node.type === 'tag' && node.name === 'a' ) {
|
|
288
|
-
const text = node.children.find( child => child.type === 'text' )?.data;
|
|
289
|
-
const href = node.attribs?.href;
|
|
290
|
-
const skipValidation = node.attribs?.[ 'data-skip-validation' ] !== undefined;
|
|
291
|
-
|
|
292
|
-
if ( !skipValidation && text && href && !href.match( /[a-z:]*\/\// ) && !href.match( /mailto:/ ) ) {
|
|
293
|
-
linkElements.push( { href, text } );
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
if ( !node.childNodes ) {
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
for ( const childNode of node.childNodes ) {
|
|
302
|
-
processNode( childNode, linkElements );
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* Calculates the optimal chunk count for splitting files based on available system memory.
|
|
308
|
-
* Tries to keep each chunk small enough to avoid OOM, but not too small for efficiency.
|
|
309
|
-
*/
|
|
310
|
-
function chunkBasedOnMemory( maxWorkers, array ) {
|
|
311
|
-
const totalMem = os.totalmem();
|
|
312
|
-
const freeMem = os.freemem();
|
|
313
|
-
|
|
314
|
-
// Assume each file may take up to 8MB in memory (parsing, DOM, etc.)
|
|
315
|
-
const perFileEstimate = 8 * 1024 * 1024;
|
|
316
|
-
|
|
317
|
-
// Use only a portion of free memory to be safe (e.g., 60%)
|
|
318
|
-
const usableMem = Math.max( freeMem, totalMem * 0.2 ) * 0.6;
|
|
319
|
-
const maxFilesPerChunk = Math.max( 1, Math.floor( usableMem / perFileEstimate ) );
|
|
320
|
-
|
|
321
|
-
// Try to keep chunks small, but not less than maxWorkers
|
|
322
|
-
const chunkCount = Math.max( maxWorkers, Math.ceil( array.length / maxFilesPerChunk ) );
|
|
323
|
-
|
|
324
|
-
return chunkArray( array, chunkCount );
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* Splits an array into chunkCount fragments of similar size.
|
|
329
|
-
*
|
|
330
|
-
* @param {Array} array
|
|
331
|
-
* @param {number} chunkCount
|
|
332
|
-
* @returns {Array<Array>}
|
|
333
|
-
*/
|
|
334
|
-
function chunkArray( array, chunkCount ) {
|
|
335
|
-
const chunks = [];
|
|
336
|
-
const chunkSize = Math.ceil( array.length / chunkCount );
|
|
337
|
-
|
|
338
|
-
for ( let i = 0; i < array.length; i += chunkSize ) {
|
|
339
|
-
chunks.push( array.slice( i, i + chunkSize ) );
|
|
340
|
-
}
|
|
142
|
+
arr.forEach( ( item, i ) => result[ i % numParts ].push( item ) );
|
|
341
143
|
|
|
342
|
-
return
|
|
144
|
+
return result;
|
|
343
145
|
}
|
|
@@ -48,6 +48,9 @@ if page.BASE_PATH && page.BASE_PATH !== '.' && !page.BASE_PATH.includes( 'latest
|
|
|
48
48
|
if kapa
|
|
49
49
|
+load-kapa-script(kapa)
|
|
50
50
|
|
|
51
|
+
if sentry
|
|
52
|
+
+load-sentry-script(sentry)
|
|
53
|
+
|
|
51
54
|
script(src=relative_url(page.path, pathJoin( 'assets', umbertoVersion, 'gloria/scripts/app.js')))
|
|
52
55
|
|
|
53
56
|
if page.BASE_PATH
|
|
@@ -18,7 +18,6 @@ mixin load-kapa-script( kapa )
|
|
|
18
18
|
// Global settings.
|
|
19
19
|
'data-project-color': mainColor,
|
|
20
20
|
'data-project-logo': 'https://ckeditor.com/docs/ckeditor5/latest/assets/img/ckeditor5-logo.svg',
|
|
21
|
-
'data-font-family': '"Title Mullish", text public sans',
|
|
22
21
|
|
|
23
22
|
// Floating button.
|
|
24
23
|
'data-button-text': 'Need help?',
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
mixin load-sentry-script( sentry )
|
|
2
|
+
script.
|
|
3
|
+
const hostname = window.location.hostname;
|
|
4
|
+
const isProductionHost = hostname === 'ckeditor.com' || hostname === 'ckeditor5.github.io';
|
|
5
|
+
|
|
6
|
+
if ( isProductionHost ) {
|
|
7
|
+
window.sentryOnLoad = function () {
|
|
8
|
+
Sentry.init( {
|
|
9
|
+
environment: '!{ sentry.environment }',
|
|
10
|
+
integrations: [
|
|
11
|
+
Sentry.browserTracingIntegration(),
|
|
12
|
+
Sentry.replayIntegration()
|
|
13
|
+
],
|
|
14
|
+
tracesSampleRate: 1.0,
|
|
15
|
+
tracePropagationTargets: [
|
|
16
|
+
'https://ckeditor.com/docs',
|
|
17
|
+
'https://ckeditor5.github.io/docs/nightly'
|
|
18
|
+
],
|
|
19
|
+
replaysSessionSampleRate: 1.0,
|
|
20
|
+
replaysOnErrorSampleRate: 1.0
|
|
21
|
+
} );
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const script = document.createElement( 'script' );
|
|
25
|
+
script.src = '!{ sentry.src }';
|
|
26
|
+
script.crossOrigin = 'anonymous';
|
|
27
|
+
document.head.prepend( script );
|
|
28
|
+
}
|
|
@@ -1,90 +0,0 @@
|
|
|
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 cheerio = require( 'cheerio' );
|
|
9
|
-
const createPrerenderPugTemplate = require( '../../../utils/pug-renderer/create-prerender-pug-template' );
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Renders the button using Pug template.
|
|
13
|
-
*/
|
|
14
|
-
const renderButton = createPrerenderPugTemplate( {
|
|
15
|
-
requires: [
|
|
16
|
-
'_components/svg/index',
|
|
17
|
-
'_components/icon/index',
|
|
18
|
-
'_components/tooltip-popover/index',
|
|
19
|
-
'_components/heading-link/index'
|
|
20
|
-
],
|
|
21
|
-
component: 'heading-link',
|
|
22
|
-
componentAttrs: {
|
|
23
|
-
mask: [ 'headingId' ]
|
|
24
|
-
}
|
|
25
|
-
} );
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Enforce priority 30 to run it after apply design doc classes filter.
|
|
29
|
-
*/
|
|
30
|
-
hexo.extend.filter.register( 'after_post_render', page => {
|
|
31
|
-
if ( page.projectTheme !== 'gloria' ) {
|
|
32
|
-
return page;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const $ = cheerio.load( page.content, null, false );
|
|
36
|
-
|
|
37
|
-
// Do not process h1 tags as it's processed further by Umberto and may be removed.
|
|
38
|
-
$( 'h2, h3, h4, h5, h6' ).each( ( _, heading ) => {
|
|
39
|
-
const $heading = $( heading );
|
|
40
|
-
|
|
41
|
-
if ( hasClassOrParentWithClass( $heading, 'no-transform' ) ) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
removeAllEmptyAnchors( $, heading );
|
|
46
|
-
|
|
47
|
-
const headingId = $( heading ).attr( 'id' );
|
|
48
|
-
const $container = $( '<div class="doc b-heading"></div>' );
|
|
49
|
-
|
|
50
|
-
// Create the copy button
|
|
51
|
-
const button = renderButton( {
|
|
52
|
-
headingId
|
|
53
|
-
} );
|
|
54
|
-
|
|
55
|
-
// Place the heading element before the container in the DOM
|
|
56
|
-
$( heading ).before( $container );
|
|
57
|
-
|
|
58
|
-
$container.append( $( heading ) );
|
|
59
|
-
$container.append( $( button ) );
|
|
60
|
-
} );
|
|
61
|
-
|
|
62
|
-
page.content = $.html();
|
|
63
|
-
|
|
64
|
-
return page;
|
|
65
|
-
}, 30 );
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Removes all empty anchors that were created to hold the heading links buttons.
|
|
69
|
-
*/
|
|
70
|
-
function removeAllEmptyAnchors( $, heading ) {
|
|
71
|
-
$( heading ).find( 'a' ).each( ( _, anchor ) => {
|
|
72
|
-
const $anchor = $( anchor );
|
|
73
|
-
const text = $anchor.text().trim().replace( '#', '' );
|
|
74
|
-
|
|
75
|
-
if ( !$anchor.children().length && !text ) {
|
|
76
|
-
$anchor.remove();
|
|
77
|
-
}
|
|
78
|
-
} );
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Checks if the element itself or any of its parents has the specified class.
|
|
83
|
-
*
|
|
84
|
-
* @param $element - The jQuery element to check.
|
|
85
|
-
* @param className - The class name to look for.
|
|
86
|
-
* @returns True if the element or any parent has the class, false otherwise.
|
|
87
|
-
*/
|
|
88
|
-
function hasClassOrParentWithClass( $element, className ) {
|
|
89
|
-
return $element.hasClass( className ) || $element.parents( `.${ className }` ).length > 0;
|
|
90
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
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 applyDesignDocClasses = require( '../../../utils/apply-design-doc-classes' );
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Global mapping of HTML elements to their corresponding design system classes.
|
|
12
|
-
*/
|
|
13
|
-
const ELEMENTS_CLASSES_MAPPINGS = {
|
|
14
|
-
'h1': 'b-h1',
|
|
15
|
-
'h2': 'b-h2',
|
|
16
|
-
'h3': 'b-h3',
|
|
17
|
-
'h4': 'b-h4',
|
|
18
|
-
'h5': 'b-h5',
|
|
19
|
-
'h6': 'b-h6',
|
|
20
|
-
'a': 'b-link',
|
|
21
|
-
|
|
22
|
-
// Text formatting elements
|
|
23
|
-
'p': 'b-paragraph',
|
|
24
|
-
'strong': 'b-bold',
|
|
25
|
-
'b': 'b-bold',
|
|
26
|
-
'em': 'b-italic',
|
|
27
|
-
'i': 'b-italic',
|
|
28
|
-
'code': 'b-inline-code',
|
|
29
|
-
'pre': 'b-code-block',
|
|
30
|
-
'blockquote': 'b-quote',
|
|
31
|
-
'hr': 'b-separator',
|
|
32
|
-
'span': 'b-text',
|
|
33
|
-
'small': 'b-small-text',
|
|
34
|
-
'sub': 'b-subscript',
|
|
35
|
-
'sup': 'b-superscript',
|
|
36
|
-
|
|
37
|
-
// List elements
|
|
38
|
-
'ul': [ 'b-list', 'b-list--unordered' ],
|
|
39
|
-
'ol': [ 'b-list', 'b-list--ordered' ],
|
|
40
|
-
'li': 'b-list__item',
|
|
41
|
-
'dl': 'b-description',
|
|
42
|
-
'dt': 'b-description__term',
|
|
43
|
-
'dd': 'b-description__details',
|
|
44
|
-
|
|
45
|
-
// Details
|
|
46
|
-
'details': 'b-details',
|
|
47
|
-
'summary': [ 'b-reset-button', 'b-details__summary' ],
|
|
48
|
-
|
|
49
|
-
// Table elements
|
|
50
|
-
'table': $element => {
|
|
51
|
-
const variant = $element.data( 'variant' ) || 'striped';
|
|
52
|
-
|
|
53
|
-
return [ 'b-table', `b-table--${ variant }` ];
|
|
54
|
-
},
|
|
55
|
-
'thead': 'b-table__header',
|
|
56
|
-
'tbody': 'b-table__body',
|
|
57
|
-
'tr': 'b-table__row',
|
|
58
|
-
'td': 'b-table__cell',
|
|
59
|
-
'th': [ 'b-table__cell', 'b-table__cell-header' ],
|
|
60
|
-
|
|
61
|
-
// Image and media elements
|
|
62
|
-
'img': 'b-image',
|
|
63
|
-
'figure': 'b-figure',
|
|
64
|
-
'figcaption': 'b-figure-caption',
|
|
65
|
-
'iframe': 'b-iframe',
|
|
66
|
-
|
|
67
|
-
// Accessibility elements
|
|
68
|
-
'kbd': 'c-keyboard-shortcut c-keyboard-shortcut--raised',
|
|
69
|
-
'mark': 'b-highlight'
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Classes that are allowed to be added to elements with existing classes.
|
|
74
|
-
*/
|
|
75
|
-
const ELEMENTS_WITH_WHITELIST_CLASSES = [
|
|
76
|
-
'headerlink'
|
|
77
|
-
];
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Add design system classes to the parsed document elements.
|
|
81
|
-
* It prevents leaking of styling to the embedded third party components such
|
|
82
|
-
* as CKEditor 5, or any other components that are not part of the design system.
|
|
83
|
-
*/
|
|
84
|
-
hexo.extend.filter.register( 'after_post_render', page => {
|
|
85
|
-
if ( page.projectTheme !== 'gloria' ) {
|
|
86
|
-
return page;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
page.content = applyDesignDocClasses( {
|
|
90
|
-
elementsClassesMappings: ELEMENTS_CLASSES_MAPPINGS,
|
|
91
|
-
elementsWithWhitelistClasses: ELEMENTS_WITH_WHITELIST_CLASSES,
|
|
92
|
-
content: page.content
|
|
93
|
-
} );
|
|
94
|
-
|
|
95
|
-
return page;
|
|
96
|
-
}, 40 );
|
|
@@ -1,36 +0,0 @@
|
|
|
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 cheerio = require( 'cheerio' );
|
|
9
|
-
|
|
10
|
-
function wrapTableIntoWrappers( page ) {
|
|
11
|
-
if ( page.projectTheme !== 'gloria' ) {
|
|
12
|
-
return page;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const $ = cheerio.load( page.content, null, false );
|
|
16
|
-
|
|
17
|
-
$( 'table' ).each( ( _, table ) => {
|
|
18
|
-
if ( $( table ).parent().prop( 'tagName' ) !== 'FIGURE' ) {
|
|
19
|
-
// If the table is not wrapped in a figure, wrap it.
|
|
20
|
-
$( table ).wrap( '<figure class="doc b-table-wrapper"></figure>' );
|
|
21
|
-
}
|
|
22
|
-
} );
|
|
23
|
-
|
|
24
|
-
page.content = $.html();
|
|
25
|
-
|
|
26
|
-
return page;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Enforce priority 30 to run it after apply design doc classes filter.
|
|
31
|
-
*/
|
|
32
|
-
if ( typeof hexo !== 'undefined' ) {
|
|
33
|
-
hexo.extend.filter.register( 'after_post_render', wrapTableIntoWrappers, 30 );
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
module.exports = wrapTableIntoWrappers;
|
|
@@ -1,14 +0,0 @@
|
|
|
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 inlineSvg = require( '../../../utils/inline-svg' );
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Inlines SVG icons into HTML elements with data-inline-svg-image attribute.
|
|
12
|
-
* The attribute value should contain the path to the SVG file.
|
|
13
|
-
*/
|
|
14
|
-
hexo.extend.filter.register( 'after_render:html', inlineSvg, 500 );
|
|
@@ -1,14 +0,0 @@
|
|
|
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 spritesheetSvg = require( '../../../utils/spritesheet-svg' );
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Creates a spritesheet from SVG files referenced by data-spritesheet-svg attributes
|
|
12
|
-
* and injects it at the beginning of the body with display: none.
|
|
13
|
-
*/
|
|
14
|
-
hexo.extend.filter.register( 'after_render:html', spritesheetSvg, 499 );
|