mirror of https://github.com/artf/grapesjs.git
nocodeframeworkdrag-and-dropsite-buildersite-generatortemplate-builderui-builderweb-builderweb-builder-frameworkwebsite-builderno-codepage-builder
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
426 lines
12 KiB
426 lines
12 KiB
/**
|
|
* This module contains and manage CSS rules for the template inside the canvas.
|
|
* You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object](https://github.com/artf/grapesjs/blob/master/src/css_composer/config/config.js)
|
|
* ```js
|
|
* const editor = grapesjs.init({
|
|
* cssComposer: {
|
|
* // options
|
|
* }
|
|
* })
|
|
* ```
|
|
*
|
|
* Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance
|
|
*
|
|
* ```js
|
|
* const cssComposer = editor.CssComposer;
|
|
* ```
|
|
*
|
|
* * [load](#load)
|
|
* * [store](#store)
|
|
* * [add](#add)
|
|
* * [get](#get)
|
|
* * [getAll](#getall)
|
|
* * [clear](#clear)
|
|
* * [setIdRule](#setidrule)
|
|
* * [getIdRule](#getidrule)
|
|
* * [setClassRule](#setclassrule)
|
|
* * [getClassRule](#getclassrule)
|
|
*
|
|
* @module CssComposer
|
|
*/
|
|
|
|
import { isArray } from 'underscore';
|
|
|
|
module.exports = () => {
|
|
let em;
|
|
var c = {},
|
|
defaults = require('./config/config'),
|
|
CssRule = require('./model/CssRule'),
|
|
CssRules = require('./model/CssRules'),
|
|
CssRulesView = require('./view/CssRulesView');
|
|
const Selectors = require('selector_manager/model/Selectors');
|
|
const Selector = require('selector_manager/model/Selector');
|
|
|
|
var rules, rulesView;
|
|
|
|
return {
|
|
Selectors,
|
|
|
|
/**
|
|
* Name of the module
|
|
* @type {String}
|
|
* @private
|
|
*/
|
|
name: 'CssComposer',
|
|
|
|
/**
|
|
* Mandatory for the storage manager
|
|
* @type {String}
|
|
* @private
|
|
*/
|
|
storageKey() {
|
|
var keys = [];
|
|
var smc = (c.stm && c.stm.getConfig()) || {};
|
|
if (smc.storeCss) keys.push('css');
|
|
if (smc.storeStyles) keys.push('styles');
|
|
return keys;
|
|
},
|
|
|
|
/**
|
|
* Initializes module. Automatically called with a new instance of the editor
|
|
* @param {Object} config Configurations
|
|
* @private
|
|
*/
|
|
init(config) {
|
|
c = config || {};
|
|
for (var name in defaults) {
|
|
if (!(name in c)) c[name] = defaults[name];
|
|
}
|
|
|
|
var ppfx = c.pStylePrefix;
|
|
if (ppfx) c.stylePrefix = ppfx + c.stylePrefix;
|
|
|
|
var elStyle = (c.em && c.em.config.style) || '';
|
|
c.rules = elStyle || c.rules;
|
|
|
|
em = c.em;
|
|
rules = new CssRules([], c);
|
|
rulesView = new CssRulesView({
|
|
collection: rules,
|
|
config: c
|
|
});
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* On load callback
|
|
* @private
|
|
*/
|
|
onLoad() {
|
|
rules.add(c.rules);
|
|
},
|
|
|
|
/**
|
|
* Do stuff after load
|
|
* @param {Editor} em
|
|
* @private
|
|
*/
|
|
postLoad(em) {
|
|
const ev = 'add remove';
|
|
const rules = this.getAll();
|
|
const um = em.get('UndoManager');
|
|
um && um.add(rules);
|
|
em.stopListening(rules, ev, this.handleChange);
|
|
em.listenTo(rules, ev, this.handleChange);
|
|
rules.each(rule => this.handleChange(rule, { avoidStore: 1 }));
|
|
},
|
|
|
|
/**
|
|
* Handle rule changes
|
|
* @private
|
|
*/
|
|
handleChange(model, opts = {}) {
|
|
const ev = 'change:style';
|
|
const um = em.get('UndoManager');
|
|
um && um.add(model);
|
|
const handleUpdates = em.handleUpdates.bind(em);
|
|
em.stopListening(model, ev, handleUpdates);
|
|
em.listenTo(model, ev, handleUpdates);
|
|
!opts.avoidStore && handleUpdates('', '', opts);
|
|
},
|
|
|
|
/**
|
|
* Load data from the passed object, if the object is empty will try to fetch them
|
|
* autonomously from the storage manager.
|
|
* The fetched data will be added to the collection
|
|
* @param {Object} data Object of data to load
|
|
* @return {Object} Loaded rules
|
|
*/
|
|
load(data) {
|
|
var d = data || '';
|
|
|
|
if (!d && c.stm) {
|
|
d = c.em.getCacheLoad();
|
|
}
|
|
|
|
var obj = d.styles || '';
|
|
|
|
if (d.styles) {
|
|
try {
|
|
obj = JSON.parse(d.styles);
|
|
} catch (err) {}
|
|
} else if (d.css) {
|
|
obj = c.em.get('Parser').parseCss(d.css);
|
|
}
|
|
|
|
if (isArray(obj)) {
|
|
obj.length && rules.reset(obj);
|
|
} else if (obj) {
|
|
rules.reset(obj);
|
|
}
|
|
|
|
return obj;
|
|
},
|
|
|
|
/**
|
|
* Store data to the selected storage
|
|
* @param {Boolean} noStore If true, won't store
|
|
* @return {Object} Data to store
|
|
*/
|
|
store(noStore) {
|
|
if (!c.stm) return;
|
|
var obj = {};
|
|
var keys = this.storageKey();
|
|
if (keys.indexOf('css') >= 0) obj.css = c.em.getCss();
|
|
if (keys.indexOf('styles') >= 0) obj.styles = JSON.stringify(rules);
|
|
if (!noStore) c.stm.store(obj);
|
|
return obj;
|
|
},
|
|
|
|
/**
|
|
* Add new rule to the collection, if not yet exists with the same selectors
|
|
* @param {Array<Selector>} selectors Array of selectors
|
|
* @param {String} state Css rule state
|
|
* @param {String} width For which device this style is oriented
|
|
* @param {Object} opts Other options for the rule
|
|
* @return {Model}
|
|
* @example
|
|
* var sm = editor.SelectorManager;
|
|
* var sel1 = sm.add('myClass1');
|
|
* var sel2 = sm.add('myClass2');
|
|
* var rule = cssComposer.add([sel1, sel2], 'hover');
|
|
* rule.set('style', {
|
|
* width: '100px',
|
|
* color: '#fff',
|
|
* });
|
|
* */
|
|
add(selectors, state, width, opts = {}) {
|
|
var s = state || '';
|
|
var w = width || '';
|
|
var opt = { ...opts };
|
|
var rule = this.get(selectors, s, w, opt);
|
|
|
|
// do not create rules that were found before
|
|
// unless this is a single at-rule, for which multiple declarations
|
|
// make sense (e.g. multiple `@font-type`s)
|
|
if (rule && rule.config && !rule.config.singleAtRule) {
|
|
return rule;
|
|
} else {
|
|
opt.state = s;
|
|
opt.mediaText = w;
|
|
opt.selectors = '';
|
|
rule = new CssRule(opt, c);
|
|
rule.get('selectors').add(selectors);
|
|
rules.add(rule);
|
|
return rule;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 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 {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');
|
|
* // Update the style
|
|
* rule.set('style', {
|
|
* width: '300px',
|
|
* color: '#000',
|
|
* });
|
|
* */
|
|
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;
|
|
},
|
|
|
|
/**
|
|
* Get the collection of rules
|
|
* @return {Collection}
|
|
* */
|
|
getAll() {
|
|
return rules;
|
|
},
|
|
|
|
/**
|
|
* Remove all rules
|
|
* @return {this}
|
|
*/
|
|
clear() {
|
|
this.getAll().reset();
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Add a raw collection of rule objects
|
|
* This method overrides styles, in case, of already defined rule
|
|
* @param {Array<Object>} data Array of rule objects, eg . [{selectors: ['class1'], style: {....}}, ..]
|
|
* @param {Object} opts Options
|
|
* @return {Array<Model>}
|
|
* @private
|
|
*/
|
|
addCollection(data, opts = {}) {
|
|
var result = [];
|
|
var d = data instanceof Array ? data : [data];
|
|
|
|
for (var i = 0, l = d.length; i < l; i++) {
|
|
var rule = d[i] || {};
|
|
if (!rule.selectors) continue;
|
|
var sm = c.em && c.em.get('SelectorManager');
|
|
if (!sm) console.warn('Selector Manager not found');
|
|
var sl = rule.selectors;
|
|
var sels = sl instanceof Array ? sl : [sl];
|
|
var newSels = [];
|
|
|
|
for (var j = 0, le = sels.length; j < le; j++) {
|
|
var selec = sm.add(sels[j]);
|
|
newSels.push(selec);
|
|
}
|
|
|
|
var modelExists = this.get(newSels, rule.state, rule.mediaText, rule);
|
|
var model = this.add(newSels, rule.state, rule.mediaText, rule);
|
|
var updateStyle = !modelExists || !opts.avoidUpdateStyle;
|
|
const style = rule.style || {};
|
|
|
|
if (updateStyle) {
|
|
let styleUpdate = opts.extend
|
|
? { ...model.get('style'), ...style }
|
|
: style;
|
|
model.set('style', styleUpdate);
|
|
}
|
|
|
|
result.push(model);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
|
|
setRule(selectors, style, opts = {}) {
|
|
const { atRuleType, atRuleParams } = opts;
|
|
const node = em.get('Parser').parserCss.checkNode({
|
|
selectors,
|
|
style
|
|
})[0];
|
|
const { state, selectorsAdd } = node;
|
|
const sm = em.get('SelectorManager');
|
|
const selector = sm.add(node.selectors);
|
|
const rule = this.add(selector, state, atRuleParams, {
|
|
selectorsAdd,
|
|
atRule: atRuleType
|
|
});
|
|
rule.setStyle(style, opts);
|
|
return rule;
|
|
},
|
|
|
|
getRule(selectors, opts = {}) {
|
|
const sm = em.get('SelectorManager');
|
|
const node = em.get('Parser').parserCss.checkNode({ selectors })[0];
|
|
const selector = sm.get(node.selectors);
|
|
const { state, selectorsAdd } = node;
|
|
const { atRuleType, atRuleParams } = opts;
|
|
return (
|
|
selector &&
|
|
this.get(selector, state, atRuleParams, {
|
|
selectorsAdd,
|
|
atRule: atRuleType
|
|
})
|
|
);
|
|
},
|
|
|
|
/**
|
|
* Add/update the CSS rule with id selector
|
|
* @param {string} name Id selector name, eg. 'my-id'
|
|
* @param {Object} style Style properties and values
|
|
* @param {Object} [opts={}] Custom options, like `state` and `mediaText`
|
|
* @return {CssRule} The new/updated rule
|
|
* @example
|
|
* const rule = cc.setIdRule('myid', { color: 'red' });
|
|
* const ruleHover = cc.setIdRule('myid', { color: 'blue' }, { state: 'hover' });
|
|
* // This will add current CSS:
|
|
* // #myid { color: red }
|
|
* // #myid:hover { color: blue }
|
|
*/
|
|
setIdRule(name, style = {}, opts = {}) {
|
|
const state = opts.state || '';
|
|
const media = opts.mediaText || em.getCurrentMedia();
|
|
const sm = em.get('SelectorManager');
|
|
const selector = sm.add({ name, type: Selector.TYPE_ID });
|
|
const rule = this.add(selector, state, media);
|
|
rule.setStyle(style, opts);
|
|
return rule;
|
|
},
|
|
|
|
/**
|
|
* Get the CSS rule by id selector
|
|
* @param {string} name Id selector name, eg. 'my-id'
|
|
* @param {Object} [opts={}] Custom options, like `state` and `mediaText`
|
|
* @return {CssRule}
|
|
* @example
|
|
* const rule = cc.getIdRule('myid');
|
|
* const ruleHover = cc.setIdRule('myid', { state: 'hover' });
|
|
*/
|
|
getIdRule(name, opts = {}) {
|
|
const state = opts.state || '';
|
|
const media = opts.mediaText || em.getCurrentMedia();
|
|
const selector = em.get('SelectorManager').get(name, Selector.TYPE_ID);
|
|
return selector && this.get(selector, state, media);
|
|
},
|
|
|
|
/**
|
|
* Add/update the CSS rule with class selector
|
|
* @param {string} name Class selector name, eg. 'my-class'
|
|
* @param {Object} style Style properties and values
|
|
* @param {Object} [opts={}] Custom options, like `state` and `mediaText`
|
|
* @return {CssRule} The new/updated rule
|
|
* @example
|
|
* const rule = cc.setClassRule('myclass', { color: 'red' });
|
|
* const ruleHover = cc.setClassRule('myclass', { color: 'blue' }, { state: 'hover' });
|
|
* // This will add current CSS:
|
|
* // .myclass { color: red }
|
|
* // .myclass:hover { color: blue }
|
|
*/
|
|
setClassRule(name, style = {}, opts = {}) {
|
|
const state = opts.state || '';
|
|
const media = opts.mediaText || em.getCurrentMedia();
|
|
const sm = em.get('SelectorManager');
|
|
const selector = sm.add({ name, type: Selector.TYPE_CLASS });
|
|
const rule = this.add(selector, state, media);
|
|
rule.setStyle(style, opts);
|
|
return rule;
|
|
},
|
|
|
|
/**
|
|
* Get the CSS rule by class selector
|
|
* @param {string} name Class selector name, eg. 'my-class'
|
|
* @param {Object} [opts={}] Custom options, like `state` and `mediaText`
|
|
* @return {CssRule}
|
|
* @example
|
|
* const rule = cc.getClassRule('myclass');
|
|
* const ruleHover = cc.getClassRule('myclass', { state: 'hover' });
|
|
*/
|
|
getClassRule(name, opts = {}) {
|
|
const state = opts.state || '';
|
|
const media = opts.mediaText || em.getCurrentMedia();
|
|
const selector = em.get('SelectorManager').get(name, Selector.TYPE_CLASS);
|
|
return selector && this.get(selector, state, media);
|
|
},
|
|
|
|
/**
|
|
* Render the block of CSS rules
|
|
* @return {HTMLElement}
|
|
* @private
|
|
*/
|
|
render() {
|
|
return rulesView.render().el;
|
|
}
|
|
};
|
|
};
|
|
|