').get(0),P=h+S+"-el",A=h+T+"-el",O=P+" "+(h+S),L=A+" "+(h+T);g=n('
').get(0),v=n('
').get(0),m=n('
').get(0),y=n('
').get(0),b=n('
').get(0),x=n('
').get(0),w=n('
').get(0),C=n('
').get(0),this["marginT"+o]=g,this["marginB"+o]=v,this["marginL"+o]=m,this["marginR"+o]=y,this["padT"+o]=b,this["padB"+o]=x,this["padL"+o]=w,this["padR"+o]=C,E.appendChild(g),E.appendChild(v),E.appendChild(m),E.appendChild(y),M.appendChild(b),M.appendChild(x),M.appendChild(w),M.appendChild(C),p.appendChild(E),p.appendChild(M),this[d]="1"}var N=u.marginLeft.replace("px",""),D=parseInt(u.marginTop.replace("px","")),I=parseInt(u.marginBottom.replace("px","")),_=g.style,F=v.style,R=m.style,z=y.style,V=b.style,$=x.style,H=w.style,j=C.style,B=parseInt(c.left);_.height=u.marginTop,_.width=u.width,_.top=c.top-u.marginTop.replace("px","")+"px",_.left=B+"px",F.height=u.marginBottom,F.width=u.width,F.top=c.top+c.height+"px",F.left=B+"px";var W=c.height+D+I+"px",U=c.top-D+"px";R.height=W,R.width=u.marginLeft,R.top=U,R.left=B-N+"px",z.height=W,z.width=u.marginRight,z.top=U,z.left=B+c.width+"px";var q=parseInt(u.paddingTop.replace("px",""));V.height=u.paddingTop,V.width=u.width,V.top=c.top+"px",V.left=B+"px";var K=parseInt(u.paddingBottom.replace("px",""));$.height=u.paddingBottom,$.width=u.width,$.top=c.top+c.height-K+"px",$.left=B+"px";var G=c.height-K-q+"px",Y=c.top+q+"px";H.height=G,H.width=u.paddingLeft,H.top=Y,H.left=c.left+"px";var X=parseInt(u.paddingRight.replace("px",""));j.height=G,j.width=u.paddingRight,j.top=Y,j.left=c.left+c.width-X+"px"}},stop:function(t,e,n){var i=n||{},r=i.state||"",o=this.getOffsetMethod(r);t.Canvas[o]().style.display="none"}}}).call(e,n(0))},function(t,e,n){"use strict";t.exports={run:function(t){for(var e=t.getSelected(),n=e&&e.parent();n&&!n.get("selectable");)n=n.parent();n&&t.select(n)}}},function(t,e,n){"use strict";t.exports={isEnabled:function(){var t=document;return t.fullscreenElement||t.webkitFullscreenElement||t.mozFullScreenElement?1:0},enable:function(t){var e="";return t.requestFullscreen?t.requestFullscreen():t.webkitRequestFullscreen?(e="webkit",t.webkitRequestFullscreen()):t.mozRequestFullScreen?(e="moz",t.mozRequestFullScreen()):t.msRequestFullscreen?t.msRequestFullscreen():console.warn("Fullscreen not supported"),e},disable:function(){var t=document;t.exitFullscreen?t.exitFullscreen():t.webkitExitFullscreen?t.webkitExitFullscreen():t.mozCancelFullScreen?t.mozCancelFullScreen():t.msExitFullscreen&&t.msExitFullscreen()},fsChanged:function(t,e){var n=(document,(t||"")+"fullscreenchange");this.isEnabled()||(this.stop(null,this.sender),document.removeEventListener(n,this.fsChanged))},run:function(t,e){this.sender=e;var n=this.enable(t.getContainer());this.fsChanged=this.fsChanged.bind(this,n),document.addEventListener(n+"fullscreenchange",this.fsChanged),t&&t.trigger("change:canvasOffset")},stop:function(t,e){e&&e.set&&e.set("active",!1),this.disable(),t&&t.trigger("change:canvasOffset")}}},function(t,e,n){"use strict";(function(e){t.exports={getPanels:function(t){return this.panels||(this.panels=t.Panels.getPanelsEl()),this.panels},tglPointers:function(t,n){var i=t.Canvas.getBody().querySelectorAll("."+this.ppfx+"no-pointer");e.each(i,function(t){t.style.pointerEvents=n?"":"all"})},run:function(t,e){e&&e.set&&e.set("active",!1),t.stopCommand("sw-visibility"),t.getModel().stopDefault();var n=this.getPanels(t),i=t.Canvas.getElement(),r=t.getEl(),o=t.Config.stylePrefix;this.helper||(this.helper=document.createElement("span"),this.helper.className=o+"off-prv fa fa-eye-slash",r.appendChild(this.helper),this.helper.onclick=function(){t.stopCommand("preview")}),this.helper.style.display="inline-block",this.tglPointers(t),n.style.display="none";var s=i.style;s.width="100%",s.height="100%",s.top="0",s.left="0",s.padding="0",s.margin="0",t.trigger("change:canvasOffset")},stop:function(t,e){var n=this.getPanels(t);t.runCommand("sw-visibility"),t.getModel().runDefault(),n.style.display="block",t.Canvas.getElement().setAttribute("style",""),this.helper&&(this.helper.style.display="none"),t.trigger("change:canvasOffset"),this.tglPointers(t,1)}}}).call(e,n(1))},function(t,e,n){"use strict";t.exports={run:function(t,e,n){var i=n||{},r=i.el||"",o=t.Canvas,s=this.canvasResizer,a=i.options||{},l=o.getCanvasView();return a.ratioDefault=1,a.appendTo=o.getResizerEl(),a.prefix=t.getConfig().stylePrefix,a.posFetcher=l.getElementPos.bind(l),a.mousePosFetcher=o.getMouseRelativePos,s&&!i.forceNew||(this.canvasResizer=t.Utils.Resizer.init(a),s=this.canvasResizer),s.setOptions(a),s.focus(r),s},stop:function(){var t=this.canvasResizer;t&&t.blur()}}},function(t,e,n){"use strict";t.exports={run:function(t,e,n){var i=n&&n.el||"",r=t.Canvas,o=this.dragger,s=n.options||{},a=r.getCanvasView();return s.prefix=t.getConfig().stylePrefix,s.mousePosFetcher=r.getMouseRelativePos,s.posFetcher=a.getElementPos.bind(a),o||(o=t.Utils.Dragger.init(s),this.dragger=o),o.setOptions(s),o.focus(i),s.event&&o.start(s.event),o},stop:function(){this.canvasResizer&&this.canvasResizer.blur()}}},function(t,e,n){"use strict";var i=n(1);t.exports=function(){var t,e,r,o={},s=n(210),a=n(211),l=n(213),c=n(214),u=[];return{name:"BlockManager",init:function(n){o=n||{};var i=o.em;for(var h in s)h in o||(o[h]=s[h]);return t=new a([]),e=new a([]),u=new l,r=new c({collection:e,categories:u},o),t.listenTo(t,"add",function(t){e.add(t),i&&i.trigger("block:add",t)}),t.listenTo(t,"remove",function(t){e.remove(t),i&&i.trigger("block:remove",t)}),t.listenTo(t,"reset",function(t){e.reset(t.models)}),this},getConfig:function(){return o},onLoad:function(){var t=this.getAll();!t.length&&t.reset(o.blocks)},postRender:function(){var t=this.getConfig().appendTo;if(t){((0,i.isElement)(t)?t:document.querySelector(t)).appendChild(this.render())}},add:function(e,n){var i=n||{};return i.id=e,t.add(i)},get:function(e){return t.get(e)},getAll:function(){return t},getAllVisible:function(){return e},remove:function(e){return t.remove(e)},getCategories:function(){return u},getContainer:function(){return r.el},render:function(t){var e=t||this.getAll().models;return r.rendered||(r.render(),r.rendered=1),r.collection.reset(e),this.getContainer()}}}},function(t,e,n){"use strict";t.exports={appendTo:"",blocks:[]}},function(t,e,n){"use strict";var i=n(0),r=n(212);t.exports=i.Collection.extend({model:r})},function(t,e,n){"use strict";var i=n(0),r=n(58);t.exports=i.Model.extend({defaults:{label:"",content:"",category:"",attributes:{}},initialize:function(){var t=(arguments.length>0&&void 0!==arguments[0]&&arguments[0],this.get("category"));if(t&&"string"==typeof t){new r({id:t,label:t})}}})},function(t,e,n){"use strict";var i=n(0);t.exports=i.Collection.extend({model:n(58)})},function(t,e,n){"use strict";(function(e){var i=n(1),r=n(215),o=n(216);t.exports=n(0).View.extend({initialize:function(t,n){e.bindAll(this,"getSorter","onDrag","onDrop"),this.config=n||{},this.categories=t.categories||"",this.renderedCategories=[];var i=this.config.pStylePrefix||"";this.ppfx=i,this.noCatClass=i+"blocks-no-cat",this.blockContClass=i+"blocks-c",this.catsClass=i+"block-categories";var r=this.collection;this.listenTo(r,"add",this.addTo),this.listenTo(r,"reset",this.render),this.em=this.config.em,this.tac="test-tac",this.grabbingCls=this.ppfx+"grabbing",this.em&&(this.config.getSorter=this.getSorter,this.canvas=this.em.get("Canvas"))},getSorter:function(){if(this.em){if(!this.sorter){var t=this.em.get("Utils"),e=this.canvas;this.sorter=new t.Sorter({container:e.getBody(),placer:e.getPlacerEl(),containerSel:"*",itemSel:"*",pfx:this.ppfx,onStart:this.onDrag,onEndMove:this.onDrop,onMove:this.onMove,document:e.getFrameEl().contentDocument,direction:"a",wmargin:1,nested:1,em:this.em,canvasRelative:1})}return this.sorter}},onDrag:function(t){this.em.stopDefault(),this.em.trigger("block:drag:start",t)},onMove:function(t){this.em.trigger("block:drag:move",t)},onDrop:function(t){var e=this.em;e.runDefault(),t&&t.get&&(t.get("activeOnRender")&&(t.trigger("active"),t.set("activeOnRender",0)),e.trigger("block:drag:stop",t))},addTo:function(t){this.add(t)},add:function(t,e){var n=e||null,s=new r({model:t,attributes:t.get("attributes")},this.config),a=s.render().el,l=t.get("category");if(l&&this.categories){(0,i.isString)(l)?l={id:l,label:l}:(0,i.isObject)(l)&&!l.id&&(l.id=l.label);var c=this.categories.add(l),u=c.get("id"),h=this.renderedCategories[u],d=this.getCategoriesEl();return t.set("category",c),!h&&d&&(h=new o({model:c},this.config).render(),this.renderedCategories[u]=h,d.appendChild(h.el)),void(h&&h.append(a))}n?n.appendChild(a):this.append(a)},getCategoriesEl:function(){return this.catsEl||(this.catsEl=this.el.querySelector("."+this.catsClass)),this.catsEl},getBlocksEl:function(){return this.blocksEl||(this.blocksEl=this.el.querySelector("."+this.noCatClass+" ."+this.blockContClass)),this.blocksEl},append:function(t){var e=this.getBlocksEl();e&&e.appendChild(t)},render:function(){var t=this,e=this.ppfx,n=document.createDocumentFragment();this.catsEl=null,this.blocksEl=null,this.renderedCategories=[],this.el.innerHTML='\n
\n
\n ',this.collection.each(function(e){return t.add(e,n)}),this.append(n);var i=this.blockContClass+"s "+e+"one-bg "+e+"two-color";return this.$el.addClass(i),this}})}).call(e,n(1))},function(t,e,n){"use strict";(function(e){var i=n(1),r=n(2);t.exports=e.View.extend({events:{mousedown:"startDrag",dragstart:"handleDragStart",dragend:"handleDragEnd"},initialize:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};this.em=e.em,this.config=e,this.endDrag=this.endDrag.bind(this),this.ppfx=e.pStylePrefix||"",this.listenTo(this.model,"destroy remove",this.remove)},startDrag:function(t){var e=this.config;if(0===t.button&&e.getSorter&&!this.el.draggable){e.em.refreshCanvas();var n=e.getSorter();n.setDragHelper(this.el,t),n.setDropContent(this.model.get("content")),n.startSort(this.el),(0,r.on)(document,"mouseup",this.endDrag)}},handleDragStart:function(t){var e=this.model.get("content"),n=(0,i.isObject)(e),r=n?JSON.stringify(e):e;t.dataTransfer.setData("text",r),this.em.set("dragContent",e)},handleDragEnd:function(){this.em.set("dragContent","")},endDrag:function(t){(0,r.off)(document,"mouseup",this.endDrag);var e=this.config.getSorter();e.moved=0,e.endMove()},render:function(){var t=this.el,e=this.ppfx,n=e+"block",i=this.model.get("label");return t.className+=" "+n+" "+e+"one-bg "+e+"four-color-h",t.innerHTML='
'+i+"
",t.title=t.textContent.trim(),(0,r.hasDnd)(this.em)&&t.setAttribute("draggable",!0),this}})}).call(e,n(0))},function(t,e,n){"use strict";(function(e){var i=n(0);t.exports=i.View.extend({template:e.template('\n
\n \n <%= label %>\n
\n
\n '),events:{},initialize:function(){var t=(arguments.length>0&&void 0!==arguments[0]&&arguments[0],arguments.length>1&&void 0!==arguments[1]?arguments[1]:{});this.config=t;var e=this.config.pStylePrefix||"";this.pfx=e,this.caretR="fa fa-caret-right",this.caretD="fa fa-caret-down",this.iconClass=e+"caret-icon",this.activeClass=e+"open",this.className=e+"block-category",this.events["click ."+e+"title"]="toggle",this.listenTo(this.model,"change:open",this.updateVisibility),this.delegateEvents()},updateVisibility:function(){this.model.get("open")?this.open():this.close()},open:function(){this.el.className=this.className+" "+this.activeClass,this.getIconEl().className=this.iconClass+" "+this.caretD,this.getBlocksEl().style.display=""},close:function(){this.el.className=this.className,this.getIconEl().className=this.iconClass+" "+this.caretR,this.getBlocksEl().style.display="none"},toggle:function(){var t=this.model;t.set("open",!t.get("open"))},getIconEl:function(){return this.iconEl||(this.iconEl=this.el.querySelector("."+this.iconClass)),this.iconEl},getBlocksEl:function(){return this.blocksEl||(this.blocksEl=this.el.querySelector("."+this.pfx+"blocks-c")),this.blocksEl},append:function(t){this.getBlocksEl().appendChild(t)},render:function(){return this.el.innerHTML=this.template({pfx:this.pfx,label:this.model.get("label")}),this.el.className=this.className,this.$el.css({order:this.model.get("order")}),this.updateVisibility(),this}})}).call(e,n(1))},function(t,e,n){"use strict";var i=n(1);t.exports=function(t){var e=t.$,n=t.Backbone;if(n){var r=n.View.prototype,o={};r.eventNsMap=o,r.delegate=function(t,e,n){var i=".delegateEvents"+this.cid;this.$el.on(t,e,n);var r=o[i];return r||(r=[],o[i]=r),r.push({eventName:t,selector:e,listener:n}),this},r.undelegateEvents=function(){var t=this,e=".delegateEvents"+this.cid;if(this.$el){var n=o[e];n&&n.forEach(function(e){var n=e.eventName;e.selector,e.listener;t.$el.off(n)})}return this},r.undelegate=function(t,e,n){var i=this,r=".delegateEvents"+this.cid,s=o[r];return s&&s.forEach(function(n){var r=n.eventName,o=n.selector;n.listener;r==t&&o==e&&i.$el.off(r)}),this}}if(e&&"jQuery"!==e.prototype.constructor.name){var s=e.fn,a=e.prototype.on,l=e.prototype.off,c=e.prototype.trigger,u=e.prototype.offset,h=function(t){return t.split(/[,\s]+/g)},d=function(t){return t.split(".")};s.on=function(t,e,n,i){var r=this;if("string"==typeof t){var o=h(t);if(1==o.length){t=o[0];var s=d(t);if(0!==t.indexOf(".")&&(t=s[0]),s=s.slice(1),s.length){this.data("_cashNs");this.data("_cashNs",s)}return a.call(this,t,e,n,i)}return o.forEach(function(t){return r.on(t,e,n,i)}),this}return a.call(this,t,e,n,i)},s.off=function(t,e){var n=this;if("string"==typeof t){var i=h(t);if(1==i.length){t=i[0];var r=d(t);return 0!==t.indexOf(".")&&(t=r[0]),r=r.slice(1),r.length,l.call(this,t,e)}return i.forEach(function(t){return n.off(t,e)}),this}return l.call(this,t,e)},s.trigger=function(t,n){var i=this;if(t instanceof e.Event)return this.trigger(t.type,n);if("string"==typeof t){var r=h(t);if(1==r.length){t=r[0];var o=d(t);return 0!==t.indexOf(".")&&(t=o[0]),o=o.slice(1),o.length,c.call(this,t,n)}return r.forEach(function(t){return i.trigger(t,n)}),this}return c.call(this,t,n)},s.hide=function(){return this.css("display","none")},s.show=function(){return this.css("display","block")},s.focus=function(){var t=this.get(0);return t&&t.focus(),this},s.removeClass=function(t){if(!arguments.length)return this.attr("class","");var e=(0,i.isString)(t)&&t.match(/\S+/g);return e?this.each(function(t){(0,i.each)(e,function(e){if(t.classList)t.classList.remove(e);else{var n=t.className,r=t.className.baseVal;(0,i.isUndefined)(r)?t.className=n.replace(e,""):n.baseVal=r.replace(e,"")}})}):this},s.remove=function(){return this.each(function(t){return t.parentNode&&t.parentNode.removeChild(t)})},s.bind=function(t,e){return this.on(t,e)},s.unbind=function(t,e){if((0,i.isObject)(t)){for(var n in t)t.hasOwnProperty(n)&&this.off(n,t[n]);return this}return this.off(t,e)},s.click=function(t){return t?this.on("click",t):this.trigger("click")},s.change=function(t){return t?this.on("change",t):this.trigger("change")},s.keydown=function(t){return t?this.on("keydown",t):this.trigger("keydown")},s.delegate=function(t,e,n,i){return i||(i=n),this.on(e,t,function(t){t.data=n,i(t)})},s.scrollLeft=function(){var t=this.get(0);t=9==t.nodeType?t.defaultView:t;var e=t instanceof Window?t:null;return e?e.pageXOffset:t.scrollLeft||0},s.scrollTop=function(){var t=this.get(0);t=9==t.nodeType?t.defaultView:t;var e=t instanceof Window?t:null;return e?e.pageYOffset:t.scrollTop||0},s.offset=function(t){var e=void 0,n=void 0;return t&&(e=t.top,n=t.left),void 0!==e&&this.css("top",e+"px"),void 0!==n&&this.css("left",n+"px"),u.call(this)},e.map=function(t,e){for(var n=[],i=0;i
{
const name = selector.getFullName();
- if (this.compCls.indexOf(name) >= 0 || this.ids.indexOf(name) >= 0) {
+ if (
+ this.compCls.indexOf(name) >= 0 ||
+ this.ids.indexOf(name) >= 0 ||
+ opts.dumpUnusedStyles
+ ) {
found = 1;
}
});
diff --git a/src/commands/view/CommandAbstract.js b/src/commands/view/CommandAbstract.js
index 39a5ca65e..3c21dc5c8 100644
--- a/src/commands/view/CommandAbstract.js
+++ b/src/commands/view/CommandAbstract.js
@@ -107,6 +107,7 @@ module.exports = Backbone.View.extend({
const result = this.run(editor, editor, options);
editor.trigger(`run:${id}`, result, options);
+ return result;
},
/**
@@ -120,6 +121,7 @@ module.exports = Backbone.View.extend({
editor.trigger(`stop:${id}:before`, options);
const result = this.stop(editor, editor, options);
editor.trigger(`stop:${id}`, result, options);
+ return result;
},
/**
diff --git a/src/commands/view/Resize.js b/src/commands/view/Resize.js
index a35becd63..90d0e6752 100644
--- a/src/commands/view/Resize.js
+++ b/src/commands/view/Resize.js
@@ -19,6 +19,7 @@ module.exports = {
}
canvasResizer.setOptions(options);
+ canvasResizer.blur();
canvasResizer.focus(el);
return canvasResizer;
},
diff --git a/src/commands/view/SelectComponent.js b/src/commands/view/SelectComponent.js
index 9ebe6b1dd..c58decf07 100644
--- a/src/commands/view/SelectComponent.js
+++ b/src/commands/view/SelectComponent.js
@@ -1,4 +1,4 @@
-import { bindAll } from 'underscore';
+import { bindAll, isElement } from 'underscore';
import { on, off, getUnitFromValue } from 'utils/mixins';
const ToolbarView = require('dom_components/view/ToolbarView');
@@ -186,6 +186,7 @@ module.exports = {
if (model) {
if (model.get('selectable')) {
editor.select(model);
+ this.initResize(model);
} else {
let parent = model.parent();
while (parent && !parent.get('selectable')) parent = parent.parent();
@@ -262,7 +263,8 @@ module.exports = {
* @private
* */
onSelect() {
- const editor = this.editor;
+ // Get the selected model directly from the Editor as the event might
+ // be triggered manually without the model
const model = this.em.getSelected();
this.updateToolbar(model);
@@ -273,26 +275,27 @@ module.exports = {
this.hideHighlighter();
this.initResize(el);
} else {
- editor.stopCommand('resize');
+ this.editor.stopCommand('resize');
}
},
/**
* Init resizer on the element if possible
- * @param {HTMLElement} el
+ * @param {HTMLElement|Component} elem
* @private
*/
- initResize(el) {
- var em = this.em;
- var editor = em ? em.get('Editor') : '';
- var config = em ? em.get('Config') : '';
- var pfx = config.stylePrefix || '';
- var attrName = `data-${pfx}handler`;
- var resizeClass = `${pfx}resizing`;
- var model = em.get('selectedComponent');
- var resizable = model.get('resizable');
- var options = {};
- var modelToStyle;
+ initResize(elem) {
+ const em = this.em;
+ const editor = em ? em.get('Editor') : '';
+ const config = em ? em.get('Config') : '';
+ const pfx = config.stylePrefix || '';
+ const attrName = `data-${pfx}handler`;
+ const resizeClass = `${pfx}resizing`;
+ const model = !isElement(elem) ? elem : em.getSelected();
+ const resizable = model.get('resizable');
+ const el = isElement(elem) ? elem : model.getEl();
+ let options = {};
+ let modelToStyle;
var toggleBodyClass = (method, e, opts) => {
const docs = opts.docs;
@@ -376,11 +379,12 @@ module.exports = {
if (typeof resizable == 'object') {
options = { ...options, ...resizable };
}
-
editor.runCommand('resize', { el, options });
// On undo/redo the resizer rect is not updating, need somehow to call
// this.updateRect on undo/redo action
+ } else {
+ editor.stopCommand('resize');
}
},
diff --git a/src/css_composer/index.js b/src/css_composer/index.js
index 1a5dd374d..c78d681c7 100644
--- a/src/css_composer/index.js
+++ b/src/css_composer/index.js
@@ -94,22 +94,25 @@ module.exports = () => {
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));
+ rules.each(rule => this.handleChange(rule, { avoidStore: 1 }));
},
/**
* Handle rule changes
* @private
*/
- handleChange(model) {
+ 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);
},
/**
@@ -182,8 +185,13 @@ module.exports = () => {
var w = width || '';
var opt = { ...opts };
var rule = this.get(selectors, s, w, opt);
- if (rule) return rule;
- else {
+
+ // do not create rules that were found before
+ // unless this is an at-rule, for which multiple declarations
+ // make sense (e.g. multiple `@font-type`s)
+ if (rule && rule.config && !rule.config.atRuleType) {
+ return rule;
+ } else {
opt.state = s;
opt.mediaText = w;
opt.selectors = '';
diff --git a/src/css_composer/view/CssRulesView.js b/src/css_composer/view/CssRulesView.js
index 42297d6d1..6bf40869d 100644
--- a/src/css_composer/view/CssRulesView.js
+++ b/src/css_composer/view/CssRulesView.js
@@ -2,6 +2,10 @@ const CssRuleView = require('./CssRuleView');
const CssGroupRuleView = require('./CssGroupRuleView');
const $ = Backbone.$;
+// % is not a valid character for classes
+const getBlockId = (pfx, widthMedia) =>
+ `${pfx}${widthMedia ? `-${widthMedia.replace('%', 'pc')}` : ''}`;
+
module.exports = require('backbone').View.extend({
initialize(o) {
const config = o.config || {};
@@ -32,6 +36,11 @@ module.exports = require('backbone').View.extend({
* @private
* */
addToCollection(model, fragmentEl) {
+ // If the render is not yet started
+ if (!this.renderStarted) {
+ return;
+ }
+
var fragment = fragmentEl || null;
var viewObject = CssRuleView;
var config = this.config;
@@ -62,16 +71,32 @@ module.exports = require('backbone').View.extend({
rendered = view.render().el;
}
- const mediaWidth = this.getMediaWidth(model.get('mediaText'));
- const styleBlockId = `#${this.pfx}rules-${mediaWidth}`;
+ const clsName = this.className;
+ const mediaText = model.get('mediaText');
+ const defaultBlockId = getBlockId(clsName);
+ let blockId = defaultBlockId;
+
+ // If the rule contains a media query it might have a different container
+ // for it (eg. rules created with Device Manager)
+ if (mediaText) {
+ blockId = getBlockId(clsName, this.getMediaWidth(mediaText));
+ }
if (rendered) {
- if (fragment) {
- fragment.querySelector(styleBlockId).appendChild(rendered);
- } else {
- let $stylesContainer = this.$el.find(styleBlockId);
- $stylesContainer.append(rendered);
+ const container = fragment || this.el;
+ let contRules;
+
+ // Try to find a specific container for the rule (if it
+ // containes a media query), otherwise get the default one
+ try {
+ contRules = container.querySelector(`#${blockId}`);
+ } catch (e) {}
+
+ if (!contRules) {
+ contRules = container.querySelector(`#${defaultBlockId}`);
}
+
+ contRules.appendChild(rendered);
}
return rendered;
@@ -87,13 +112,14 @@ module.exports = require('backbone').View.extend({
},
render() {
+ this.renderStarted = 1;
this.atRules = {};
const $el = this.$el;
const frag = document.createDocumentFragment();
+ const className = this.className;
$el.empty();
// Create devices related DOM structure
- const pfx = this.pfx;
this.em
.get('DeviceManager')
.getAll()
@@ -104,13 +130,14 @@ module.exports = require('backbone').View.extend({
((left && left.replace('px', '')) || Number.MAX_VALUE)
)
.forEach(widthMedia => {
- const blockId = pfx + 'rules-' + widthMedia;
- $(``).appendTo(frag);
+ $(``).appendTo(
+ frag
+ );
});
this.collection.each(model => this.addToCollection(model, frag));
$el.append(frag);
- $el.attr('class', this.className);
+ $el.attr('class', className);
return this;
}
});
diff --git a/src/dom_components/model/Component.js b/src/dom_components/model/Component.js
index 77304b691..55fb44fa9 100644
--- a/src/dom_components/model/Component.js
+++ b/src/dom_components/model/Component.js
@@ -6,6 +6,7 @@ import {
has,
clone,
isString,
+ forEach,
keys
} from 'underscore';
import { shallowDiff, hasDnd } from 'utils/mixins';
@@ -513,9 +514,10 @@ const Component = Backbone.Model.extend(Styleable).extend(
traits.each(trait => {
found = 1;
if (!trait.get('changeProp')) {
+ const name = trait.get('name');
const value = trait.getInitValue();
- if (value) {
- attrs[trait.get('name')] = value;
+ if (name && value) {
+ attrs[name] = value;
}
}
});
@@ -722,6 +724,32 @@ const Component = Backbone.Model.extend(Styleable).extend(
delete obj.attributes.class;
delete obj.toolbar;
+ if (this.em.getConfig('avoidDefaults')) {
+ const defaults = this.defaults;
+
+ forEach(defaults, (value, key) => {
+ if (key !== 'type' && obj[key] === value) {
+ delete obj[key];
+ }
+ });
+
+ if (isEmpty(obj.type)) {
+ delete obj.type;
+ }
+
+ forEach(['attributes', 'style'], prop => {
+ if (isEmpty(defaults[prop]) && isEmpty(obj[prop])) {
+ delete obj[prop];
+ }
+ });
+
+ forEach(['classes', 'components'], prop => {
+ if (isEmpty(defaults[prop]) && !obj[prop].length) {
+ delete obj[prop];
+ }
+ });
+ }
+
return obj;
},
diff --git a/src/domain_abstract/model/Styleable.js b/src/domain_abstract/model/Styleable.js
index 844d88cd9..e4413b797 100644
--- a/src/domain_abstract/model/Styleable.js
+++ b/src/domain_abstract/model/Styleable.js
@@ -43,8 +43,8 @@ export default {
const em = this.em;
this.trigger(`change:style:${pr}`);
if (em) {
- em.trigger(`styleable:change`);
- em.trigger(`styleable:change:${pr}`);
+ em.trigger(`styleable:change`, this, pr);
+ em.trigger(`styleable:change:${pr}`, this, pr);
}
});
diff --git a/src/editor/config/config.js b/src/editor/config/config.js
index bd0786b70..a2320d5cb 100644
--- a/src/editor/config/config.js
+++ b/src/editor/config/config.js
@@ -50,7 +50,7 @@ module.exports = {
overflow: auto;
overflow-x: hidden;
}
-
+
* ::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.1)
}
@@ -96,6 +96,9 @@ module.exports = {
// Ending tag for variable inside scripts in Components
tagVarEnd: ' ]}',
+ // When false, removes empty text nodes when parsed, unless they contain a space
+ keepEmptyTextNodes: 0,
+
// Return JS of components inside HTML from 'editor.getHtml()'
jsInHtml: true,
@@ -114,6 +117,11 @@ module.exports = {
// When `avoidInlineStyle` is true all styles are inserted inside the css rule
avoidInlineStyle: 0,
+ // Avoid default properties from storable JSON data, like `components` and `styles`.
+ // With this option enabled your data will be smaller (usefull if need to
+ // save some storage space)
+ avoidDefaults: 0,
+
// (experimental)
// The structure of components is always on the screen but it's not the same
// for style rules. When you delete a component you might leave a lot of styles
@@ -271,6 +279,8 @@ module.exports = {
traitManager: {},
// Texts
+ textViewCode: 'Code',
- textViewCode: 'Code'
+ // Dump unused styles within the editor
+ dumpUnusedStyles: 0
};
diff --git a/src/editor/index.js b/src/editor/index.js
index f5b71ecf8..cd3d5f42a 100644
--- a/src/editor/index.js
+++ b/src/editor/index.js
@@ -17,6 +17,7 @@
* * `component:styleUpdate` - Triggered when the style of the component is updated, the model is passed as an argument to the callback
* * `component:styleUpdate:{propertyName}` - Listen for a specific style property change, the model is passed as an argument to the callback
* * `component:selected` - New component selected, the selected model is passed as an argument to the callback
+ * * `component:deselected` - Component deselected, the deselected model is passed as an argument to the callback
* ## Blocks
* * `block:add` - New block added
* * `block:remove` - Block removed
@@ -39,9 +40,13 @@
* * `styleManager:change:{propertyName}` - As above but for a specific style property
* ## Storages
* * `storage:start` - Before the storage request is started
+ * * `storage:start:store` - Before the store request. The object to store is passed as an argumnet (which you can edit)
+ * * `storage:start:load` - Before the load request. Items to load are passed as an argumnet (which you can edit)
* * `storage:load` - Triggered when something was loaded from the storage, loaded object passed as an argumnet
* * `storage:store` - Triggered when something is stored to the storage, stored object passed as an argumnet
* * `storage:end` - After the storage request is ended
+ * * `storage:end:store` - After the store request
+ * * `storage:end:load` - After the load request
* * `storage:error` - On any error on storage request, passes the error as an argument
* ## Canvas
* * `canvas:dragenter` - When something is dragged inside the canvas, `DataTransfer` instance passed as an argument
@@ -83,6 +88,7 @@
* @param {Object} [config.domComponents={}] Components configuration, see the relative documentation
* @param {Object} [config.panels={}] Panels configuration, see the relative documentation
* @param {Object} [config.showDevices=true] If true render a select of available devices inside style manager panel
+ * @param {Boolean} [config.keepEmptyTextNodes=false] If false, removes empty text nodes when parsed (unless they contain a space)
* @param {string} [config.defaultCommand='select-comp'] Command to execute when no other command is running
* @param {Array} [config.plugins=[]] Array of plugins to execute on start
* @param {Object} [config.pluginsOpts={}] Custom options for plugins
diff --git a/src/editor/model/Editor.js b/src/editor/model/Editor.js
index 9cdfa75d8..983f079ba 100644
--- a/src/editor/model/Editor.js
+++ b/src/editor/model/Editor.js
@@ -197,13 +197,10 @@ module.exports = Backbone.Model.extend({
* @param {Object} Options
* @private
* */
- componentSelected(model, val, options) {
- if (!this.get('selectedComponent')) {
- this.trigger('deselect-comp');
- } else {
- this.trigger('select-comp', [model, val, options]);
- this.trigger('component:selected', arguments);
- }
+ componentSelected(editor, selected, options) {
+ const prev = this.previous('selectedComponent');
+ prev && this.trigger('component:deselected', prev, options);
+ selected && this.trigger('component:selected', selected, options);
},
/**
@@ -305,6 +302,9 @@ module.exports = Backbone.Model.extend({
const config = this.config;
const wrappesIsBody = config.wrappesIsBody;
const avoidProt = opts.avoidProtected;
+ const dumpUnusedStyles = !isUndefined(opts.dumpUnusedStyles)
+ ? opts.dumpUnusedStyles
+ : config.dumpUnusedStyles;
const cssc = this.get('CssComposer');
const wrp = this.get('DomComponents').getComponent();
const protCss = !avoidProt ? config.protectedCss : '';
@@ -313,7 +313,8 @@ module.exports = Backbone.Model.extend({
protCss +
this.get('CodeManager').getCode(wrp, 'css', {
cssc,
- wrappesIsBody
+ wrappesIsBody,
+ dumpUnusedStyles
})
);
},
diff --git a/src/panels/model/Button.js b/src/panels/model/Button.js
index 5218e477e..250ea6244 100644
--- a/src/panels/model/Button.js
+++ b/src/panels/model/Button.js
@@ -3,6 +3,7 @@ var Backbone = require('backbone');
module.exports = Backbone.Model.extend({
defaults: {
id: '',
+ label: '',
className: '',
command: '',
context: '',
diff --git a/src/panels/view/ButtonView.js b/src/panels/view/ButtonView.js
index e420016b0..287fae240 100644
--- a/src/panels/view/ButtonView.js
+++ b/src/panels/view/ButtonView.js
@@ -4,18 +4,11 @@ const $ = Backbone.$;
module.exports = Backbone.View.extend({
tagName: 'span',
+ events: {
+ click: 'clicked'
+ },
+
initialize(o) {
- _.bindAll(
- this,
- 'startTimer',
- 'stopTimer',
- 'showButtons',
- 'hideButtons',
- 'closeOnKeyPress',
- 'onDrop',
- 'initSorter',
- 'stopDrag'
- );
var cls = this.model.get('className');
this.config = o.config || {};
this.em = this.config.em || {};
@@ -27,7 +20,6 @@ module.exports = Backbone.View.extend({
this.activeCls = `${pfx}active ${ppfx}four-color`;
this.disableCls = pfx + 'active';
this.btnsVisCls = pfx + 'visible';
- this.parentM = o.parentM || null;
this.className = pfx + 'btn' + (cls ? ' ' + cls : '');
this.listenTo(this.model, 'change:active updateActive', this.updateActive);
this.listenTo(this.model, 'checkActive', this.checkActive);
@@ -36,78 +28,7 @@ module.exports = Backbone.View.extend({
this.listenTo(this.model, 'change:className', this.updateClassName);
this.listenTo(this.model, 'change:disable', this.updateDisable);
- if (this.model.get('buttons').length) {
- this.$el.on('mousedown', this.startTimer);
- this.$el.append($('', { class: pfx + 'arrow-rd' }));
- }
-
if (this.em && this.em.get) this.commands = this.em.get('Commands');
-
- this.events = {};
-
- if (this.model.get('dragDrop')) {
- this.events.mousedown = 'initDrag';
- this.em.on('loaded', this.initSorter);
- } else this.events.click = 'clicked';
- this.delegateEvents();
- },
-
- initSorter() {
- if (this.em.Canvas) {
- var canvas = this.em.Canvas;
- this.canvasEl = canvas.getBody();
- this.sorter = new this.em.Utils.Sorter({
- container: this.canvasEl,
- placer: canvas.getPlacerEl(),
- containerSel: '*',
- itemSel: '*',
- pfx: this.ppfx,
- onMove: this.onDrag,
- onEndMove: this.onDrop,
- document: canvas.getFrameEl().contentDocument,
- direction: 'a',
- wmargin: 1,
- nested: 1
- });
- var offDim = canvas.getOffset();
- this.sorter.offTop = offDim.top;
- this.sorter.offLeft = offDim.left;
- }
- },
-
- /**
- * Init dragging element
- * @private
- */
- initDrag() {
- this.model.collection.deactivateAll(this.model.get('context'));
- this.sorter.startSort(this.el);
- this.sorter.setDropContent(this.model.get('options').content);
- this.canvasEl.style.cursor = 'grabbing';
- $(document).on('mouseup', this.stopDrag);
- },
-
- /**
- * Stop dragging
- * @private
- */
- stopDrag() {
- $(document).off('mouseup', this.stopDrag);
- this.sorter.endMove();
- },
-
- /**
- * During drag method
- * @private
- */
- onDrag(e) {},
-
- /**
- * During drag method
- * @private
- */
- onDrop(e) {
- this.canvasEl.style.cursor = 'default';
},
/**
@@ -141,63 +62,6 @@ module.exports = Backbone.View.extend({
else this.$buttons.removeClass(this.btnsVisCls);
},
- /**
- * Start timer for showing children buttons
- *
- * @return void
- * */
- startTimer() {
- this.timeout = setTimeout(this.showButtons, this.config.delayBtnsShow);
- $(document).on('mouseup', this.stopTimer);
- },
-
- /**
- * Stop timer for showing children buttons
- *
- * @return void
- * */
- stopTimer() {
- $(document).off('mouseup', this.stopTimer);
- if (this.timeout) clearTimeout(this.timeout);
- },
-
- /**
- * Show children buttons
- *
- * @return void
- * */
- showButtons() {
- clearTimeout(this.timeout);
- this.model.set('bntsVis', true);
- $(document).on('mousedown', this.hideButtons);
- $(document).on('keypress', this.closeOnKeyPress);
- },
-
- /**
- * Hide children buttons
- *
- * @return void
- * */
- hideButtons(e) {
- if (e) {
- $(e.target).trigger('click');
- }
- this.model.set('bntsVis', false);
- $(document).off('mousedown', this.hideButtons);
- $(document).off('keypress', this.closeOnKeyPress);
- },
-
- /**
- * Close buttons on ESC key press
- * @param {Object} e Event
- *
- * @return void
- * */
- closeOnKeyPress(e) {
- var key = e.which || e.keyCode;
- if (key == 27) this.hideButtons();
- },
-
/**
* Update active status of the button
*
@@ -206,7 +70,6 @@ module.exports = Backbone.View.extend({
updateActive() {
const model = this.model;
const context = model.get('context');
- const parent = this.parentM;
let command = {};
var editor = this.em && this.em.get ? this.em.get('Editor') : null;
var commandName = model.get('command');
@@ -222,8 +85,6 @@ module.exports = Backbone.View.extend({
if (model.get('active')) {
model.collection.deactivateAll(context);
model.set('active', true, { silent: true }).trigger('checkActive');
- parent &&
- parent.set('active', true, { silent: true }).trigger('checkActive');
if (command.run) {
command.run(editor, model, model.get('options'));
@@ -235,8 +96,6 @@ module.exports = Backbone.View.extend({
} else {
this.$el.removeClass(this.activeCls);
model.collection.deactivateAll(context);
- parent &&
- parent.set('active', false, { silent: true }).trigger('checkActive');
if (command.stop) {
command.stop(editor, model, model.get('options'));
@@ -278,8 +137,6 @@ module.exports = Backbone.View.extend({
},
toogleActive() {
- if (this.parentM) this.swapParent();
-
var active = this.model.get('active');
this.model.set('active', !active);
@@ -293,35 +150,12 @@ module.exports = Backbone.View.extend({
}
},
- /**
- * Updates parent model swapping properties
- *
- * @return void
- * */
- swapParent() {
- this.parentM.collection.deactivateAll(this.model.get('context'));
- this.parentM.set('attributes', this.model.get('attributes'));
- this.parentM.set('options', this.model.get('options'));
- this.parentM.set('command', this.model.get('command'));
- this.parentM.set('className', this.model.get('className'));
- this.parentM.set('active', true, { silent: true }).trigger('checkActive');
- },
-
render() {
+ const label = this.model.get('label');
+ const $el = this.$el;
this.updateAttributes();
- this.$el.attr('class', this.className);
-
- if (this.model.get('buttons').length) {
- var btnsView = require('./ButtonsView'); //Avoid Circular Dependencies
- var view = new btnsView({
- collection: this.model.get('buttons'),
- config: this.config,
- parentM: this.model
- });
- this.$buttons = view.render().$el;
- this.$buttons.append($('
', { class: this.pfx + 'arrow-l' }));
- this.$el.append(this.$buttons); //childNodes avoids wrapping 'div'
- }
+ $el.attr('class', this.className);
+ label && $el.append(label);
return this;
}
diff --git a/src/parser/model/ParserHtml.js b/src/parser/model/ParserHtml.js
index 021c5dbf9..5b4664e8e 100644
--- a/src/parser/model/ParserHtml.js
+++ b/src/parser/model/ParserHtml.js
@@ -152,9 +152,11 @@ module.exports = config => {
}
// Throw away empty nodes (keep spaces)
- const content = node.nodeValue;
- if (content != ' ' && !content.trim()) {
- continue;
+ if (!config.keepEmptyTextNodes) {
+ const content = node.nodeValue;
+ if (content != ' ' && !content.trim()) {
+ continue;
+ }
}
}
diff --git a/src/selector_manager/index.js b/src/selector_manager/index.js
index 11342bb27..69635c23c 100644
--- a/src/selector_manager/index.js
+++ b/src/selector_manager/index.js
@@ -73,6 +73,15 @@ module.exports = config => {
* @private
*/
name: 'SelectorManager',
+
+ /**
+ * Get configuration object
+ * @return {Object}
+ * @private
+ */
+ getConfig() {
+ return c;
+ },
getConfig() {
return c;
diff --git a/src/storage_manager/index.js b/src/storage_manager/index.js
index 9f9b8efe2..1fd24cb98 100644
--- a/src/storage_manager/index.js
+++ b/src/storage_manager/index.js
@@ -11,8 +11,11 @@ module.exports = () => {
LocalStorage = require('./model/LocalStorage'),
RemoteStorage = require('./model/RemoteStorage');
+ let em;
var storages = {};
var defaultStorages = {};
+ const eventStart = 'storage:start';
+ const eventEnd = 'storage:end';
return {
/**
@@ -42,6 +45,7 @@ module.exports = () => {
*/
init(config) {
c = config || {};
+ em = c.em;
for (var name in defaults) {
if (!(name in c)) c[name] = defaults[name];
@@ -171,12 +175,20 @@ module.exports = () => {
* storageManager.store({item1: value1, item2: value2});
* */
store(data, clb) {
- var st = this.get(this.getCurrent());
- var dataF = {};
+ const st = this.get(this.getCurrent());
+ const toStore = {};
+ this.onStart('store', data);
- for (var key in data) dataF[c.id + key] = data[key];
+ for (let key in data) {
+ toStore[c.id + key] = data[key];
+ }
- return st ? st.store(dataF, clb) : null;
+ return st
+ ? st.store(toStore, res => {
+ clb && clb(res);
+ this.onEnd('store', res);
+ })
+ : null;
},
/**
@@ -197,9 +209,11 @@ module.exports = () => {
var result = {};
if (typeof keys === 'string') keys = [keys];
+ this.onStart('load', keys);
- for (var i = 0, len = keys.length; i < len; i++)
+ for (var i = 0, len = keys.length; i < len; i++) {
keysF.push(c.id + keys[i]);
+ }
if (st) {
st.load(keysF, res => {
@@ -211,6 +225,7 @@ module.exports = () => {
}
clb && clb(result);
+ this.onEnd('load', result);
});
} else {
clb && clb(result);
@@ -235,6 +250,28 @@ module.exports = () => {
return this.get(this.getCurrent());
},
+ /**
+ * On start callback
+ * @private
+ */
+ onStart(ctx, data) {
+ if (em) {
+ em.trigger(eventStart);
+ ctx && em.trigger(`${eventStart}:${ctx}`, data);
+ }
+ },
+
+ /**
+ * On end callback
+ * @private
+ */
+ onEnd(ctx, data) {
+ if (em) {
+ em.trigger(eventEnd);
+ ctx && em.trigger(`${eventEnd}:${ctx}`, data);
+ }
+ },
+
/**
* Check if autoload is possible
* @return {Boolean}
diff --git a/src/storage_manager/model/RemoteStorage.js b/src/storage_manager/model/RemoteStorage.js
index da5a56b1c..fdf53aed3 100644
--- a/src/storage_manager/model/RemoteStorage.js
+++ b/src/storage_manager/model/RemoteStorage.js
@@ -21,7 +21,6 @@ module.exports = require('backbone').Model.extend({
const em = this.get('em');
const before = this.get('beforeSend');
before && before();
- em && em.trigger('storage:start');
},
/**
@@ -33,17 +32,6 @@ module.exports = require('backbone').Model.extend({
const em = this.get('em');
console.error(err);
em && em.trigger('storage:error', err);
- this.onEnd(err);
- },
-
- /**
- * Triggered after the request is ended
- * @param {Object|string} res End result
- * @private
- */
- onEnd(res) {
- const em = this.get('em');
- em && em.trigger('storage:end', res);
},
/**
@@ -60,7 +48,6 @@ module.exports = require('backbone').Model.extend({
complete && complete(res);
clb && clb(res);
em && em.trigger('storage:response', res);
- this.onEnd(text);
},
store(data, clb) {
diff --git a/test/specs/commands/index.js b/test/specs/commands/index.js
index dc09a0b61..46ce61755 100644
--- a/test/specs/commands/index.js
+++ b/test/specs/commands/index.js
@@ -1,5 +1,6 @@
var Commands = require('commands');
var Models = require('./model/CommandModels');
+var CommandAbstract = require('./view/CommandAbstract');
describe('Commands', () => {
describe('Main', () => {
@@ -61,3 +62,4 @@ describe('Commands', () => {
});
Models.run();
+CommandAbstract.run();
diff --git a/test/specs/commands/view/CommandAbstract.js b/test/specs/commands/view/CommandAbstract.js
new file mode 100644
index 000000000..33c6959df
--- /dev/null
+++ b/test/specs/commands/view/CommandAbstract.js
@@ -0,0 +1,83 @@
+const CommandAbstract = require('commands/view/CommandAbstract');
+const Editor = require('editor/model/Editor');
+
+module.exports = {
+ run() {
+ describe('CommandAbstract', () => {
+ let editor, editorTriggerSpy, command;
+
+ beforeEach(() => {
+ editor = new Editor();
+ editorTriggerSpy = sinon.spy(editor, 'trigger');
+
+ command = new CommandAbstract();
+ command.id = 'test';
+ });
+
+ afterEach(() => {
+ command = null;
+ editorTriggerSpy = null;
+ editor = null;
+ });
+
+ it('callRun returns result when no "abort" option specified', () => {
+ const runStub = sinon.stub(command, 'run').returns('result');
+
+ const result = command.callRun(editor);
+
+ expect(editorTriggerSpy.calledTwice).toEqual(true);
+ expect(editorTriggerSpy.getCall(0).args).toEqual([
+ 'run:test:before',
+ {}
+ ]);
+ expect(editorTriggerSpy.getCall(1).args).toEqual([
+ 'run:test',
+ 'result',
+ {}
+ ]);
+
+ expect(result).toEqual('result');
+ expect(runStub.calledOnce).toEqual(true);
+ });
+
+ it('callRun returns undefined when "abort" option is specified', () => {
+ const runStub = sinon.stub(command, 'run').returns('result');
+
+ const result = command.callRun(editor, { abort: true });
+
+ expect(editorTriggerSpy.calledTwice).toEqual(true);
+ expect(editorTriggerSpy.getCall(0).args).toEqual([
+ 'run:test:before',
+ { abort: true }
+ ]);
+ expect(editorTriggerSpy.getCall(1).args).toEqual([
+ 'abort:test',
+ { abort: true }
+ ]);
+
+ expect(result).toEqual(undefined);
+ expect(runStub.notCalled).toEqual(true);
+ });
+
+ it('callStop returns result', () => {
+ const stopStub = sinon.stub(command, 'stop').returns('stopped');
+
+ const result = command.callStop(editor);
+
+ expect(editorTriggerSpy.calledTwice).toEqual(true);
+ expect(editorTriggerSpy.getCall(0).args).toEqual([
+ 'stop:test:before',
+ {}
+ ]);
+ expect(editorTriggerSpy.getCall(1).args).toEqual([
+ 'stop:test',
+ 'stopped',
+ {}
+ ]);
+
+ expect(result).toEqual('stopped');
+ expect(stopStub.calledOnce).toEqual(true);
+ });
+ });
+ }
+};
diff --git a/test/specs/css_composer/view/CssRulesView.js b/test/specs/css_composer/view/CssRulesView.js
index 5feeb93eb..7a9c3710c 100644
--- a/test/specs/css_composer/view/CssRulesView.js
+++ b/test/specs/css_composer/view/CssRulesView.js
@@ -6,7 +6,7 @@ module.exports = {
run() {
describe('CssRulesView', () => {
let obj;
- const prefix = 'rules-';
+ const prefix = 'rules';
const devices = [
{
name: 'Mobile portrait',
@@ -62,8 +62,9 @@ module.exports = {
((left && left.replace('px', '')) || Number.MAX_VALUE)
);
});
- foundStylesContainers.each(function($styleC, idx) {
- expect($styleC.id).toEqual(prefix + sortedDevicesWidthMedia[idx]);
+ foundStylesContainers.each(($styleC, idx) => {
+ const width = sortedDevicesWidthMedia[idx];
+ expect($styleC.id).toEqual(`${prefix}${width ? `-${width}` : ''}`);
});
});
@@ -73,6 +74,30 @@ module.exports = {
expect(obj.addToCollection.calledOnce).toExist(true);
});
+ it('Add correctly rules with different media queries', () => {
+ const foundStylesContainers = obj.$el.find('div');
+ const rules = [
+ {
+ selectorsAdd: '#testid'
+ },
+ {
+ selectorsAdd: '#testid2',
+ mediaText: '(max-width: 1000px)'
+ },
+ {
+ selectorsAdd: '#testid3',
+ mediaText: '(min-width: 900px)'
+ },
+ {
+ selectorsAdd: '#testid4',
+ mediaText: 'screen and (max-width: 900px) and (min-width: 600px)'
+ }
+ ];
+ obj.collection.add(rules);
+ const stylesCont = obj.el.querySelector(`#${obj.className}`);
+ expect(stylesCont.children.length).toEqual(rules.length);
+ });
+
it('Render new rule', () => {
obj.collection.add({});
expect(obj.$el.find(`#${prefix}`).html()).toExist();
diff --git a/test/specs/dom_components/model/Component.js b/test/specs/dom_components/model/Component.js
index ae5da0f8e..c3ced43f3 100644
--- a/test/specs/dom_components/model/Component.js
+++ b/test/specs/dom_components/model/Component.js
@@ -67,6 +67,21 @@ module.exports = {
expect(obj.get('stylable')).toEqual(true);
});
+ it('Sets attributes correctly from traits', () => {
+ obj.set('traits', [
+ {
+ label: 'Title',
+ name: 'title',
+ value: 'The title'
+ },
+ {
+ label: 'Context',
+ value: 'primary'
+ }
+ ]);
+ expect(obj.get('attributes')).toEqual({ title: 'The title' });
+ });
+
it('Has expected name', () => {
expect(obj.getName()).toEqual('Box');
});
diff --git a/test/specs/grapesjs/index.js b/test/specs/grapesjs/index.js
index db4723538..f89d0d666 100644
--- a/test/specs/grapesjs/index.js
+++ b/test/specs/grapesjs/index.js
@@ -157,6 +157,27 @@ describe('GrapesJS', () => {
expect(editor.getStyle().length).toEqual(2);
});
+ it('Init editor from element with multiple font-face at-rules', () => {
+ config.fromElement = 1;
+ config.storageManager = { type: 0 };
+ fixture.innerHTML =
+ `
+ ` + htmlString;
+ const editor = obj.init(config);
+ const css = editor.getCss();
+ const styles = editor.getStyle();
+ expect(styles.length).toEqual(2);
+ });
+
it('Set components as HTML', () => {
var editor = obj.init(config);
editor.setComponents(htmlString);
@@ -355,5 +376,27 @@ describe('GrapesJS', () => {
editor = obj.init(config);
expect(editor.Commands.get('export-template').test).toEqual(1);
});
+
+ it('Dump unused css classes/selectors', () => {
+ config.fromElement = 1;
+ config.storageManager = { type: 0 };
+ fixture.innerHTML = documentEl;
+ const editor = obj.init(config);
+ const css = editor.getCss({ dumpUnusedStyles: 1 });
+ const protCss = editor.getConfig().protectedCss;
+ expect(editor.getStyle().length).toEqual(2);
+ expect(css).toEqual(`${protCss}.test2{color:red;}.test3{color:blue;}`);
+ });
+
+ it('Dump unused css classes/selectors using the init option', () => {
+ config.fromElement = 1;
+ config.storageManager = { type: 0 };
+ fixture.innerHTML = documentEl;
+ const editor = obj.init({ ...config, dumpUnusedStyles: 1 });
+ const css = editor.getCss();
+ const protCss = editor.getConfig().protectedCss;
+ expect(editor.getStyle().length).toEqual(2);
+ expect(css).toEqual(`${protCss}.test2{color:red;}.test3{color:blue;}`);
+ });
});
});
diff --git a/test/specs/parser/model/ParserCss.js b/test/specs/parser/model/ParserCss.js
index dc5c8bae8..99bdef6f3 100644
--- a/test/specs/parser/model/ParserCss.js
+++ b/test/specs/parser/model/ParserCss.js
@@ -277,6 +277,39 @@ module.exports = {
expect(obj.parse(str)).toEqual(result);
});
+ it('Parses multiple font-face at-rules', () => {
+ const str = `
+ @font-face {
+ font-family: "Open Sans";
+ }
+ @font-face {
+ font-family: 'Glyphicons Halflings';
+ src:url(https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/fonts/glyphicons-halflings-regular.eot)
+ }`;
+ const result = [
+ {
+ selectors: [],
+ selectorsAdd: '',
+ style: { 'font-family': '"Open Sans"' },
+ singleAtRule: 1,
+ atRuleType: 'font-face'
+ },
+ {
+ selectors: [],
+ selectorsAdd: '',
+ style: {
+ 'font-family': "'Glyphicons Halflings'",
+ src:
+ 'url(https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/fonts/glyphicons-halflings-regular.eot)'
+ },
+ singleAtRule: 1,
+ atRuleType: 'font-face'
+ }
+ ];
+ const parsed = obj.parse(str);
+ expect(parsed).toEqual(result);
+ });
+
it('Parse ID rule', () => {
var str = `#test { color: red }`;
var result = {
diff --git a/test/specs/parser/model/ParserHtml.js b/test/specs/parser/model/ParserHtml.js
index f7e5ba1a9..683c1afa1 100644
--- a/test/specs/parser/model/ParserHtml.js
+++ b/test/specs/parser/model/ParserHtml.js
@@ -372,6 +372,50 @@ module.exports = {
expect(res.css).toEqual(resCss);
});
+ it('Respect multiple font-faces contained in styles in html', () => {
+ const str = `
+
+
a div
+ `;
+
+ const expected = [
+ {
+ selectors: [],
+ selectorsAdd: '',
+ style: {
+ 'font-family': '"Open Sans"',
+ src:
+ 'url(https://fonts.gstatic.com/s/droidsans/v8/SlGVmQWMvZQIdix7AFxXkHNSbRYXags.woff2)'
+ },
+ singleAtRule: 1,
+ atRuleType: 'font-face'
+ },
+ {
+ selectors: [],
+ selectorsAdd: '',
+ style: {
+ 'font-family': "'Glyphicons Halflings'",
+ src:
+ 'url(https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/fonts/glyphicons-halflings-regular.eot)'
+ },
+ singleAtRule: 1,
+ atRuleType: 'font-face'
+ }
+ ];
+
+ const res = obj.parse(str, new ParserCss());
+ expect(res.css).toEqual(expected);
+ });
+
it('Parse nested div with text and spaces', () => {
var str = '
';
var result = {