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.
Files changed (38) hide show
  1. package/CHANGELOG.md +14 -9
  2. package/package.json +8 -13
  3. package/scripts/filter/after-post-render/fix-code-samples.js +82 -18
  4. package/scripts/filter/after-post-render/gloria.js +27 -0
  5. package/scripts/filter/after-post-render/insert-error-codes.js +34 -26
  6. package/scripts/filter/after-post-render/validate-after-render.js +27 -6
  7. package/scripts/filter/after-render/process-svg.js +21 -0
  8. package/scripts/filter/before-post-render/gloria/render-post-render-pug-components.js +46 -18
  9. package/scripts/helper/u-extract-and-cache-title.js +27 -8
  10. package/scripts/helper/u-split-to-title-and-content.js +32 -8
  11. package/scripts/utils/gloria-after-post-render/append-copy-heading-buttons.js +119 -0
  12. package/scripts/utils/gloria-after-post-render/apply-design-doc-classes.js +157 -0
  13. package/scripts/utils/gloria-after-post-render/wrap-table-into-wrappers.js +25 -0
  14. package/scripts/utils/inline-svg.js +63 -94
  15. package/scripts/utils/spritesheet-svg.js +82 -102
  16. package/scripts/utils/toc.js +85 -31
  17. package/src/api-builder/api-builder.js +53 -40
  18. package/src/api-builder/build-page-worker.js +35 -0
  19. package/src/api-builder/classes/description-parser.js +77 -38
  20. package/src/data-converter/converters/jsduck2umberto.js +43 -15
  21. package/src/hexo/filter/project-locals.js +3 -0
  22. package/src/sdk-builder/get-sdk-sources.js +81 -44
  23. package/src/tasks/build-documentation.js +4 -0
  24. package/src/tasks/minify-html.js +1 -1
  25. package/src/tasks/validate-links-collect-worker.js +34 -0
  26. package/src/tasks/validate-links-worker.js +127 -0
  27. package/src/tasks/validate-links.js +61 -259
  28. package/themes/umberto/layout/gloria/_head/head.pug +3 -0
  29. package/themes/umberto/layout/gloria/_modules/index.pug +1 -0
  30. package/themes/umberto/layout/gloria/_modules/kapa/index.pug +0 -1
  31. package/themes/umberto/layout/gloria/_modules/sentry/index.pug +27 -0
  32. package/scripts/filter/after-post-render/gloria/append-copy-heading-buttons.js +0 -90
  33. package/scripts/filter/after-post-render/gloria/apply-design-doc-classes.js +0 -96
  34. package/scripts/filter/after-post-render/gloria/wrap-table-into-wrappers.js +0 -36
  35. package/scripts/filter/after-render/gloria/inline-svg.js +0 -14
  36. package/scripts/filter/after-render/gloria/spritesheet-svg.js +0 -14
  37. package/scripts/utils/apply-design-doc-classes.js +0 -82
  38. /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 os = require( 'os' );
18
- const { Worker, isMainThread, parentPort, workerData } = require( 'worker_threads' );
16
+ const upath = require( 'upath' );
19
17
  const { globSync } = require( 'glob' );
20
- const { parseDocument } = require( 'htmlparser2' );
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[]} pathsToFiles Array of file paths to process.
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( pathsToFiles, options ) {
33
- const links = new Set();
34
- pathsToFiles = pathsToFiles.filter( p => {
35
- if ( p.includes( '/vendors/' ) ) {
36
- return false;
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
- if ( options.skipApi && p.includes( '/api/' ) ) {
40
- return false;
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
- for ( const filePath of pathsToFiles ) {
54
- links.add( upath.resolve( filePath ) );
55
- links.add( upath.resolve( filePath + '#' ) );
56
+ const pool = new TinyPool( {
57
+ filename: require.resolve( './validate-links-collect-worker.js' )
58
+ } );
56
59
 
57
- const content = fs.readFileSync( filePath, 'utf-8' );
58
- // Extract both quoted and unquoted "id" attributes.
59
- const ids = [
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
- for ( const id of ids ) {
65
- if ( !isMaskedID( id ) && !ids.includes( 'icons-' ) ) {
66
- links.add( upath.resolve( `${ filePath }#${ id }` ) );
67
- }
68
- }
64
+ links = new Set( [ ...links, ...results.flat() ] );
65
+ } finally {
66
+ await pool.destroy();
69
67
  }
70
68
 
71
69
  return {
72
- links: Array.from( 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
- if ( isMainThread ) {
148
- const validateLinks = ( buildPath, options = {} ) => {
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
- return new Promise( ( resolve, reject ) => {
168
- const workers = [];
169
-
170
- function startWorker( workerId ) {
171
- if ( nextChunkIdx >= totalChunks ) {
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
- worker.on( 'error', err => {
204
- finished++;
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
- const numWorkers = Math.min( workerCount, totalChunks );
216
- for ( let i = 0; i < numWorkers; i++ ) {
217
- startWorker( i );
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
- module.exports = validateLinks;
223
- } else {
224
- const { chunk, links, options } = workerData;
225
- const errors = validateChunk( { chunk, links: new Set( links ), options } );
99
+ if ( errors.length ) {
100
+ printErrors( errors, buildPath );
101
+ throw new Error( `Found ${ errors.length } invalid links.` );
102
+ }
226
103
 
227
- parentPort.postMessage( errors );
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
- * Resolves the target path for the given href relative to the file and public directory.
133
+ * Splits an array into smaller chunks, each containing approximately equal number of elements.
254
134
  *
255
- * @param {string} href
256
- * @param {string} filePath
257
- * @param {Object} options
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 getResolvedPath( href, filePath, options ) {
262
- if ( href.startsWith( '#' ) ) {
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
- return upath.resolve( filePath, '..', href );
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 chunks;
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
@@ -13,3 +13,4 @@ include ./breadcrumbs/index
13
13
  include ./algolia-search/index
14
14
  include ./not-found/index
15
15
  include ./kapa/index
16
+ include ./sentry/index
@@ -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,27 @@
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
+ integrations: [
10
+ Sentry.browserTracingIntegration(),
11
+ Sentry.replayIntegration()
12
+ ],
13
+ tracesSampleRate: 1.0,
14
+ tracePropagationTargets: [
15
+ 'https://ckeditor.com/docs',
16
+ 'https://ckeditor5.github.io/docs/nightly'
17
+ ],
18
+ replaysSessionSampleRate: 1.0,
19
+ replaysOnErrorSampleRate: 1.0
20
+ } );
21
+ };
22
+
23
+ const script = document.createElement( 'script' );
24
+ script.src = '!{ sentry.src }';
25
+ script.crossOrigin = 'anonymous';
26
+ document.head.prepend( script );
27
+ }
@@ -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 );