umberto 10.1.4 → 10.3.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 (42) hide show
  1. package/CHANGELOG.md +23 -16
  2. package/package.json +1 -1
  3. package/src/api-builder/classes/doc-data-factory.js +10 -2
  4. package/src/data-converter/converters/typedoc/abstractparser.js +14 -0
  5. package/src/data-converter/converters/typedoc/accessorparser.js +1 -0
  6. package/src/data-converter/converters/typedoc/classparser.js +1 -0
  7. package/src/data-converter/converters/typedoc/constantparser.js +1 -0
  8. package/src/data-converter/converters/typedoc/eventparser.js +1 -0
  9. package/src/data-converter/converters/typedoc/functionparser.js +1 -0
  10. package/src/data-converter/converters/typedoc/interfaceparser.js +1 -0
  11. package/src/data-converter/converters/typedoc/methodparser.js +1 -0
  12. package/src/data-converter/converters/typedoc/propertyparser.js +1 -0
  13. package/src/data-converter/converters/typedoc/typeparser.js +1 -0
  14. package/src/helpers/create-filtering-data-attribs.js +4 -0
  15. package/src/hexo/filter/project-locals.js +1 -1
  16. package/src/tasks/build-documentation.js +2 -2
  17. package/themes/umberto/layout/gloria/404.pug +1 -1
  18. package/themes/umberto/layout/gloria/_api-docs/_header/index.pug +2 -0
  19. package/themes/umberto/layout/gloria/_api-docs/_mixin/_badge-tag.pug +6 -0
  20. package/themes/umberto/layout/gloria/_api-docs/_mixin/_class-item.pug +2 -4
  21. package/themes/umberto/layout/gloria/_api-docs/_mixin/_method.pug +2 -4
  22. package/themes/umberto/layout/gloria/_api-docs/_mixin/_property.pug +2 -5
  23. package/themes/umberto/layout/gloria/_api-docs/_partial/filter.pug +4 -0
  24. package/themes/umberto/layout/gloria/_api-docs/_subheader/_style.scss +7 -0
  25. package/themes/umberto/layout/gloria/_api-docs/_subheader/index.pug +4 -0
  26. package/themes/umberto/layout/gloria/_components/tag/_style.scss +12 -0
  27. package/themes/umberto/layout/gloria/_modules/header-bar/_style.scss +59 -0
  28. package/themes/umberto/layout/gloria/_modules/header-lts-info/index.pug +69 -0
  29. package/themes/umberto/layout/gloria/_modules/header-nightly-info/index.pug +8 -9
  30. package/themes/umberto/layout/gloria/_modules/index.pug +1 -1
  31. package/themes/umberto/layout/gloria/_modules/kapa/index.pug +19 -0
  32. package/themes/umberto/layout/gloria/api.pug +1 -1
  33. package/themes/umberto/layout/gloria/base.pug +1 -1
  34. package/themes/umberto/layout/gloria/index.pug +1 -0
  35. package/themes/umberto/layout/gloria/theme.pug +2 -0
  36. package/themes/umberto/layout/umberto/_api-docs/_partial/filter.pug +4 -0
  37. package/themes/umberto/src/gloria/js/modules/api-filter.js +19 -10
  38. package/themes/umberto/src/gloria/js/modules/links-prefetch.js +4 -0
  39. package/themes/umberto/src/umberto/css/_badge.scss +1 -0
  40. package/themes/umberto/src/umberto/css/helpers/_color.scss +1 -0
  41. package/themes/umberto/src/umberto/js/_filtering.js +1 -0
  42. package/themes/umberto/layout/gloria/_modules/header-legacy-warning/index.pug +0 -11
package/CHANGELOG.md CHANGED
@@ -1,44 +1,51 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
- ## [10.1.4](https://github.com/cksource/umberto/compare/v10.1.3...v10.1.4) (March 20, 2026)
4
+ ## [10.3.0](https://github.com/cksource/umberto/compare/v10.2.0...v10.3.0) (April 3, 2026)
5
5
 
6
- ### Other changes
6
+ ### Features
7
7
 
8
- * Filter search results from Algolia to match the current docs version: `latest` or `lts-v47`. Filtering remains backward-compatible: hits without the docs version are still allowed.
8
+ * Added support for displaying and filtering experimental API items in TypeDoc-based API docs.
9
9
 
10
+ Experimental annotations now appear in page headers and member listings, and can be toggled with the API filters.
10
11
 
11
- ## [10.1.3](https://github.com/cksource/umberto/compare/v10.1.2...v10.1.3) (March 10, 2026)
12
+
13
+ ## [10.2.0](https://github.com/cksource/umberto/compare/v10.1.4...v10.2.0) (April 2, 2026)
14
+
15
+ ### Features
16
+
17
+ * Added support for configuring Kapa AI source groups via the `sourceGroupIds` option. The option can be set globally in `kapa.default` or per project to filter the widget's knowledge base to specific source groups.
12
18
 
13
19
  ### Bug fixes
14
20
 
15
- * Fix regression introduced in `v10.1.2` affecting slugs generated for headings. Slugs generated now should be the same as in `v10.1.1` and previous versions of Umberto.
21
+ * Fixed a console error thrown when hovering over a CKEditor bookmark element. The `links-prefetch` module now silently ignores anchor elements with no `href` instead of calling `new URL( '' )` and logging a spurious error.
16
22
 
23
+ ### Other changes
17
24
 
18
- ## [10.1.2](https://github.com/cksource/umberto/compare/v10.1.1...v10.1.2) (March 9, 2026)
25
+ * Removed the legacy warning banner.
26
+ * Added cyan LTS banner at the top of the page on LTS branch.
27
+ * Updated the colour of nightly banner to yellow.
19
28
 
20
- ### Bug fixes
21
29
 
22
- * Removed unmaintained `hexo-renderer-markdown-it-plus` and `@ckeditor/jsdoc-plugins` dependencies by replacing them with maintained and local implementations, resolving the reported `markdown-it`-related vulnerabilities in Umberto's dependency tree.
30
+ ## [10.1.4](https://github.com/cksource/umberto/compare/v10.1.3...v10.1.4) (March 20, 2026)
23
31
 
32
+ ### Other changes
24
33
 
25
- ## [10.1.1](https://github.com/cksource/umberto/compare/v10.1.0...v10.1.1) (March 4, 2026)
34
+ * Filter search results from Algolia to match the current docs version: `latest` or `lts-v47`. Filtering remains backward-compatible: hits without the docs version are still allowed.
26
35
 
27
- ### Bug fixes
28
36
 
29
- * Fixed a deprecation warning displayed during documentation builds by migrating Sass compilation to the modern Dart Sass JavaScript API. The `legacy-js-api` warning is no longer emitted.
37
+ ## [10.1.3](https://github.com/cksource/umberto/compare/v10.1.2...v10.1.3) (March 10, 2026)
30
38
 
39
+ ### Bug fixes
31
40
 
32
- ## [10.1.0](https://github.com/cksource/umberto/compare/v10.0.0...v10.1.0) (March 4, 2026)
41
+ * Fix regression introduced in `v10.1.2` affecting slugs generated for headings. Slugs generated now should be the same as in `v10.1.1` and previous versions of Umberto.
33
42
 
34
- ### Features
35
43
 
36
- * Add support for experimental features in the callout component and in the badges normalization script.
44
+ ## [10.1.2](https://github.com/cksource/umberto/compare/v10.1.1...v10.1.2) (March 9, 2026)
37
45
 
38
46
  ### Bug fixes
39
47
 
40
- * Fixed the deprecation warning regarding the `Sass if() syntax` thrown during building docs.
41
- * Disabled inserting a space between the anchor link and heading when converting a document from Markdown to HTML.
48
+ * Removed unmaintained `hexo-renderer-markdown-it-plus` and `@ckeditor/jsdoc-plugins` dependencies by replacing them with maintained and local implementations, resolving the reported `markdown-it`-related vulnerabilities in Umberto's dependency tree.
42
49
 
43
50
  ---
44
51
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "umberto",
3
- "version": "10.1.4",
3
+ "version": "10.3.0",
4
4
  "description": "CKSource Documentation builder",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -62,6 +62,7 @@ export class DocDataFactory {
62
62
  access: doclet.access,
63
63
  inherited: doclet.mixed ? false : doclet.inherited,
64
64
  mixed: doclet.mixed,
65
+ experimental: doclet.experimental || false,
65
66
  deprecated: doclet.deprecated,
66
67
  since: doclet.since,
67
68
  file: doclet.file,
@@ -264,6 +265,7 @@ export class DocDataFactory {
264
265
  */
265
266
  getClassFull( doclet ) {
266
267
  const classMembersCollection = this._dataCollection.get( `memberof:${ doclet.longname }` );
268
+ const base = this._getBase( doclet, { headingDecreaseLevel: 2 } );
267
269
  const configOptions = classMembersCollection.get( 'cfg' );
268
270
  const properties = classMembersCollection.get( 'member' );
269
271
  const methods = classMembersCollection.get( 'function' );
@@ -328,7 +330,7 @@ export class DocDataFactory {
328
330
  augmentsNested.unshift( doclet.longname );
329
331
  augmentsNested.reverse();
330
332
 
331
- return Object.assign( this._getBase( doclet, { headingDecreaseLevel: 2 } ), {
333
+ return Object.assign( base, {
332
334
  configOptions: configOptionsProcessed,
333
335
  properties: propertiesProcessed,
334
336
  staticProperties,
@@ -644,6 +646,13 @@ function prepareBadges( item, context ) {
644
646
  badges.push( deprecatedBadge );
645
647
  }
646
648
 
649
+ if ( item.experimental ) {
650
+ badges.push( {
651
+ class: 'cyan badge--experimental',
652
+ text: 'experimental'
653
+ } );
654
+ }
655
+
647
656
  if ( item.chainable ) {
648
657
  badges.push( {
649
658
  class: 'green',
@@ -724,7 +733,6 @@ function prepareBadges( item, context ) {
724
733
 
725
734
  return badges;
726
735
  }
727
-
728
736
  function sortLongnamesAlphabetically( arr ) {
729
737
  if ( !arr || !arr.length ) {
730
738
  return;
@@ -370,6 +370,20 @@ export class AbstractParser {
370
370
  return false;
371
371
  }
372
372
 
373
+ /**
374
+ * Checks if the member is marked as `experimental` one.
375
+ *
376
+ * @param {BaseReflection} item
377
+ * @returns {Boolean}
378
+ */
379
+ isExperimental( item ) {
380
+ if ( item.comment && item.comment.modifierTags ) {
381
+ return item.comment.modifierTags.some( tag => tag === '@experimental' );
382
+ }
383
+
384
+ return false;
385
+ }
386
+
373
387
  /**
374
388
  * Checks if the member is marked by the `@deprecated` JSDoc tag.
375
389
  *
@@ -53,6 +53,7 @@ export class AccessorParser extends AbstractParser {
53
53
  description: this.getComment( getSignature || setSignature ),
54
54
  file: this.getFile( item ),
55
55
  skipSource: this.shouldSkipSource( getSignature || setSignature ),
56
+ experimental: this.isExperimental( getSignature || setSignature ),
56
57
  deprecated: this.isDeprecated( getSignature || setSignature ),
57
58
  inherited: !!item.inheritedFrom,
58
59
 
@@ -37,6 +37,7 @@ export class ClassParser extends AbstractParser {
37
37
  description: this.getComment( item ),
38
38
  file: this.getFile( item ),
39
39
  skipSource: this.shouldSkipSource( item ),
40
+ experimental: this.isExperimental( item ),
40
41
  deprecated: this.isDeprecated( item ),
41
42
 
42
43
  // Properties specified below will be mapped to module names once the entire project is transformed.
@@ -35,6 +35,7 @@ export class ConstantParser extends AbstractParser {
35
35
  extraId: `constant-${ item.name }`,
36
36
  file: this.getFile( item ),
37
37
  skipSource: this.shouldSkipSource( item ),
38
+ experimental: this.isExperimental( item ),
38
39
  description: this.getComment( item ),
39
40
  deprecated: this.isDeprecated( item ),
40
41
 
@@ -38,6 +38,7 @@ export class EventParser extends AbstractParser {
38
38
  description: this.getComment( item ),
39
39
  file: this.getFile( item ),
40
40
  skipSource: this.shouldSkipSource( item ),
41
+ experimental: this.isExperimental( item ),
41
42
  deprecated: this.isDeprecated( item ),
42
43
  inherited: !!item.inheritedFrom,
43
44
  see: this.getRelated( item, parentName ),
@@ -38,6 +38,7 @@ export class FunctionParser extends AbstractParser {
38
38
  description: this.getComment( signature ),
39
39
  file: this.getFile( item, index ),
40
40
  skipSource: this.shouldSkipSource( signature ),
41
+ experimental: this.isExperimental( signature ),
41
42
  deprecated: this.isDeprecated( signature ),
42
43
  see: this.getRelated( signature, parentName ),
43
44
 
@@ -36,6 +36,7 @@ export class InterfaceParser extends AbstractParser {
36
36
  description: this.getComment( item ),
37
37
  file: this.getFile( item ),
38
38
  skipSource: this.shouldSkipSource( item ),
39
+ experimental: this.isExperimental( item ),
39
40
  deprecated: this.isDeprecated( item ),
40
41
 
41
42
  typeParameters: null,
@@ -39,6 +39,7 @@ export class MethodParser extends AbstractParser {
39
39
  description: this.getComment( signature ),
40
40
  file: this.getFile( item, index ),
41
41
  skipSource: this.shouldSkipSource( signature ),
42
+ experimental: this.isExperimental( signature ),
42
43
  deprecated: this.isDeprecated( signature ),
43
44
  inherited: !!signature.inheritedFrom,
44
45
  see: this.getRelated( signature, parentName ),
@@ -40,6 +40,7 @@ export class PropertyParser extends AbstractParser {
40
40
  description: this.getComment( item ),
41
41
  file: this.getFile( item ),
42
42
  skipSource: this.shouldSkipSource( item ),
43
+ experimental: this.isExperimental( item ),
43
44
  deprecated: this.isDeprecated( item ),
44
45
  inherited: !!item.inheritedFrom,
45
46
  see: this.getRelated( item, parentName ),
@@ -32,6 +32,7 @@ export class TypeParser extends AbstractParser {
32
32
  extraId: this.getExtraId( item ),
33
33
  file: this.getFile( item ),
34
34
  skipSource: this.shouldSkipSource( item ),
35
+ experimental: this.isExperimental( item ),
35
36
  description: this.getComment( item ),
36
37
  deprecated: this.isDeprecated( item ),
37
38
  firedBy: this.getFiredBy( item, parentName ),
@@ -28,5 +28,9 @@ export const createFilteringDataAttribs = item => {
28
28
  attrData[ 'data-deprecated' ] = 'true';
29
29
  }
30
30
 
31
+ if ( item.experimental ) {
32
+ attrData[ 'data-experimental' ] = 'true';
33
+ }
34
+
31
35
  return attrData;
32
36
  };
@@ -193,8 +193,8 @@ export const projectLocals = ( ctx, {
193
193
  // `sitemap` entry is not consistent across building methods of Umberto.
194
194
  // It's often defined in the API, but it's not always available in the hexo locals.
195
195
  // In order to make it available, it's safer to use `docSearch.indexName` which is always defined.
196
- locals.isLegacy = locals.path.includes( '/legacy/' );
197
196
  locals.isNightly = docSearch.indexName?.includes( 'nightly' ) || false;
197
+ locals.isLts = /\/ckeditor5\/(lts-v\d+)(?:\/|$)/.test( `/${ locals.path }` );
198
198
 
199
199
  locals.mainOg = og || {};
200
200
  } );
@@ -510,8 +510,8 @@ async function buildApis( projectConfigs, options = {} ) {
510
510
  feedbackWidget: options.feedbackWidget,
511
511
  pathJoin: upath.join,
512
512
  mainOg: options.og || {},
513
- isLegacy: false,
514
- isNightly: options.docSearch?.indexName?.includes( 'nightly' ) || false
513
+ isNightly: options.docSearch?.indexName?.includes( 'nightly' ) || false,
514
+ isLts: /\/ckeditor5\/(lts-v\d+)(?:\/|$)/.test( `/${ BASE_PATH }` )
515
515
  },
516
516
  projectConfig: config,
517
517
  canonicalUrlBeginning: getCanonicalBeginning( { config, options, hexoManager } )
@@ -20,7 +20,7 @@ html(lang='en')
20
20
 
21
21
  body.l-layout.l-layout--muted.js-layout
22
22
  +header-nightly-info
23
- +header-legacy-warning
23
+ +header-lts-info
24
24
  +header-promobar
25
25
  +header({ className: 'l-layout__header', withChooseProject: false })
26
26
 
@@ -37,6 +37,8 @@ mixin apiHeader( config )
37
37
  +checkbox({ id:'f7', name: 'name4', className: 'js-api-filter-checkbox', label: 'Internal', value: 'internal', expanded: true })
38
38
  +dropdown-item({ narrow: true })
39
39
  +checkbox({ id:'f5', name: 'name7', className: 'js-api-filter-checkbox', label: 'Deprecated', value: 'deprecated', expanded: true, checked: true })
40
+ +dropdown-item({ narrow: true })
41
+ +checkbox({ id:'f8', name: 'name8', className: 'js-api-filter-checkbox', label: 'Experimental', value: 'experimental', expanded: true, checked: true })
40
42
 
41
43
  +button({
42
44
  icon: 'unordered-list',
@@ -0,0 +1,6 @@
1
+ mixin badgeTag( badge, options = {} )
2
+ - const iconNameByBadgeText = { private: 'lock', observable: 'eye', experimental: 'experiment' };
3
+ - const iconName = iconNameByBadgeText[ badge.text ] || null;
4
+ - const linkTo = badge && badge.linkTo ? badge.linkTo : false;
5
+ - const className = [ options.className || '', linkTo ? 'tag--link' : '', badge.text == 'experimental' ? 'tag--experimental' : '' ].filter( Boolean ).join( ' ' );
6
+ +tag({ label: badge.text, icon: iconName, iconSize: options.iconSize || 'base', href: linkTo, className })
@@ -1,5 +1,6 @@
1
1
  include ./_link-or-text.pug
2
2
  include ./_badge.pug
3
+ include ./_badge-tag.pug
3
4
  include ./_dev-names.pug
4
5
  include ./_api-see-source.pug
5
6
  include ../../_components/icon/index
@@ -22,10 +23,7 @@ mixin classItem( item )
22
23
  if isNonEmptyArray( item.badges )
23
24
  +tagWrapper()
24
25
  each badge in item.badges
25
- - const iconName = badge.text == 'private' ? 'lock' : badge.text == 'observable' ? 'eye' : null;
26
- - const linkTo = badge && badge.linkTo ? badge.linkTo : null;
27
- - const linkClassName = linkTo ? 'tag--link' : '';
28
- +tag({ label: badge.text, icon: iconName, iconSize: 'sm', href: linkTo, className: linkClassName })
26
+ +badgeTag( badge, { iconSize: 'sm' } )
29
27
 
30
28
  +devNames( item.longname )
31
29
 
@@ -1,4 +1,5 @@
1
1
  include _badge
2
+ include _badge-tag.pug
2
3
  include _type
3
4
  include _params
4
5
  include _return
@@ -52,10 +53,7 @@ mixin method( met )
52
53
  if isNonEmptyArray( met.badges )
53
54
  +tagWrapper()
54
55
  each badge in met.badges
55
- - const iconName = badge.text == 'private' ? 'lock' : badge.text == 'observable' ? 'eye' : null;
56
- - const linkTo = badge && badge.linkTo ? badge.linkTo : null;
57
- - const linkClassName = linkTo ? 'tag--link' : '';
58
- +tag({ label: badge.text, icon: iconName, iconSize: 'sm', href: linkTo, className: linkClassName })
56
+ +badgeTag( badge, { iconSize: 'sm' } )
59
57
 
60
58
  +devNames( met.longname, met.label )
61
59
 
@@ -1,6 +1,7 @@
1
1
  include _params
2
2
  include ./_type.pug
3
3
  include ./_badge.pug
4
+ include ./_badge-tag.pug
4
5
  include ./_dev-names.pug
5
6
  include ./_api-see-source.pug
6
7
  include ./_related.pug
@@ -26,10 +27,7 @@ mixin property( prop )
26
27
  if isNonEmptyArray( prop.badges )
27
28
  +tagWrapper()
28
29
  each badge in prop.badges
29
- - const iconName = badge.text == 'private' ? 'lock' : badge.text == 'observable' ? 'eye' : null;
30
- - const linkTo = badge && badge.linkTo ? badge.linkTo : null;
31
- - const linkClassName = linkTo ? 'tag--link' : '';
32
- +tag({ label: badge.text, icon: iconName, iconSize: 'sm', href: linkTo, className: linkClassName })
30
+ +badgeTag( badge, { iconSize: 'sm' } )
33
31
 
34
32
  +devNames( prop.longname )
35
33
 
@@ -54,4 +52,3 @@ mixin property( prop )
54
52
 
55
53
  if isNonEmptyArray( prop.see )
56
54
  +related( prop.see )
57
-
@@ -38,3 +38,7 @@ div( class="notice notice__filter api-props-filter hidden-loading" )
38
38
  input( type="checkbox" id="f5" value="deprecated" )
39
39
  label( for="f5" )
40
40
  span( class="badge badge--red" ) Deprecated
41
+ li
42
+ input( type="checkbox" id="f8" value="experimental" checked )
43
+ label( for="f8" )
44
+ span( class="badge badge--cyan" ) Experimental
@@ -2,4 +2,11 @@
2
2
  .api-subheader {
3
3
  width: 100%;
4
4
  margin-top: var(--spacing-2);
5
+
6
+ &__badges {
7
+ display: flex;
8
+ align-items: center;
9
+ flex-wrap: wrap;
10
+ gap: var(--spacing-2);
11
+ }
5
12
  }
@@ -1,10 +1,14 @@
1
1
  include ../_mixin/_badge
2
+ include ../_mixin/_badge-tag.pug
2
3
 
3
4
  mixin apiSubheader()
4
5
  - const iconName = "api-" + data.kind.toLowerCase();
5
6
  - const iconClassName = "api-icon api-icon--" + data.kind.toLowerCase();
6
7
  - const tagClassName = "tag--api tag--api-" + data.kind.toLowerCase();
8
+ - const experimentalBadge = isNonEmptyArray( data.badges ) ? data.badges.find( badge => badge.text == 'experimental' ) : null;
7
9
 
8
10
  div(class="api-subheader hidden-loading")
9
11
  div.api-subheader__badges
10
12
  +tag({ label: data.kind, icon : iconName, className: tagClassName })
13
+ if experimentalBadge
14
+ +badgeTag( experimentalBadge )
@@ -20,6 +20,18 @@
20
20
  }
21
21
  }
22
22
 
23
+ &--experimental {
24
+ color: var(--color-cyan-600);
25
+ border-color: var(--color-cyan-600);
26
+ background-color: var(--color-cyan-50);
27
+
28
+ &.tag--link {
29
+ &:hover {
30
+ background-color: var(--color-cyan-100);
31
+ }
32
+ }
33
+ }
34
+
23
35
  &__icon {
24
36
 
25
37
  + .tag__text {
@@ -27,6 +27,29 @@
27
27
  color: var(--color-common-white);
28
28
  }
29
29
 
30
+ &--lts {
31
+ --a11y-focus-ring-color: color-mix(in srgb, var(--color-cyan-500) 40%, transparent);
32
+ --a11y-focus-border-color: var(--color-common-white);
33
+
34
+ background-color: var(--color-cyan-500);
35
+ color: var(--color-common-white);
36
+ }
37
+
38
+ &--warning,
39
+ &--lts {
40
+ font-size: var(--font-size-sm);
41
+
42
+ .c-icon-message {
43
+ line-height: 1;
44
+ }
45
+
46
+ .c-icon-message__content {
47
+ flex: none;
48
+ display: inline-flex;
49
+ align-items: center;
50
+ }
51
+ }
52
+
30
53
  &--info-gradient {
31
54
  background: linear-gradient(240deg, var(--color-info-800), var(--color-info-500));
32
55
  color: var(--color-common-white);
@@ -37,6 +60,42 @@
37
60
  }
38
61
  }
39
62
 
63
+ .b-prose a,
64
+ .b-link {
65
+ color: inherit;
66
+ text-decoration-color: currentColor;
67
+
68
+ &:hover,
69
+ &:focus,
70
+ &:active {
71
+ color: inherit;
72
+ text-decoration-color: currentColor;
73
+ }
74
+ }
75
+
76
+ &__label {
77
+ flex-shrink: 0;
78
+ padding: 0.15rem 0.55rem;
79
+ border: 1px solid currentColor;
80
+ border-radius: 999px;
81
+ background-color: color-mix(in srgb, currentColor 12%, transparent);
82
+ font-size: var(--font-size-xs);
83
+ font-weight: var(--font-weight-bold);
84
+ letter-spacing: 0.08em;
85
+ text-transform: uppercase;
86
+ }
87
+
88
+ &__label-text {
89
+ font-weight: inherit;
90
+ }
91
+
92
+ &__message {
93
+ display: flex;
94
+ flex-wrap: wrap;
95
+ align-items: center;
96
+ font-weight: var(--font-weight-medium);
97
+ }
98
+
40
99
  @include mobile {
41
100
  gap: var(--spacing-2);
42
101
  justify-content: flex-start;
@@ -0,0 +1,69 @@
1
+ mixin header-lts-info()
2
+ +preload-svg-spritesheet-icon({ icon: 'exclamation-circle' })
3
+ if isLts
4
+ -
5
+ const latestUrl = `${ page.canonicalUrlBeginning }${ page.path }`
6
+ .replace('ckeditor5.github.io/', 'ckeditor.com/')
7
+ .replace(/\/ckeditor5\/lts-v\d+(?=\/|$)/, '/ckeditor5/latest')
8
+ .replace(projectLocals.projectVersion, 'latest')
9
+ .replace(/\/+$/, '');
10
+
11
+ +header-bar({ variant: 'lts', ariaLabel: 'LTS documentation' })
12
+ span.c-icon-message.u-font-weight--bold
13
+ span.c-icon-message__icon
14
+ +svg-icon({ icon: 'exclamation-circle', size: 'lg', ariaHidden: true })
15
+ span.c-icon-message__content LTS
16
+
17
+ span.c-header-bar__message
18
+ span You're in the Long Term Support Edition documentation.
19
+ |  
20
+ a.b-link.u-text-decoration--underline(href=latestUrl) Go to the latest version of the standard edition.
21
+
22
+ script.
23
+ (function() {
24
+ document.addEventListener( 'DOMContentLoaded', () => {
25
+ if ( !/\/ckeditor5\/lts-v\d+(?:\/|$)/.test( window.location.pathname ) ) {
26
+ return;
27
+ }
28
+
29
+ if ( document.querySelector( '.c-header-bar--lts' ) ) {
30
+ return;
31
+ }
32
+
33
+ document.querySelector( '.c-header-bar[aria-label="Legacy documentation warning"]' )?.remove();
34
+
35
+ const insertionPoint = document.querySelector( '.js-header-bar, .js-header' );
36
+
37
+ if ( !insertionPoint?.parentNode ) {
38
+ return;
39
+ }
40
+
41
+ const latestUrl = new URL( window.location.href );
42
+
43
+ latestUrl.pathname = latestUrl.pathname.replace( /\/ckeditor5\/lts-v\d+(?=\/|$)/, '/ckeditor5/latest' );
44
+ latestUrl.hash = '';
45
+
46
+ const banner = document.createElement( 'div' );
47
+
48
+ banner.innerHTML = `
49
+ <div class="c-header-bar c-header-bar--lts js-header-bar" role="region" aria-label="LTS documentation">
50
+ <span class="c-icon-message u-font-weight--bold">
51
+ <span class="c-icon-message__icon">
52
+ <svg-icon icon="exclamation-circle" size="lg" aria-hidden="true"></svg-icon>
53
+ </span>
54
+ <span class="c-icon-message__content">LTS</span>
55
+ </span>
56
+ <span class="c-header-bar__message">
57
+ <span>You're in the Long Term Support Edition documentation.&nbsp;</span>
58
+ <a class="b-link u-text-decoration--underline" href="${ latestUrl.toString() }">Go to the latest version of the standard edition.</a>
59
+ </span>
60
+ </div>
61
+ `.trim();
62
+
63
+ insertionPoint.parentNode.insertBefore( banner.firstElementChild, insertionPoint );
64
+
65
+ if ( window.umberto?.Header?.the ) {
66
+ window.umberto.Header.the.dispatchHeaderResizeEvent();
67
+ }
68
+ }, { once: true } );
69
+ })();
@@ -1,4 +1,5 @@
1
1
  mixin header-nightly-info()
2
+ +preload-svg-spritesheet-icon({ icon: 'exclamation-circle' })
2
3
  if isNightly
3
4
  -
4
5
  const stableUrl = `${ page.canonicalUrlBeginning }${ page.path }`
@@ -7,15 +8,13 @@ mixin header-nightly-info()
7
8
  .replace(projectLocals.projectVersion, 'latest')
8
9
  .replace(/\/+$/, '');
9
10
 
10
- +header-bar({ variant: 'info', ariaLabel: 'Nightly build documentation' })
11
- +icon-message({
12
- icon: 'info-circle',
13
- iconSize: 'lg',
14
- className: 'font-weight--bold'
15
- })
16
- | NIGHTLY
11
+ +header-bar({ variant: 'warning', ariaLabel: 'Nightly build documentation' })
12
+ span.c-icon-message.u-font-weight--bold
13
+ span.c-icon-message__icon
14
+ +svg-icon({ icon: 'exclamation-circle', size: 'lg', ariaHidden: true })
15
+ span.c-icon-message__content NIGHTLY
17
16
 
18
- span
17
+ span.c-header-bar__message
19
18
  span You're viewing the nightly build documentation.
20
19
  | &nbsp;
21
- a.b-link.u-color--white.u-text-decoration--underline(href=stableUrl) Go to the stable version.
20
+ a.b-link.u-text-decoration--underline(href=stableUrl) Go to the stable version.
@@ -6,8 +6,8 @@ include ./sidebar/index
6
6
  include ./sidebar-api/index
7
7
  include ./mobile-nav/index
8
8
  include ./header-bar/index
9
- include ./header-legacy-warning/index
10
9
  include ./header-nightly-info/index
10
+ include ./header-lts-info/index
11
11
  include ./header-promobar/index
12
12
  include ./breadcrumbs/index
13
13
  include ./algolia-search/index
@@ -121,9 +121,28 @@ mixin load-kapa-script( kapa )
121
121
  return projectConfig.exampleQuestions.join( ',' );
122
122
  }
123
123
 
124
+ function getSourceGroupIds() {
125
+ const defaultSourceGroupIds = ( kapa.default.sourceGroupIds || [] ).join( ',' );
126
+
127
+ if ( !projectConfig ) {
128
+ return defaultSourceGroupIds;
129
+ }
130
+
131
+ if ( !projectConfig.sourceGroupIds ) {
132
+ return defaultSourceGroupIds;
133
+ }
134
+
135
+ if ( !projectConfig.sourceGroupIds.length ) {
136
+ return defaultSourceGroupIds;
137
+ }
138
+
139
+ return projectConfig.sourceGroupIds.join( ',' );
140
+ }
141
+
124
142
  attributes[ 'data-modal-title' ] = getModalTitle( projectConfig );
125
143
  attributes[ 'data-modal-ask-ai-input-placeholder' ] = getQuestionPlaceholder();
126
144
  attributes[ 'data-modal-example-questions' ] = getExampleQuestions();
145
+ attributes[ 'data-source-group-ids-include' ] = getSourceGroupIds();
127
146
 
128
147
  script&attributes(attributes)
129
148
 
@@ -8,7 +8,7 @@ html(lang='en')
8
8
  include _head/index
9
9
  body.l-layout.js-layout
10
10
  +header-nightly-info
11
- +header-legacy-warning
11
+ +header-lts-info
12
12
  +header-promobar
13
13
  +header({ className: 'l-layout__header' })
14
14
 
@@ -7,7 +7,7 @@ html(lang='en')
7
7
  include _head/index
8
8
  body.l-layout.js-layout
9
9
  +header-nightly-info
10
- +header-legacy-warning
10
+ +header-lts-info
11
11
  +header-promobar
12
12
  +header({ className: 'l-layout__header' })
13
13
 
@@ -8,6 +8,7 @@ html(lang='en')
8
8
 
9
9
  body.l-layout.l-layout--muted.js-layout
10
10
  +header-nightly-info
11
+ +header-lts-info
11
12
  +header-promobar
12
13
  +header({
13
14
  className: 'l-layout__header',
@@ -191,6 +191,8 @@ div
191
191
  +checkbox({ id:'f5', name: 'name7', label: 'Deprecated', value: 'deprecated', expanded: true })
192
192
  +dropdown-item()
193
193
  +checkbox({ id:'f7', name: 'name4', label: 'Internal', value: 'internal', expanded: true })
194
+ +dropdown-item()
195
+ +checkbox({ id:'f8', name: 'name8', label: 'Experimental', value: 'experimental', expanded: true, checked: true })
194
196
  +dropdown-item()
195
197
  +checkbox({ id:'f6', name: 'name3', label: 'Mixed', value: 'mixed', expanded: true })
196
198
 
@@ -38,3 +38,7 @@ div( class="notice notice__filter api-props-filter hidden-loading" )
38
38
  input( type="checkbox" id="f5" value="deprecated" )
39
39
  label( for="f5" )
40
40
  span( class="badge badge--red" ) Deprecated
41
+ li
42
+ input( type="checkbox" id="f8" value="experimental" checked )
43
+ label( for="f8" )
44
+ span( class="badge badge--cyan" ) Experimental
@@ -139,21 +139,23 @@ export class ApiFilter extends BaseComponent {
139
139
  * Sets the initial state of the API filter into local storage when there is no set yet.
140
140
  */
141
141
  setInitialApiFilterState() {
142
- const { apiFilterState } = this.state;
143
142
  const { filterDropdown } = this.elements;
143
+ const apiFilterState = { ...this.state.apiFilterState };
144
144
 
145
- if ( Object.keys( apiFilterState ).length === 0 ) {
146
- Array.from( filterDropdown.querySelectorAll( '.js-api-filter-checkbox input' ) ).forEach( input => {
147
- const isChecked = input.checked;
148
- const filterType = input.value;
145
+ Array.from( filterDropdown.querySelectorAll( '.js-api-filter-checkbox input' ) ).forEach( input => {
146
+ const isChecked = input.checked;
147
+ const filterType = input.value;
149
148
 
150
- this.saveApiFilterState( filterType, isChecked );
151
- } );
152
- }
149
+ if ( apiFilterState[ filterType ] === undefined ) {
150
+ apiFilterState[ filterType ] = isChecked;
151
+ }
152
+ } );
153
+
154
+ window.localStorage.setItem( API_FILTER_LOCAL_STORAGE_KEY, JSON.stringify( apiFilterState ) );
153
155
 
154
156
  this.setState( {
155
- apiFilterState: JSON.parse( localStorage.getItem( API_FILTER_LOCAL_STORAGE_KEY ) ) || {},
156
- activeFilters: Object.keys( this.state.apiFilterState ).filter( key => this.state.apiFilterState[ key ] )
157
+ apiFilterState,
158
+ activeFilters: Object.keys( apiFilterState ).filter( key => apiFilterState[ key ] )
157
159
  } );
158
160
  }
159
161
 
@@ -163,14 +165,21 @@ export class ApiFilter extends BaseComponent {
163
165
  initialApplyApiFilterState() {
164
166
  const { apiFilterState } = this.state;
165
167
  const { filterDropdown } = this.elements;
168
+ const activeFilters = [];
166
169
 
167
170
  Array.from( filterDropdown.querySelectorAll( '.js-api-filter-checkbox input' ) ).forEach( input => {
168
171
  const isChecked = input.checked;
169
172
  const filterType = input.value;
170
173
 
171
174
  input.checked = apiFilterState[ filterType ] !== undefined ? apiFilterState[ filterType ] : isChecked;
175
+
176
+ if ( input.checked ) {
177
+ activeFilters.push( filterType );
178
+ }
172
179
  } );
173
180
 
181
+ this.setState( { activeFilters } );
182
+
174
183
  // this.dispatchInitialFolderStateDoneEvent();
175
184
  }
176
185
 
@@ -124,6 +124,10 @@ export class LinksPrefetch {
124
124
  * @returns Whether the URL should be preloaded
125
125
  */
126
126
  function isPreloadable( url ) {
127
+ if ( !url ) {
128
+ return false;
129
+ }
130
+
127
131
  try {
128
132
  const urlObj = new URL( url );
129
133
  const currentDomain = window.location.hostname;
@@ -51,6 +51,7 @@
51
51
  @include badge( 'red' );
52
52
  @include badge( 'orange' );
53
53
  @include badge( 'purple' );
54
+ @include badge( 'cyan' );
54
55
 
55
56
  /* #357. */
56
57
  &.badge--observable {
@@ -32,6 +32,7 @@ $u-colors: (
32
32
  'badge-red': #E50037,
33
33
  'badge-black': #212121,
34
34
  'badge-purple': #AB47BC,
35
+ 'badge-cyan': hsl(189, 100%, 31%),
35
36
 
36
37
  // Hints
37
38
  'hint-info': #64B5F6,
@@ -14,6 +14,7 @@ export function enableFiltering() {
14
14
  protected: storagePrefix + 'protected',
15
15
  private: storagePrefix + 'private',
16
16
  deprecated: storagePrefix + 'deprecated',
17
+ experimental: storagePrefix + 'experimental',
17
18
  tree: 'filtering:tree'
18
19
  };
19
20
 
@@ -1,11 +0,0 @@
1
- mixin header-legacy-warning()
2
- if isLegacy && (!sitemap || !isNightly)
3
- +header-bar({ variant: 'warning', ariaLabel: 'Legacy documentation warning' })
4
- +icon-message({
5
- icon: 'exclamation-circle',
6
- iconSize: 'lg',
7
- className: 'u-font-weight--bold'
8
- })
9
- | WARNING
10
-
11
- span You're reading the legacy documentation.