Browse Source

Fix CssComposer get

pull/3905/head
Artur Arseniev 5 years ago
parent
commit
ffdb65e309
  1. 39
      docs/modules/Components.md
  2. 33
      src/css_composer/index.js
  3. 62
      src/css_composer/model/CssRule.js
  4. 8
      src/style_manager/index.js
  5. 62
      test/specs/css_composer/index.js

39
docs/modules/Components.md

@ -747,6 +747,45 @@ editor.on(`component:remove`, model => console.log('Global hook: component:remov
## Components & CSS
::: warning
This section is referring to GrapesJS v0.17.27 or higher
:::
If you need to add component-related styles, you can do it via `styles` property.
```js
domc.addType('component-css', {
model: {
defaults: {
attributes: { class: 'cmp-css' },
components: `
<span>Component with styles<span>
<div class="cmp-css-a">Component A</div>
<div class="cmp-css-b">Component B</div>
`,
styles: `
.cmp-css { color: red }
.cmp-css-a { color: green }
.cmp-css-b { color: blue }
@media (max-width: 992px) {
.cmp-css{ color: darkred; }
.cmp-css-a { color: darkgreen }
.cmp-css-b { color: darkblue }
}
`,
},
},
});
```
This approach allows the editor to group these styles (CSSRule instances) and remove them accordingly in case all references of the same component are removed.
## Components & JS ## Components & JS
If you want to know how to create Components with javascript attached (eg. counters, galleries, slideshows, etc.) check the dedicated page If you want to know how to create Components with javascript attached (eg. counters, galleries, slideshows, etc.) check the dedicated page

33
src/css_composer/index.js

@ -190,6 +190,7 @@ export default () => {
opt.state = s; opt.state = s;
opt.mediaText = w; opt.mediaText = w;
opt.selectors = []; opt.selectors = [];
w && (opt.atRuleType = 'media');
rule = new CssRule(opt, c); rule = new CssRule(opt, c);
rule.get('selectors').add(selectors, addOpts); rule.get('selectors').add(selectors, addOpts);
rules.add(rule, addOpts); rules.add(rule, addOpts);
@ -199,16 +200,16 @@ export default () => {
/** /**
* Get the rule * Get the rule
* @param {Array<Selector>} selectors Array of selectors * @param {String|Array<Selector>} selectors Array of selectors or selector string, eg `.myClass1.myClass2`
* @param {String} state Css rule state * @param {String} state Css rule state, eg. 'hover'
* @param {String} width For which device this style is oriented * @param {String} width Media rule value, eg. '(max-width: 992px)'
* @param {Object} ruleProps Other rule props * @param {Object} ruleProps Other rule props
* @return {Model|null} * @return {Model|null}
* @example * @example
* var sm = editor.SelectorManager; * const sm = editor.SelectorManager;
* var sel1 = sm.add('myClass1'); * const sel1 = sm.add('myClass1');
* var sel2 = sm.add('myClass2'); * const sel2 = sm.add('myClass2');
* var rule = cssComposer.get([sel1, sel2], 'hover'); * const rule = cssComposer.get([sel1, sel2], 'hover', '(max-width: 992px)');
* // Update the style * // Update the style
* rule.set('style', { * rule.set('style', {
* width: '300px', * width: '300px',
@ -216,12 +217,18 @@ export default () => {
* }); * });
* */ * */
get(selectors, state, width, ruleProps) { get(selectors, state, width, ruleProps) {
var rule = null; let slc = selectors;
rules.each(m => { if (isString(selectors)) {
if (rule) return; const sm = em.get('SelectorManager');
if (m.compare(selectors, state, width, ruleProps)) rule = m; const singleSel = selectors.split(',')[0].trim();
}); const node = em
return rule; .get('Parser')
.parserCss.checkNode({ selectors: singleSel })[0];
slc = sm.get(node.selectors);
}
return (
rules.find(rule => rule.compare(slc, state, width, ruleProps)) || null
);
}, },
/** /**

62
src/css_composer/model/CssRule.js

@ -1,7 +1,6 @@
import { map } from 'underscore';
import { Model } from 'backbone'; import { Model } from 'backbone';
import Styleable from 'domain_abstract/model/Styleable'; import Styleable from 'domain_abstract/model/Styleable';
import { isEmpty, forEach, isString } from 'underscore'; import { isEmpty, forEach, isString, isArray } from 'underscore';
import Selectors from 'selector_manager/model/Selectors'; import Selectors from 'selector_manager/model/Selectors';
import { isEmptyObj, hasWin } from 'utils/mixins'; import { isEmptyObj, hasWin } from 'utils/mixins';
@ -19,7 +18,7 @@ export default class CssRule extends Model.extend(Styleable) {
// Css properties style // Css properties style
style: {}, style: {},
// On which device width this rule should be rendered, eg. @media (max-width: 1000px) // On which device width this rule should be rendered, eg. `(max-width: 1000px)`
mediaText: '', mediaText: '',
// State of the rule, eg: hover | pressed | focused // State of the rule, eg: hover | pressed | focused
@ -33,12 +32,12 @@ export default class CssRule extends Model.extend(Styleable) {
// This particolar property is used only on at-rules, like 'page' or // This particolar property is used only on at-rules, like 'page' or
// 'font-face', where the block containes only style declarations // 'font-face', where the block containes only style declarations
singleAtRule: 0, singleAtRule: false,
// If true, sets '!important' on all properties // If true, sets '!important' on all properties
// You can use an array to specify properties to set important // You can use an array to specify properties to set important
// Used in view // Used in view
important: 0, important: false,
group: '', group: '',
@ -205,41 +204,44 @@ export default class CssRule extends Model.extend(Styleable) {
/** /**
* Compare the actual model with parameters * Compare the actual model with parameters
* @param {Object} selectors Collection of selectors * @param {Object} selectors Collection of selectors
* @param {String} state Css rule state * @param {String} state Css rule state
* @param {String} width For which device this style is oriented * @param {String} width For which device this style is oriented
* @param {Object} ruleProps Other rule props * @param {Object} ruleProps Other rule props
* @return {Boolean} * @returns {Boolean}
* @private * @private
*/ */
compare(selectors, state, width, ruleProps = {}) { compare(selectors, state, width, ruleProps = {}) {
var st = state || ''; const st = state || '';
var wd = width || ''; const wd = width || '';
var selectorsAdd = ruleProps.selectorsAdd || ''; const selAdd = ruleProps.selectorsAdd || '';
var atRuleType = ruleProps.atRuleType || ''; let atRule = ruleProps.atRuleType || '';
if (!(selectors instanceof Array) && !selectors.models) const sel =
selectors = [selectors]; !isArray(selectors) && !selectors.models
var a1 = map(selectors.models || selectors, model => model.getFullName()); ? [selectors]
var a2 = map(this.get('selectors').models, model => model.getFullName()); : selectors.models || selectors;
var f = false;
// Fix atRuleType in case is not specified with width
if (a1.length !== a2.length) return f; if (wd && !atRule) atRule = 'media';
for (var i = 0; i < a1.length; i++) { const a1 = sel.map(model => model.getFullName());
var re = 0; const a2 = this.get('selectors').map(model => model.getFullName());
for (var j = 0; j < a2.length; j++) {
if (a1[i] === a2[j]) re = 1; // Check selectors
} const a1S = a1.slice().sort();
if (re === 0) return f; const a2S = a2.slice().sort();
if (a1.length !== a2.length || !a1S.every((v, i) => v === a2S[i])) {
return false;
} }
// Check other properties
if ( if (
this.get('state') !== st || this.get('state') !== st ||
this.get('mediaText') !== wd || this.get('mediaText') !== wd ||
this.get('selectorsAdd') !== selectorsAdd || this.get('selectorsAdd') !== selAdd ||
this.get('atRuleType') !== atRuleType this.get('atRuleType') !== atRule
) { ) {
return f; return false;
} }
return true; return true;

8
src/style_manager/index.js

@ -287,7 +287,13 @@ export default () => {
if (hasClasses && useClasses) { if (hasClasses && useClasses) {
const deviceW = em.getCurrentMedia(); const deviceW = em.getCurrentMedia();
rule = cssC.get(valid, state, deviceW); rule = cssC.get(valid, state, deviceW);
console.log('SM', {
valid,
state,
deviceW,
rule,
validNames: valid.map(v => v.getFullName())
});
if (!rule && !skipAdd) { if (!rule && !skipAdd) {
rule = cssC.add(valid, state, deviceW, {}, addOpts); rule = cssC.add(valid, state, deviceW, {}, addOpts);
} }

62
test/specs/css_composer/index.js

@ -1,8 +1,4 @@
import Models from './model/CssModels';
import CssRuleView from './view/CssRuleView';
import CssRulesView from './view/CssRulesView';
import CssComposer from 'css_composer'; import CssComposer from 'css_composer';
import e2e from './e2e/CssComposer';
import utils from './../../test_utils.js'; import utils from './../../test_utils.js';
import Editor from 'editor/model/Editor'; import Editor from 'editor/model/Editor';
@ -32,6 +28,12 @@ describe('Css Composer', () => {
config.em = editorModel; config.em = editorModel;
}; };
const getCSS = obj =>
obj
.getAll()
.map(r => r.toCSS())
.join('');
beforeEach(() => { beforeEach(() => {
em = new Editor({}); em = new Editor({});
config = { em }; config = { em };
@ -39,7 +41,7 @@ describe('Css Composer', () => {
}); });
afterEach(() => { afterEach(() => {
obj = null; em.destroy();
}); });
test('Object exists', () => { test('Object exists', () => {
@ -341,5 +343,55 @@ describe('Css Composer', () => {
expect(obj.get(ruleClass.getSelectors())).toBe(ruleClass); expect(obj.get(ruleClass.getSelectors())).toBe(ruleClass);
expect(obj.get(ruleId.getSelectors())).toBe(ruleId); expect(obj.get(ruleId.getSelectors())).toBe(ruleId);
}); });
describe('Collections', () => {
test('Add a single rule as CSS string', () => {
const cssRule = `.test-rule{color:red;}`;
obj.addCollection(cssRule);
expect(obj.getAll().length).toEqual(1);
expect(getCSS(obj)).toEqual(cssRule);
});
test('Add multiple rules as CSS string', () => {
const cssRules = [
`.test-rule{color:red;}`,
`.test-rule:hover{color:blue;}`,
`@media (max-width: 992px){.test-rule{color:darkred;}}`,
`@media (max-width: 992px){.test-rule:hover{color:darkblue;}}`
];
const cssString = cssRules.join('');
obj.addCollection(cssString);
expect(obj.getAll().length).toEqual(cssRules.length);
expect(getCSS(obj)).toEqual(cssString);
});
test('Able to return the rule inserted as string', () => {
const cssRule = `
.test-rule1 {color:red;}
.test-rule2:hover {
color: blue;
}
@media (max-width: 992px) {
.test-rule3 {
color: darkred;
}
.test-rule4:hover {
color: darkblue;
}
}
`;
const result = obj.addCollection(cssRule);
const [rule1, rule2, rule3, rule4] = result;
expect(result.length).toEqual(4);
expect(obj.getAll().length).toEqual(4);
expect(obj.get('.test-rule1')).toBe(rule1);
expect(obj.get('.test-rule1', 'hover')).toBe(null);
expect(obj.get('.test-rule2', 'hover')).toBe(rule2);
expect(rule3.get('mediaText')).toBe('(max-width: 992px)');
expect(obj.get('.test-rule3', null, '(max-width: 992px)')).toBe(rule3);
expect(obj.get('.test-rule4', 'hover', '(max-width: 992px)')).toBe(
rule4
);
});
});
}); });
}); });

Loading…
Cancel
Save