-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
collectStylesheets
that allows to pick stylesheets from provide…
…d urls. (#17752) Feature (utils): Add a `collectStylesheets` helper function to retrieve stylesheets from the provided URLs.
- Loading branch information
Showing
3 changed files
with
176 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; }' ); | ||
} ); | ||
} ); |