From a16b04568506de6ab9c0a888def7f2d860aae2ab Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Wed, 24 Jan 2018 01:34:41 +0100 Subject: [PATCH 01/12] Add `atRuleType` to CSSRule --- src/css_composer/model/CssRule.js | 40 ++++++++++++++++------ test/specs/css_composer/model/CssModels.js | 12 ++++++- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/src/css_composer/model/CssRule.js b/src/css_composer/model/CssRule.js index 1ea86c623..096a542dd 100644 --- a/src/css_composer/model/CssRule.js +++ b/src/css_composer/model/CssRule.js @@ -23,6 +23,9 @@ module.exports = Backbone.Model.extend(Styleable).extend({ // Indicates if the rule is stylable stylable: true, + // Type of at-rule, eg. 'media', 'font-face', etc. + atRuleType: '', + // If true, sets '!important' on all properties // You can use an array to specify properties to set important // Used in view @@ -47,6 +50,18 @@ module.exports = Backbone.Model.extend(Styleable).extend({ this.set('selectors', new Selectors(selectors)); }, + /** + * Returns an at-rule statement if possible, eg. '@media (...)', '@keyframes' + * @return {string} + */ + getAtRule() { + const type = this.get('atRuleType'); + const condition = this.get('mediaText'); + return ( + (type ? `@${type}` : '') + (condition && type ? ` ${condition}` : '') + ); + }, + /** * Return selectors fo the rule as a string * @return {string} @@ -69,7 +84,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({ */ toCSS(opts = {}) { let result = ''; - const media = this.get('mediaText'); + const atRule = this.getAtRule(); const style = this.styleToString(opts); const selectors = this.selectorsToString(); @@ -77,8 +92,8 @@ module.exports = Backbone.Model.extend(Styleable).extend({ result = `${selectors}{${style}}`; } - if (media && result) { - result = `@media ${media}{${result}}`; + if (atRule && result) { + result = `${atRule}{${result}}`; } return result; @@ -93,11 +108,11 @@ module.exports = Backbone.Model.extend(Styleable).extend({ * @return {Boolean} * @private */ - compare(selectors, state, width, ruleProps) { - var otherRule = ruleProps || {}; + compare(selectors, state, width, ruleProps = {}) { var st = state || ''; var wd = width || ''; - var selectorsAdd = otherRule.selectorsAdd || ''; + var selectorsAdd = ruleProps.selectorsAdd || ''; + var atRuleType = ruleProps.atRuleType || ''; var cId = 'cid'; //var a1 = _.pluck(selectors.models || selectors, cId); //var a2 = _.pluck(this.get('selectors').models, cId); @@ -117,11 +132,14 @@ module.exports = Backbone.Model.extend(Styleable).extend({ if (re === 0) return f; } - if (this.get('state') !== st) return f; - - if (this.get('mediaText') !== wd) return f; - - if (this.get('selectorsAdd') !== selectorsAdd) return f; + if ( + this.get('state') !== st || + this.get('mediaText') !== wd || + this.get('selectorsAdd') !== selectorsAdd || + this.get('atRuleType') !== atRuleType + ) { + return f; + } return true; } diff --git a/test/specs/css_composer/model/CssModels.js b/test/specs/css_composer/model/CssModels.js index 44ea7753b..d7c423969 100644 --- a/test/specs/css_composer/model/CssModels.js +++ b/test/specs/css_composer/model/CssModels.js @@ -5,7 +5,7 @@ var Selector = require('selector_manager/model/Selector'); module.exports = { run() { - describe('CssRule', () => { + describe.only('CssRule', () => { let obj; beforeEach(() => { @@ -74,11 +74,21 @@ module.exports = { it('toCSS wraps correctly inside media rule', () => { const media = '(max-width: 768px)'; + obj.set('atRuleType', 'media'); obj.set('mediaText', media); obj.get('selectors').add({ name: 'test1' }); obj.setStyle({ color: 'red' }); expect(obj.toCSS()).toEqual(`@media ${media}{.test1{color:red;}}`); }); + + it('toCSS with a generic at-rule', () => { + obj.set('atRuleType', 'font-face'); + obj.get('selectors').add({ name: 'test1' }); + obj.setStyle({ 'font-family': 'Open Sans' }); + expect(obj.toCSS()).toEqual( + `@font-face{.test1{font-family:Open Sans;}}` + ); + }); }); describe('CssRules', () => { From 51b1c338883ec794c61df957d3bdeadcf2b69db9 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Wed, 24 Jan 2018 02:21:40 +0100 Subject: [PATCH 02/12] Setup multiple CSS at-tules --- src/parser/model/ParserCss.js | 38 ++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/src/parser/model/ParserCss.js b/src/parser/model/ParserCss.js index 7746a49eb..d88f49975 100644 --- a/src/parser/model/ParserCss.js +++ b/src/parser/model/ParserCss.js @@ -1,3 +1,20 @@ +import { keys } from 'underscore'; + +// At-rules +// https://developer.mozilla.org/it/docs/Web/API/CSSRule#Type_constants +const atRules = { + 4: 'media', + 5: 'font-face', + 6: 'page', + 7: 'keyframes', + 11: 'counter-style', + 12: 'supports', + 13: 'document', + 14: 'font-feature-values', + 15: 'viewport' +}; +const atRuleKeys = keys(atRules); + module.exports = config => ({ /** * Parse selector string to array. @@ -37,28 +54,35 @@ module.exports = config => ({ /** * Fetch data from node - * @param {StyleSheet|CSSMediaRule} el + * @param {StyleSheet|CSSRule} el * @return {Array} */ parseNode(el) { var result = []; - var nodes = el.cssRules; + var nodes = el.cssRules || []; for (var i = 0, len = nodes.length; i < len; i++) { var node = nodes[i]; var sels = node.selectorText; var selsAdd = []; + const type = node.type.toString(); - // It's a CSSMediaRule - if (node.cssRules) { + if (atRuleKeys.indexOf(type) >= 0) { + console.log(node); var subRules = this.parseNode(node); - var mediaText = node.media.mediaText; + const condition = + node.conditionText || + (node.media && node.media.mediaText) || + node.selectorText || + node.name || + ''; for (var s = 0, lens = subRules.length; s < lens; s++) { var subRule = subRules[s]; - subRule.mediaText = mediaText ? mediaText.trim() : ''; + subRule.mediaText = condition.trim(); + subRule.atRuleType = atRules[type]; } - + console.log(subRules); result = result.concat(subRules); } From 2a66122b2acc1c2a26dcb9653d20c54d00402781 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Wed, 24 Jan 2018 02:41:58 +0100 Subject: [PATCH 03/12] Create parseStyle method in ParserCss --- src/css_composer/model/CssRule.js | 7 ++++++- src/parser/model/ParserCss.js | 33 ++++++++++++++++++++----------- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/css_composer/model/CssRule.js b/src/css_composer/model/CssRule.js index 096a542dd..23ae3490b 100644 --- a/src/css_composer/model/CssRule.js +++ b/src/css_composer/model/CssRule.js @@ -26,6 +26,10 @@ module.exports = Backbone.Model.extend(Styleable).extend({ // Type of at-rule, eg. 'media', 'font-face', etc. atRuleType: '', + // This particolar property is used only on at-rules, like 'page' or + // 'font-face', where the block containes only style declarations + noSelectors: 0, + // If true, sets '!important' on all properties // You can use an array to specify properties to set important // Used in view @@ -87,9 +91,10 @@ module.exports = Backbone.Model.extend(Styleable).extend({ const atRule = this.getAtRule(); const style = this.styleToString(opts); const selectors = this.selectorsToString(); + const noSelectors = this.get('noSelectors'); if (selectors && style) { - result = `${selectors}{${style}}`; + result = noSelectors ? style : `${selectors}{${style}}`; } if (atRule && result) { diff --git a/src/parser/model/ParserCss.js b/src/parser/model/ParserCss.js index d88f49975..e2f544af6 100644 --- a/src/parser/model/ParserCss.js +++ b/src/parser/model/ParserCss.js @@ -52,6 +52,25 @@ module.exports = config => ({ }; }, + /** + * Parse style declarations of the node + * @param {CSSRule} node + * @return {Object} + */ + parseStyle(node) { + const stl = node.style; + const style = {}; + + for (var i = 0, len = stl.length; i < len; i++) { + const propName = stl[i]; + const propValue = stl.getPropertyValue(propName); + const important = stl.getPropertyPriority(propName); + style[propName] = `${propValue}${important ? ` !${important}` : ''}`; + } + + return style; + }, + /** * Fetch data from node * @param {StyleSheet|CSSRule} el @@ -73,8 +92,8 @@ module.exports = config => ({ const condition = node.conditionText || (node.media && node.media.mediaText) || - node.selectorText || node.name || + sels || ''; for (var s = 0, lens = subRules.length; s < lens; s++) { @@ -92,16 +111,8 @@ module.exports = config => ({ sels = selsParsed.result; selsAdd = selsParsed.add; - // Create style object from the big one - var stl = node.style; - var style = {}; - - for (var j = 0, len2 = stl.length; j < len2; j++) { - const propName = stl[j]; - const propValue = stl.getPropertyValue(propName); - const important = stl.getPropertyPriority(propName); - style[propName] = `${propValue}${important ? ` !${important}` : ''}`; - } + // Create the style object from the big one + const style = this.parseStyle(node); var lastRule = ''; // For each group of selectors From 62231cbaff43b88ca49d6f70530f32e9c08d5998 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Wed, 24 Jan 2018 03:28:40 +0100 Subject: [PATCH 04/12] Isolate single block at-rules --- src/css_composer/model/CssRule.js | 6 +-- src/parser/model/ParserCss.js | 67 ++++++++++++++++++++----------- 2 files changed, 47 insertions(+), 26 deletions(-) diff --git a/src/css_composer/model/CssRule.js b/src/css_composer/model/CssRule.js index 23ae3490b..f0f63b923 100644 --- a/src/css_composer/model/CssRule.js +++ b/src/css_composer/model/CssRule.js @@ -28,7 +28,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({ // This particolar property is used only on at-rules, like 'page' or // 'font-face', where the block containes only style declarations - noSelectors: 0, + singleAtRule: 0, // If true, sets '!important' on all properties // You can use an array to specify properties to set important @@ -91,10 +91,10 @@ module.exports = Backbone.Model.extend(Styleable).extend({ const atRule = this.getAtRule(); const style = this.styleToString(opts); const selectors = this.selectorsToString(); - const noSelectors = this.get('noSelectors'); + const singleAtRule = this.get('singleAtRule'); if (selectors && style) { - result = noSelectors ? style : `${selectors}{${style}}`; + result = singleAtRule ? style : `${selectors}{${style}}`; } if (atRule && result) { diff --git a/src/parser/model/ParserCss.js b/src/parser/model/ParserCss.js index e2f544af6..c25e35dac 100644 --- a/src/parser/model/ParserCss.js +++ b/src/parser/model/ParserCss.js @@ -14,6 +14,7 @@ const atRules = { 15: 'viewport' }; const atRuleKeys = keys(atRules); +const singleAtRules = ['5', '6', '11', '15']; module.exports = config => ({ /** @@ -31,7 +32,7 @@ module.exports = config => ({ * //add: ['.test2 .test3'] * //} */ - parseSelector(str) { + parseSelector(str = '') { var add = []; var result = []; var sels = str.split(','); @@ -71,6 +72,21 @@ module.exports = config => ({ return style; }, + /** + * Get the condition when possible + * @param {CSSRule} node + * @return {string} + */ + parseCondition(node) { + const condition = + node.conditionText || + (node.media && node.media.mediaText) || + node.name || + node.selectorText || + ''; + return condition.trim(); + }, + /** * Fetch data from node * @param {StyleSheet|CSSRule} el @@ -81,44 +97,44 @@ module.exports = config => ({ var nodes = el.cssRules || []; for (var i = 0, len = nodes.length; i < len; i++) { - var node = nodes[i]; - var sels = node.selectorText; - var selsAdd = []; + const node = nodes[i]; const type = node.type.toString(); - - if (atRuleKeys.indexOf(type) >= 0) { + let singleAtRule = 0; + let atRuleType = ''; + let condition = ''; + let sels = node.selectorText; + const isSingleAtRule = singleAtRules.indexOf(type) >= 0; + + // Check if the node is an at-rule + if (isSingleAtRule) { + singleAtRule = 1; + atRuleType = atRules[type]; + condition = this.parseCondition(node); console.log(node); + } else if (atRuleKeys.indexOf(type) >= 0) { var subRules = this.parseNode(node); - const condition = - node.conditionText || - (node.media && node.media.mediaText) || - node.name || - sels || - ''; + condition = this.parseCondition(node); for (var s = 0, lens = subRules.length; s < lens; s++) { var subRule = subRules[s]; - subRule.mediaText = condition.trim(); + subRule.mediaText = condition; subRule.atRuleType = atRules[type]; } - console.log(subRules); + result = result.concat(subRules); } - if (!sels) continue; - - var selsParsed = this.parseSelector(sels); - sels = selsParsed.result; - selsAdd = selsParsed.add; - - // Create the style object from the big one + if (!sels && !isSingleAtRule) continue; const style = this.parseStyle(node); + const selsParsed = this.parseSelector(sels); + const selsAdd = selsParsed.add; + sels = selsParsed.result; - var lastRule = ''; + let lastRule; // For each group of selectors for (var k = 0, len3 = sels.length; k < len3; k++) { var selArr = sels[k]; - var model = {}; + var model = { singleAtRule, atRuleType }; //Isolate state from selector var stateArr = selArr[selArr.length - 1].split(/:(.+)/); @@ -142,12 +158,17 @@ module.exports = config => ({ lastRule.selectorsAdd = selsAddStr; } else { result.push({ + singleAtRule, + atRuleType, selectors: [], selectorsAdd: selsAddStr, + mediaText: condition, style }); } } + + console.log('Last PUSH', result[result.length - 1]); } return result; From 42c8cca38030eda34940fd90f69c9452285495d6 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Wed, 24 Jan 2018 03:37:46 +0100 Subject: [PATCH 05/12] Clean from debug --- src/parser/model/ParserCss.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/parser/model/ParserCss.js b/src/parser/model/ParserCss.js index c25e35dac..a287eb6aa 100644 --- a/src/parser/model/ParserCss.js +++ b/src/parser/model/ParserCss.js @@ -102,7 +102,8 @@ module.exports = config => ({ let singleAtRule = 0; let atRuleType = ''; let condition = ''; - let sels = node.selectorText; + // keyText if for CSSKeyframeRule + let sels = node.selectorText || node.keyText; const isSingleAtRule = singleAtRules.indexOf(type) >= 0; // Check if the node is an at-rule @@ -110,7 +111,6 @@ module.exports = config => ({ singleAtRule = 1; atRuleType = atRules[type]; condition = this.parseCondition(node); - console.log(node); } else if (atRuleKeys.indexOf(type) >= 0) { var subRules = this.parseNode(node); condition = this.parseCondition(node); @@ -120,7 +120,6 @@ module.exports = config => ({ subRule.mediaText = condition; subRule.atRuleType = atRules[type]; } - result = result.concat(subRules); } @@ -167,8 +166,6 @@ module.exports = config => ({ }); } } - - console.log('Last PUSH', result[result.length - 1]); } return result; From 47987b9cbc196d85a5a41e7eb1f19f9b65f815c8 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Wed, 24 Jan 2018 14:38:10 +0100 Subject: [PATCH 06/12] Update ParserCSS, set some property conditionally --- src/parser/model/ParserCss.js | 15 +++++++++------ test/specs/css_composer/model/CssModels.js | 12 +++++++++++- test/specs/parser/model/ParserCss.js | 2 +- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/parser/model/ParserCss.js b/src/parser/model/ParserCss.js index a287eb6aa..5b6a8b945 100644 --- a/src/parser/model/ParserCss.js +++ b/src/parser/model/ParserCss.js @@ -133,7 +133,9 @@ module.exports = config => ({ // For each group of selectors for (var k = 0, len3 = sels.length; k < len3; k++) { var selArr = sels[k]; - var model = { singleAtRule, atRuleType }; + var model = {}; + singleAtRule && (model.singleAtRule = singleAtRule); + atRuleType && (model.atRuleType = atRuleType); //Isolate state from selector var stateArr = selArr[selArr.length - 1].split(/:(.+)/); @@ -156,14 +158,15 @@ module.exports = config => ({ if (lastRule) { lastRule.selectorsAdd = selsAddStr; } else { - result.push({ - singleAtRule, - atRuleType, + const model = { selectors: [], selectorsAdd: selsAddStr, - mediaText: condition, style - }); + }; + singleAtRule && (model.singleAtRule = singleAtRule); + atRuleType && (model.atRuleType = atRuleType); + condition && (model.mediaText = condition); + result.push(model); } } } diff --git a/test/specs/css_composer/model/CssModels.js b/test/specs/css_composer/model/CssModels.js index d7c423969..f97bcce06 100644 --- a/test/specs/css_composer/model/CssModels.js +++ b/test/specs/css_composer/model/CssModels.js @@ -5,7 +5,7 @@ var Selector = require('selector_manager/model/Selector'); module.exports = { run() { - describe.only('CssRule', () => { + describe('CssRule', () => { let obj; beforeEach(() => { @@ -89,6 +89,16 @@ module.exports = { `@font-face{.test1{font-family:Open Sans;}}` ); }); + + it('toCSS with a generic at-rule and condition', () => { + obj.set('atRuleType', 'font-face'); + obj.set('mediaText', 'some-condition'); + obj.get('selectors').add({ name: 'test1' }); + obj.setStyle({ 'font-family': 'Open Sans' }); + expect(obj.toCSS()).toEqual( + `@font-face some-condition{.test1{font-family:Open Sans;}}` + ); + }); }); describe('CssRules', () => { diff --git a/test/specs/parser/model/ParserCss.js b/test/specs/parser/model/ParserCss.js index f0e439580..262f945fb 100644 --- a/test/specs/parser/model/ParserCss.js +++ b/test/specs/parser/model/ParserCss.js @@ -2,7 +2,7 @@ const ParserCss = require('parser/model/ParserCss'); module.exports = { run() { - describe('ParserCss', () => { + describe.only('ParserCss', () => { var obj; beforeEach(() => { From 6a5b55a42c7b0fea2e607dbd2b94dc9ab130ef1e Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Wed, 24 Jan 2018 15:13:44 +0100 Subject: [PATCH 07/12] Clear the canvas offset on resize. Fixes #699 --- src/canvas/view/CanvasView.js | 3 ++- test/specs/parser/model/ParserCss.js | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/canvas/view/CanvasView.js b/src/canvas/view/CanvasView.js index a42b89d4d..68fda7a75 100644 --- a/src/canvas/view/CanvasView.js +++ b/src/canvas/view/CanvasView.js @@ -1,10 +1,11 @@ +import { on, off } from 'utils/mixins'; const FrameView = require('./FrameView'); const $ = Backbone.$; module.exports = Backbone.View.extend({ initialize(o) { _.bindAll(this, 'renderBody', 'onFrameScroll', 'clearOff'); - window.onscroll = this.clearOff; + on(window, 'scroll resize', this.clearOff); this.config = o.config || {}; this.em = this.config.em || {}; this.ppfx = this.config.pStylePrefix || ''; diff --git a/test/specs/parser/model/ParserCss.js b/test/specs/parser/model/ParserCss.js index 262f945fb..0d0583bf5 100644 --- a/test/specs/parser/model/ParserCss.js +++ b/test/specs/parser/model/ParserCss.js @@ -2,7 +2,7 @@ const ParserCss = require('parser/model/ParserCss'); module.exports = { run() { - describe.only('ParserCss', () => { + describe('ParserCss', () => { var obj; beforeEach(() => { @@ -133,6 +133,7 @@ module.exports = { var str = '@media only screen and (max-width: 992px){ .test1.test2:hover{ color:red }}'; var result = { + atRuleType: 'media', selectors: ['test1', 'test2'], style: { color: 'red' }, state: 'hover', @@ -145,6 +146,7 @@ module.exports = { it('Parse rule inside media query', () => { var str = '@media (max-width: 992px){ .test1.test2:hover{ color:red }}'; var result = { + atRuleType: 'media', selectors: ['test1', 'test2'], style: { color: 'red' }, state: 'hover', @@ -167,11 +169,13 @@ module.exports = { selectors: ['test1', 'test2'], style: { color: 'red' }, state: 'hover', + atRuleType: 'media', mediaText: '(max-width: 992px)' }, { selectors: ['test2'], style: { color: 'blue' }, + atRuleType: 'media', mediaText: '(max-width: 992px)' } ]; From 82cb2dc07cdd6e4ce6a8271ad98e3678484fa2de Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Wed, 24 Jan 2018 21:40:05 +0100 Subject: [PATCH 08/12] Update tests for at rules --- src/parser/model/ParserCss.js | 19 +++++--------- test/specs/parser/model/ParserCss.js | 37 ++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/parser/model/ParserCss.js b/src/parser/model/ParserCss.js index 5b6a8b945..dbe6cd038 100644 --- a/src/parser/model/ParserCss.js +++ b/src/parser/model/ParserCss.js @@ -117,7 +117,7 @@ module.exports = config => ({ for (var s = 0, lens = subRules.length; s < lens; s++) { var subRule = subRules[s]; - subRule.mediaText = condition; + condition && (subRule.mediaText = condition); subRule.atRuleType = atRules[type]; } result = result.concat(subRules); @@ -180,22 +180,15 @@ module.exports = config => ({ * @return {Object|Array} */ parse(str) { - var el = document.createElement('style'); - /* - el.innerHTML = ".cssClass {border: 2px solid black; background-color: blue;} " + - ".red, .red2 {color:red; padding:5px} .test1.red {color:black} .red:hover{color: blue} " + - "@media screen and (min-width: 480px){ .red{color:white} }"; - */ + const el = document.createElement('style'); el.innerHTML = str; - // There is no .sheet without adding it to the + // There is no .sheet before adding it to the document.head.appendChild(el); - var sheet = el.sheet; + const sheet = el.sheet; document.head.removeChild(el); - var result = this.parseNode(sheet); + const result = this.parseNode(sheet); - if (result.length == 1) result = result[0]; - - return result; + return result.length == 1 ? result[0] : result; } }); diff --git a/test/specs/parser/model/ParserCss.js b/test/specs/parser/model/ParserCss.js index 0d0583bf5..6b126f8b8 100644 --- a/test/specs/parser/model/ParserCss.js +++ b/test/specs/parser/model/ParserCss.js @@ -238,6 +238,43 @@ module.exports = { }; expect(obj.parse(str)).toEqual(result); }); + + // Can't test keyframes https://github.com/NV/CSSOM/issues/95 + it.skip('Parse rule with a keyframes at-rule', () => { + var str = `@keyframes { + from {opacity: 0;} + to {opacity: 1;} + }`; + var result = [ + { + selectors: [], + atRuleType: 'keyframes', + selectorsAdd: 'from', + style: { opacity: '0' } + }, + { + selectors: [], + atRuleType: 'keyframes', + selectorsAdd: 'to', + style: { opacity: '1' } + } + ]; + expect(obj.parse(str)).toEqual(result); + }); + + it('Parse rule with font-face at-rule', () => { + var str = `@font-face { + font-family: "Open Sans"; + }`; + var result = { + selectors: [], + selectorsAdd: '', + atRuleType: 'font-face', + singleAtRule: 1, + style: { 'font-family': '"Open Sans"' } + }; + expect(obj.parse(str)).toEqual(result); + }); }); } }; From 563eb2d954eceae9fded33d62d371834d75392f5 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Wed, 24 Jan 2018 22:04:18 +0100 Subject: [PATCH 09/12] Refactor CSS Generator --- src/code_manager/model/CssGenerator.js | 19 +++++++++---------- src/css_composer/model/CssRule.js | 7 ++++--- test/specs/css_composer/e2e/CssComposer.js | 6 ++++++ 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/code_manager/model/CssGenerator.js b/src/code_manager/model/CssGenerator.js index d715c50db..fb977267f 100644 --- a/src/code_manager/model/CssGenerator.js +++ b/src/code_manager/model/CssGenerator.js @@ -43,19 +43,18 @@ module.exports = require('backbone').Model.extend({ if (cssc) { const rules = cssc.getAll(); - const mediaRules = {}; + const atRules = {}; const dump = []; rules.each(rule => { - const media = rule.get('mediaText'); + const atRule = rule.getAtRule(); - // If media is setted, I'll render it later - if (media) { - const mRules = mediaRules[media]; + if (atRule) { + const mRules = atRules[atRule]; if (mRules) { mRules.push(rule); } else { - mediaRules[media] = [rule]; + atRules[atRule] = [rule]; } return; } @@ -63,14 +62,14 @@ module.exports = require('backbone').Model.extend({ code += this.buildFromRule(rule, dump); }); - // Get media rules - for (let media in mediaRules) { + // Get at-rules + for (let atRule in atRules) { let rulesStr = ''; - const mRules = mediaRules[media]; + const mRules = atRules[atRule]; mRules.forEach(rule => (rulesStr += this.buildFromRule(rule, dump))); if (rulesStr) { - code += `@media ${media}{${rulesStr}}`; + code += `${atRule}{${rulesStr}}`; } } diff --git a/src/css_composer/model/CssRule.js b/src/css_composer/model/CssRule.js index f0f63b923..a3d410f14 100644 --- a/src/css_composer/model/CssRule.js +++ b/src/css_composer/model/CssRule.js @@ -61,9 +61,10 @@ module.exports = Backbone.Model.extend(Styleable).extend({ getAtRule() { const type = this.get('atRuleType'); const condition = this.get('mediaText'); - return ( - (type ? `@${type}` : '') + (condition && type ? ` ${condition}` : '') - ); + // Avoid breaks with the last condition + const typeStr = type ? `@${type}` : condition ? '@media' : ''; + + return typeStr + (condition && typeStr ? ` ${condition}` : ''); }, /** diff --git a/test/specs/css_composer/e2e/CssComposer.js b/test/specs/css_composer/e2e/CssComposer.js index ed7675446..07cf70471 100644 --- a/test/specs/css_composer/e2e/CssComposer.js +++ b/test/specs/css_composer/e2e/CssComposer.js @@ -140,6 +140,8 @@ module.exports = { protected: false } ], + atRuleType: '', + singleAtRule: 0, important: 0, selectorsAdd: '', state: '', @@ -178,6 +180,8 @@ module.exports = { rule1Out = JSON.parse(JSON.stringify(rule1Out)); rule2Out = JSON.parse(JSON.stringify(rule2Out)); var rule1Result = { + atRuleType: '', + singleAtRule: 0, important: 0, mediaText: '', selectors: [], @@ -190,6 +194,8 @@ module.exports = { } }; var rule2Result = { + atRuleType: '', + singleAtRule: 0, important: 0, mediaText: '', selectors: [], From 482494f5036ec62af3a9ac58d2fae0fb73ae1845 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Wed, 24 Jan 2018 22:31:04 +0100 Subject: [PATCH 10/12] Update CssRule and CssGenerator --- src/code_manager/model/CssGenerator.js | 12 +++++------- src/css_composer/model/CssRule.js | 23 ++++++++++++++++++----- src/parser/model/ParserCss.js | 3 ++- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/code_manager/model/CssGenerator.js b/src/code_manager/model/CssGenerator.js index fb977267f..3b857cad0 100644 --- a/src/code_manager/model/CssGenerator.js +++ b/src/code_manager/model/CssGenerator.js @@ -86,8 +86,9 @@ module.exports = require('backbone').Model.extend({ */ buildFromRule(rule, dump) { let result = ''; - const selectorStr = rule.selectorsToString(); const selectorStrNoAdd = rule.selectorsToString({ skipAdd: 1 }); + const selectorsAdd = rule.get('selectorsAdd'); + const singleAtRule = rule.get('singleAtRule'); let found; // This will not render a rule if there is no its component @@ -98,12 +99,9 @@ module.exports = require('backbone').Model.extend({ } }); - if ((selectorStrNoAdd && found) || rule.get('selectorsAdd')) { - const style = rule.styleToString(); - - if (style) { - result += `${selectorStr}{${style}}`; - } + if ((selectorStrNoAdd && found) || selectorsAdd || singleAtRule) { + const block = rule.getDeclaration(); + block && (result += block); } else { dump.push(rule); } diff --git a/src/css_composer/model/CssRule.js b/src/css_composer/model/CssRule.js index a3d410f14..7599db4a1 100644 --- a/src/css_composer/model/CssRule.js +++ b/src/css_composer/model/CssRule.js @@ -83,21 +83,34 @@ module.exports = Backbone.Model.extend(Styleable).extend({ }, /** - * Returns CSS string of the rule + * Get declaration block * @param {Object} [opts={}] Options * @return {string} */ - toCSS(opts = {}) { + getDeclaration(opts = {}) { let result = ''; - const atRule = this.getAtRule(); - const style = this.styleToString(opts); const selectors = this.selectorsToString(); + const style = this.styleToString(opts); const singleAtRule = this.get('singleAtRule'); - if (selectors && style) { + if ((selectors || singleAtRule) && style) { result = singleAtRule ? style : `${selectors}{${style}}`; } + return result; + }, + + /** + * Returns CSS string of the rule + * @param {Object} [opts={}] Options + * @return {string} + */ + toCSS(opts = {}) { + let result = ''; + const atRule = this.getAtRule(); + const block = this.getDeclaration(opts); + block && (result = block); + if (atRule && result) { result = `${atRule}{${result}}`; } diff --git a/src/parser/model/ParserCss.js b/src/parser/model/ParserCss.js index dbe6cd038..55360ec3f 100644 --- a/src/parser/model/ParserCss.js +++ b/src/parser/model/ParserCss.js @@ -102,7 +102,7 @@ module.exports = config => ({ let singleAtRule = 0; let atRuleType = ''; let condition = ''; - // keyText if for CSSKeyframeRule + // keyText is for CSSKeyframeRule let sels = node.selectorText || node.keyText; const isSingleAtRule = singleAtRules.indexOf(type) >= 0; @@ -169,6 +169,7 @@ module.exports = config => ({ result.push(model); } } + // console.log('LAST PUSH', result[result.length - 1]); } return result; From 62f2a06861304dd06fadf71ffeea38f0e14c38f2 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Wed, 24 Jan 2018 22:37:57 +0100 Subject: [PATCH 11/12] Add a test for a single at rule like a font-face --- test/specs/css_composer/model/CssModels.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/specs/css_composer/model/CssModels.js b/test/specs/css_composer/model/CssModels.js index f97bcce06..8817413c6 100644 --- a/test/specs/css_composer/model/CssModels.js +++ b/test/specs/css_composer/model/CssModels.js @@ -82,14 +82,21 @@ module.exports = { }); it('toCSS with a generic at-rule', () => { - obj.set('atRuleType', 'font-face'); + obj.set('atRuleType', 'supports'); obj.get('selectors').add({ name: 'test1' }); obj.setStyle({ 'font-family': 'Open Sans' }); expect(obj.toCSS()).toEqual( - `@font-face{.test1{font-family:Open Sans;}}` + `@supports{.test1{font-family:Open Sans;}}` ); }); + it('toCSS with a generic single at-rule', () => { + obj.set('atRuleType', 'font-face'); + obj.set('singleAtRule', 1); + obj.setStyle({ 'font-family': 'Sans' }); + expect(obj.toCSS()).toEqual(`@font-face{font-family:Sans;}`); + }); + it('toCSS with a generic at-rule and condition', () => { obj.set('atRuleType', 'font-face'); obj.set('mediaText', 'some-condition'); From f90a968281866e66659c88fb9e13c5a4c36a571a Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Thu, 25 Jan 2018 00:00:29 +0100 Subject: [PATCH 12/12] Create CssGroupRuleView, used for keyframes rules --- src/css_composer/view/CssGroupRuleView.js | 12 +++++ src/css_composer/view/CssRulesView.js | 45 ++++++++++++++----- .../view/ComponentTextNodeView.js | 4 +- 3 files changed, 48 insertions(+), 13 deletions(-) create mode 100644 src/css_composer/view/CssGroupRuleView.js diff --git a/src/css_composer/view/CssGroupRuleView.js b/src/css_composer/view/CssGroupRuleView.js new file mode 100644 index 000000000..6450bde4c --- /dev/null +++ b/src/css_composer/view/CssGroupRuleView.js @@ -0,0 +1,12 @@ +module.exports = require('./CssRuleView').extend({ + _createElement: function(tagName) { + return document.createTextNode(''); + }, + + render() { + const model = this.model; + const important = model.get('important'); + this.el.textContent = model.getDeclaration({ important }); + return this; + } +}); diff --git a/src/css_composer/view/CssRulesView.js b/src/css_composer/view/CssRulesView.js index 0c2e132f7..03b38d74c 100644 --- a/src/css_composer/view/CssRulesView.js +++ b/src/css_composer/view/CssRulesView.js @@ -1,9 +1,10 @@ -var Backbone = require('backbone'); -var CssRuleView = require('./CssRuleView'); +const CssRuleView = require('./CssRuleView'); +const CssGroupRuleView = require('./CssGroupRuleView'); -module.exports = Backbone.View.extend({ +module.exports = require('backbone').View.extend({ initialize(o) { const config = o.config || {}; + this.atRules = {}; this.config = config; this.em = config.em; this.pfx = config.stylePrefix || ''; @@ -32,20 +33,44 @@ module.exports = Backbone.View.extend({ addToCollection(model, fragmentEl) { var fragment = fragmentEl || null; var viewObject = CssRuleView; + var config = this.config; + let rendered, view; + const opts = { model, config }; - var view = new viewObject({ - model, - config: this.config - }); - var rendered = view.render().el; + // I have to render keyframes of the same name together + // Unfortunately at the moment I didn't find the way of appending them + // if not staticly, via appendData + if (model.get('atRuleType') == 'keyframes') { + const atRule = model.getAtRule(); + let atRuleEl = this.atRules[atRule]; - if (fragment) fragment.appendChild(rendered); - else this.$el.append(rendered); + if (!atRuleEl) { + const styleEl = document.createElement('style'); + atRuleEl = document.createTextNode(''); + styleEl.appendChild(document.createTextNode(`${atRule}{`)); + styleEl.appendChild(atRuleEl); + styleEl.appendChild(document.createTextNode(`}`)); + this.atRules[atRule] = atRuleEl; + rendered = styleEl; + } + + view = new CssGroupRuleView(opts); + atRuleEl.appendData(view.render().el.textContent); + } else { + view = new CssRuleView(opts); + rendered = view.render().el; + } + + if (rendered) { + if (fragment) fragment.appendChild(rendered); + else this.$el.append(rendered); + } return rendered; }, render() { + this.atRules = {}; const $el = this.$el; const frag = document.createDocumentFragment(); $el.empty(); diff --git a/src/dom_components/view/ComponentTextNodeView.js b/src/dom_components/view/ComponentTextNodeView.js index 5510a2d8c..fb190d371 100644 --- a/src/dom_components/view/ComponentTextNodeView.js +++ b/src/dom_components/view/ComponentTextNodeView.js @@ -1,3 +1 @@ -var Backbone = require('backbone'); - -module.exports = Backbone.View.extend({}); +module.exports = require('backbone').View.extend({});