umberto 10.4.0 → 10.5.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 +14 -14
- package/package.json +1 -1
- package/scripts/filter/after-post-render/gloria/validate-call-for-feedback-once.cjs +24 -0
- package/scripts/filter/before-post-render/gloria/prerender-xml-pug-components.cjs +10 -0
- package/scripts/utils/count-call-for-feedback-blocks.cjs +27 -0
- package/src/tasks/build-documentation.js +1 -2
- package/themes/umberto/layout/gloria/_components/call-for-feedback/_style.scss +47 -0
- package/themes/umberto/layout/gloria/_components/call-for-feedback/index.pug +16 -0
- package/themes/umberto/layout/gloria/_components/dropdown/index.pug +2 -3
- package/themes/umberto/layout/gloria/_components/index.pug +1 -0
- package/themes/umberto/layout/gloria/_modules/header/nav-project-select-dropdown.pug +31 -11
- package/themes/umberto/layout/gloria/_modules/mobile-nav/index.pug +32 -13
- package/themes/umberto/layout/gloria/_modules/mobile-nav/slide-parts.pug +3 -5
- package/themes/umberto/src/gloria/css/components/_index.scss +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
Changelog
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
+
## [10.5.0](https://github.com/cksource/umberto/compare/v10.4.1...v10.5.0) (April 14, 2026)
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* Added the Call for feedback component for documentation pages.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## [10.4.1](https://github.com/cksource/umberto/compare/v10.4.0...v10.4.1) (April 13, 2026)
|
|
12
|
+
|
|
13
|
+
### Bug fixes
|
|
14
|
+
|
|
15
|
+
* Fixed URL in the dropdown for LTS item to have absolute URL to pass documentation validation.
|
|
16
|
+
|
|
17
|
+
|
|
4
18
|
## [10.4.0](https://github.com/cksource/umberto/compare/v10.3.0...v10.4.0) (April 9, 2026)
|
|
5
19
|
|
|
6
20
|
### Features
|
|
@@ -33,20 +47,6 @@ Changelog
|
|
|
33
47
|
* Added cyan LTS banner at the top of the page on LTS branch.
|
|
34
48
|
* Updated the colour of nightly banner to yellow.
|
|
35
49
|
|
|
36
|
-
|
|
37
|
-
## [10.1.4](https://github.com/cksource/umberto/compare/v10.1.3...v10.1.4) (March 20, 2026)
|
|
38
|
-
|
|
39
|
-
### Other changes
|
|
40
|
-
|
|
41
|
-
* 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.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
## [10.1.3](https://github.com/cksource/umberto/compare/v10.1.2...v10.1.3) (March 10, 2026)
|
|
45
|
-
|
|
46
|
-
### Bug fixes
|
|
47
|
-
|
|
48
|
-
* 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.
|
|
49
|
-
|
|
50
50
|
---
|
|
51
51
|
|
|
52
52
|
To see all releases, visit the [release page](https://github.com/cksource/umberto/releases).
|
package/package.json
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2017-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const countCallForFeedbackBlocks = require( '../../../utils/count-call-for-feedback-blocks.cjs' );
|
|
9
|
+
|
|
10
|
+
hexo.extend.filter.register( 'after_post_render', page => {
|
|
11
|
+
if ( page.projectTheme !== 'gloria' || typeof page.content !== 'string' ) {
|
|
12
|
+
return page;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const count = countCallForFeedbackBlocks( page.content );
|
|
16
|
+
|
|
17
|
+
if ( count > 1 ) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`Call for feedback: expected at most one <ck:call-for-feedback /> per page, found ${ count } in ${ page.source }.`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return page;
|
|
24
|
+
}, 47 );
|
|
@@ -144,6 +144,16 @@ const PATTERN_ELEMENTS = [
|
|
|
144
144
|
]
|
|
145
145
|
},
|
|
146
146
|
|
|
147
|
+
// Call for feedback
|
|
148
|
+
{
|
|
149
|
+
pattern: /^(?:ck:)?call-for-feedback$/,
|
|
150
|
+
mixinName: 'call-for-feedback',
|
|
151
|
+
allowMarkdownContent: false,
|
|
152
|
+
requires: [
|
|
153
|
+
'_components/call-for-feedback/index'
|
|
154
|
+
]
|
|
155
|
+
},
|
|
156
|
+
|
|
147
157
|
// Labels
|
|
148
158
|
{
|
|
149
159
|
pattern: /(?:ck:)?snippet-footer$/,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2017-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const { parseDocument } = require( 'htmlparser2' );
|
|
9
|
+
const { selectAll } = require( 'css-select' );
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Counts rendered Call for feedback blocks (`aside.c-call-for-feedback`) in HTML.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} html
|
|
15
|
+
* @returns {number}
|
|
16
|
+
*/
|
|
17
|
+
function countCallForFeedbackBlocks( html ) {
|
|
18
|
+
if ( !html || typeof html !== 'string' ) {
|
|
19
|
+
return 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const doc = parseDocument( html );
|
|
23
|
+
|
|
24
|
+
return selectAll( 'aside.c-call-for-feedback', doc ).length;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = countCallForFeedbackBlocks;
|
|
@@ -163,8 +163,7 @@ export const buildDocumentation = options => {
|
|
|
163
163
|
{ name: 'CKEditor 5', slug: 'ckeditor5' },
|
|
164
164
|
{
|
|
165
165
|
name: 'CKEditor 5 LTS Edition',
|
|
166
|
-
|
|
167
|
-
channel: 'lts-v47',
|
|
166
|
+
href: 'https://ckeditor.com/docs/ckeditor5/lts-v47/index.html',
|
|
168
167
|
rel: 'nofollow'
|
|
169
168
|
},
|
|
170
169
|
{ name: 'Cloud Services', slug: 'cs' },
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
.c-call-for-feedback {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
align-items: flex-start;
|
|
6
|
+
gap: var(--spacing-2);
|
|
7
|
+
margin: var(--spacing-7) 0 0;
|
|
8
|
+
padding: var(--spacing-4);
|
|
9
|
+
border: 1.5px solid var(--color-primary-300);
|
|
10
|
+
border-radius: var(--radius-1);
|
|
11
|
+
background: var(--color-common-white);
|
|
12
|
+
|
|
13
|
+
&__title {
|
|
14
|
+
margin: 0;
|
|
15
|
+
font-family: var(--font-family-heading);
|
|
16
|
+
font-size: var(--font-size-lg);
|
|
17
|
+
line-height: var(--line-height-sm);
|
|
18
|
+
font-weight: var(--font-weight-bold);
|
|
19
|
+
color: var(--color-primary-500);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
&__text {
|
|
23
|
+
margin: 0;
|
|
24
|
+
font-family: var(--font-family-text);
|
|
25
|
+
font-weight: var(--font-weight-thin);
|
|
26
|
+
font-size: var(--font-size-base);
|
|
27
|
+
line-height: var(--line-height-base);
|
|
28
|
+
color: var(--color-text-primary);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
&__link {
|
|
32
|
+
color: var(--color-primary-500);
|
|
33
|
+
text-decoration: underline;
|
|
34
|
+
text-decoration-color: var(--color-primary-300);
|
|
35
|
+
text-underline-offset: 0.15em;
|
|
36
|
+
|
|
37
|
+
&:hover {
|
|
38
|
+
text-decoration-color: var(--color-primary-500);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
&:focus-visible,
|
|
42
|
+
&:active {
|
|
43
|
+
color: var(--color-primary-600);
|
|
44
|
+
text-decoration-color: var(--color-primary-600);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//- Call for feedback component
|
|
2
|
+
//-
|
|
3
|
+
//- Examples:
|
|
4
|
+
//- +call-for-feedback()
|
|
5
|
+
//-
|
|
6
|
+
//- <ck:call-for-feedback />
|
|
7
|
+
//-
|
|
8
|
+
//- <call-for-feedback />
|
|
9
|
+
mixin call-for-feedback()
|
|
10
|
+
//- Stable id: at most one block per page (build fails if more); see validate-call-for-feedback-once.cjs.
|
|
11
|
+
aside.c-call-for-feedback( role='complementary' aria-labelledby='call-for-feedback-title' )
|
|
12
|
+
h3.c-call-for-feedback__title#call-for-feedback-title Call for feedback
|
|
13
|
+
p.c-call-for-feedback__text
|
|
14
|
+
| Have an idea for future improvements? We'd love to hear from you! Share your thoughts and suggestions with us through our
|
|
15
|
+
a.c-call-for-feedback__link( href='https://ckeditor.com/contact/' ) contact form
|
|
16
|
+
| .
|
|
@@ -52,7 +52,7 @@ mixin dropdown({ id: dropdownId, className, preferredDirection, contentId, updat
|
|
|
52
52
|
//- +dropdown-item({ label: 'Item with icon', icon: 'github' })
|
|
53
53
|
//- +dropdown-item({ label: 'Custom link', href: '/custom-link', rel: 'nofollow' })
|
|
54
54
|
//- +dropdown-item({ label: 'With ID', itemId: 'special-item' })
|
|
55
|
-
mixin dropdown-item({ itemId, href, rel, active = false, narrow, label, icon, value,
|
|
55
|
+
mixin dropdown-item({ itemId, href, rel, active = false, narrow, label, icon, value, projectKey } = {})
|
|
56
56
|
- const tag = href ? 'a' : 'div';
|
|
57
57
|
|
|
58
58
|
li.c-dropdown__list-item
|
|
@@ -65,8 +65,7 @@ mixin dropdown-item({ itemId, href, rel, active = false, narrow, label, icon, va
|
|
|
65
65
|
'c-dropdown__item--narrow': !!narrow
|
|
66
66
|
},
|
|
67
67
|
data-item-id=itemId,
|
|
68
|
-
data-project-
|
|
69
|
-
data-project-channel=projectChannel,
|
|
68
|
+
data-project-key=projectKey,
|
|
70
69
|
data-value=value,
|
|
71
70
|
tabindex='-1'
|
|
72
71
|
)
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
mixin nav-project-select-dropdown()
|
|
2
2
|
-
|
|
3
|
-
const
|
|
3
|
+
const getProjectKey = pathname => {
|
|
4
|
+
const segments = pathname.split( '/' ).filter( Boolean );
|
|
5
|
+
|
|
6
|
+
if ( segments[ 0 ] === 'docs' ) {
|
|
7
|
+
segments.shift();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const [ slug, rawChannel ] = segments;
|
|
11
|
+
const channel = /^\d+\.\d+\.\d+$/.test( rawChannel || '' ) ? 'latest' : ( rawChannel || 'latest' );
|
|
12
|
+
|
|
13
|
+
return slug ? `/${ slug }/${ channel }` : '';
|
|
14
|
+
};
|
|
15
|
+
const getProjectHref = project => project.href || relative_url( page.path, `${ project.slug }/${ project.channel || 'latest' }/index.html` );
|
|
16
|
+
const getItemProjectKey = project => getProjectKey( project.href ? new URL( project.href ).pathname : `/${ project.slug }/${ project.channel || 'latest' }/index.html` );
|
|
4
17
|
const currentProject = projectLocals && quickNavigationProjects && quickNavigationProjects.find(
|
|
5
18
|
project => project.slug === projectLocals.projectSlug && !project.channel
|
|
6
19
|
);
|
|
@@ -16,29 +29,36 @@ mixin nav-project-select-dropdown()
|
|
|
16
29
|
( project.channel || 'latest' ) === 'latest',
|
|
17
30
|
href: getProjectHref( project ),
|
|
18
31
|
rel: project.rel,
|
|
19
|
-
|
|
20
|
-
projectChannel: project.channel || 'latest'
|
|
32
|
+
projectKey: getItemProjectKey( project )
|
|
21
33
|
})
|
|
22
34
|
|
|
23
35
|
if currentProject
|
|
24
36
|
script.
|
|
25
37
|
(function() {
|
|
26
38
|
const script = document.currentScript;
|
|
39
|
+
const getProjectKey = pathname => {
|
|
40
|
+
const segments = pathname.split( '/' ).filter( Boolean );
|
|
41
|
+
|
|
42
|
+
if ( segments[ 0 ] === 'docs' ) {
|
|
43
|
+
segments.shift();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const [ slug, rawChannel ] = segments;
|
|
47
|
+
const channel = /^\d+\.\d+\.\d+$/.test( rawChannel || '' ) ? 'latest' : ( rawChannel || 'latest' );
|
|
48
|
+
|
|
49
|
+
return slug ? `/${ slug }/${ channel }` : '';
|
|
50
|
+
};
|
|
27
51
|
|
|
28
52
|
document.addEventListener( 'DOMContentLoaded', () => {
|
|
29
53
|
const dropdown = script?.previousElementSibling;
|
|
30
|
-
const
|
|
31
|
-
const segments = window.location.pathname.split( '/' ).filter( Boolean );
|
|
32
|
-
const slugIndex = segments.indexOf( currentProjectSlug );
|
|
54
|
+
const currentProjectKey = getProjectKey( window.location.pathname );
|
|
33
55
|
|
|
34
|
-
if ( !dropdown ||
|
|
56
|
+
if ( !dropdown || !currentProjectKey ) {
|
|
35
57
|
return;
|
|
36
58
|
}
|
|
37
59
|
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
const items = Array.from( dropdown.querySelectorAll( '.c-dropdown__item[data-project-slug][data-project-channel]' ) );
|
|
41
|
-
const activeItem = items.find( item => item.dataset.projectSlug === currentProjectSlug && item.dataset.projectChannel === currentChannel );
|
|
60
|
+
const items = Array.from( dropdown.querySelectorAll( '.c-dropdown__item[data-project-key]' ) );
|
|
61
|
+
const activeItem = items.find( item => item.dataset.projectKey === currentProjectKey );
|
|
42
62
|
|
|
43
63
|
if ( !activeItem ) {
|
|
44
64
|
return;
|
|
@@ -3,7 +3,20 @@ include ./slide-parts
|
|
|
3
3
|
mixin mobile-nav()
|
|
4
4
|
-
|
|
5
5
|
const currentProject = projectLocals && projectsData.find( project => project.slug === projectLocals.projectSlug );
|
|
6
|
-
const
|
|
6
|
+
const getProjectKey = pathname => {
|
|
7
|
+
const segments = pathname.split( '/' ).filter( Boolean );
|
|
8
|
+
|
|
9
|
+
if ( segments[ 0 ] === 'docs' ) {
|
|
10
|
+
segments.shift();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const [ slug, rawChannel ] = segments;
|
|
14
|
+
const channel = /^\d+\.\d+\.\d+$/.test( rawChannel || '' ) ? 'latest' : ( rawChannel || 'latest' );
|
|
15
|
+
|
|
16
|
+
return slug ? `/${ slug }/${ channel }` : '';
|
|
17
|
+
};
|
|
18
|
+
const getProjectHref = project => project.href || relative_url( page.path, `${ project.slug }/${ project.channel || 'latest' }/index.html` );
|
|
19
|
+
const getItemProjectKey = project => getProjectKey( project.href ? new URL( project.href ).pathname : `/${ project.slug }/${ project.channel || 'latest' }/index.html` );
|
|
7
20
|
let initialSlideId = projectLocals ? `project-${projectLocals.projectSlug}` : 'products';
|
|
8
21
|
|
|
9
22
|
// If the page has a category, set the initial slide to that category's slide
|
|
@@ -30,37 +43,43 @@ mixin mobile-nav()
|
|
|
30
43
|
label: project.name,
|
|
31
44
|
active: true,
|
|
32
45
|
targetSlideId: `project-${ project.slug }`,
|
|
33
|
-
|
|
34
|
-
projectChannel: project.channel || 'latest'
|
|
46
|
+
projectKey: getItemProjectKey( project )
|
|
35
47
|
})
|
|
36
48
|
else
|
|
37
49
|
+mobile-nav-item({
|
|
38
50
|
href: getProjectHref( project ),
|
|
39
51
|
label: project.name,
|
|
40
52
|
rel: project.rel,
|
|
41
|
-
|
|
42
|
-
projectChannel: project.channel || 'latest'
|
|
53
|
+
projectKey: getItemProjectKey( project )
|
|
43
54
|
})
|
|
44
55
|
|
|
45
56
|
if currentProject
|
|
46
57
|
script.
|
|
47
58
|
(function() {
|
|
48
59
|
const script = document.currentScript;
|
|
60
|
+
const getProjectKey = pathname => {
|
|
61
|
+
const segments = pathname.split( '/' ).filter( Boolean );
|
|
62
|
+
|
|
63
|
+
if ( segments[ 0 ] === 'docs' ) {
|
|
64
|
+
segments.shift();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const [ slug, rawChannel ] = segments;
|
|
68
|
+
const channel = /^\d+\.\d+\.\d+$/.test( rawChannel || '' ) ? 'latest' : ( rawChannel || 'latest' );
|
|
69
|
+
|
|
70
|
+
return slug ? `/${ slug }/${ channel }` : '';
|
|
71
|
+
};
|
|
49
72
|
|
|
50
73
|
document.addEventListener( 'DOMContentLoaded', () => {
|
|
51
74
|
const mobileNav = script?.previousElementSibling;
|
|
52
|
-
const
|
|
53
|
-
const segments = window.location.pathname.split( '/' ).filter( Boolean );
|
|
54
|
-
const slugIndex = segments.indexOf( currentProjectSlug );
|
|
75
|
+
const currentProjectKey = getProjectKey( window.location.pathname );
|
|
55
76
|
|
|
56
|
-
if ( !mobileNav ||
|
|
77
|
+
if ( !mobileNav || !currentProjectKey ) {
|
|
57
78
|
return;
|
|
58
79
|
}
|
|
59
80
|
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
const items = Array.from( mobileNav.querySelectorAll( '.c-mobile-nav__item[data-project-slug][data-project-channel]' ) );
|
|
63
|
-
const activeItem = items.find( item => item.dataset.projectSlug === currentProjectSlug && item.dataset.projectChannel === currentChannel );
|
|
81
|
+
const items = Array.from( mobileNav.querySelectorAll( '.c-mobile-nav__item[data-project-key]' ) );
|
|
82
|
+
const activeItem = items.find( item => item.dataset.projectKey === currentProjectKey );
|
|
64
83
|
|
|
65
84
|
if ( !activeItem ) {
|
|
66
85
|
return;
|
|
@@ -32,7 +32,7 @@ mixin mobile-nav-heading({ href, label })
|
|
|
32
32
|
iconPosition: 'right',
|
|
33
33
|
})
|
|
34
34
|
|
|
35
|
-
mixin mobile-nav-item({ href, label, rel, targetSlideId, target, active = false,
|
|
35
|
+
mixin mobile-nav-item({ href, label, rel, targetSlideId, target, active = false, projectKey })
|
|
36
36
|
li
|
|
37
37
|
if targetSlideId
|
|
38
38
|
+button({
|
|
@@ -49,15 +49,13 @@ mixin mobile-nav-item({ href, label, rel, targetSlideId, target, active = false,
|
|
|
49
49
|
'aria-controls'=`slide-${targetSlideId}`,
|
|
50
50
|
'aria-expanded'='false',
|
|
51
51
|
'aria-haspopup'='true',
|
|
52
|
-
'data-project-
|
|
53
|
-
'data-project-channel'=projectChannel
|
|
52
|
+
'data-project-key'=projectKey
|
|
54
53
|
)
|
|
55
54
|
else
|
|
56
55
|
a.b-link.c-mobile-nav__item(
|
|
57
56
|
href=href,
|
|
58
57
|
rel=rel,
|
|
59
|
-
data-project-
|
|
60
|
-
data-project-channel=projectChannel,
|
|
58
|
+
data-project-key=projectKey,
|
|
61
59
|
class=active ? 'is-active' : '',
|
|
62
60
|
target=target
|
|
63
61
|
)= label
|