Browse Source

Fix CssComposer get

pull/3905/head
Artur Arseniev 4 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
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.mediaText = w;
opt.selectors = [];
w && (opt.atRuleType = 'media');
rule = new CssRule(opt, c);
rule.get('selectors').add(selectors, addOpts);
rules.add(rule, addOpts);
@ -199,16 +200,16 @@ export default () => {
/**
* Get the rule
* @param {Array<Selector>} selectors Array of selectors
* @param {String} state Css rule state
* @param {String} width For which device this style is oriented
* @param {String|Array<Selector>} selectors Array of selectors or selector string, eg `.myClass1.myClass2`
* @param {String} state Css rule state, eg. 'hover'
* @param {String} width Media rule value, eg. '(max-width: 992px)'
* @param {Object} ruleProps Other rule props
* @return {Model|null}
* @example
* var sm = editor.SelectorManager;
* var sel1 = sm.add('myClass1');
* var sel2 = sm.add('myClass2');
* var rule = cssComposer.get([sel1, sel2], 'hover');
* const sm = editor.SelectorManager;
* const sel1 = sm.add('myClass1');
* const sel2 = sm.add('myClass2');
* const rule = cssComposer.get([sel1, sel2], 'hover', '(max-width: 992px)');
* // Update the style
* rule.set('style', {
* width: '300px',
@ -216,12 +217,18 @@ export default () => {
* });
* */
get(selectors, state, width, ruleProps) {
var rule = null;
rules.each(m => {
if (rule) return;
if (m.compare(selectors, state, width, ruleProps)) rule = m;
});
return rule;
let slc = selectors;
if (isString(selectors)) {
const sm = em.get('SelectorManager');
const singleSel = selectors.split(',')[0].trim();
const node = em
.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 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 { isEmptyObj, hasWin } from 'utils/mixins';
@ -19,7 +18,7 @@ export default class CssRule extends Model.extend(Styleable) {
// Css properties 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: '',
// 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
// 'font-face', where the block containes only style declarations
singleAtRule: 0,
singleAtRule: false,
// If true, sets '!important' on all properties
// You can use an array to specify properties to set important
// Used in view
important: 0,
important: false,
group: '',
@ -205,41 +204,44 @@ export default class CssRule extends Model.extend(Styleable) {
/**
* Compare the actual model with parameters
* @param {Object} selectors Collection of selectors
* @param {String} state Css rule state
* @param {String} width For which device this style is oriented
* @param {Object} selectors Collection of selectors
* @param {String} state Css rule state
* @param {String} width For which device this style is oriented
* @param {Object} ruleProps Other rule props
* @return {Boolean}
* @returns {Boolean}
* @private
*/
compare(selectors, state, width, ruleProps = {}) {
var st = state || '';
var wd = width || '';
var selectorsAdd = ruleProps.selectorsAdd || '';
var atRuleType = ruleProps.atRuleType || '';
if (!(selectors instanceof Array) && !selectors.models)
selectors = [selectors];
var a1 = map(selectors.models || selectors, model => model.getFullName());
var a2 = map(this.get('selectors').models, model => model.getFullName());
var f = false;
if (a1.length !== a2.length) return f;
for (var i = 0; i < a1.length; i++) {
var re = 0;
for (var j = 0; j < a2.length; j++) {
if (a1[i] === a2[j]) re = 1;
}
if (re === 0) return f;
const st = state || '';
const wd = width || '';
const selAdd = ruleProps.selectorsAdd || '';
let atRule = ruleProps.atRuleType || '';
const sel =
!isArray(selectors) && !selectors.models
? [selectors]
: selectors.models || selectors;
// Fix atRuleType in case is not specified with width
if (wd && !atRule) atRule = 'media';
const a1 = sel.map(model => model.getFullName());
const a2 = this.get('selectors').map(model => model.getFullName());
// Check selectors
const a1S = a1.slice().sort();
const a2S = a2.slice().sort();
if (a1.length !== a2.length || !a1S.every((v, i) => v === a2S[i])) {
return false;
}
// Check other properties
if (
this.get('state') !== st ||
this.get('mediaText') !== wd ||
this.get('selectorsAdd') !== selectorsAdd ||
this.get('atRuleType') !== atRuleType
this.get('selectorsAdd') !== selAdd ||
this.get('atRuleType') !== atRule
) {
return f;
return false;
}
return true;

8
src/style_manager/index.js

@ -287,7 +287,13 @@ export default () => {
if (hasClasses && useClasses) {
const deviceW = em.getCurrentMedia();
rule = cssC.get(valid, state, deviceW);
console.log('SM', {
valid,
state,
deviceW,
rule,
validNames: valid.map(v => v.getFullName())
});
if (!rule && !skipAdd) {
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 e2e from './e2e/CssComposer';
import utils from './../../test_utils.js';
import Editor from 'editor/model/Editor';
@ -32,6 +28,12 @@ describe('Css Composer', () => {
config.em = editorModel;
};
const getCSS = obj =>
obj
.getAll()
.map(r => r.toCSS())
.join('');
beforeEach(() => {
em = new Editor({});
config = { em };
@ -39,7 +41,7 @@ describe('Css Composer', () => {
});
afterEach(() => {
obj = null;
em.destroy();
});
test('Object exists', () => {
@ -341,5 +343,55 @@ describe('Css Composer', () => {
expect(obj.get(ruleClass.getSelectors())).toBe(ruleClass);
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