umberto 5.0.1 → 6.0.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
+ ## [6.0.0](https://github.com/cksource/umberto/compare/v5.0.2...v6.0.0) (2025-04-01)
5
+
6
+ ### BREAKING CHANGES
7
+
8
+ * The support for cross-project links in all tags (all variants of the `@link`, `@glink` and `@icon` tags) is now dropped. In case you want to insert a link to another project, use Markdown syntax (`[link text](link target)`) or a regular HTML `<a>` tag.
9
+
10
+ ### Other changes
11
+
12
+ * Cross-project links in `@link` (and all its variants), `@glink` and `@icon` tags are no longer supported. When detected, error message is printed in the console. Closes [#1243](https://github.com/cksource/umberto/issues/1243). ([commit](https://github.com/cksource/umberto/commit/0929fafee7e770a618496bd1ad43abac280d58c6))
13
+
14
+
15
+ ## [5.0.2](https://github.com/cksource/umberto/compare/v5.0.1...v5.0.2) (2025-03-24)
16
+
17
+ ### Bug fixes
18
+
19
+ * Improve handling CKEditor 5 icons on the API pages. ([commit](https://github.com/cksource/umberto/commit/89f1856de2fcbd99dbdf24d17cdd05b89a08a147))
20
+
21
+
4
22
  ## [5.0.1](https://github.com/cksource/umberto/compare/v5.0.0...v5.0.1) (2025-03-13)
5
23
 
6
24
  Internal changes only (updated dependencies, documentation, etc.).
package/README.md CHANGED
@@ -338,13 +338,13 @@ Tags are strings which can be used in guides and are replaced by Umberto with ge
338
338
  Syntax:
339
339
 
340
340
  ```
341
- {@link @projectName longname linkText}
341
+ {@link longname linkText}
342
342
  ```
343
343
 
344
344
  Example:
345
345
 
346
346
  ```
347
- {@link @ckeditor5 module:ui/template~Template#render Render method}
347
+ {@link module:ui/template~Template#render Render method}
348
348
  ```
349
349
 
350
350
  **Note:** If `longname` doesn't start with `module:`, please use `@linkapi` instead of `@link`.
@@ -357,9 +357,7 @@ Example links for CKEditor 4:
357
357
 
358
358
  `{@linkapi CKEDITOR.dialog.definition#resizable resizable}` -> https://docs.ckeditor.com/ckeditor4/latest/api/CKEDITOR_dialog_definition.html#resizable
359
359
 
360
- ##### @projectName
361
-
362
- It is optional and must be used only if creating a link to other project's API (in case of a multi-project documentation).
360
+ **Important:** Cross-project links `{@link @projectName longname linkText}` are no longer supported.
363
361
 
364
362
  ##### longname
365
363
 
@@ -426,28 +424,12 @@ A link to https://github.com/ckeditor/ckeditor5/blob/master/docs/builds/guides/o
426
424
  {@link builds/guides/overview Builds overview}
427
425
  ```
428
426
 
429
- In order to link to a guide of another project please follow this syntax:
430
-
431
- ```
432
- {@link @projectslug pathToGuide linkText}
433
- ```
434
-
435
- where `@projectslug` is the slug of another project and can be find in the project's `umberto.json`
436
-
437
- Example:
438
-
439
- ```
440
- {@link @letters index Home page of Letters}
441
- ```
442
-
443
427
  **Note:** It is possible to link to a guide from a JSDoc comment using `{@glink}` tag (so that Umberto can distinguish it from a regular JSDoc's `{@link}` tag):
444
428
 
445
429
  ```
446
- {@glink @projectSlug finalPathToGuide linkText}
430
+ {@glink finalPathToGuide linkText}
447
431
  ```
448
432
 
449
- where `@projectslug` is the slug of another project and can be find in the project's `umberto.json`. This parameter is optional.
450
-
451
433
  **Important:** In this case `finalPathToGuide` **must be** a guide's final URL. Final URL may be different than guide's physical path (usually it's the same though)
452
434
 
453
435
  Example:
@@ -456,6 +438,8 @@ Example:
456
438
  {@glink builds/guides/migrate Migration guide}
457
439
  ```
458
440
 
441
+ **Important:** Cross-project links (`{@link @projectName pathToGuide linkText}` or `{@glink @projectName finalPathToGuide linkText}`) are no longer supported.
442
+
459
443
  #### Links escaping
460
444
 
461
445
  Umberto allows on link escaping, when curly brackets are preceed with backslash.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "umberto",
3
- "version": "5.0.1",
3
+ "version": "6.0.0",
4
4
  "description": "CKSource Documentation builder",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -0,0 +1,29 @@
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 chalk = require( 'chalk' );
9
+
10
+ /**
11
+ * Logs an error message about a cross-project reference found in the parsed expression.
12
+ * See: https://github.com/cksource/umberto/issues/1243.
13
+ *
14
+ * @param {Object} data
15
+ * @param {String} data.expression
16
+ * @param {String} [data.source]
17
+ * @returns {String}
18
+ */
19
+ module.exports = function logCrossProjectReference( { expression, source } ) {
20
+ process.exitCode = 1;
21
+
22
+ const message = `${ chalk.red( 'Error:' ) } Failed while convert ${ chalk.cyanBright( expression ) } tag`;
23
+ const messageLocation = source ? ` in ${ chalk.magentaBright( source ) }.` : '.';
24
+ const messageDescription = ' Cross-project references are not supported.';
25
+
26
+ console.log( message + messageLocation + messageDescription );
27
+
28
+ return expression;
29
+ };
@@ -7,6 +7,7 @@
7
7
 
8
8
  const crypto = require( 'crypto' );
9
9
  const upath = require( 'upath' );
10
+ const logCrossProjectReference = require( './logcrossprojectreference' );
10
11
 
11
12
  const ICON_REGEXP = /\\?{@icon(?:\s+@(\w+))?\s+([^}\s]+)\s*([^}]*)\\?}/g;
12
13
 
@@ -21,26 +22,18 @@ const ICON_REGEXP = /\\?{@icon(?:\s+@(\w+))?\s+([^}\s]+)\s*([^}]*)\\?}/g;
21
22
  */
22
23
  module.exports = function parseIconTag( page, hexo ) {
23
24
  const relativeUrlHelper = hexo.extend.helper.store.relative_url;
24
- const availableProjects = Object.keys( hexo.projectGlobals ).filter( projectName => projectName !== 'common' );
25
25
 
26
26
  page.content = page.content.replace( ICON_REGEXP, ( match, iconProject, iconPath, alternativeText ) => {
27
- const options = {
28
- isSingleProject: hexo.projectGlobals.common.isSingleProject,
29
- pageProject: page.projectName,
30
- iconProject,
31
- availableProjects
32
- };
33
-
34
- if ( !shouldTransformIcon( options ) ) {
35
- return match;
27
+ // Cross project references are not supported.
28
+ if ( iconProject ) {
29
+ return logCrossProjectReference( { expression: match, source: page.source } );
36
30
  }
37
31
 
38
32
  if ( match.startsWith( '\\{' ) ) {
39
33
  return match.replace( /^\\{/, '{' ).replace( /\\}$/, '}' );
40
34
  }
41
35
 
42
- const projectName = iconProject || page.projectName;
43
- const projectDetails = hexo.projectGlobals[ projectName ];
36
+ const projectDetails = hexo.projectGlobals[ page.projectName ];
44
37
  const fileName = sha1( iconPath ).substr( 0, 10 ) + upath.extname( iconPath );
45
38
 
46
39
  // Map the original file name with the hashed version to predict the path when copying assets.
@@ -52,7 +45,7 @@ module.exports = function parseIconTag( page, hexo ) {
52
45
  alt = `alt="${ alternativeText }" `;
53
46
  }
54
47
 
55
- const absoluteLikePath = upath.join( projectName, projectDetails.config.version, 'assets', 'icons', fileName );
48
+ const absoluteLikePath = upath.join( page.projectName, projectDetails.config.version, 'assets', 'icons', fileName );
56
49
 
57
50
  return [
58
51
  '<span class="editor-icon">' +
@@ -64,36 +57,6 @@ module.exports = function parseIconTag( page, hexo ) {
64
57
  return page;
65
58
  };
66
59
 
67
- /**
68
- * Checks whether the `{@icon}` expression should be transformed to proper `<img>` tag.
69
- *
70
- * @param {Object} options
71
- * @param {Boolean} options.isSingleProject Whether building for a single project.
72
- * @param {String} options.pageProject The project of the current processed page.
73
- * @param {String} options.iconProject The project specified in the `{@icon}` expression.
74
- * @param {Array.<String>} options.availableProjects Available projects when building the non-single project documentation.
75
- * @return {Boolean}
76
- */
77
- function shouldTransformIcon( options ) {
78
- // {@icon path} - missing the @projectName part.
79
- // Assuming, that loading an icon for the page's project.
80
- if ( !options.iconProject ) {
81
- return true;
82
- }
83
-
84
- // Building a page that belongs to the same project as specified in the icon.
85
- if ( options.iconProject === options.pageProject ) {
86
- return true;
87
- }
88
-
89
- // Building many projects. Check whether the project exists.
90
- if ( !options.isSingleProject && options.availableProjects.includes( options.iconProject ) ) {
91
- return true;
92
- }
93
-
94
- return false;
95
- }
96
-
97
60
  /**
98
61
  * @param {String} value
99
62
  * @return {String}
@@ -13,6 +13,7 @@ const LINK_REGEXP = /\\?{(?:@linkapi|@link|@linksdk|@linkexample)\s+[^{]+?({[^}]
13
13
 
14
14
  const findTargetDoclet = require( '../../src/api-builder/utils/findtargetdoclet' );
15
15
  const { hasDedicatedApiPages, LONG_NAME_LABEL_REGEXP } = require( '../../src/api-builder/utils/utils' );
16
+ const logCrossProjectReference = require( './logcrossprojectreference' );
16
17
 
17
18
  /**
18
19
  * Converts @link or @linkapi tags into working links to guides or API docs.
@@ -70,7 +71,13 @@ function linkToApi( str, data, hexo, { relativeUrlHelper, isSilentError } ) {
70
71
  } );
71
72
  }
72
73
 
73
- const projectName = linkContentMatchResult[ 1 ] || data.projectName;
74
+ const projectName = linkContentMatchResult[ 1 ];
75
+
76
+ // Cross project references are not supported.
77
+ if ( projectName ) {
78
+ return logCrossProjectReference( { expression: str, source: data.source } );
79
+ }
80
+
74
81
  const { name, label } = splitLongname( linkContentMatchResult[ 2 ] );
75
82
  const itemName = linkContentMatchResult[ 2 ].replace( LONG_NAME_LABEL_REGEXP, '' );
76
83
  let linkText = linkContentMatchResult[ 3 ] || name;
@@ -92,7 +99,7 @@ function linkToApi( str, data, hexo, { relativeUrlHelper, isSilentError } ) {
92
99
  const baseItemName = getBaseItemName( itemName );
93
100
  let hashWithoutPrefix = itemName.replace( baseItemName, '' ).slice( 1 );
94
101
  const hash = hashWithoutPrefix ? `#${ hashWithoutPrefix }` : '';
95
- const project = hexo.projectGlobals[ projectName ];
102
+ const project = hexo.projectGlobals[ data.projectName ];
96
103
 
97
104
  if ( hashWithoutPrefix.includes( '-' ) ) {
98
105
  let [ type, description ] = hashWithoutPrefix.split( '-' );
@@ -113,18 +120,18 @@ function linkToApi( str, data, hexo, { relativeUrlHelper, isSilentError } ) {
113
120
 
114
121
  if ( project && project.config ) {
115
122
  apiSlug = getSlug( project.config, 'api-reference' ) || 'api-reference';
116
- } else if ( isIgnoredProject( projectName, hexo ) || hexo.projectGlobals.common.isSingleProject ) {
123
+ } else if ( isIgnoredProject( data.projectName, hexo ) || hexo.projectGlobals.common.isSingleProject ) {
117
124
  return str;
118
125
  } else {
119
126
  return onError( {
120
127
  value: str,
121
128
  source: data.source,
122
129
  isSilent: isSilentError,
123
- description: `Project ${ chalk.redBright( projectName ) } is not defined.`
130
+ description: `Project ${ chalk.redBright( data.projectName ) } is not defined.`
124
131
  } );
125
132
  }
126
133
 
127
- const basePath = projectName === data.projectName ? data.BASE_PATH : `${ projectName }/latest`;
134
+ const basePath = data.BASE_PATH;
128
135
  const fileName = getFileName( baseItemName );
129
136
 
130
137
  let href = relativeUrlHelper( data.path, upath.join( basePath, apiSlug, fileName + hash ) );
@@ -236,9 +243,15 @@ function linkToSdk( str, data, hexo, { relativeUrlHelper, isSilentError } ) {
236
243
  } );
237
244
  }
238
245
 
239
- const projectName = match[ 1 ] ? match[ 1 ] : data.projectName;
240
- const sdkSlug = getSlug( hexo.projectGlobals[ projectName ].config, 'sdk' );
241
- const basePath = projectName === data.projectName ? data.BASE_PATH : `${ projectName }/latest`;
246
+ const projectName = match[ 1 ];
247
+
248
+ // Cross project references are not supported.
249
+ if ( projectName ) {
250
+ return logCrossProjectReference( { expression: str, source: data.source } );
251
+ }
252
+
253
+ const sdkSlug = getSlug( hexo.projectGlobals[ data.projectName ].config, 'sdk' );
254
+ const basePath = data.BASE_PATH;
242
255
  const sdkFileName = match[ 2 ];
243
256
  const sdkFileHash = match[ 3 ] || '';
244
257
  const sdkLinkName = match[ 4 ];
@@ -271,23 +284,15 @@ function linkToGuide( str, data, hexo, { relativeUrlHelper, isSilentError } ) {
271
284
  const pathToGuide = pathToGuideSplit[ 0 ];
272
285
  const hash = pathToGuideSplit.length > 1 ? `#${ pathToGuideSplit[ 1 ] }` : '';
273
286
  const linkText = match[ 3 ] ? match[ 3 ] : pathToGuide;
274
- const projectName = match[ 1 ] ? match[ 1 ] : data.projectName;
275
- const pagePaths = hexo.projectGlobals[ projectName ] ? hexo.projectGlobals[ projectName ].pagePaths : null;
287
+ const projectName = match[ 1 ];
276
288
 
277
- if ( shouldIgnoreGuideLink( match, data, projectName, hexo ) ) {
278
- if ( isIgnoredProject( projectName, hexo ) || hexo.projectGlobals.common.isSingleProject ) {
279
- return str;
280
- }
281
-
282
- return onError( {
283
- value: str,
284
- source: data.source,
285
- isSilent: isSilentError,
286
- description: `Project ${ chalk.redBright( projectName ) } is not defined.`
287
- } );
289
+ // Cross project references are not supported.
290
+ if ( projectName ) {
291
+ return logCrossProjectReference( { expression: str, source: data.source } );
288
292
  }
289
293
 
290
- const projectBasePath = match[ 1 ] ? hexo.projectGlobals[ projectName ].BASE_PATH : data.BASE_PATH;
294
+ const pagePaths = hexo.projectGlobals[ data.projectName ] ? hexo.projectGlobals[ data.projectName ].pagePaths : null;
295
+ const projectBasePath = data.BASE_PATH;
291
296
 
292
297
  let targetPath;
293
298
 
@@ -360,29 +365,3 @@ function getSlug( config, name ) {
360
365
 
361
366
  return null;
362
367
  }
363
-
364
- /**
365
- * When linking to an external project and the documentation is being built for a single project, the link should remain untouched.
366
- *
367
- * However, if the link points to the same project, let's transform it. See #914.
368
- *
369
- * @param {Object} match
370
- * @param {Object} page
371
- * @param {String} projectName
372
- * @param {Object} hexo
373
- * @return {Boolean}
374
- */
375
- function shouldIgnoreGuideLink( match, page, projectName, hexo ) {
376
- // If the project `{@link @projectName ...}` is not specified, the URL should be transformed.
377
- if ( !match[ 1 ] ) {
378
- return false;
379
- }
380
-
381
- // Also, if the `page` belongs to the same project as `@projectName`, the link should be transformed.
382
- if ( match[ 1 ] === page.projectName ) {
383
- return false;
384
- }
385
-
386
- // Building the single project or the project does not exist. Do not transform links.
387
- return ( hexo.projectGlobals.common.isSingleProject || !hexo.projectGlobals[ projectName ] );
388
- }
@@ -11,6 +11,7 @@ const chalk = require( 'chalk' );
11
11
  const macroReplacer = require( '../../tasks/macro-replacer' );
12
12
  const findTargetDoclet = require( '../utils/findtargetdoclet' );
13
13
  const { hasDedicatedApiPages, LONG_NAME_LABEL_REGEXP, LONG_NAME_MEMBER_SEPARATOR_REGEXP } = require( '../utils/utils' );
14
+ const logCrossProjectReference = require( '../../../scripts/utils/logcrossprojectreference' );
14
15
 
15
16
  /**
16
17
  * Class responsible for parsing doc descriptions.
@@ -83,20 +84,27 @@ module.exports = class DescriptionParser {
83
84
  return result;
84
85
  }
85
86
 
86
- let match = guideLinkRegExp.exec( str );
87
+ let match;
87
88
 
88
- while ( match !== null ) {
89
+ while ( ( match = guideLinkRegExp.exec( str ) ) !== null ) {
89
90
  const detailedRegExp = /@glink\s+(?:@(\w+)\s+)?([^}\s]+)\s*([^}]*)}/;
90
91
  const detailedMatch = detailedRegExp.exec( match[ 0 ] );
91
- const projectSlug = detailedMatch[ 1 ] ? detailedMatch[ 1 ] : null;
92
+ const projectSlug = detailedMatch[ 1 ];
93
+
94
+ // Cross project references are not supported.
95
+ if ( projectSlug ) {
96
+ logCrossProjectReference( { expression: match[ 0 ] } );
97
+
98
+ continue;
99
+ }
100
+
92
101
  const pathToGuideSplit = detailedMatch[ 2 ].split( '#' );
93
102
  const pathToGuide = pathToGuideSplit[ 0 ];
94
103
  const hash = pathToGuideSplit.length > 1 ? `#${ pathToGuideSplit[ 1 ] }` : '';
95
104
  const linkText = detailedMatch[ 3 ] ? detailedMatch[ 3 ] : pathToGuide;
96
- const href = projectSlug ? `../../../${ projectSlug }/latest/${ pathToGuide }.html` : `../${ pathToGuide }.html`;
105
+ const href = `../${ pathToGuide }.html`;
97
106
 
98
107
  result = result.replace( match[ 0 ], `<a href=${ href }${ hash } data-glink>${ linkText }</a>` );
99
- match = guideLinkRegExp.exec( str );
100
108
  }
101
109
 
102
110
  return result;
@@ -172,6 +172,25 @@ class TypedocConverter {
172
172
  * @param {String|null} [parentName=null]
173
173
  */
174
174
  _convertChild( reflection, parentName = null ) {
175
+ // For unknown reasons, TypeScript or TypeDoc treats all CKEditor 5 icons (except for the first one)
176
+ // as a reference to the first exported member by a module.
177
+ // We map them manually to display a proper list of the available doclets.
178
+ // See: https://github.com/cksource/ckeditor5-internal/issues/3993.
179
+ if ( parentName === 'module:icons/index' && reflection.kindString === 'Reference' ) {
180
+ reflection = {
181
+ ...reflection,
182
+ kind: 32,
183
+ kindString: 'Variable',
184
+ flags: {
185
+ isConst: true
186
+ },
187
+ type: {
188
+ type: 'intrinsic',
189
+ name: 'string'
190
+ }
191
+ };
192
+ }
193
+
175
194
  const parser = this._parsers.find( parser => parser.canParse( reflection ) );
176
195
 
177
196
  let doclets;
@@ -28,7 +28,6 @@ import showWarningBanner from './_warningbanner';
28
28
  import { enableBadgeTooltips, createTooltip } from './_tooltips';
29
29
  import attachPermalinkListener from './_attachpermalinklistener';
30
30
  import { createCodeSwitcherButtons } from './_codeswitcherbuttons';
31
- import { sendUserDate } from './_sendUserDate';
32
31
 
33
32
  // Changing documentation theme. To enable, type in the console: `localStorage.setItem( 'theme', 'theme-dark' )`.
34
33
  if ( localStorage.getItem( 'theme' ) === 'theme-dark' ) {
@@ -68,5 +67,4 @@ $( document ).ready( () => {
68
67
  sampleCode(); // control accordion of sample code and buttons(copy/download)
69
68
  enableBadgeTooltips(); // attaches tippy.js tooltip for all elements with data-badge-tooltip
70
69
  createCodeSwitcherButtons();
71
- sendUserDate();
72
70
  } );
@@ -1,54 +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
- const IS_USER_DATE_SENT_KEY = 'is_local_user_os_date_sent';
7
- const STATS_EVENT_URL_DEV = 'https://builder-api.internal.cke-cs-dev.com/events';
8
- const STATS_EVENT_URL_PROD = 'https://builder-api.ckeditor.com/events';
9
-
10
- export function sendUserDate() {
11
- const isUserDateSent = localStorage.getItem( IS_USER_DATE_SENT_KEY );
12
- const environment = getEnvironment();
13
- const isProdOrNightly = environment === 'production' || environment === 'nightly';
14
- const userDateUrl = isProdOrNightly ? STATS_EVENT_URL_PROD : STATS_EVENT_URL_DEV;
15
-
16
- if ( isUserDateSent ) {
17
- // User date info is already collected. Aborting.
18
- return;
19
- }
20
-
21
- fetch( userDateUrl, {
22
- method: 'POST',
23
- headers: {
24
- 'Content-Type': 'application/json'
25
- },
26
- body: JSON.stringify( {
27
- environment,
28
- event_type: 'cksource_docs_date',
29
- event_data: {
30
- date: new Date().toISOString(),
31
- user_agent: window.navigator.userAgent,
32
- language: window.navigator.language
33
- }
34
- } )
35
- } ).then( () => {
36
- localStorage.setItem( IS_USER_DATE_SENT_KEY, 'true' );
37
- } ).catch( () => {
38
- // There is no need to report if sending user date fails.
39
- } );
40
- }
41
-
42
- function getEnvironment() {
43
- const hostname = window.location.hostname;
44
-
45
- if ( hostname.startsWith( 'ckeditor5.github.io' ) ) {
46
- return 'nightly';
47
- }
48
-
49
- if ( hostname.startsWith( 'ckeditor.com' ) ) {
50
- return 'production';
51
- }
52
-
53
- return 'local';
54
- }