Browse Source

Merge pull request #961 from artf/dev

Merge dev
pull/1023/head
Artur Arseniev 9 years ago
committed by GitHub
parent
commit
376a60867d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      dist/css/grapes.min.css
  2. 291
      dist/grapes.js
  3. 6
      dist/grapes.min.js
  4. 2
      package-lock.json
  5. 2
      package.json
  6. 20
      src/asset_manager/view/FileUploader.js
  7. 1
      src/block_manager/view/BlockView.js
  8. 1
      src/canvas/index.js
  9. 18
      src/canvas/view/CanvasView.js
  10. 59
      src/commands/view/OpenTraitManager.js
  11. 26
      src/commands/view/SelectComponent.js
  12. 12
      src/dom_components/model/Component.js
  13. 9
      src/dom_components/model/Components.js
  14. 11
      src/dom_components/view/ComponentsView.js
  15. 4
      src/editor/model/Editor.js
  16. 16
      src/parser/model/ParserCss.js
  17. 18
      src/rich_text_editor/index.js
  18. 20
      src/selector_manager/index.js
  19. 6
      src/styles/scss/_gjs_blocks.scss
  20. 5
      src/styles/scss/_gjs_traits.scss
  21. 5
      src/trait_manager/config/config.js
  22. 1
      src/utils/Sorter.js
  23. 28
      test/specs/dom_components/index.js
  24. 23
      test/specs/dom_components/model/Component.js
  25. 30
      test/specs/parser/model/ParserCss.js
  26. 8
      test/specs/selector_manager/index.js
  27. 2
      webpack.config.js

2
dist/css/grapes.min.css

File diff suppressed because one or more lines are too long

291
dist/grapes.js

@ -4398,11 +4398,13 @@ var Component = Backbone.Model.extend(_Styleable2.default).extend({
if (em && em.getConfig('avoidInlineStyle')) { if (em && em.getConfig('avoidInlineStyle')) {
prop = (0, _underscore.isString)(prop) ? this.parseStyle(prop) : prop; prop = (0, _underscore.isString)(prop) ? this.parseStyle(prop) : prop;
prop = _extends({}, prop, this.get('style'));
var state = this.get('state'); var state = this.get('state');
var cc = em.get('CssComposer'); var cc = em.get('CssComposer');
var propOrig = this.getStyle(); var propOrig = this.getStyle();
this.rule = cc.setIdRule(this.getId(), prop, _extends({}, opts, { state: state })); this.rule = cc.setIdRule(this.getId(), prop, _extends({}, opts, { state: state }));
var diff = (0, _mixins.shallowDiff)(propOrig, prop); var diff = (0, _mixins.shallowDiff)(propOrig, prop);
this.set('style', {}, { silent: 1 });
(0, _underscore.keys)(diff).forEach(function (pr) { (0, _underscore.keys)(diff).forEach(function (pr) {
return _this3.trigger('change:style:' + pr); return _this3.trigger('change:style:' + pr);
}); });
@ -4768,10 +4770,15 @@ var Component = Backbone.Model.extend(_Styleable2.default).extend({
var attributes = this.getAttrToHTML(); var attributes = this.getAttrToHTML();
for (var attr in attributes) { for (var attr in attributes) {
var value = attributes[attr]; var val = attributes[attr];
var value = (0, _underscore.isString)(val) ? val.replace(/"/g, '"') : val;
if (!(0, _underscore.isUndefined)(value)) { if (!(0, _underscore.isUndefined)(value)) {
attrs.push(attr + '="' + value + '"'); if ((0, _underscore.isBoolean)(value)) {
value && attrs.push(attr);
} else {
attrs.push(attr + '="' + value + '"');
}
} }
} }
@ -17739,12 +17746,14 @@ module.exports = {
var u = 'px'; var u = 'px';
bStyle.display = 'block'; bStyle.display = 'block';
var canvasPos = canvas.getCanvasView().getPosition(); var canvasPos = canvas.getCanvasView().getPosition();
var badgeH = badge ? badge.offsetHeight : 0; if (canvasPos) {
var badgeW = badge ? badge.offsetWidth : 0; var badgeH = badge ? badge.offsetHeight : 0;
var top = pos.top - badgeH < canvasPos.top ? canvasPos.top : pos.top - badgeH; var badgeW = badge ? badge.offsetWidth : 0;
var left = pos.left + badgeW < canvasPos.left ? canvasPos.left : pos.left; var top = pos.top - badgeH < canvasPos.top ? canvasPos.top : pos.top - badgeH;
bStyle.top = top + u; var left = pos.left + badgeW < canvasPos.left ? canvasPos.left : pos.left;
bStyle.left = left + u; bStyle.top = top + u;
bStyle.left = left + u;
}
}, },
@ -17966,10 +17975,12 @@ module.exports = {
elPos: elPos, elPos: elPos,
event: 'toolbarPosUpdate' event: 'toolbarPosUpdate'
}); });
var leftPos = pos.left + pos.elementWidth - pos.targetWidth; if (pos) {
toolbarStyle.top = pos.top + unit; var leftPos = pos.left + pos.elementWidth - pos.targetWidth;
toolbarStyle.left = (leftPos < 0 ? 0 : leftPos) + unit; toolbarStyle.top = pos.top + unit;
toolbarStyle.display = origDisp; toolbarStyle.left = (leftPos < 0 ? 0 : leftPos) + unit;
toolbarStyle.display = origDisp;
}
}, },
@ -21952,24 +21963,6 @@ module.exports = Backbone.View.extend({
videoNode.src = fileURL videoNode.src = fileURL
*/ */
/*
// Show local video files, http://jsfiddle.net/dsbonev/cCCZ2/embedded/result,js,html,css/
var URL = window.URL || window.webkitURL
var file = this.files[0]
var type = file.type
var videoNode = document.createElement('video');
var canPlay = videoNode.canPlayType(type) // can use also for 'audio' types
if (canPlay === '') canPlay = 'no'
var message = 'Can play type "' + type + '": ' + canPlay
var isError = canPlay === 'no'
displayMessage(message, isError)
if (isError) {
return
}
var fileURL = URL.createObjectURL(file)
videoNode.src = fileURL
*/
// If it's an image, try to find its size // If it's an image, try to find its size
if (type === 'image') { if (type === 'image') {
var data = { var data = {
@ -22520,11 +22513,10 @@ module.exports = Backbone.Collection.extend({
var opt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var opt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
if (typeof models === 'string') { if (typeof models === 'string') {
var cssc = this.em.get('CssComposer');
var parsed = this.em.get('Parser').parseHtml(models); var parsed = this.em.get('Parser').parseHtml(models);
models = parsed.html; models = parsed.html;
var cssc = this.em.get('CssComposer');
if (parsed.css && cssc) { if (parsed.css && cssc) {
var avoidUpdateStyle = opt.avoidUpdateStyle; var avoidUpdateStyle = opt.avoidUpdateStyle;
@ -22572,18 +22564,20 @@ module.exports = Backbone.View.extend({
/** /**
* Add to collection * Add to collection
* @param {Object} Model * @param {Model} model
* * @param {Collection} coll
* @return void * @param {Object} opts
* @private * @private
* */ * */
addTo: function addTo(model) { addTo: function addTo(model) {
var coll = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var opts = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var em = this.config.em; var em = this.config.em;
var i = this.collection.indexOf(model); var i = this.collection.indexOf(model);
this.addToCollection(model, null, i); this.addToCollection(model, null, i);
if (em && !model.opt.temporary) { if (em && !opts.temporary) {
em.trigger('add:component', model); // @deprecated
em.trigger('component:add', model); em.trigger('component:add', model);
} }
}, },
@ -23689,7 +23683,7 @@ module.exports = function () {
plugins: plugins, plugins: plugins,
// Will be replaced on build // Will be replaced on build
version: '0.14.5', version: '0.14.6',
/** /**
* Initializes an editor based on passed options * Initializes an editor based on passed options
@ -24951,8 +24945,8 @@ module.exports = Backbone.Model.extend({
} }
}); });
sm.store(store, function () { sm.store(store, function (res) {
clb && clb(); clb && clb(res);
_this4.set('changesCount', 0); _this4.set('changesCount', 0);
_this4.trigger('storage:store', store); _this4.trigger('storage:store', store);
}); });
@ -26125,6 +26119,7 @@ module.exports = Backbone.View.extend({
if (!dropContent) { if (!dropContent) {
// Putting `avoidStore` here will make the UndoManager behave wrong // Putting `avoidStore` here will make the UndoManager behave wrong
opts.temporary = 1;
modelTemp = targetCollection.add({}, _extends({}, opts)); modelTemp = targetCollection.add({}, _extends({}, opts));
if (model) { if (model) {
@ -31928,17 +31923,22 @@ module.exports = function (config) {
var add = []; var add = [];
var result = []; var result = [];
var sels = str.split(','); var sels = str.split(',');
for (var i = 0, len = sels.length; i < len; i++) { for (var i = 0, len = sels.length; i < len; i++) {
var sel = sels[i].trim(); var sel = sels[i].trim();
// Will accept only concatenated classes and last // Will accept only concatenated classes and last
// class might be with state (eg. :hover), nothing else. // class might be with state (eg. :hover), nothing else.
if (/^(\.{1}[\w\-]+)+(:{1,2}[\w\-()]+)?$/gi.test(sel)) { // Can also accept SINGLE ID selectors, eg. `#myid`, `#myid:hover`
// Composed are not valid: `#myid.some-class`, `#myid.some-class:hover`
if (/^(\.{1}[\w\-]+)+(:{1,2}[\w\-()]+)?$/gi.test(sel) || /^(#{1}[\w\-]+){1}(:{1,2}[\w\-()]+)?$/gi.test(sel)) {
var cls = sel.split('.').filter(Boolean); var cls = sel.split('.').filter(Boolean);
result.push(cls); result.push(cls);
} else { } else {
add.push(sel); add.push(sel);
} }
} }
return { return {
result: result, result: result,
add: add add: add
@ -32093,59 +32093,65 @@ module.exports = function (config) {
"use strict"; "use strict";
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; /**
* Selectors in GrapesJS are used in CSS Composer inside Rules and in Components as classes. To get better this concept let's take
* a look at this code:
*
* ```css
* span > #send-btn.btn{
* ...
* }
* ```
* ```html
* <span>
* <button id="send-btn" class="btn"></button>
* </span>
* ```
*
* In this scenario we get:
* span -> selector of type `tag`
* send-btn -> selector of type `id`
* btn -> selector of type `class`
*
* So, for example, being `btn` the same class entity it'll be easier to refactor and track things.
*
* Before using methods you should get first the module from the editor instance, in this way:
*
* ```js
* var selectorManager = editor.SelectorManager;
* ```
*
* @module SelectorManager
* @param {Object} config Configurations
* @param {Array<Object>} [config.selectors=[]] Default selectors
* @param {Array<Object>} [config.states=[]] Default states
* @param {String} [config.label='Classes'] Classes label
* @param {String} [config.statesLabel='- State -'] The empty state label
* @return {this}
* @example
* ...
* {
* selectors: [
* {name:'myselector1'},
* ...
* ],
* states: [{
* name: 'hover', label: 'Hover'
* },{
* name: 'active', label: 'Click'
* }],
* statesLabel: '- Selecte State -',
* }
*/
var _underscore = __webpack_require__(1); var _underscore = __webpack_require__(1);
var isId = function isId(str) {
return (0, _underscore.isString)(str) && str[0] == '#';
}; /**
* Selectors in GrapesJS are used in CSS Composer inside Rules and in Components as classes. To get better this concept let's take
* a look at this code:
*
* ```css
* span > #send-btn.btn{
* ...
* }
* ```
* ```html
* <span>
* <button id="send-btn" class="btn"></button>
* </span>
* ```
*
* In this scenario we get:
* span -> selector of type `tag`
* send-btn -> selector of type `id`
* btn -> selector of type `class`
*
* So, for example, being `btn` the same class entity it'll be easier to refactor and track things.
*
* Before using methods you should get first the module from the editor instance, in this way:
*
* ```js
* var selectorManager = editor.SelectorManager;
* ```
*
* @module SelectorManager
* @param {Object} config Configurations
* @param {Array<Object>} [config.selectors=[]] Default selectors
* @param {Array<Object>} [config.states=[]] Default states
* @param {String} [config.label='Classes'] Classes label
* @param {String} [config.statesLabel='- State -'] The empty state label
* @return {this}
* @example
* ...
* {
* selectors: [
* {name:'myselector1'},
* ...
* ],
* states: [{
* name: 'hover', label: 'Hover'
* },{
* name: 'active', label: 'Click'
* }],
* statesLabel: '- Selecte State -',
* }
*/
var isClass = function isClass(str) {
return (0, _underscore.isString)(str) && str[0] == '.';
};
module.exports = function (config) { module.exports = function (config) {
var c = config || {}, var c = config || {},
defaults = __webpack_require__(89), defaults = __webpack_require__(89),
@ -32219,25 +32225,30 @@ module.exports = function (config) {
* @param {String} name Selector name * @param {String} name Selector name
* @param {Object} opts Selector options * @param {Object} opts Selector options
* @param {String} [opts.label=''] Label for the selector, if it's not provided the label will be the same as the name * @param {String} [opts.label=''] Label for the selector, if it's not provided the label will be the same as the name
* @param {String} [opts.type='class'] Type of the selector. At the moment, only 'class' is available * @param {String} [opts.type=1] Type of the selector. At the moment, only 'class' (1) is available
* @return {Model} * @return {Model}
* @example * @example
* var selector = selectorManager.add('selectorName'); * var selector = selectorManager.add('selectorName');
* // Same as * // Same as
* var selector = selectorManager.add('selectorName', { * var selector = selectorManager.add('selectorName', {
* type: 'class', * type: 1,
* label: 'selectorName' * label: 'selectorName'
* }); * });
* */ * */
add: function add(name) { add: function add(name) {
var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
if ((typeof name === 'undefined' ? 'undefined' : _typeof(name)) == 'object') { if ((0, _underscore.isObject)(name)) {
opts = name; opts = name;
} else { } else {
opts.name = name; opts.name = name;
} }
if (isId(opts.name)) {
opts.name = opts.name.substr(1);
opts.type = Selector.TYPE_ID;
}
if (opts.label && !opts.name) { if (opts.label && !opts.name) {
opts.name = Selector.escapeName(opts.label); opts.name = Selector.escapeName(opts.label);
} }
@ -32288,6 +32299,10 @@ module.exports = function (config) {
get: function get(name) { get: function get(name) {
var type = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Selector.TYPE_CLASS; var type = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Selector.TYPE_CLASS;
if (isId(name)) {
name = name.substr(1);
type = Selector.TYPE_ID;
}
return selectors.where({ name: name, type: type })[0]; return selectors.where({ name: name, type: type })[0];
}, },
@ -36381,16 +36396,18 @@ module.exports = function () {
event: 'rteToolbarPosUpdate' event: 'rteToolbarPosUpdate'
}); });
if (config.adjustToolbar) { if (pos) {
// Move the toolbar down when the top canvas edge is reached if (config.adjustToolbar) {
if (pos.top <= pos.canvasTop) { // Move the toolbar down when the top canvas edge is reached
pos.top = pos.elementTop + pos.elementHeight; if (pos.top <= pos.canvasTop) {
pos.top = pos.elementTop + pos.elementHeight;
}
} }
}
var toolbarStyle = toolbar.style; var toolbarStyle = toolbar.style;
toolbarStyle.top = pos.top + un; toolbarStyle.top = pos.top + un;
toolbarStyle.left = pos.left + un; toolbarStyle.left = pos.left + un;
}
}, },
@ -41942,7 +41959,10 @@ module.exports = {
labelPlhHref: 'eg. https://google.com', labelPlhHref: 'eg. https://google.com',
// Default options for the target input // Default options for the target input
optionsTarget: [{ value: '', name: 'This window' }, { value: '_blank', name: 'New window' }] optionsTarget: [{ value: '', name: 'This window' }, { value: '_blank', name: 'New window' }],
// Text to show in case no element selected
textNoElement: 'Select an element before using Trait Manager'
}; };
/***/ }), /***/ }),
@ -44405,6 +44425,7 @@ module.exports = function () {
getTargetToElementDim: function getTargetToElementDim(target, element, options) { getTargetToElementDim: function getTargetToElementDim(target, element, options) {
var opts = options || {}; var opts = options || {};
var canvasPos = CanvasView.getPosition(); var canvasPos = CanvasView.getPosition();
if (!canvasPos) return;
var pos = opts.elPos || CanvasView.getElementPos(element); var pos = opts.elPos || CanvasView.getElementPos(element);
var toRight = options.toRight || 0; var toRight = options.toRight || 0;
var targetHeight = opts.targetHeight || target.offsetHeight; var targetHeight = opts.targetHeight || target.offsetHeight;
@ -45088,7 +45109,9 @@ module.exports = Backbone.View.extend({
* @private * @private
*/ */
getPosition: function getPosition() { getPosition: function getPosition() {
var bEl = this.frame.el.contentDocument.body; var doc = this.frame.el.contentDocument;
if (!doc) return;
var bEl = doc.body;
var fo = this.getFrameOffset(); var fo = this.getFrameOffset();
var co = this.getCanvasOffset(); var co = this.getCanvasOffset();
return { return {
@ -45116,8 +45139,12 @@ module.exports = Backbone.View.extend({
// In editor, I make use of setTimeout as during the append process of elements // In editor, I make use of setTimeout as during the append process of elements
// those will not be available immediatly, therefore 'item' variable // those will not be available immediatly, therefore 'item' variable
var script = document.createElement('script'); var script = document.createElement('script');
script.innerText = '\n setTimeout(function() {\n var item = document.getElementById(\'' + id + '\');\n if (!item) return;\n (function(){\n ' + model.getScriptString() + ';\n }.bind(item))()\n }, 1);'; script.innerHTML = '\n setTimeout(function() {\n var item = document.getElementById(\'' + id + '\');\n if (!item) return;\n (function(){\n ' + model.getScriptString() + ';\n }.bind(item))()\n }, 1);';
view.scriptContainer.get(0).appendChild(script); // #873
// Adding setTimeout will make js components work on init of the editor
setTimeout(function () {
return view.scriptContainer.get(0).appendChild(script);
}, 0);
}, },
@ -46323,33 +46350,64 @@ module.exports = {
/***/ (function(module, exports, __webpack_require__) { /***/ (function(module, exports, __webpack_require__) {
"use strict"; "use strict";
/* WEBPACK VAR INJECTION */(function(Backbone) {
var $ = Backbone.$;
var $ = __webpack_require__(0).$;
module.exports = { module.exports = {
run: function run(editor, sender) { run: function run(editor, sender) {
this.sender = sender;
var config = editor.Config; var config = editor.Config;
var pfx = config.stylePrefix; var pfx = config.stylePrefix;
var tm = editor.TraitManager; var tm = editor.TraitManager;
var panelC; var panelC;
if (!this.obj) {
if (!this.$cn) {
var tmView = tm.getTraitsViewer(); var tmView = tm.getTraitsViewer();
var confTm = tm.getConfig(); var confTm = tm.getConfig();
this.obj = $('<div></div>').append('<div class="' + pfx + 'traits-label">' + confTm.labelContainer + '</div>').get(0); this.$cn = $('<div></div>');
this.obj.appendChild(tmView.render().el); this.$cn2 = $('<div></div>');
this.$cn.append(this.$cn2);
this.$header = $('<div>').append('<div class="' + confTm.stylePrefix + 'header">' + confTm.textNoElement + '</div>');
this.$cn.append(this.$header);
this.$cn2.append('<div class="' + pfx + 'traits-label">' + confTm.labelContainer + '</div>');
this.$cn2.append(tmView.render().el);
var panels = editor.Panels; var panels = editor.Panels;
if (!panels.getPanel('views-container')) panelC = panels.addPanel({ id: 'views-container' });else panelC = panels.getPanel('views-container'); if (!panels.getPanel('views-container')) panelC = panels.addPanel({ id: 'views-container' });else panelC = panels.getPanel('views-container');
panelC.set('appendContent', this.obj).trigger('change:appendContent');
panelC.set('appendContent', this.$cn.get(0)).trigger('change:appendContent');
this.target = editor.getModel();
this.listenTo(this.target, 'change:selectedComponent', this.toggleTm);
} }
this.obj.style.display = 'block'; this.toggleTm();
},
/**
* Toggle Trait Manager visibility
* @private
*/
toggleTm: function toggleTm() {
var sender = this.sender;
if (sender && sender.get && !sender.get('active')) return;
if (this.target.get('selectedComponent')) {
this.$cn2.show();
this.$header.hide();
} else {
this.$cn2.hide();
this.$header.show();
}
}, },
stop: function stop() { stop: function stop() {
if (this.obj) this.obj.style.display = 'none'; this.$cn2 && this.$cn2.hide();
this.$header && this.$header.hide();
} }
}; };
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(0)))
/***/ }), /***/ }),
/* 201 */ /* 201 */
@ -47418,6 +47476,7 @@ module.exports = Backbone.View.extend({
var label = this.model.get('label'); var label = this.model.get('label');
el.className += ' ' + className + ' ' + pfx + 'one-bg ' + pfx + 'four-color-h'; el.className += ' ' + className + ' ' + pfx + 'one-bg ' + pfx + 'four-color-h';
el.innerHTML = '<div class="' + className + '-label">' + label + '</div>'; el.innerHTML = '<div class="' + className + '-label">' + label + '</div>';
el.title = el.textContent.trim();
(0, _mixins.hasDnd)(this.em) && el.setAttribute('draggable', true); (0, _mixins.hasDnd)(this.em) && el.setAttribute('draggable', true);
return this; return this;
} }

6
dist/grapes.min.js

File diff suppressed because one or more lines are too long

2
package-lock.json

@ -1,6 +1,6 @@
{ {
"name": "grapesjs", "name": "grapesjs",
"version": "0.14.5", "version": "0.14.6",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

2
package.json

@ -1,7 +1,7 @@
{ {
"name": "grapesjs", "name": "grapesjs",
"description": "Free and Open Source Web Builder Framework", "description": "Free and Open Source Web Builder Framework",
"version": "0.14.5", "version": "0.14.6",
"author": "Artur Arseniev", "author": "Artur Arseniev",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"homepage": "http://grapesjs.com", "homepage": "http://grapesjs.com",

20
src/asset_manager/view/FileUploader.js

@ -289,26 +289,6 @@ module.exports = Backbone.View.extend(
videoNode.src = fileURL videoNode.src = fileURL
*/ */
/*
// Show local video files, http://jsfiddle.net/dsbonev/cCCZ2/embedded/result,js,html,css/
var URL = window.URL || window.webkitURL
var file = this.files[0]
var type = file.type
var videoNode = document.createElement('video');
var canPlay = videoNode.canPlayType(type) // can use also for 'audio' types
if (canPlay === '') canPlay = 'no'
var message = 'Can play type "' + type + '": ' + canPlay
var isError = canPlay === 'no'
displayMessage(message, isError)
if (isError) {
return
}
var fileURL = URL.createObjectURL(file)
videoNode.src = fileURL
*/
// If it's an image, try to find its size // If it's an image, try to find its size
if (type === 'image') { if (type === 'image') {
const data = { const data = {

1
src/block_manager/view/BlockView.js

@ -71,6 +71,7 @@ module.exports = Backbone.View.extend({
const label = this.model.get('label'); const label = this.model.get('label');
el.className += ` ${className} ${pfx}one-bg ${pfx}four-color-h`; el.className += ` ${className} ${pfx}one-bg ${pfx}four-color-h`;
el.innerHTML = `<div class="${className}-label">${label}</div>`; el.innerHTML = `<div class="${className}-label">${label}</div>`;
el.title = el.textContent.trim();
hasDnd(this.em) && el.setAttribute('draggable', true); hasDnd(this.em) && el.setAttribute('draggable', true);
return this; return this;
} }

1
src/canvas/index.js

@ -245,6 +245,7 @@ module.exports = () => {
getTargetToElementDim(target, element, options) { getTargetToElementDim(target, element, options) {
var opts = options || {}; var opts = options || {};
var canvasPos = CanvasView.getPosition(); var canvasPos = CanvasView.getPosition();
if (!canvasPos) return;
var pos = opts.elPos || CanvasView.getElementPos(element); var pos = opts.elPos || CanvasView.getElementPos(element);
var toRight = options.toRight || 0; var toRight = options.toRight || 0;
var targetHeight = opts.targetHeight || target.offsetHeight; var targetHeight = opts.targetHeight || target.offsetHeight;

18
src/canvas/view/CanvasView.js

@ -308,9 +308,11 @@ module.exports = Backbone.View.extend({
* @private * @private
*/ */
getPosition() { getPosition() {
var bEl = this.frame.el.contentDocument.body; const doc = this.frame.el.contentDocument;
var fo = this.getFrameOffset(); if (!doc) return;
var co = this.getCanvasOffset(); const bEl = doc.body;
const fo = this.getFrameOffset();
const co = this.getCanvasOffset();
return { return {
top: fo.top + bEl.scrollTop - co.top, top: fo.top + bEl.scrollTop - co.top,
left: fo.left + bEl.scrollLeft - co.left left: fo.left + bEl.scrollLeft - co.left
@ -328,14 +330,14 @@ module.exports = Backbone.View.extend({
this.getJsContainer().append(view.scriptContainer.get(0)); this.getJsContainer().append(view.scriptContainer.get(0));
} }
var model = view.model; const model = view.model;
var id = model.getId(); const id = model.getId();
view.el.id = id; view.el.id = id;
view.scriptContainer.html(''); view.scriptContainer.html('');
// In editor, I make use of setTimeout as during the append process of elements // In editor, I make use of setTimeout as during the append process of elements
// those will not be available immediatly, therefore 'item' variable // those will not be available immediatly, therefore 'item' variable
const script = document.createElement('script'); const script = document.createElement('script');
script.innerText = ` script.innerHTML = `
setTimeout(function() { setTimeout(function() {
var item = document.getElementById('${id}'); var item = document.getElementById('${id}');
if (!item) return; if (!item) return;
@ -343,7 +345,9 @@ module.exports = Backbone.View.extend({
${model.getScriptString()}; ${model.getScriptString()};
}.bind(item))() }.bind(item))()
}, 1);`; }, 1);`;
view.scriptContainer.get(0).appendChild(script); // #873
// Adding setTimeout will make js components work on init of the editor
setTimeout(() => view.scriptContainer.get(0).appendChild(script), 0);
}, },
/** /**

59
src/commands/view/OpenTraitManager.js

@ -1,35 +1,64 @@
const $ = Backbone.$; const $ = require('backbone').$;
module.exports = { module.exports = {
run(editor, sender) { run(editor, sender) {
this.sender = sender;
var config = editor.Config; var config = editor.Config;
var pfx = config.stylePrefix; var pfx = config.stylePrefix;
var tm = editor.TraitManager; var tm = editor.TraitManager;
var panelC; var panelC;
if (!this.obj) {
if (!this.$cn) {
var tmView = tm.getTraitsViewer(); var tmView = tm.getTraitsViewer();
var confTm = tm.getConfig(); var confTm = tm.getConfig();
this.obj = $('<div></div>') this.$cn = $('<div></div>');
.append( this.$cn2 = $('<div></div>');
'<div class="' + this.$cn.append(this.$cn2);
pfx + this.$header = $('<div>').append(
'traits-label">' + `<div class="${confTm.stylePrefix}header">${confTm.textNoElement}</div>`
confTm.labelContainer + );
'</div>' this.$cn.append(this.$header);
) this.$cn2.append(
.get(0); `<div class="${pfx}traits-label">${confTm.labelContainer}</div>`
this.obj.appendChild(tmView.render().el); );
this.$cn2.append(tmView.render().el);
var panels = editor.Panels; var panels = editor.Panels;
if (!panels.getPanel('views-container')) if (!panels.getPanel('views-container'))
panelC = panels.addPanel({ id: 'views-container' }); panelC = panels.addPanel({ id: 'views-container' });
else panelC = panels.getPanel('views-container'); else panelC = panels.getPanel('views-container');
panelC.set('appendContent', this.obj).trigger('change:appendContent');
panelC
.set('appendContent', this.$cn.get(0))
.trigger('change:appendContent');
this.target = editor.getModel();
this.listenTo(this.target, 'change:selectedComponent', this.toggleTm);
} }
this.obj.style.display = 'block'; this.toggleTm();
},
/**
* Toggle Trait Manager visibility
* @private
*/
toggleTm() {
const sender = this.sender;
if (sender && sender.get && !sender.get('active')) return;
if (this.target.get('selectedComponent')) {
this.$cn2.show();
this.$header.hide();
} else {
this.$cn2.hide();
this.$header.show();
}
}, },
stop() { stop() {
if (this.obj) this.obj.style.display = 'none'; this.$cn2 && this.$cn2.hide();
this.$header && this.$header.hide();
} }
}; };

26
src/commands/view/SelectComponent.js

@ -222,13 +222,15 @@ module.exports = {
var u = 'px'; var u = 'px';
bStyle.display = 'block'; bStyle.display = 'block';
var canvasPos = canvas.getCanvasView().getPosition(); var canvasPos = canvas.getCanvasView().getPosition();
var badgeH = badge ? badge.offsetHeight : 0; if (canvasPos) {
var badgeW = badge ? badge.offsetWidth : 0; var badgeH = badge ? badge.offsetHeight : 0;
var top = var badgeW = badge ? badge.offsetWidth : 0;
pos.top - badgeH < canvasPos.top ? canvasPos.top : pos.top - badgeH; var top =
var left = pos.left + badgeW < canvasPos.left ? canvasPos.left : pos.left; pos.top - badgeH < canvasPos.top ? canvasPos.top : pos.top - badgeH;
bStyle.top = top + u; var left = pos.left + badgeW < canvasPos.left ? canvasPos.left : pos.left;
bStyle.left = left + u; bStyle.top = top + u;
bStyle.left = left + u;
}
}, },
/** /**
@ -446,10 +448,12 @@ module.exports = {
elPos, elPos,
event: 'toolbarPosUpdate' event: 'toolbarPosUpdate'
}); });
var leftPos = pos.left + pos.elementWidth - pos.targetWidth; if (pos) {
toolbarStyle.top = pos.top + unit; var leftPos = pos.left + pos.elementWidth - pos.targetWidth;
toolbarStyle.left = (leftPos < 0 ? 0 : leftPos) + unit; toolbarStyle.top = pos.top + unit;
toolbarStyle.display = origDisp; toolbarStyle.left = (leftPos < 0 ? 0 : leftPos) + unit;
toolbarStyle.display = origDisp;
}
}, },
/** /**

12
src/dom_components/model/Component.js

@ -2,6 +2,7 @@ import {
isUndefined, isUndefined,
isArray, isArray,
isEmpty, isEmpty,
isBoolean,
has, has,
clone, clone,
isString, isString,
@ -322,11 +323,13 @@ const Component = Backbone.Model.extend(Styleable).extend(
if (em && em.getConfig('avoidInlineStyle')) { if (em && em.getConfig('avoidInlineStyle')) {
prop = isString(prop) ? this.parseStyle(prop) : prop; prop = isString(prop) ? this.parseStyle(prop) : prop;
prop = { ...prop, ...this.get('style') };
const state = this.get('state'); const state = this.get('state');
const cc = em.get('CssComposer'); const cc = em.get('CssComposer');
const propOrig = this.getStyle(); const propOrig = this.getStyle();
this.rule = cc.setIdRule(this.getId(), prop, { ...opts, state }); this.rule = cc.setIdRule(this.getId(), prop, { ...opts, state });
const diff = shallowDiff(propOrig, prop); const diff = shallowDiff(propOrig, prop);
this.set('style', {}, { silent: 1 });
keys(diff).forEach(pr => this.trigger(`change:style:${pr}`)); keys(diff).forEach(pr => this.trigger(`change:style:${pr}`));
} else { } else {
prop = Styleable.setStyle.apply(this, arguments); prop = Styleable.setStyle.apply(this, arguments);
@ -670,10 +673,15 @@ const Component = Backbone.Model.extend(Styleable).extend(
const attributes = this.getAttrToHTML(); const attributes = this.getAttrToHTML();
for (let attr in attributes) { for (let attr in attributes) {
const value = attributes[attr]; const val = attributes[attr];
const value = isString(val) ? val.replace(/"/g, '&quot;') : val;
if (!isUndefined(value)) { if (!isUndefined(value)) {
attrs.push(`${attr}="${value}"`); if (isBoolean(value)) {
value && attrs.push(attr);
} else {
attrs.push(`${attr}="${value}"`);
}
} }
} }

9
src/dom_components/model/Components.js

@ -34,14 +34,13 @@ module.exports = Backbone.Collection.extend({
add(models, opt = {}) { add(models, opt = {}) {
if (typeof models === 'string') { if (typeof models === 'string') {
var parsed = this.em.get('Parser').parseHtml(models); const cssc = this.em.get('CssComposer');
const parsed = this.em.get('Parser').parseHtml(models);
models = parsed.html; models = parsed.html;
var cssc = this.em.get('CssComposer');
if (parsed.css && cssc) { if (parsed.css && cssc) {
var { avoidUpdateStyle } = opt; const { avoidUpdateStyle } = opt;
var added = cssc.addCollection(parsed.css, { const added = cssc.addCollection(parsed.css, {
extend: 1, extend: 1,
avoidUpdateStyle avoidUpdateStyle
}); });

11
src/dom_components/view/ComponentsView.js

@ -11,18 +11,17 @@ module.exports = Backbone.View.extend({
/** /**
* Add to collection * Add to collection
* @param {Object} Model * @param {Model} model
* * @param {Collection} coll
* @return void * @param {Object} opts
* @private * @private
* */ * */
addTo(model) { addTo(model, coll = {}, opts = {}) {
const em = this.config.em; const em = this.config.em;
const i = this.collection.indexOf(model); const i = this.collection.indexOf(model);
this.addToCollection(model, null, i); this.addToCollection(model, null, i);
if (em && !model.opt.temporary) { if (em && !opts.temporary) {
em.trigger('add:component', model); // @deprecated
em.trigger('component:add', model); em.trigger('component:add', model);
} }
}, },

4
src/editor/model/Editor.js

@ -346,8 +346,8 @@ module.exports = Backbone.Model.extend({
for (var el in obj) store[el] = obj[el]; for (var el in obj) store[el] = obj[el];
}); });
sm.store(store, () => { sm.store(store, (res) => {
clb && clb(); clb && clb(res);
this.set('changesCount', 0); this.set('changesCount', 0);
this.trigger('storage:store', store); this.trigger('storage:store', store);
}); });

16
src/parser/model/ParserCss.js

@ -33,20 +33,28 @@ module.exports = config => ({
* //} * //}
*/ */
parseSelector(str = '') { parseSelector(str = '') {
var add = []; const add = [];
var result = []; const result = [];
var sels = str.split(','); const sels = str.split(',');
for (var i = 0, len = sels.length; i < len; i++) { for (var i = 0, len = sels.length; i < len; i++) {
var sel = sels[i].trim(); var sel = sels[i].trim();
// Will accept only concatenated classes and last // Will accept only concatenated classes and last
// class might be with state (eg. :hover), nothing else. // class might be with state (eg. :hover), nothing else.
if (/^(\.{1}[\w\-]+)+(:{1,2}[\w\-()]+)?$/gi.test(sel)) { // Can also accept SINGLE ID selectors, eg. `#myid`, `#myid:hover`
// Composed are not valid: `#myid.some-class`, `#myid.some-class:hover`
if (
/^(\.{1}[\w\-]+)+(:{1,2}[\w\-()]+)?$/gi.test(sel) ||
/^(#{1}[\w\-]+){1}(:{1,2}[\w\-()]+)?$/gi.test(sel)
) {
var cls = sel.split('.').filter(Boolean); var cls = sel.split('.').filter(Boolean);
result.push(cls); result.push(cls);
} else { } else {
add.push(sel); add.push(sel);
} }
} }
return { return {
result, result,
add add

18
src/rich_text_editor/index.js

@ -225,16 +225,18 @@ module.exports = () => {
event: 'rteToolbarPosUpdate' event: 'rteToolbarPosUpdate'
}); });
if (config.adjustToolbar) { if (pos) {
// Move the toolbar down when the top canvas edge is reached if (config.adjustToolbar) {
if (pos.top <= pos.canvasTop) { // Move the toolbar down when the top canvas edge is reached
pos.top = pos.elementTop + pos.elementHeight; if (pos.top <= pos.canvasTop) {
pos.top = pos.elementTop + pos.elementHeight;
}
} }
}
const toolbarStyle = toolbar.style; const toolbarStyle = toolbar.style;
toolbarStyle.top = pos.top + un; toolbarStyle.top = pos.top + un;
toolbarStyle.left = pos.left + un; toolbarStyle.left = pos.left + un;
}
}, },
/** /**

20
src/selector_manager/index.js

@ -49,7 +49,10 @@
* } * }
*/ */
import { isString, isElement } from 'underscore'; import { isString, isElement, isObject } from 'underscore';
const isId = str => isString(str) && str[0] == '#';
const isClass = str => isString(str) && str[0] == '.';
module.exports = config => { module.exports = config => {
var c = config || {}, var c = config || {},
@ -130,23 +133,28 @@ module.exports = config => {
* @param {String} name Selector name * @param {String} name Selector name
* @param {Object} opts Selector options * @param {Object} opts Selector options
* @param {String} [opts.label=''] Label for the selector, if it's not provided the label will be the same as the name * @param {String} [opts.label=''] Label for the selector, if it's not provided the label will be the same as the name
* @param {String} [opts.type='class'] Type of the selector. At the moment, only 'class' is available * @param {String} [opts.type=1] Type of the selector. At the moment, only 'class' (1) is available
* @return {Model} * @return {Model}
* @example * @example
* var selector = selectorManager.add('selectorName'); * var selector = selectorManager.add('selectorName');
* // Same as * // Same as
* var selector = selectorManager.add('selectorName', { * var selector = selectorManager.add('selectorName', {
* type: 'class', * type: 1,
* label: 'selectorName' * label: 'selectorName'
* }); * });
* */ * */
add(name, opts = {}) { add(name, opts = {}) {
if (typeof name == 'object') { if (isObject(name)) {
opts = name; opts = name;
} else { } else {
opts.name = name; opts.name = name;
} }
if (isId(opts.name)) {
opts.name = opts.name.substr(1);
opts.type = Selector.TYPE_ID;
}
if (opts.label && !opts.name) { if (opts.label && !opts.name) {
opts.name = Selector.escapeName(opts.label); opts.name = Selector.escapeName(opts.label);
} }
@ -193,6 +201,10 @@ module.exports = config => {
* var selector = selectorManager.get('selectorName'); * var selector = selectorManager.get('selectorName');
* */ * */
get(name, type = Selector.TYPE_CLASS) { get(name, type = Selector.TYPE_CLASS) {
if (isId(name)) {
name = name.substr(1);
type = Selector.TYPE_ID;
}
return selectors.where({ name, type })[0]; return selectors.where({ name, type })[0];
}, },

6
src/styles/scss/_gjs_blocks.scss

@ -31,14 +31,14 @@
width: 45%; width: 45%;
padding: 1em; padding: 1em;
box-sizing: border-box; box-sizing: border-box;
height: 90px; min-height: 90px;
cursor: all-scroll; cursor: all-scroll;
font-size: 11px; font-size: 11px;
font-weight: lighter; font-weight: lighter;
text-align: center; text-align: center;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: flex-end; justify-content: space-between;
border: 1px solid rgba(0, 0, 0, 0.2); border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 3px; border-radius: 3px;
margin: 10px 2.5% 5px; margin: 10px 2.5% 5px;
@ -71,6 +71,8 @@
font-size: 0.65rem; font-size: 0.65rem;
font-weight: normal; font-weight: normal;
font-family: Helvetica, sans-serif; font-family: Helvetica, sans-serif;
overflow: hidden;
text-overflow: ellipsis;
} }
.#{$app-prefix}block.#{$app-prefix}bdrag { .#{$app-prefix}block.#{$app-prefix}bdrag {

5
src/styles/scss/_gjs_traits.scss

@ -8,6 +8,11 @@
} }
} }
.#{$trt-prefix}header {
font-weight: lighter;
padding: 10px;
}
.#{$trt-prefix}trait { .#{$trt-prefix}trait {
display: flex; display: flex;
justify-content: start; justify-content: start;

5
src/trait_manager/config/config.js

@ -17,5 +17,8 @@ module.exports = {
optionsTarget: [ optionsTarget: [
{ value: '', name: 'This window' }, { value: '', name: 'This window' },
{ value: '_blank', name: 'New window' } { value: '_blank', name: 'New window' }
] ],
// Text to show in case no element selected
textNoElement: 'Select an element before using Trait Manager'
}; };

1
src/utils/Sorter.js

@ -969,6 +969,7 @@ module.exports = Backbone.View.extend({
if (!dropContent) { if (!dropContent) {
// Putting `avoidStore` here will make the UndoManager behave wrong // Putting `avoidStore` here will make the UndoManager behave wrong
opts.temporary = 1;
modelTemp = targetCollection.add({}, { ...opts }); modelTemp = targetCollection.add({}, { ...opts });
if (model) { if (model) {

28
test/specs/dom_components/index.js

@ -44,7 +44,9 @@ describe('DOM Components', () => {
}; };
beforeEach(() => { beforeEach(() => {
em = new Editor(); em = new Editor({
avoidInlineStyle: 1
});
config = { config = {
em, em,
storeWrapper: 1 storeWrapper: 1
@ -117,6 +119,30 @@ describe('DOM Components', () => {
it('Render wrapper', () => { it('Render wrapper', () => {
expect(obj.render()).toExist(); expect(obj.render()).toExist();
}); });
it('Import propertly components and styles with the same ids', () => {
obj = em.get('DomComponents');
const cc = em.get('CssComposer');
const id = 'idtest';
const comp = obj.addComponent(`
<div id="${id}" style="color:red; padding: 50px 100px">Text</div>
<style>
#${id} { background-color: red }
</style>`);
expect(em.getHtml()).toEqual(`<div id="${id}">Text</div>`);
expect(obj.getComponents().length).toEqual(1);
obj
.getComponents()
.first()
.addStyle({ margin: '10px' });
expect(cc.getAll().length).toEqual(1);
expect(cc.getIdRule(id).getStyle()).toEqual({
color: 'red',
'background-color': 'red',
padding: '50px 100px',
margin: '10px'
});
});
}); });
ComponentModels.run(); ComponentModels.run();

23
test/specs/dom_components/model/Component.js

@ -135,6 +135,29 @@ module.exports = {
expect(obj.toHTML()).toEqual('<div/>'); expect(obj.toHTML()).toEqual('<div/>');
}); });
it('Component toHTML with quotes in attribute', () => {
obj = new Component();
let attrs = obj.get('attributes');
attrs['data-test'] = '"value"';
obj.set('attributes', attrs);
expect(obj.toHTML()).toEqual(
'<div data-test="&quot;value&quot;"></div>'
);
});
it('Manage correctly boolean attributes', () => {
obj = new Component();
obj.set('attributes', {
'data-test': 'value',
checked: false,
required: true,
avoid: true
});
expect(obj.toHTML()).toEqual(
'<div data-test="value" required avoid></div>'
);
});
it('Component parse empty div', () => { it('Component parse empty div', () => {
var el = document.createElement('div'); var el = document.createElement('div');
obj = Component.isComponent(el); obj = Component.isComponent(el);

30
test/specs/parser/model/ParserCss.js

@ -1,4 +1,5 @@
const ParserCss = require('parser/model/ParserCss'); const ParserCss = require('parser/model/ParserCss');
const Selector = require('selector_manager/model/Selector');
module.exports = { module.exports = {
run() { run() {
@ -275,6 +276,35 @@ module.exports = {
}; };
expect(obj.parse(str)).toEqual(result); expect(obj.parse(str)).toEqual(result);
}); });
it('Parse ID rule', () => {
var str = `#test { color: red }`;
var result = {
selectors: ['#test'],
style: { color: 'red' }
};
expect(obj.parse(str)).toEqual(result);
});
it('Parse ID rule with state', () => {
var str = `#test:hover { color: red }`;
var result = {
selectors: ['#test'],
state: 'hover',
style: { color: 'red' }
};
expect(obj.parse(str)).toEqual(result);
});
it('Avoid composed selectors with ID', () => {
var str = `#test.class, #test.class:hover, .class { color: red }`;
var result = {
selectors: ['class'],
selectorsAdd: '#test.class, #test.class:hover',
style: { color: 'red' }
};
expect(obj.parse(str)).toEqual(result);
});
}); });
} }
}; };

8
test/specs/selector_manager/index.js

@ -45,6 +45,14 @@ describe('SelectorManager', () => {
expect(obj.get('test').get('type')).toEqual(obj.Selector.TYPE_CLASS); expect(obj.get('test').get('type')).toEqual(obj.Selector.TYPE_CLASS);
}); });
it('Add a selector as an id', () => {
const name = 'test';
var sel = obj.add(`#${name}`);
expect(sel.get('name')).toEqual(name);
expect(sel.get('label')).toEqual(name);
expect(obj.get(`#${name}`).get('type')).toEqual(obj.Selector.TYPE_ID);
});
it('Check name property', () => { it('Check name property', () => {
var name = 'test'; var name = 'test';
var sel = obj.add(name); var sel = obj.add(name);

2
webpack.config.js

@ -24,7 +24,7 @@ module.exports = env => {
const index = 'index.html'; const index = 'index.html';
const indexDev = `_${index}`; const indexDev = `_${index}`;
const template = fs.existsSync(indexDev) ? indexDev : index; const template = fs.existsSync(indexDev) ? indexDev : index;
plugins.push(new HtmlWebpackPlugin({ template })); plugins.push(new HtmlWebpackPlugin({ template, inject: false }));
} }
plugins.push(new webpack.ProvidePlugin({ plugins.push(new webpack.ProvidePlugin({

Loading…
Cancel
Save