umberto 8.0.2 → 8.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 CHANGED
@@ -1,6 +1,24 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
+ ## [8.1.0](https://github.com/cksource/umberto/compare/v8.0.3...v8.1.0) (August 12, 2025)
5
+
6
+ ### Features
7
+
8
+ * Added support for relative paths in `packagesDir` option.
9
+
10
+ ### Bug fixes
11
+
12
+ * Updated how `<iframe>` elements are created to fix issues with automated tests using Chrome v139.
13
+
14
+
15
+ ## [8.0.3](https://github.com/cksource/umberto/compare/v8.0.2...v8.0.3) (August 5, 2025)
16
+
17
+ ### Bug fixes
18
+
19
+ * The backticks in the XML components parser were not escaped correctly, causing issues with parsing XML content. This fix ensures that backticks are properly escaped, allowing the parser to handle XML content containing backticks without errors.
20
+
21
+
4
22
  ## [8.0.2](https://github.com/cksource/umberto/compare/v8.0.1...v8.0.2) (July 31, 2025)
5
23
 
6
24
  ### Bug fixes
@@ -47,22 +65,6 @@ Changelog
47
65
  * The repository now uses ESLint v9. Therefore, the required Node.js version has been upgraded to 22 to match the ESLint requirements.
48
66
  * Update `LICENSE.md` file to include licenses of direct dependencies
49
67
 
50
-
51
- ## [7.0.2](https://github.com/cksource/umberto/compare/v7.0.1...v7.0.2) (2025-05-20)
52
-
53
- ### Bug fixes
54
-
55
- * Fixed an invalid protocol (should be `https://` instead of `https:/`) in the generated index sitemap file. Closes [#1277](https://github.com/cksource/umberto/issues/1277). ([commit](https://github.com/cksource/umberto/commit/6f59654e85b1230fbfe029efe68f708755ec6342))
56
-
57
-
58
- ## [7.0.1](https://github.com/cksource/umberto/compare/v7.0.0...v7.0.1) (2025-05-14)
59
-
60
- ### Other changes
61
-
62
- * Updated the project dependencies. Closes [#1253](https://github.com/cksource/umberto/issues/1253). ([commit](https://github.com/cksource/umberto/commit/28d74364207ab055ad76c2857d58cccb1866559b))
63
- * Removed dependencies that can be replaced with native APIs or other already used dependencies. ([commit](https://github.com/cksource/umberto/commit/28d74364207ab055ad76c2857d58cccb1866559b))
64
- * Sitemaps will be generated separately for each project. Closes [#1254](https://github.com/cksource/umberto/issues/1254). ([commit](https://github.com/cksource/umberto/commit/7968755b2a4b1c23768a9420292eb1d168b898b3))
65
-
66
68
  ---
67
69
 
68
70
  To see all releases, visit the [release page](https://github.com/cksource/umberto/releases).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "umberto",
3
- "version": "8.0.2",
3
+ "version": "8.1.0",
4
4
  "description": "CKSource Documentation builder",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -5,7 +5,7 @@
5
5
 
6
6
  'use strict';
7
7
 
8
- const cheerio = require( 'cheerio' );
8
+ const applyDesignDocClasses = require( '../../../utils/apply-design-doc-classes' );
9
9
 
10
10
  /**
11
11
  * Global mapping of HTML elements to their corresponding design system classes.
@@ -86,60 +86,11 @@ hexo.extend.filter.register( 'after_post_render', page => {
86
86
  return page;
87
87
  }
88
88
 
89
- const $ = cheerio.load( page.content, null, false );
90
-
91
- // Apply classes based on the global mapping
92
- for ( const [ element, classesOrCallback ] of Object.entries( ELEMENTS_CLASSES_MAPPINGS ) ) {
93
- const $elements = $( element );
94
-
95
- $elements.each( ( _, element ) => {
96
- const $element = $( element );
97
-
98
- if ( hasClassOrParentWithClass( $element, 'no-transform' ) ) {
99
- return;
100
- }
101
-
102
- // Add the 'doc' class to each element that gets styled
103
- $element.addClass( 'doc' );
104
-
105
- const classList = ( $element.attr( 'class' )?.split( /\s+/ ) || [] ).filter(
106
- className => !ELEMENTS_WITH_WHITELIST_CLASSES.includes( className )
107
- );
108
-
109
- // Avoid applying classes to elements that already have one.
110
- if ( classList.length > 1 ) {
111
- return;
112
- }
113
-
114
- // Add the specified classes
115
- if ( typeof classesOrCallback === 'function' ) {
116
- const elementClasses = classesOrCallback( $element );
117
-
118
- if ( Array.isArray( elementClasses ) ) {
119
- $element.addClass( elementClasses.join( ' ' ) );
120
- } else if ( typeof elementClasses === 'string' ) {
121
- $element.addClass( elementClasses );
122
- }
123
- } else if ( Array.isArray( classesOrCallback ) ) {
124
- $element.addClass( classesOrCallback.join( ' ' ) );
125
- } else {
126
- $element.addClass( classesOrCallback );
127
- }
128
- } );
129
- }
130
-
131
- page.content = $.html();
89
+ page.content = applyDesignDocClasses( {
90
+ elementsClassesMappings: ELEMENTS_CLASSES_MAPPINGS,
91
+ elementsWithWhitelistClasses: ELEMENTS_WITH_WHITELIST_CLASSES,
92
+ content: page.content
93
+ } );
132
94
 
133
95
  return page;
134
96
  }, 40 );
135
-
136
- /**
137
- * Checks if the element itself or any of its parents has the specified class.
138
- *
139
- * @param $element - The jQuery element to check.
140
- * @param className - The class name to look for.
141
- * @returns True if the element or any parent has the class, false otherwise.
142
- */
143
- function hasClassOrParentWithClass( $element, className ) {
144
- return $element.hasClass( className ) || $element.parents( `.${ className }` ).length > 0;
145
- }
@@ -7,10 +7,7 @@
7
7
 
8
8
  const cheerio = require( 'cheerio' );
9
9
 
10
- /**
11
- * Enforce priority 30 to run it after apply design doc classes filter.
12
- */
13
- hexo.extend.filter.register( 'after_post_render', page => {
10
+ function wrapTableIntoWrappers( page ) {
14
11
  if ( page.projectTheme !== 'gloria' ) {
15
12
  return page;
16
13
  }
@@ -27,4 +24,13 @@ hexo.extend.filter.register( 'after_post_render', page => {
27
24
  page.content = $.html();
28
25
 
29
26
  return page;
30
- }, 30 );
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;
@@ -0,0 +1,82 @@
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
+ /**
11
+ * Add design system classes to the parsed document elements. If element has 0 CSS classes
12
+ * it'll be considered as not styled and the design system classes will be applied.
13
+
14
+ * @param options - Options for applying design classes.
15
+ * @param options.elementsClassesMappings - Mappings of elements to their classes or
16
+ * functions that return classes.
17
+ * @param options.elementsWithWhitelistClasses - List of classes that are
18
+ * considered as transparent in checks for amount of the classes.
19
+ * @param options.content - The HTML content to process.
20
+ */
21
+ module.exports = function applyDesignDocClasses(
22
+ {
23
+ elementsClassesMappings,
24
+ elementsWithWhitelistClasses,
25
+ content
26
+ }
27
+ ) {
28
+ const $ = cheerio.load( content, null, false );
29
+
30
+ // Apply classes based on the global mapping
31
+ for ( const [ element, classesOrCallback ] of Object.entries( elementsClassesMappings ) ) {
32
+ const $elements = $( element );
33
+
34
+ $elements.each( ( _, element ) => {
35
+ const $element = $( element );
36
+
37
+ if ( hasClassOrParentWithClass( $element, 'no-transform' ) ) {
38
+ return;
39
+ }
40
+
41
+ // Add the 'doc' class to each element that gets styled
42
+ $element.addClass( 'doc' );
43
+
44
+ const classList = ( $element.attr( 'class' )?.split( /\s+/ ) || [] ).filter(
45
+ className => !elementsWithWhitelistClasses.includes( className )
46
+ );
47
+
48
+ // Avoid applying classes to elements that already have one.
49
+ if ( classList.length > 1 ) {
50
+ return;
51
+ }
52
+
53
+ // Add the specified classes
54
+ if ( typeof classesOrCallback === 'function' ) {
55
+ const elementClasses = classesOrCallback( $element );
56
+
57
+ if ( Array.isArray( elementClasses ) ) {
58
+ $element.addClass( elementClasses.join( ' ' ) );
59
+ } else if ( typeof elementClasses === 'string' ) {
60
+ $element.addClass( elementClasses );
61
+ }
62
+ } else if ( Array.isArray( classesOrCallback ) ) {
63
+ $element.addClass( classesOrCallback.join( ' ' ) );
64
+ } else {
65
+ $element.addClass( classesOrCallback );
66
+ }
67
+ } );
68
+ }
69
+
70
+ return $.html();
71
+ };
72
+
73
+ /**
74
+ * Checks if the element itself or any of its parents has the specified class.
75
+ *
76
+ * @param $element - The jQuery element to check.
77
+ * @param className - The class name to look for.
78
+ * @returns True if the element or any parent has the class, false otherwise.
79
+ */
80
+ function hasClassOrParentWithClass( $element, className ) {
81
+ return $element.hasClass( className ) || $element.parents( `.${ className }` ).length > 0;
82
+ }
@@ -12,6 +12,6 @@
12
12
  * @param {*} value - The initial value to be processed by the functions.
13
13
  * @returns {*} The final result after all functions have been applied.
14
14
  */
15
- module.exports = function pipe( ...fns ) {
15
+ module.exports = function compose( ...fns ) {
16
16
  return value => fns.reduce( ( acc, fn ) => fn( acc ), value );
17
17
  };
@@ -7,13 +7,13 @@
7
7
 
8
8
  const dropInitSlash = require( './drop-init-slash' );
9
9
  const dropTrailingSlash = require( './drop-trailing-slash' );
10
- const pipe = require( './pipe' );
10
+ const compose = require( './compose' );
11
11
 
12
12
  module.exports = function concatUrlParts( ...urls ) {
13
13
  return (
14
14
  urls
15
15
  .filter( Boolean )
16
- .map( pipe( dropTrailingSlash, dropInitSlash ) )
16
+ .map( compose( dropTrailingSlash, dropInitSlash ) )
17
17
  .join( '/' )
18
18
  );
19
19
  };
@@ -11,7 +11,7 @@ const removeIndentation = require( '../../remove-indentation.js' );
11
11
  const ParserCursor = require( '../../parser-cursor.js' );
12
12
 
13
13
  /**
14
- * A parser for converting text containing HTML-like markup into a tree structure.
14
+ * A parser for converting markdown containing HTML-like markup into a tree structure.
15
15
  * Uses a cursor-based approach with backtracking capabilities for robust parsing.
16
16
  */
17
17
  module.exports = class XMLComponentsParser {
@@ -292,32 +292,28 @@ module.exports = class XMLComponentsParser {
292
292
  * @returns The content between backticks including the backticks themselves.
293
293
  */
294
294
  #eatBacktick = this.#tryOrRestoreCursor( () => {
295
- let acc = '`';
296
- this.#cursor.index++;
295
+ const { text } = this.#cursor;
296
+ const startIndex = this.#cursor.index;
297
297
 
298
- while ( this.#cursor.index < this.#cursor.text.length ) {
299
- const ch = this.#cursor.text[ this.#cursor.index ];
298
+ let backtickSequence = '';
300
299
 
301
- if ( ch === '`' ) {
302
- acc += '`';
303
- this.#cursor.index++;
300
+ while ( this.#cursor.index < text.length && text[ this.#cursor.index ] === '`' ) {
301
+ backtickSequence += '`';
302
+ this.#cursor.index++;
303
+ }
304
+
305
+ const openingBackticksLength = backtickSequence.length;
306
+
307
+ while ( this.#cursor.index < text.length ) {
308
+ if ( text.substring( this.#cursor.index, this.#cursor.index + openingBackticksLength ) === backtickSequence ) {
309
+ this.#cursor.index += openingBackticksLength;
304
310
  break;
305
311
  }
306
312
 
307
- if ( ch === '\\' ) {
308
- acc += '\\';
309
- this.#cursor.index++;
310
- if ( this.#cursor.index < this.#cursor.text.length ) {
311
- acc += this.#cursor.text[ this.#cursor.index ];
312
- this.#cursor.index++;
313
- }
314
- } else {
315
- acc += ch;
316
- this.#cursor.index++;
317
- }
313
+ this.#cursor.index++;
318
314
  }
319
315
 
320
- return acc;
316
+ return text.substring( startIndex, this.#cursor.index );
321
317
  } );
322
318
 
323
319
  /**
@@ -0,0 +1,13 @@
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
+ /**
9
+ * Wrapper around `require.resolve()` to allow mocking it in tests.
10
+ */
11
+ module.exports = function resolvePath( path, options ) {
12
+ return require.resolve( path, options );
13
+ };
@@ -17,7 +17,7 @@ module.exports = ( {
17
17
  guidesToRepos.default = defaultRepoUrl;
18
18
 
19
19
  for ( const item of packages ) {
20
- const packageRepoUrl = getRepoUrl( upath.join( rootPath, item.path ) );
20
+ const packageRepoUrl = getRepoUrl( upath.resolve( rootPath, item.path ) );
21
21
  globSync( upath.join( rootPath, item.path, 'docs', '**', '*.md' ) )
22
22
  .map( p => upath.normalize( p ) )
23
23
  .map( p => p.replace( upath.join( rootPath, item.path, 'docs' ), '' ) )
@@ -58,13 +58,13 @@ module.exports = ( rootPath, config, options ) => {
58
58
  if ( config.items && Array.isArray( config.items ) ) {
59
59
  for ( const item of config.items ) {
60
60
  hexoManager.addOriginPath(
61
- upath.join( rootPath, item.path, config.path ),
62
- upath.join( hexoManager.getSourceDir(), BASE_PATH )
61
+ upath.resolve( rootPath, item.path, config.path ),
62
+ upath.resolve( hexoManager.getSourceDir(), BASE_PATH )
63
63
  );
64
64
 
65
65
  promises.push( copyDocFiles(
66
- upath.join( rootPath, item.path, config.path ),
67
- upath.join( hexoManager.getSourceDir(), BASE_PATH ),
66
+ upath.resolve( rootPath, item.path, config.path ),
67
+ upath.resolve( hexoManager.getSourceDir(), BASE_PATH ),
68
68
  {
69
69
  match,
70
70
  allOutputPaths,
@@ -7,6 +7,7 @@
7
7
 
8
8
  const upath = require( 'upath' );
9
9
  const fs = require( 'fs-extra' );
10
+ const resolvePath = require( '../helpers/resolve-path' );
10
11
 
11
12
  /**
12
13
  * @returns {Promise<void>}
@@ -19,7 +20,9 @@ module.exports = async function copyProjectIcons( projectGlobals, buildDirectory
19
20
  const destinationIconsPath = upath.join( buildDirectory, projectDetails.BASE_PATH, 'assets', 'icons' );
20
21
 
21
22
  for ( const [ sourceFile, destinationName ] of projectDetails._icons ) {
22
- const fullSourcePath = upath.join( projectDetails.config.projectRootPath, 'node_modules', sourceFile );
23
+ const fullSourcePath = resolvePath( sourceFile, {
24
+ paths: [ projectDetails.config.projectRootPath ]
25
+ } );
23
26
  const fullDestinationPath = upath.join( destinationIconsPath, destinationName );
24
27
 
25
28
  try {
@@ -71,10 +71,7 @@ export class IFrame extends BaseComponent {
71
71
  * @param content - HTML content to load
72
72
  */
73
73
  setContent( content ) {
74
- const iframe = this.elements.iframe;
75
- const iframeDocument = iframe.contentDocument || iframe.contentWindow?.document;
76
-
77
- const wrappedHTML = `
74
+ this.elements.iframe.srcdoc = `
78
75
  <!DOCTYPE html>
79
76
  ${ content }
80
77
  ${ this.config.autoResize ? `
@@ -91,12 +88,6 @@ export class IFrame extends BaseComponent {
91
88
  </script>
92
89
  ` : '' }
93
90
  `;
94
-
95
- if ( iframeDocument ) {
96
- iframeDocument.open();
97
- iframeDocument.write( wrappedHTML );
98
- iframeDocument.close();
99
- }
100
91
  }
101
92
 
102
93
  /**
@@ -3,6 +3,6 @@
3
3
  * For licensing, see LICENSE.md.
4
4
  */
5
5
 
6
- export function pipe( ...fns ) {
6
+ export function compose( ...fns ) {
7
7
  return value => fns.reduce( ( acc, fn ) => fn( acc ), value );
8
8
  }
@@ -6,7 +6,7 @@
6
6
  import { BaseComponent } from '../components/base-component';
7
7
  import { identity } from '../helpers/identity';
8
8
  import { injectScript } from '../helpers/inject-script';
9
- import { pipe } from '../helpers/pipe';
9
+ import { compose } from '../helpers/compose';
10
10
 
11
11
  const DOCSEARCH_JS_URL = 'https://cdn.jsdelivr.net/npm/@docsearch/js@3.9.0/dist/umd/index.min.js';
12
12
 
@@ -67,7 +67,7 @@ export class AlgoliaSearch extends BaseComponent {
67
67
 
68
68
  client.search = async ( options, ...rest ) => {
69
69
  const { results: [ firstResult ] } = await originalSearch( options, ...rest );
70
- const mappedResults = pipe(
70
+ const mappedResults = compose(
71
71
  filterByTags( { slug, tags } ),
72
72
  boostByTags( boost.tags )
73
73
  )( firstResult.hits );
@@ -110,7 +110,7 @@ function boostByTags( tags ) {
110
110
  return identity;
111
111
  }
112
112
 
113
- return pipe(
113
+ return compose(
114
114
  groupByTag,
115
115
  sortObjectKeysByIndices( tags ),
116
116
  Object.values,