umberto 8.0.3 → 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,17 @@
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
+
4
15
  ## [8.0.3](https://github.com/cksource/umberto/compare/v8.0.2...v8.0.3) (August 5, 2025)
5
16
 
6
17
  ### Bug fixes
@@ -54,13 +65,6 @@ Changelog
54
65
  * The repository now uses ESLint v9. Therefore, the required Node.js version has been upgraded to 22 to match the ESLint requirements.
55
66
  * Update `LICENSE.md` file to include licenses of direct dependencies
56
67
 
57
-
58
- ## [7.0.2](https://github.com/cksource/umberto/compare/v7.0.1...v7.0.2) (2025-05-20)
59
-
60
- ### Bug fixes
61
-
62
- * 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))
63
-
64
68
  ---
65
69
 
66
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.3",
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
  };
@@ -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,