Skip to content

Commit 8742690

Browse files
committed
Add collectStylesheets that allows to pick stylesheets from provided urls.
1 parent 06cf625 commit 8742690

File tree

3 files changed

+142
-0
lines changed

3 files changed

+142
-0
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
3+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
4+
*/
5+
6+
/**
7+
* A helper function for getting concatenated CSS rules from external stylesheets.
8+
*
9+
* @param stylesheets An array of stylesheet paths delivered by the user through the plugin configuration.
10+
*/
11+
export default function collectStylesheets( stylesheets?: Array<string> ): Promise<string> {
12+
if ( !stylesheets ) {
13+
return new Promise( resolve => resolve( '' ) );
14+
}
15+
16+
const styles = [];
17+
18+
for ( const stylesheet of stylesheets ) {
19+
if ( stylesheet === 'EDITOR_STYLES' ) {
20+
styles.push( getEditorStyles() );
21+
22+
continue;
23+
}
24+
25+
styles.push( window.fetch( stylesheet ).then( response => response.text() ) );
26+
}
27+
28+
return Promise.all( styles ).then( values => {
29+
// We want to trim the returned value in case of `[ "", "", "", ... ]`.
30+
return values.join( ' ' ).trim();
31+
} );
32+
}
33+
34+
/**
35+
* A helper function for getting the basic editor content styles for the `.ck-content` class
36+
* and all CSS variables defined in the document.
37+
*/
38+
function getEditorStyles(): string {
39+
const editorStyles = [];
40+
const editorCSSVariables = [];
41+
42+
for ( const styleSheet of Array.from( document.styleSheets ) ) {
43+
const ownerNode = styleSheet.ownerNode as Element;
44+
45+
if ( ownerNode.hasAttribute( 'data-cke' ) ) {
46+
for ( const rule of Array.from( styleSheet.cssRules ) ) {
47+
if ( rule.cssText.indexOf( '.ck-content' ) !== -1 ) {
48+
editorStyles.push( rule.cssText );
49+
} else if ( rule.cssText.indexOf( ':root' ) !== -1 ) {
50+
editorCSSVariables.push( rule.cssText );
51+
}
52+
}
53+
}
54+
}
55+
56+
if ( !editorStyles.length ) {
57+
console.warn(
58+
'The editor stylesheet could not be found in the document. ' +
59+
'Check your webpack config – style-loader should use data-cke=true attribute for the editor stylesheet.'
60+
);
61+
}
62+
63+
// We want to trim the returned value in case of `[ "", "", ... ]`.
64+
return [ ...editorCSSVariables, ...editorStyles ].join( ' ' ).trim();
65+
}

packages/ckeditor5-utils/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ export { default as delay, type DelayedFunc } from './delay.js';
9494
export { default as wait } from './wait.js';
9595
export { default as parseBase64EncodedObject } from './parsebase64encodedobject.js';
9696
export { default as crc32, type CRCData } from './crc32.js';
97+
export { default as collectStylesheets } from './collectstylesheets.js';
9798

9899
export * from './unicode.js';
99100

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/**
2+
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
3+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
4+
*/
5+
6+
/* global document, window, console, Response */
7+
8+
import collectStylesheets from '../src/collectstylesheets.js';
9+
10+
describe( 'collectStylesheets', () => {
11+
const dataCkeStylesheet = document.createElement( 'style' );
12+
dataCkeStylesheet.setAttribute( 'data-cke', true );
13+
dataCkeStylesheet.id = 'data-cke-stylesheet';
14+
dataCkeStylesheet.append( `
15+
:root {
16+
--variable: white;
17+
}
18+
.ck-content { color: black }
19+
.some-styles { color: red }
20+
` );
21+
22+
const stylesheet = document.createElement( 'style' );
23+
stylesheet.id = 'stylesheet';
24+
stylesheet.append( 'h2 { color: black }' );
25+
26+
before( async () => {
27+
for ( const style of document.styleSheets ) {
28+
document.head.removeChild( style.ownerNode );
29+
}
30+
} );
31+
32+
beforeEach( async () => {
33+
document.head.appendChild( dataCkeStylesheet );
34+
document.head.appendChild( stylesheet );
35+
36+
sinon.stub( window, 'fetch' ).callsFake( () => {
37+
const callback = res => res( new Response() );
38+
39+
return new Promise( callback );
40+
} );
41+
} );
42+
43+
afterEach( async () => {
44+
sinon.restore();
45+
46+
dataCkeStylesheet.remove();
47+
stylesheet.remove();
48+
} );
49+
50+
it( 'should not return any styles if no paths to stylesheets provided', async () => {
51+
expect( await collectStylesheets( undefined ) ).to.equal( '' );
52+
} );
53+
54+
it( 'should log into the console when ".ck-content" styles are missing', async () => {
55+
const dataCkeStylesheet = document.getElementById( 'data-cke-stylesheet' );
56+
57+
dataCkeStylesheet.remove();
58+
59+
const consoleSpy = sinon.stub( console, 'warn' );
60+
61+
await collectStylesheets( [ 'EDITOR_STYLES' ] );
62+
63+
sinon.assert.calledOnce( consoleSpy );
64+
} );
65+
66+
it( 'should get ".ck-content" styles when "EDITOR_STYLES" token is provided', async () => {
67+
const consoleSpy = sinon.stub( console, 'warn' );
68+
69+
const styles = await collectStylesheets( [ './foo.css', 'EDITOR_STYLES' ] );
70+
71+
sinon.assert.notCalled( consoleSpy );
72+
73+
expect( styles.length > 0 ).to.be.true;
74+
expect( styles.indexOf( '.ck-content' ) !== -1 ).to.be.true;
75+
} );
76+
} );

0 commit comments

Comments
 (0)