umberto 10.6.0 → 10.7.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,47 +1,57 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
- ## [10.6.0](https://github.com/cksource/umberto/compare/v10.5.1...v10.6.0) (June 8, 2026)
4
+ ## [10.7.0](https://github.com/cksource/umberto/compare/v10.6.1...v10.7.0) (July 2, 2026)
5
5
 
6
6
  ### Features
7
7
 
8
- * Improved rendering of complex TypeScript types in API docs.
8
+ * Enabled MCP install menu in the Kapa widget.
9
9
 
10
- Intersection (`A & B`), tuple (`[ key: T, value: U ]`), and conditional (`T extends U ? X : Y`) types now render with their full structure preserved and each reference linkable, instead of collapsing to a generic `object` or `tuple` placeholder. Optional tuple elements display the trailing `?` from the source. The `infer X` keyword inside conditionals renders as such and no longer triggers "unsupported type" warnings during the docs build.
10
+ ### Bug fixes
11
11
 
12
- Properties tables on intersection type aliases now list the fields from the inline-literal half including nested cases like `abc: A & { b: T }`, which now exposes `abc.b` as a sub-row, and cases where the inline literal is wrapped in `Partial<...>`, `Readonly<...>`, or `Required<...>`, where the wrapper's modifier is applied to each field. Per-field inline TSDoc comments (`/** ... */`) above each field render their descriptions in the Properties table, where they were previously dropped.
12
+ * Fixed scrolling to a section in the Gloria theme when navigating to an anchor without a full page reload for example, changing the anchor in the address bar, using the browser's back/forward buttons, or opening a link to a section on the current page. The target heading is now offset below the sticky header instead of being hidden beneath it.
13
+ * Moved `chokidar` from `devDependencies` to `dependencies`. It is imported at runtime by the file watcher (`src/tasks/watcher.js`), which ships with the package, so it must be installed for consumers. Previously it resolved only by chance through a hoisted copy in the consumer's tree, which fails when the consumer enables pnpm's `enableGlobalVirtualStore` and the package is linked from the global store, outside the consumer's `node_modules`.
13
14
 
14
15
  ### Other changes
15
16
 
16
- * Updated the GTM integration to use the new server-side setup in both Gloria and legacy Umberto themes.
17
+ * The tabbed content component now sets a `data-tab-label` attribute on each tab panel, carrying the label of its tab. This exposes the label to tools that process the rendered HTML, where it otherwise lives only inside the tab button.
17
18
 
18
19
 
19
- ## [10.5.1](https://github.com/cksource/umberto/compare/v10.5.0...v10.5.1) (May 14, 2026)
20
+ ## [10.6.1](https://github.com/cksource/umberto/compare/v10.6.0...v10.6.1) (June 18, 2026)
20
21
 
21
- ### Bug fixes
22
+ ### Other changes
22
23
 
23
- * Changed the call for feedback title from `<h3>` to `<div>` to prevent it from appearing in the table of contents.
24
+ * Updated homepage link.
25
+ * Restored the web-based Google Tag Manager snippets in both Gloria and legacy Umberto themes.
24
26
 
25
27
 
26
- ## [10.5.0](https://github.com/cksource/umberto/compare/v10.4.1...v10.5.0) (April 14, 2026)
28
+ ## [10.6.0](https://github.com/cksource/umberto/compare/v10.5.1...v10.6.0) (June 8, 2026)
27
29
 
28
30
  ### Features
29
31
 
30
- * Added the Call for feedback component for documentation pages.
32
+ * Improved rendering of complex TypeScript types in API docs.
33
+
34
+ Intersection (`A & B`), tuple (`[ key: T, value: U ]`), and conditional (`T extends U ? X : Y`) types now render with their full structure preserved and each reference linkable, instead of collapsing to a generic `object` or `tuple` placeholder. Optional tuple elements display the trailing `?` from the source. The `infer X` keyword inside conditionals renders as such and no longer triggers "unsupported type" warnings during the docs build.
31
35
 
36
+ Properties tables on intersection type aliases now list the fields from the inline-literal half — including nested cases like `abc: A & { b: T }`, which now exposes `abc.b` as a sub-row, and cases where the inline literal is wrapped in `Partial<...>`, `Readonly<...>`, or `Required<...>`, where the wrapper's modifier is applied to each field. Per-field inline TSDoc comments (`/** ... */`) above each field render their descriptions in the Properties table, where they were previously dropped.
32
37
 
33
- ## [10.4.1](https://github.com/cksource/umberto/compare/v10.4.0...v10.4.1) (April 13, 2026)
38
+ ### Other changes
39
+
40
+ * Updated the GTM integration to use the new server-side setup in both Gloria and legacy Umberto themes.
41
+
42
+
43
+ ## [10.5.1](https://github.com/cksource/umberto/compare/v10.5.0...v10.5.1) (May 14, 2026)
34
44
 
35
45
  ### Bug fixes
36
46
 
37
- * Fixed URL in the dropdown for LTS item to have absolute URL to pass documentation validation.
47
+ * Changed the call for feedback title from `<h3>` to `<div>` to prevent it from appearing in the table of contents.
38
48
 
39
49
 
40
- ## [10.4.0](https://github.com/cksource/umberto/compare/v10.3.0...v10.4.0) (April 9, 2026)
50
+ ## [10.5.0](https://github.com/cksource/umberto/compare/v10.4.1...v10.5.0) (April 14, 2026)
41
51
 
42
52
  ### Features
43
53
 
44
- * Added the LTS edition to the docs product picker.
54
+ * Added the Call for feedback component for documentation pages.
45
55
 
46
56
  ---
47
57
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "umberto",
3
- "version": "10.6.0",
3
+ "version": "10.7.0",
4
4
  "description": "CKSource Documentation builder",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -20,6 +20,7 @@
20
20
  "@minify-html/node": "^0.17.1",
21
21
  "@vscode/markdown-it-katex": "^1.1.2",
22
22
  "babel-loader": "^10.0.0",
23
+ "chokidar": "^4.0.3",
23
24
  "css-select": "^6.0.0",
24
25
  "dom-serializer": "^2.0.0",
25
26
  "domhandler": "^5.0.3",
@@ -70,6 +70,7 @@ mixin tabs({ id: tabsId, ariaLabel = 'Tabbed content', className, align = 'left'
70
70
  aria-labelledby=`${idPrefix}-${ id }-tab`
71
71
  aria-hidden=tab.active ? 'false' : 'true'
72
72
  data-tab-id=id
73
+ data-tab-label=tab.label
73
74
  class={
74
75
  [className]: !!className,
75
76
  'is-spaced': tab.spaced,
@@ -12,8 +12,8 @@ mixin header({ withChooseProject = true, className = '' } = {})
12
12
  .c-header__mobile.l-hide-desktop
13
13
  .c-header__mobile-brand
14
14
  a.b-link.u-display-inline-flex(
15
- title='Navigate to the CKEditor Ecosystem Documentation homepage',
16
- href=relative_url(page.path, 'index.html')
15
+ title='Navigate to the CKEditor homepage',
16
+ href='https://ckeditor.com'
17
17
  )
18
18
  +inline-svg({ file: 'ckeditor-logo', ariaHidden: true, className: 'c-header__brand-logo' })
19
19
 
@@ -39,8 +39,8 @@ mixin header({ withChooseProject = true, className = '' } = {})
39
39
  .c-header__brand-row
40
40
  .c-header__brand-row-selector.u-flex-horizontal.u-align-items-center
41
41
  a.b-link.u-display-inline-flex(
42
- title='Navigate to the CKEditor Ecosystem Documentation homepage',
43
- href=relative_url(page.path, 'index.html')
42
+ title='Navigate to the CKEditor homepage',
43
+ href='https://ckeditor.com'
44
44
  )
45
45
  +inline-svg({ file: 'ckeditor-logo', ariaHidden: true, className: 'c-header__brand-logo' })
46
46
 
@@ -139,11 +139,32 @@ mixin load-kapa-script( kapa )
139
139
  return projectConfig.sourceGroupIds.join( ',' );
140
140
  }
141
141
 
142
+ function getMcpServerUrl() {
143
+ const defaultMcpServerUrl = kapa.default.mcpServerUrl || null;
144
+
145
+ if ( !projectConfig ) {
146
+ return defaultMcpServerUrl;
147
+ }
148
+
149
+ if ( !projectConfig.mcpServerUrl ) {
150
+ return defaultMcpServerUrl;
151
+ }
152
+
153
+ return projectConfig.mcpServerUrl;
154
+ }
155
+
142
156
  attributes[ 'data-modal-title' ] = getModalTitle( projectConfig );
143
157
  attributes[ 'data-modal-ask-ai-input-placeholder' ] = getQuestionPlaceholder();
144
158
  attributes[ 'data-modal-example-questions' ] = getExampleQuestions();
145
159
  attributes[ 'data-source-group-ids-include' ] = getSourceGroupIds();
146
160
 
161
+ const mcpServerUrl = getMcpServerUrl();
162
+
163
+ if ( mcpServerUrl ) {
164
+ attributes[ 'data-mcp-enabled' ] = 'true';
165
+ attributes[ 'data-mcp-server-url' ] = mcpServerUrl;
166
+ }
167
+
147
168
  script&attributes(attributes)
148
169
 
149
170
  style#custom_kapa_styles
@@ -1,4 +1,4 @@
1
1
  mixin google-tag-manager-noscript()
2
2
  if googletagmanager && googletagmanager.gtm
3
3
  noscript
4
- iframe( src='https://load.site.ckeditor.com/ns.html?id=' + googletagmanager.gtm height='0' width='0' style='display:none;visibility:hidden' )
4
+ iframe( src='https://www.googletagmanager.com/ns.html?id=' + googletagmanager.gtm height='0' width='0' style='display:none;visibility:hidden' )
@@ -1,38 +1,14 @@
1
1
  if googletagmanager
2
2
  script.
3
3
  if (!{ JSON.stringify(googletagmanager.domains) }.indexOf(location.hostname) !== -1) {
4
- !function() {
5
- "use strict";
6
- function l(e) { for (var t = e, r = 0, n = document.cookie.split(";"); r < n.length; r++) { var o = n[r].split("="); if (o[0].trim() === t) return o[1]; } }
7
- function s(e) { return localStorage.getItem(e); }
8
- function u(e) { return window[e]; }
9
- function A(e, t) { e = document.querySelector(e); return t ? null == e ? void 0 : e.getAttribute(t) : null == e ? void 0 : e.textContent; }
10
- var e = window, t = document, r = "script", n = "dataLayer",
11
- o = "https://site.ckeditor.com",
12
- a = "https://load.site.ckeditor.com",
13
- i = "9ao1wxbphqbpj",
14
- c = "dy7f4ho=AxRfNjw9TzMsIzFHJFYURVheU1cMHl8bARUGGBwfEFYJHw8%3D",
15
- g = "cookie", v = "_stape_uid", E = "", d = !1;
16
- try {
17
- var d = !!g && (m = navigator.userAgent, !!(m = new RegExp("Version/([0-9._]+)(.*Mobile)?.*Safari.*").exec(m))) && 16.4 <= parseFloat(m[1]),
18
- f = "stapeUserId" === g,
19
- I = d && !f
20
- ? function(e, t, r) {
21
- void 0 === t && (t = "");
22
- var n = { cookie: l, localStorage: s, jsVariable: u, cssSelector: A }, t = Array.isArray(t) ? t : [t];
23
- if (e && n[e]) { for (var o = n[e], a = 0, i = t; a < i.length; a++) { var c = i[a], c = r ? o(c, r) : o(c); if (c) return c; } }
24
- else { console.warn("invalid uid source", e); }
25
- }(g, v, E)
26
- : void 0;
27
- d = d && (!!I || f);
28
- } catch(e) { console.error(e); }
29
- var m = e,
30
- g = (m[n] = m[n] || [], m[n].push({ "gtm.start": (new Date).getTime(), event: "gtm.js" }), t.getElementsByTagName(r)[0]),
31
- v = I ? "&bi=" + encodeURIComponent(I) : "",
32
- E = t.createElement(r),
33
- f = (d && (i = 8 < i.length ? i.replace(/([a-z]{8}$)/, "kp$1") : "kp" + i), !d && a ? a : o);
34
- E.async = !0;
35
- E.src = f + "/" + i + ".js?" + c + v;
36
- null != (e = g.parentNode) && e.insertBefore(E, g);
37
- }();
4
+ (function (w, d, s, l, i) {
5
+ w[l] = w[l] || [];
6
+ w[l].push({'gtm.start': new Date().getTime(), event: 'gtm.js'});
7
+ var f = d.getElementsByTagName(s)[0],
8
+ j = d.createElement(s),
9
+ dl = l != 'dataLayer' ? '&l=' + l : '';
10
+ j.async = true;
11
+ j.src = 'https://www.googletagmanager.com/gtm.js?id=' + i + dl;
12
+ f.parentNode.insertBefore(j, f);
13
+ })( window, document, 'script', 'dataLayer', '!{ googletagmanager.gtm }' );
38
14
  }
@@ -1,38 +1,14 @@
1
1
  if googletagmanager
2
2
  script.
3
3
  if (!{ JSON.stringify(googletagmanager.domains) }.indexOf(location.hostname) !== -1) {
4
- !function() {
5
- "use strict";
6
- function l(e) { for (var t = e, r = 0, n = document.cookie.split(";"); r < n.length; r++) { var o = n[r].split("="); if (o[0].trim() === t) return o[1]; } }
7
- function s(e) { return localStorage.getItem(e); }
8
- function u(e) { return window[e]; }
9
- function A(e, t) { e = document.querySelector(e); return t ? null == e ? void 0 : e.getAttribute(t) : null == e ? void 0 : e.textContent; }
10
- var e = window, t = document, r = "script", n = "dataLayer",
11
- o = "https://site.ckeditor.com",
12
- a = "https://load.site.ckeditor.com",
13
- i = "9ao1wxbphqbpj",
14
- c = "dy7f4ho=AxRfNjw9TzMsIzFHJFYURVheU1cMHl8bARUGGBwfEFYJHw8%3D",
15
- g = "cookie", v = "_stape_uid", E = "", d = !1;
16
- try {
17
- var d = !!g && (m = navigator.userAgent, !!(m = new RegExp("Version/([0-9._]+)(.*Mobile)?.*Safari.*").exec(m))) && 16.4 <= parseFloat(m[1]),
18
- f = "stapeUserId" === g,
19
- I = d && !f
20
- ? function(e, t, r) {
21
- void 0 === t && (t = "");
22
- var n = { cookie: l, localStorage: s, jsVariable: u, cssSelector: A }, t = Array.isArray(t) ? t : [t];
23
- if (e && n[e]) { for (var o = n[e], a = 0, i = t; a < i.length; a++) { var c = i[a], c = r ? o(c, r) : o(c); if (c) return c; } }
24
- else { console.warn("invalid uid source", e); }
25
- }(g, v, E)
26
- : void 0;
27
- d = d && (!!I || f);
28
- } catch(e) { console.error(e); }
29
- var m = e,
30
- g = (m[n] = m[n] || [], m[n].push({ "gtm.start": (new Date).getTime(), event: "gtm.js" }), t.getElementsByTagName(r)[0]),
31
- v = I ? "&bi=" + encodeURIComponent(I) : "",
32
- E = t.createElement(r),
33
- f = (d && (i = 8 < i.length ? i.replace(/([a-z]{8}$)/, "kp$1") : "kp" + i), !d && a ? a : o);
34
- E.async = !0;
35
- E.src = f + "/" + i + ".js?" + c + v;
36
- null != (e = g.parentNode) && e.insertBefore(E, g);
37
- }();
4
+ (function (w, d, s, l, i) {
5
+ w[l] = w[l] || [];
6
+ w[l].push({'gtm.start': new Date().getTime(), event: 'gtm.js'});
7
+ var f = d.getElementsByTagName(s)[0],
8
+ j = d.createElement(s),
9
+ dl = l != 'dataLayer' ? '&l=' + l : '';
10
+ j.async = true;
11
+ j.src = 'https://www.googletagmanager.com/gtm.js?id=' + i + dl;
12
+ f.parentNode.insertBefore(j, f);
13
+ })( window, document, 'script', 'dataLayer', '!{ googletagmanager.gtm }' );
38
14
  }
@@ -4,7 +4,7 @@ if googletagmanager && googletagmanager.gtm
4
4
 
5
5
  //- Google Tag Manager (noscript)
6
6
  noscript
7
- iframe( src="https://load.site.ckeditor.com/ns.html?id=" + googletagmanager.gtm height="0" width="0" style="display:none;visibility:hidden" )
7
+ iframe( src="https://www.googletagmanager.com/ns.html?id=" + googletagmanager.gtm height="0" width="0" style="display:none;visibility:hidden" )
8
8
  //- End Google Tag Manager (noscript)
9
9
 
10
10
  header.top( data-project= projectLocals ? projectLocals.projectSlug : undefined )
@@ -5,6 +5,10 @@
5
5
  height: 100%;
6
6
  overscroll-behavior-y: none; /* Prevent vertical bounce, allow horizontal trackpad gestures. */
7
7
  overflow-x: hidden;
8
+
9
+ /* Offset native fragment navigation (anchor jumps, pasting a hash URL) below the
10
+ sticky header. `--header-height` is kept up to date by the header module. */
11
+ scroll-padding-top: calc(var(--header-height, 0px) + 32px);
8
12
  }
9
13
 
10
14
  body {
@@ -18,6 +18,10 @@ export class HashLink extends BaseComponent {
18
18
  // Handle initial hash link navigation if present in the URL
19
19
  this._handleInitialHash();
20
20
 
21
+ // Handle hash changes on an already-loaded page (e.g. editing the hash in the
22
+ // address bar or browser back/forward navigation).
23
+ this._handleHashChange();
24
+
21
25
  // Add click event listener to handle hash link clicks
22
26
  document.addEventListener( 'click', event => {
23
27
  const target = event.target.closest( 'a[href^="#"]' );
@@ -57,4 +61,30 @@ export class HashLink extends BaseComponent {
57
61
  } );
58
62
  }, { once: true } );
59
63
  }
64
+
65
+ /**
66
+ * Handles hash changes that happen without a full page reload, e.g. editing the
67
+ * hash in the address bar or navigating with the browser's back/forward buttons.
68
+ *
69
+ * In these cases the browser performs native fragment navigation, which scrolls
70
+ * the target to the very top of the viewport without accounting for the sticky
71
+ * header. Re-running the scroll with the proper offset keeps the heading visible.
72
+ *
73
+ * Clicking an `a[href^="#"]` link does not trigger this handler, because the click
74
+ * handler updates the URL via `history.pushState()`, which does not emit a
75
+ * `hashchange` event.
76
+ *
77
+ * @private
78
+ */
79
+ static _handleHashChange() {
80
+ window.addEventListener( 'hashchange', () => {
81
+ if ( !window.location.hash ) {
82
+ return;
83
+ }
84
+
85
+ scrollToHash( window.location.hash.substring( 1 ), {
86
+ pushState: false
87
+ } );
88
+ } );
89
+ }
60
90
  }
@@ -83,6 +83,11 @@ export class Header extends BaseComponent {
83
83
  this.element.classList.toggle( 'is-on-top-overlay', state.isOnOverlay );
84
84
  this.dispatchHeaderResizeEvent();
85
85
  } else if ( prevState?.height !== state.height && state.height !== null ) {
86
+ // Expose the header height on the document root so that native fragment navigation
87
+ // (e.g. pasting a URL with a hash, which fires no `hashchange` or `load` event) can
88
+ // offset the target below the sticky header via `scroll-padding-top`. See `_base.scss`.
89
+ document.documentElement.style.setProperty( '--header-height', `${ state.height }px` );
90
+
86
91
  this.dispatchHeaderResizeEvent();
87
92
  }
88
93
  }
@@ -128,7 +133,7 @@ export class Header extends BaseComponent {
128
133
  }
129
134
 
130
135
  /**
131
- * Sets the header height variable on the body element.
136
+ * Tracks the header height and updates the component state.
132
137
  */
133
138
  _trackHeight() {
134
139
  this.setState( {