Skip to content

Commit

Permalink
Add collectStylesheets that allows to pick stylesheets from provide…
Browse files Browse the repository at this point in the history
…d urls. (#17752)

Feature (utils): Add a `collectStylesheets` helper function to retrieve stylesheets from the provided URLs.
  • Loading branch information
Mati365 authored Jan 17, 2025
1 parent a8f3527 commit 5cda75f
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 0 deletions.
66 changes: 66 additions & 0 deletions packages/ckeditor5-utils/src/collectstylesheets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/

/**
* @module utils/collectstylesheets
*/

/**
* A helper function for getting concatenated CSS rules from external stylesheets.
*
* @param stylesheets An array of stylesheet paths delivered by the user through the plugin configuration.
*/
export default async function collectStylesheets( stylesheets?: Array<string> ): Promise<string> {
if ( !stylesheets ) {
return '';
}

const results = await Promise.all(
stylesheets.map( async stylesheet => {
if ( stylesheet === 'EDITOR_STYLES' ) {
return getEditorStyles();
}

const response = await window.fetch( stylesheet );

return response.text();
} )
);

return results.join( ' ' ).trim();
}

/**
* A helper function for getting the basic editor content styles for the `.ck-content` class
* and all CSS variables defined in the document.
*/
function getEditorStyles(): string {
const editorStyles = [];
const editorCSSVariables = [];

for ( const styleSheet of Array.from( document.styleSheets ) ) {
const ownerNode = styleSheet.ownerNode as Element;

if ( ownerNode.hasAttribute( 'data-cke' ) ) {
for ( const rule of Array.from( styleSheet.cssRules ) ) {
if ( rule.cssText.indexOf( '.ck-content' ) !== -1 ) {
editorStyles.push( rule.cssText );
} else if ( rule.cssText.indexOf( ':root' ) !== -1 ) {
editorCSSVariables.push( rule.cssText );
}
}
}
}

if ( !editorStyles.length ) {
console.warn(
'The editor stylesheet could not be found in the document. ' +
'Check your webpack config - style-loader should use data-cke=true attribute for the editor stylesheet.'
);
}

// We want to trim the returned value in case of `[ "", "", ... ]`.
return [ ...editorCSSVariables, ...editorStyles ].join( ' ' ).trim();
}
1 change: 1 addition & 0 deletions packages/ckeditor5-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export { default as delay, type DelayedFunc } from './delay.js';
export { default as wait } from './wait.js';
export { default as parseBase64EncodedObject } from './parsebase64encodedobject.js';
export { default as crc32, type CRCData } from './crc32.js';
export { default as collectStylesheets } from './collectstylesheets.js';

export * from './unicode.js';

Expand Down
109 changes: 109 additions & 0 deletions packages/ckeditor5-utils/tests/collectstylesheets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/

/* global document, window, console, Response */

import collectStylesheets from '../src/collectstylesheets.js';

describe( 'collectStylesheets', () => {
let styleSheetsMock;

beforeEach( () => {
styleSheetsMock = [
{
ownerNode: {
hasAttribute: name => name === 'data-cke'
},
cssRules: [
{ cssText: ':root { --variable1: white; }' },
{ cssText: '.ck-content { color: black }' },
{ cssText: '.some-styles { color: red }' }
]
},
{
ownerNode: {
hasAttribute: name => name === 'data-cke'
},
cssRules: [
{ cssText: ':root { --variable2: blue; }' },
{ cssText: '.ck-content { background: white }' }
]
},
{
ownerNode: {
hasAttribute: () => false
},
cssRules: [
{ cssText: 'h2 { color: black }' }
]
}
];

sinon.stub( document, 'styleSheets' ).get( () => styleSheetsMock );
} );

afterEach( () => {
sinon.restore();
} );

it( 'should not return any styles if no paths to stylesheets provided', async () => {
expect( await collectStylesheets( undefined ) ).to.equal( '' );
} );

it( 'should log into the console when ".ck-content" styles are missing', async () => {
styleSheetsMock = [ {
ownerNode: {
hasAttribute: name => name === 'data-cke'
},
cssRules: [
{ cssText: ':root { --variable: white; }' }
]
} ];

const consoleSpy = sinon.stub( console, 'warn' );

await collectStylesheets( [ 'EDITOR_STYLES' ] );

sinon.assert.calledOnce( consoleSpy );
} );

it( 'should get ".ck-content" styles when "EDITOR_STYLES" token is provided', async () => {
const consoleSpy = sinon.stub( console, 'warn' );

const styles = await collectStylesheets( [ './foo.css', 'EDITOR_STYLES' ] );

sinon.assert.notCalled( consoleSpy );

expect( styles.length > 0 ).to.be.true;
expect( styles.indexOf( '.ck-content' ) !== -1 ).to.be.true;
} );

it( 'should get styles from multiple stylesheets with data-cke attribute', async () => {
const styles = await collectStylesheets( [ 'EDITOR_STYLES' ] );

expect( styles ).to.include( ':root { --variable1: white; }' );
expect( styles ).to.include( ':root { --variable2: blue; }' );
expect( styles ).to.include( '.ck-content { color: black }' );
expect( styles ).to.include( '.ck-content { background: white }' );
} );

it( 'should collect all :root styles from stylesheets with data-cke attribute', async () => {
const styles = await collectStylesheets( [ 'EDITOR_STYLES' ] );

expect( styles ).to.include( '--variable1: white' );
expect( styles ).to.include( '--variable2: blue' );
} );

it( 'should fetch stylesheets from the provided paths and return concat result', async () => {
sinon
.stub( window, 'fetch' )
.onFirstCall().resolves( new Response( '.foo { color: green; }' ) )
.onSecondCall().resolves( new Response( '.bar { color: red; }' ) );

const styles = await collectStylesheets( [ './foo.css', './bar.css' ] );

expect( styles ).to.equal( '.foo { color: green; } .bar { color: red; }' );
} );
} );

0 comments on commit 5cda75f

Please sign in to comment.