浏览代码

Merge pull request #5612 from ckeditor/i/5605

Internal: Content styles generator script should not duplicate styles or miss at-rule selectors. Closes #5605. Closes #5604.
Aleksander Nowodzinski 6 年之前
父节点
当前提交
9f00c0569c

+ 39 - 44
docs/builds/guides/integration/content-styles.md

@@ -49,7 +49,7 @@ Below there is a full list of content styles used by the editor features. You ca
 ```css
 /*
  * CKEditor 5 (v12.4.0) content styles.
- * Generated on Thu, 17 Oct 2019 07:18:45 GMT.
+ * Generated on Mon, 21 Oct 2019 11:34:56 GMT.
  * For more information, check out https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/content-styles.html
  */
 
@@ -64,26 +64,12 @@ Below there is a full list of content styles used by the editor features. You ca
 	padding: .15em;
 	border-radius: 2px;
 }
-/* ckeditor5-image/theme/imageresize.css */
-.ck-content .image.image_resized {
-	max-width: 100%;
-	display: block;
-	box-sizing: border-box
-}
-/* ckeditor5-image/theme/imageresize.css */
-.ck-content .image.image_resized img {
-	width: 100%;
-}
-/* ckeditor5-image/theme/imageresize.css */
-.ck-content .image.image_resized > figcaption {
-	display: block;
-}
 /* ckeditor5-image/theme/image.css */
 .ck-content .image {
 	display: table;
 	clear: both;
 	text-align: center;
-	margin: 1em auto
+	margin: 1em auto;
 }
 /* ckeditor5-image/theme/image.css */
 .ck-content .image > img {
@@ -103,13 +89,34 @@ Below there is a full list of content styles used by the editor features. You ca
 	font-size: .75em;
 	outline-offset: -1px;
 }
+/* ckeditor5-image/theme/imageresize.css */
+.ck-content .image.image_resized {
+	max-width: 100%;
+	display: block;
+	box-sizing: border-box;
+}
+/* ckeditor5-image/theme/imageresize.css */
+.ck-content .image.image_resized img {
+	width: 100%;
+}
+/* ckeditor5-image/theme/imageresize.css */
+.ck-content .image.image_resized > figcaption {
+	display: block;
+}
+/* ckeditor5-media-embed/theme/mediaembed.css */
+.ck-content .media {
+	clear: both;
+	margin: 1em 0;
+	display: block;
+	min-width: 15em;
+}
 /* ckeditor5-list/theme/todolist.css */
 .ck-content .todo-list {
-	list-style: none
+	list-style: none;
 }
 /* ckeditor5-list/theme/todolist.css */
 .ck-content .todo-list li {
-	margin-bottom: 5px
+	margin-bottom: 5px;
 }
 /* ckeditor5-list/theme/todolist.css */
 .ck-content .todo-list li .todo-list {
@@ -127,7 +134,7 @@ Below there is a full list of content styles used by the editor features. You ca
 	left: -25px;
 	margin-right: -15px;
 	right: 0;
-	margin-left: 0
+	margin-left: 0;
 }
 /* ckeditor5-list/theme/todolist.css */
 .ck-content .todo-list .todo-list__label > input::before {
@@ -173,20 +180,13 @@ Below there is a full list of content styles used by the editor features. You ca
 /* ckeditor5-table/theme/table.css */
 .ck-content .table {
 	margin: 1em auto;
-	display: table
+	display: table;
 }
 /* ckeditor5-table/theme/table.css */
 .ck-content .table table {
 	border-collapse: collapse;
 	border-spacing: 0;
-	border: 1px double hsl(0, 0%, 70%)
-}
-/* ckeditor5-table/theme/table.css */
-.ck-content .table table td,
-.ck-content .table table th {
-	min-width: 2em;
-	padding: .4em;
-	border-color: hsl(0, 0%, 85%);
+	border: 1px double hsl(0, 0%, 70%);
 }
 /* ckeditor5-table/theme/table.css */
 .ck-content .table table td,
@@ -207,7 +207,7 @@ Below there is a full list of content styles used by the editor features. You ca
 	padding: 5px 0;
 	display: flex;
 	align-items: center;
-	justify-content: center
+	justify-content: center;
 }
 /* ckeditor5-page-break/theme/pagebreak.css */
 .ck-content .page-break::after {
@@ -236,14 +236,6 @@ Below there is a full list of content styles used by the editor features. You ca
 	-ms-user-select: none;
 	user-select: none;
 }
-/* ckeditor5-page-break/theme/pagebreak.css */
-.ck-content .page-break {
-	padding: 0
-}
-/* ckeditor5-page-break/theme/pagebreak.css */
-.ck-content .page-break::after {
-	display: none;
-}
 /* ckeditor5-image/theme/imagestyle.css */
 .ck-content .image-style-side,
 .ck-content .image-style-align-left,
@@ -286,13 +278,6 @@ Below there is a full list of content styles used by the editor features. You ca
 	border-left: 0;
 	border-right: solid 5px hsl(0, 0%, 80%);
 }
-/* ckeditor5-media-embed/theme/mediaembed.css */
-.ck-content .media {
-	clear: both;
-	margin: 1em 0;
-	display: block;
-	min-width: 15em;
-}
 /* ckeditor5-horizontal-line/theme/horizontalline.css */
 .ck-content hr {
 	border-width: 1px 0 0;
@@ -300,4 +285,14 @@ Below there is a full list of content styles used by the editor features. You ca
 	border-color: hsl(0, 0%, 37%);
 	margin: 0;
 }
+@media print {
+	/* ckeditor5-page-break/theme/pagebreak.css */
+	.ck-content .page-break {
+		padding: 0;
+	}
+	/* ckeditor5-page-break/theme/pagebreak.css */
+	.ck-content .page-break::after {
+		display: none;
+	}
+}
 ```

+ 66 - 30
scripts/docs/build-content-styles.js

@@ -18,7 +18,8 @@ const VARIABLE_USAGE_REGEXP = /var\((--[\w-]+)\)/g;
 
 const contentRules = {
 	selector: [],
-	variables: []
+	variables: [],
+	atRules: {}
 };
 
 const webpackConfig = getWebpackConfig();
@@ -49,35 +50,8 @@ runWebpack( webpackConfig )
 		// CSS variables that are used by the `.ck-content` selector.
 		const usedVariables = new Set();
 
-		const selectorCss = contentRules.selector
-			.map( rule => {
-				// Removes all comments from the rule definition.
-				const cssAsArray = rule.css.replace( /\/\*[^*]+\*\//g, '' ).split( '\n' );
-
-				// We want to fix invalid indentations. We need to find a number of how many indentations we want to remove.
-				// Because the last line ends the block, we can use this value.
-				const lastLineIndent = cssAsArray[ cssAsArray.length - 1 ].length - 1;
-
-				const css = cssAsArray
-					.filter( line => line.trim().length > 0 )
-					.map( ( line, index ) => {
-						// Do not touch the first line. It is always correct.
-						if ( index === 0 ) {
-							return line;
-						}
-
-						return line.slice( lastLineIndent );
-					} )
-					.join( '\n' );
-
-				return `/* ${ rule.file.replace( packagesPath + path.sep, '' ) } */\n${ css }`;
-			} )
-			.filter( rule => {
-				// 1st: path to the CSS file, 2nd: selector definition - start block, 3rd: end block
-				// If the rule contains only 3 lines, it means that it does not define any rules.
-				return rule.split( '\n' ).length > 3;
-			} )
-			.join( '\n' );
+		// `.ck-content` selectors.
+		const selectorCss = transformCssRules( contentRules.selector );
 
 		// Find all CSS variables inside the `.ck-content` selector.
 		let match;
@@ -111,6 +85,18 @@ runWebpack( webpackConfig )
 			}
 		}
 
+		const atRulesDefinitions = [];
+
+		// Additional at-rules.
+		for ( const atRuleName of Object.keys( contentRules.atRules ) ) {
+			const rules = transformCssRules( contentRules.atRules[ atRuleName ] )
+				.split( '\n' )
+				.map( line => `\t${ line }` )
+				.join( '\n' );
+
+			atRulesDefinitions.push( `@${ atRuleName } {\n${ rules }\n}` );
+		}
+
 		// Build the final content of the CSS file.
 		let data = [
 			'/*',
@@ -128,6 +114,8 @@ runWebpack( webpackConfig )
 
 		data += '}\n\n';
 		data += selectorCss;
+		data += '\n';
+		data += atRulesDefinitions.join( '\n' );
 
 		return writeFile( path.join( DESTINATION_DIRECTORY, 'content-styles.css' ), data );
 	} )
@@ -239,3 +227,51 @@ function writeFile( file, data ) {
 		} );
 	} );
 }
+
+/**
+ * @param {Array} rules
+ * @returns {String}
+ */
+function transformCssRules( rules ) {
+	return rules
+		.map( rule => {
+			// Removes all comments from the rule definition.
+			const cssAsArray = rule.css.replace( /\/\*[^*]+\*\//g, '' ).split( '\n' );
+
+			// We want to fix invalid indentations. We need to find a number of how many indentations we want to remove.
+			// Because the last line ends the block, we can use this value.
+			const lastLineIndent = cssAsArray[ cssAsArray.length - 1 ].length - 1;
+
+			const css = cssAsArray
+				.filter( line => line.trim().length > 0 )
+				.map( ( line, index ) => {
+					// Do not touch the first line. It is always correct.
+					if ( index === 0 ) {
+						return line;
+					}
+
+					const newLine = line.slice( lastLineIndent );
+
+					// If a line is not a CSS definition, do not touch it.
+					if ( !newLine.match( /[A-Z-_0-9]+:/i ) ) {
+						return newLine;
+					}
+
+					// The line is a CSS definition – let's check whether it ends with a semicolon.
+					if ( newLine.endsWith( ';' ) ) {
+						return newLine;
+					}
+
+					return newLine + ';';
+				} )
+				.join( '\n' );
+
+			return `/* ${ rule.file.replace( packagesPath + path.sep, '' ) } */\n${ css }`;
+		} )
+		.filter( rule => {
+			// 1st: path to the CSS file, 2nd: selector definition - start block, 3rd: end block
+			// If the rule contains only 3 lines, it means that it does not define any rules.
+			return rule.split( '\n' ).length > 3;
+		} )
+		.join( '\n' );
+}

+ 66 - 10
scripts/docs/content-styles/list-content-styles.js

@@ -15,21 +15,77 @@ module.exports = postcss.plugin( 'list-content-styles', function( options ) {
 
 	return root => {
 		root.walkRules( rule => {
-			rule.selectors.forEach( selector => {
+			for ( const selector of rule.selectors ) {
+				const data = {
+					file: root.source.input.file,
+					css: rule.toString()
+				};
+
 				if ( selector.match( ':root' ) ) {
-					variables.push( {
-						file: root.source.input.file,
-						css: rule.toString()
-					} );
+					addDefinition( variables, data );
 				}
 
 				if ( selector.match( '.ck-content' ) ) {
-					selectorStyles.push( {
-						file: root.source.input.file,
-						css: rule.toString()
-					} );
+					if ( rule.parent.name && rule.parent.params ) {
+						const atRule = getAtRuleArray( options.contentRules.atRules, rule.parent.name, rule.parent.params );
+
+						addDefinition( atRule, data );
+					} else {
+						addDefinition( selectorStyles, data );
+					}
 				}
-			} );
+			}
 		} );
 	};
 } );
+
+/**
+ * @param {Object} collection
+ * @param {String} name Name of an `at-rule`.
+ * @param {String} params Parameters that describes the `at-rule`.
+ * @returns {Array}
+ */
+function getAtRuleArray( collection, name, params ) {
+	const definition = `${ name } ${ params }`;
+
+	if ( !collection[ definition ] ) {
+		collection[ definition ] = [];
+	}
+
+	return collection[ definition ];
+}
+
+/**
+ * Checks whether specified definition is duplicated in the colletion.
+ *
+ * @param {Array.<StyleStructure>} collection
+ * @param {StyleStructure} def
+ * @returns {Boolean}
+ */
+function isDuplicatedDefinition( collection, def ) {
+	for ( const item of collection ) {
+		if ( item.file === def.file && item.css === def.css ) {
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/**
+ * Adds definition to the collection if it does not exist in the collection.
+ *
+ * @param {Array.<StyleStructure>} collection
+ * @param {StyleStructure} def
+ */
+function addDefinition( collection, def ) {
+	if ( !isDuplicatedDefinition( collection, def ) ) {
+		collection.push( def );
+	}
+}
+
+/**
+ * @typedef {Object} StyleStructure
+ * @property {String} file An absolute path to the file where a definition is defined.
+ * @property {String} css Definition.
+ */