Browse Source

Merge dev (#2253)

Merge dev
pull/3853/head
Artur Arseniev 7 years ago
committed by GitHub
parent
commit
23eda1e1e2
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      dist/css/grapes.min.css
  2. 271
      dist/grapes.js
  3. 8
      dist/grapes.min.js
  4. 2
      dist/grapes.min.js.map
  5. 622
      docs/modules/Components-new.md
  6. 2
      package-lock.json
  7. 8
      package.json
  8. 11
      src/asset_manager/config/config.js
  9. 5
      src/asset_manager/view/FileUploader.js
  10. 4
      src/code_manager/model/CssGenerator.js
  11. 2
      src/code_manager/model/HtmlGenerator.js
  12. 8
      src/commands/view/SelectComponent.js
  13. 2
      src/css_composer/model/CssRule.js
  14. 2
      src/dom_components/config/config.js
  15. 5
      src/dom_components/model/Component.js
  16. 16
      src/dom_components/view/ComponentTextView.js
  17. 27
      src/dom_components/view/ComponentView.js
  18. 6
      src/editor/config/config.js
  19. 8
      src/editor/model/Editor.js
  20. 18
      src/rich_text_editor/index.js
  21. 8
      src/style_manager/index.js
  22. 5
      src/style_manager/model/Property.js
  23. 18
      src/style_manager/view/PropertiesView.js
  24. 2
      src/styles/scss/_gjs_selectors.scss
  25. 4
      src/trait_manager/view/TraitView.js
  26. 4
      src/utils/dom.js
  27. 19
      test/specs/css_composer/e2e/CssComposer.js

2
dist/css/grapes.min.css

File diff suppressed because one or more lines are too long

271
dist/grapes.js

@ -19268,7 +19268,15 @@ __webpack_require__.r(__webpack_exports__);
// Default title for the asset manager modal // Default title for the asset manager modal
modalTitle: 'Select Image', modalTitle: 'Select Image',
//Default placeholder for input //Default placeholder for input
inputPlaceholder: 'http://path/to/the/image.jpg' inputPlaceholder: 'http://path/to/the/image.jpg',
//method called before upload, on return false upload is canceled.
// @example
// beforeUpload: (files) => {
// // logic...
// var stopUpload = true;
// if(stopUpload) return false;
// }
beforeUpload: null
}); });
/***/ }), /***/ }),
@ -20201,6 +20209,9 @@ __webpack_require__.r(__webpack_exports__);
var files = e.dataTransfer ? e.dataTransfer.files : e.target.files; var files = e.dataTransfer ? e.dataTransfer.files : e.target.files;
var config = this.config; var config = this.config;
var beforeUpload = config.beforeUpload;
var beforeUploadResponse = beforeUpload && beforeUpload(files);
if (beforeUploadResponse === false) return;
var body = new FormData(); var body = new FormData();
var params = config.params, var params = config.params,
customFetch = config.customFetch; customFetch = config.customFetch;
@ -23113,7 +23124,7 @@ var maxValue = Number.MAX_VALUE;
var avoidInline = em && em.getConfig('avoidInlineStyle'); var avoidInline = em && em.getConfig('avoidInlineStyle');
var style = model.styleToString(); var style = model.styleToString();
var classes = model.get('classes'); var classes = model.get('classes');
var wrappesIsBody = opts.wrappesIsBody; var wrapperIsBody = opts.wrapperIsBody;
var isWrapper = model.get('wrapper'); var isWrapper = model.get('wrapper');
this.ids.push("#".concat(model.getId())); // Let's know what classes I've found this.ids.push("#".concat(model.getId())); // Let's know what classes I've found
@ -23123,7 +23134,7 @@ var maxValue = Number.MAX_VALUE;
if (!avoidInline && style) { if (!avoidInline && style) {
var selector = "#".concat(model.getId()); var selector = "#".concat(model.getId());
selector = wrappesIsBody && isWrapper ? 'body' : selector; selector = wrapperIsBody && isWrapper ? 'body' : selector;
code = "".concat(selector, "{").concat(style, "}"); code = "".concat(selector, "{").concat(style, "}");
} }
@ -23283,7 +23294,7 @@ __webpack_require__.r(__webpack_exports__);
var models = model.get('components'); var models = model.get('components');
if (opts.exportWrapper) { if (opts.exportWrapper) {
return opts.wrappesIsBody ? "<body>".concat(this.buildModels(models), "</body>") : model.toHTML(); return opts.wrapperIsBody ? "<body>".concat(this.buildModels(models), "</body>") : model.toHTML();
} }
return this.buildModels(models); return this.buildModels(models);
@ -26418,9 +26429,8 @@ var showOffsets;
var style = modelToStyle.getStyle(); var style = modelToStyle.getStyle();
if (!onlyHeight) { if (!onlyHeight) {
var padding = 10; var bodyw = canvas.getBody().offsetWidth;
var frameOffset = canvas.getCanvasView().getFrameOffset(); var width = rect.w < bodyw ? rect.w : bodyw;
var width = rect.w < frameOffset.width - padding ? rect.w : frameOffset.width - padding;
style[keyWidth] = autoWidth ? 'auto' : "".concat(width).concat(unitWidth); style[keyWidth] = autoWidth ? 'auto' : "".concat(width).concat(unitWidth);
} }
@ -27573,7 +27583,7 @@ __webpack_require__.r(__webpack_exports__);
var state = this.get('state'); var state = this.get('state');
var wrapper = this.get('wrapper'); var wrapper = this.get('wrapper');
var addSelector = this.get('selectorsAdd'); var addSelector = this.get('selectorsAdd');
var isBody = wrapper && em && em.getConfig('wrappesIsBody'); var isBody = wrapper && em && em.getConfig('wrapperIsBody');
var selectors = isBody ? 'body' : this.get('selectors').getFullString(); var selectors = isBody ? 'body' : this.get('selectors').getFullString();
var stateStr = state ? ":".concat(state) : ''; var stateStr = state ? ":".concat(state) : '';
selectors && result.push("".concat(selectors).concat(stateStr)); selectors && result.push("".concat(selectors).concat(stateStr));
@ -28299,7 +28309,7 @@ __webpack_require__.r(__webpack_exports__);
* It might be useful to transform custom objects (es. some framework specific JSX) to GrapesJS component one. * It might be useful to transform custom objects (es. some framework specific JSX) to GrapesJS component one.
* This custom function will be executed on ANY new added component to the editor so make smart checks/conditions * This custom function will be executed on ANY new added component to the editor so make smart checks/conditions
* to avoid doing useless executions * to avoid doing useless executions
* By default, GrapesJS supports already React Element * By default, GrapesJS supports already elements generated from React JSX preset
* @example * @example
* processor: (obj) => { * processor: (obj) => {
* if (obj.$$typeof) { // eg. this is a React Element * if (obj.$$typeof) { // eg. this is a React Element
@ -29084,23 +29094,26 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
"use strict"; "use strict";
__webpack_require__.r(__webpack_exports__); __webpack_require__.r(__webpack_exports__);
/* harmony import */ var _babel_runtime_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/defineProperty */ "./node_modules/@babel/runtime/helpers/defineProperty.js"); /* harmony import */ var _babel_runtime_helpers_typeof__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/typeof */ "./node_modules/@babel/runtime/helpers/typeof.js");
/* harmony import */ var _babel_runtime_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var _babel_runtime_helpers_typeof__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_typeof__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var underscore__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! underscore */ "./node_modules/underscore/underscore.js"); /* harmony import */ var _babel_runtime_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime/helpers/defineProperty */ "./node_modules/@babel/runtime/helpers/defineProperty.js");
/* harmony import */ var underscore__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(underscore__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var _babel_runtime_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var utils_mixins__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! utils/mixins */ "./src/utils/mixins.js"); /* harmony import */ var underscore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! underscore */ "./node_modules/underscore/underscore.js");
/* harmony import */ var domain_abstract_model_Styleable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! domain_abstract/model/Styleable */ "./src/domain_abstract/model/Styleable.js"); /* harmony import */ var underscore__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(underscore__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var backbone__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! backbone */ "./node_modules/backbone/backbone.js"); /* harmony import */ var utils_mixins__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! utils/mixins */ "./src/utils/mixins.js");
/* harmony import */ var backbone__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(backbone__WEBPACK_IMPORTED_MODULE_4__); /* harmony import */ var domain_abstract_model_Styleable__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! domain_abstract/model/Styleable */ "./src/domain_abstract/model/Styleable.js");
/* harmony import */ var _Components__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./Components */ "./src/dom_components/model/Components.js"); /* harmony import */ var backbone__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! backbone */ "./node_modules/backbone/backbone.js");
/* harmony import */ var selector_manager_model_Selector__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! selector_manager/model/Selector */ "./src/selector_manager/model/Selector.js"); /* harmony import */ var backbone__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(backbone__WEBPACK_IMPORTED_MODULE_5__);
/* harmony import */ var selector_manager_model_Selectors__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! selector_manager/model/Selectors */ "./src/selector_manager/model/Selectors.js"); /* harmony import */ var _Components__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./Components */ "./src/dom_components/model/Components.js");
/* harmony import */ var trait_manager_model_Traits__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! trait_manager/model/Traits */ "./src/trait_manager/model/Traits.js"); /* harmony import */ var selector_manager_model_Selector__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! selector_manager/model/Selector */ "./src/selector_manager/model/Selector.js");
/* harmony import */ var selector_manager_model_Selectors__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! selector_manager/model/Selectors */ "./src/selector_manager/model/Selectors.js");
/* harmony import */ var trait_manager_model_Traits__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! trait_manager/model/Traits */ "./src/trait_manager/model/Traits.js");
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _babel_runtime_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_0___default()(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _babel_runtime_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_1___default()(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
@ -29178,7 +29191,7 @@ var avoidInline = function avoidInline(em) {
*/ */
var Component = backbone__WEBPACK_IMPORTED_MODULE_4___default.a.Model.extend(domain_abstract_model_Styleable__WEBPACK_IMPORTED_MODULE_3__["default"]).extend({ var Component = backbone__WEBPACK_IMPORTED_MODULE_5___default.a.Model.extend(domain_abstract_model_Styleable__WEBPACK_IMPORTED_MODULE_4__["default"]).extend({
defaults: { defaults: {
tagName: 'div', tagName: 'div',
type: '', type: '',
@ -29257,7 +29270,7 @@ var Component = backbone__WEBPACK_IMPORTED_MODULE_4___default.a.Model.extend(dom
} }
var propagate = this.get('propagate'); var propagate = this.get('propagate');
propagate && this.set('propagate', Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isArray"])(propagate) ? propagate : [propagate]); // Check void elements propagate && this.set('propagate', Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isArray"])(propagate) ? propagate : [propagate]); // Check void elements
if (opt && opt.config && opt.config.voidElements.indexOf(this.get('tagName')) >= 0) { if (opt && opt.config && opt.config.voidElements.indexOf(this.get('tagName')) >= 0) {
this.set('void', true); this.set('void', true);
@ -29456,8 +29469,8 @@ var Component = backbone__WEBPACK_IMPORTED_MODULE_4___default.a.Model.extend(dom
var attrPrev = _objectSpread({}, this.previous('attributes')); var attrPrev = _objectSpread({}, this.previous('attributes'));
var diff = Object(utils_mixins__WEBPACK_IMPORTED_MODULE_2__["shallowDiff"])(attrPrev, attrs); var diff = Object(utils_mixins__WEBPACK_IMPORTED_MODULE_3__["shallowDiff"])(attrPrev, attrs);
Object(underscore__WEBPACK_IMPORTED_MODULE_1__["keys"])(diff).forEach(function (pr) { Object(underscore__WEBPACK_IMPORTED_MODULE_2__["keys"])(diff).forEach(function (pr) {
return _this2.trigger("change:attributes:".concat(pr), _this2, diff[pr]); return _this2.trigger("change:attributes:".concat(pr), _this2, diff[pr]);
}); });
return this; return this;
@ -29497,7 +29510,7 @@ var Component = backbone__WEBPACK_IMPORTED_MODULE_4___default.a.Model.extend(dom
} }
} }
return domain_abstract_model_Styleable__WEBPACK_IMPORTED_MODULE_3__["default"].getStyle.call(this); return domain_abstract_model_Styleable__WEBPACK_IMPORTED_MODULE_4__["default"].getStyle.call(this);
}, },
/** /**
@ -29516,7 +29529,7 @@ var Component = backbone__WEBPACK_IMPORTED_MODULE_4___default.a.Model.extend(dom
var opt = this.opt; var opt = this.opt;
if (em && em.getConfig('avoidInlineStyle') && !opt.temporary) { if (em && em.getConfig('avoidInlineStyle') && !opt.temporary) {
prop = Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isString"])(prop) ? this.parseStyle(prop) : prop; prop = Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isString"])(prop) ? this.parseStyle(prop) : prop;
prop = _objectSpread({}, prop, {}, this.get('style')); prop = _objectSpread({}, prop, {}, this.get('style'));
var state = this.get('state'); var state = this.get('state');
var cc = em.get('CssComposer'); var cc = em.get('CssComposer');
@ -29524,15 +29537,15 @@ var Component = backbone__WEBPACK_IMPORTED_MODULE_4___default.a.Model.extend(dom
this.rule = cc.setIdRule(this.getId(), prop, _objectSpread({}, opts, { this.rule = cc.setIdRule(this.getId(), prop, _objectSpread({}, opts, {
state: state state: state
})); }));
var diff = Object(utils_mixins__WEBPACK_IMPORTED_MODULE_2__["shallowDiff"])(propOrig, prop); var diff = Object(utils_mixins__WEBPACK_IMPORTED_MODULE_3__["shallowDiff"])(propOrig, prop);
this.set('style', {}, { this.set('style', {}, {
silent: 1 silent: 1
}); });
Object(underscore__WEBPACK_IMPORTED_MODULE_1__["keys"])(diff).forEach(function (pr) { Object(underscore__WEBPACK_IMPORTED_MODULE_2__["keys"])(diff).forEach(function (pr) {
return _this3.trigger("change:style:".concat(pr)); return _this3.trigger("change:style:".concat(pr));
}); });
} else { } else {
prop = domain_abstract_model_Styleable__WEBPACK_IMPORTED_MODULE_3__["default"].setStyle.apply(this, arguments); prop = domain_abstract_model_Styleable__WEBPACK_IMPORTED_MODULE_4__["default"].setStyle.apply(this, arguments);
} }
return prop; return prop;
@ -29552,17 +29565,17 @@ var Component = backbone__WEBPACK_IMPORTED_MODULE_4___default.a.Model.extend(dom
var id = this.getId(); // Add classes var id = this.getId(); // Add classes
this.get('classes').forEach(function (cls) { this.get('classes').forEach(function (cls) {
return classes.push(Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isString"])(cls) ? cls : cls.get('name')); return classes.push(Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isString"])(cls) ? cls : cls.get('name'));
}); });
classes.length && (attributes.class = classes.join(' ')); // Check if we need an ID on the component classes.length && (attributes.class = classes.join(' ')); // Check if we need an ID on the component
if (!Object(underscore__WEBPACK_IMPORTED_MODULE_1__["has"])(attributes, 'id')) { if (!Object(underscore__WEBPACK_IMPORTED_MODULE_2__["has"])(attributes, 'id')) {
var hasStyle; // If we don't rely on inline styling we have to check var hasStyle; // If we don't rely on inline styling we have to check
// for the ID selector // for the ID selector
if (avoidInline(em)) { if (avoidInline(em)) {
hasStyle = sm && sm.get(id, sm.Selector.TYPE_ID); hasStyle = sm && sm.get(id, sm.Selector.TYPE_ID);
} else if (!Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isEmpty"])(this.getStyle())) { } else if (!Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isEmpty"])(this.getStyle())) {
hasStyle = 1; hasStyle = 1;
} }
@ -29616,9 +29629,9 @@ var Component = backbone__WEBPACK_IMPORTED_MODULE_4___default.a.Model.extend(dom
*/ */
removeClass: function removeClass(classes) { removeClass: function removeClass(classes) {
var removed = []; var removed = [];
classes = Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isArray"])(classes) ? classes : [classes]; classes = Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isArray"])(classes) ? classes : [classes];
var selectors = this.get('classes'); var selectors = this.get('classes');
var type = selector_manager_model_Selector__WEBPACK_IMPORTED_MODULE_6__["default"].TYPE_CLASS; var type = selector_manager_model_Selector__WEBPACK_IMPORTED_MODULE_7__["default"].TYPE_CLASS;
classes.forEach(function (classe) { classes.forEach(function (classe) {
var classes = classe.split(' '); var classes = classe.split(' ');
classes.forEach(function (name) { classes.forEach(function (name) {
@ -29645,10 +29658,10 @@ var Component = backbone__WEBPACK_IMPORTED_MODULE_4___default.a.Model.extend(dom
var event = 'change:classes'; var event = 'change:classes';
var toListen = [this, event, this.initClasses]; var toListen = [this, event, this.initClasses];
var cls = this.get('classes') || []; var cls = this.get('classes') || [];
var clsArr = Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isString"])(cls) ? cls.split(' ') : cls; var clsArr = Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isString"])(cls) ? cls.split(' ') : cls;
this.stopListening.apply(this, toListen); this.stopListening.apply(this, toListen);
var classes = this.normalizeClasses(clsArr); var classes = this.normalizeClasses(clsArr);
var selectors = new selector_manager_model_Selectors__WEBPACK_IMPORTED_MODULE_7__["default"]([]); var selectors = new selector_manager_model_Selectors__WEBPACK_IMPORTED_MODULE_8__["default"]([]);
this.set('classes', selectors); this.set('classes', selectors);
selectors.add(classes); selectors.add(classes);
this.listenTo.apply(this, toListen); this.listenTo.apply(this, toListen);
@ -29660,12 +29673,12 @@ var Component = backbone__WEBPACK_IMPORTED_MODULE_4___default.a.Model.extend(dom
this.stopListening.apply(this, toListen); // Have to add components after the init, otherwise the parent this.stopListening.apply(this, toListen); // Have to add components after the init, otherwise the parent
// is not visible // is not visible
var comps = new _Components__WEBPACK_IMPORTED_MODULE_5__["default"](null, this.opt); var comps = new _Components__WEBPACK_IMPORTED_MODULE_6__["default"](null, this.opt);
comps.parent = this; comps.parent = this;
var components = this.get('components'); var components = this.get('components');
var addChild = !this.opt.avoidChildren; var addChild = !this.opt.avoidChildren;
this.set('components', comps); this.set('components', comps);
addChild && comps.add(Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isFunction"])(components) ? components(this) : components); addChild && comps.add(Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isFunction"])(components) ? components(this) : components);
this.listenTo.apply(this, toListen); this.listenTo.apply(this, toListen);
return this; return this;
}, },
@ -29709,7 +29722,7 @@ var Component = backbone__WEBPACK_IMPORTED_MODULE_4___default.a.Model.extend(dom
append: function append(components) { append: function append(components) {
var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var result = this.components().add(components, opts); var result = this.components().add(components, opts);
return Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isArray"])(result) ? result : [result]; return Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isArray"])(result) ? result : [result];
}, },
/** /**
@ -29728,7 +29741,7 @@ var Component = backbone__WEBPACK_IMPORTED_MODULE_4___default.a.Model.extend(dom
components: function components(_components) { components: function components(_components) {
var coll = this.get('components'); var coll = this.get('components');
if (Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isUndefined"])(_components)) { if (Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isUndefined"])(_components)) {
return coll; return coll;
} else { } else {
coll.reset(); coll.reset();
@ -29822,10 +29835,10 @@ var Component = backbone__WEBPACK_IMPORTED_MODULE_4___default.a.Model.extend(dom
loadTraits: function loadTraits(traits) { loadTraits: function loadTraits(traits) {
var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
traits = traits || this.get('traits'); traits = traits || this.get('traits');
traits = Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isFunction"])(traits) ? traits(this) : traits; traits = Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isFunction"])(traits) ? traits(this) : traits;
if (!(traits instanceof trait_manager_model_Traits__WEBPACK_IMPORTED_MODULE_8__["default"])) { if (!(traits instanceof trait_manager_model_Traits__WEBPACK_IMPORTED_MODULE_9__["default"])) {
var trt = new trait_manager_model_Traits__WEBPACK_IMPORTED_MODULE_8__["default"]([], this.opt); var trt = new trait_manager_model_Traits__WEBPACK_IMPORTED_MODULE_9__["default"]([], this.opt);
trt.setTarget(this); trt.setTarget(this);
if (traits.length) { if (traits.length) {
@ -29900,7 +29913,7 @@ var Component = backbone__WEBPACK_IMPORTED_MODULE_4___default.a.Model.extend(dom
var _this4 = this; var _this4 = this;
var em = this.em; var em = this.em;
var ids = Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isArray"])(id) ? id : [id]; var ids = Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isArray"])(id) ? id : [id];
var toRemove = ids.map(function (id) { var toRemove = ids.map(function (id) {
return _this4.getTrait(id); return _this4.getTrait(id);
}); });
@ -29981,7 +29994,7 @@ var Component = backbone__WEBPACK_IMPORTED_MODULE_4___default.a.Model.extend(dom
attr.view = ''; attr.view = '';
opts.collection = null; opts.collection = null;
if (em && em.getConfig('avoidInlineStyle') && !Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isEmpty"])(style)) { if (em && em.getConfig('avoidInlineStyle') && !Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isEmpty"])(style)) {
attr.style = style; attr.style = style;
} }
@ -30053,19 +30066,19 @@ var Component = backbone__WEBPACK_IMPORTED_MODULE_4___default.a.Model.extend(dom
var attributes = this.getAttrToHTML(); // Get custom attributes if requested var attributes = this.getAttrToHTML(); // Get custom attributes if requested
if (customAttr) { if (customAttr) {
if (Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isFunction"])(customAttr)) { if (Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isFunction"])(customAttr)) {
attributes = customAttr(model, attributes) || {}; attributes = customAttr(model, attributes) || {};
} else if (Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isObject"])(customAttr)) { } else if (Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isObject"])(customAttr)) {
attributes = customAttr; attributes = customAttr;
} }
} }
for (var attr in attributes) { for (var attr in attributes) {
var val = attributes[attr]; var val = attributes[attr];
var value = Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isString"])(val) ? val.replace(/"/g, '&quot;') : val; var value = Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isString"])(val) ? val.replace(/"/g, '&quot;') : val;
if (!Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isUndefined"])(value)) { if (!Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isUndefined"])(value)) {
if (Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isBoolean"])(value)) { if (Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isBoolean"])(value)) {
value && attrs.push(attr); value && attrs.push(attr);
} else { } else {
attrs.push("".concat(attr, "=\"").concat(value, "\"")); attrs.push("".concat(attr, "=\"").concat(value, "\""));
@ -30104,31 +30117,31 @@ var Component = backbone__WEBPACK_IMPORTED_MODULE_4___default.a.Model.extend(dom
args[_key2] = arguments[_key2]; args[_key2] = arguments[_key2];
} }
var obj = backbone__WEBPACK_IMPORTED_MODULE_4___default.a.Model.prototype.toJSON.apply(this, args); var obj = backbone__WEBPACK_IMPORTED_MODULE_5___default.a.Model.prototype.toJSON.apply(this, args);
obj.attributes = this.getAttributes(); obj.attributes = this.getAttributes();
delete obj.attributes.class; delete obj.attributes.class;
delete obj.toolbar; delete obj.toolbar;
delete obj.traits; delete obj.traits;
if (this.em.getConfig('avoidDefaults')) { if (this.em.getConfig('avoidDefaults')) {
var defaults = Object(underscore__WEBPACK_IMPORTED_MODULE_1__["result"])(this, 'defaults'); var defaults = Object(underscore__WEBPACK_IMPORTED_MODULE_2__["result"])(this, 'defaults');
Object(underscore__WEBPACK_IMPORTED_MODULE_1__["forEach"])(defaults, function (value, key) { Object(underscore__WEBPACK_IMPORTED_MODULE_2__["forEach"])(defaults, function (value, key) {
if (['type', 'content'].indexOf(key) === -1 && obj[key] === value) { if (['type', 'content'].indexOf(key) === -1 && obj[key] === value) {
delete obj[key]; delete obj[key];
} }
}); });
if (Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isEmpty"])(obj.type)) { if (Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isEmpty"])(obj.type)) {
delete obj.type; delete obj.type;
} }
Object(underscore__WEBPACK_IMPORTED_MODULE_1__["forEach"])(['attributes', 'style'], function (prop) { Object(underscore__WEBPACK_IMPORTED_MODULE_2__["forEach"])(['attributes', 'style'], function (prop) {
if (Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isEmpty"])(defaults[prop]) && Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isEmpty"])(obj[prop])) { if (Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isEmpty"])(defaults[prop]) && Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isEmpty"])(obj[prop])) {
delete obj[prop]; delete obj[prop];
} }
}); });
Object(underscore__WEBPACK_IMPORTED_MODULE_1__["forEach"])(['classes', 'components'], function (prop) { Object(underscore__WEBPACK_IMPORTED_MODULE_2__["forEach"])(['classes', 'components'], function (prop) {
if (Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isEmpty"])(defaults[prop]) && !obj[prop].length) { if (Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isEmpty"])(defaults[prop]) && !obj[prop].length) {
delete obj[prop]; delete obj[prop];
} }
}); });
@ -30209,7 +30222,8 @@ var Component = backbone__WEBPACK_IMPORTED_MODULE_4___default.a.Model.extend(dom
// better optimization inside JS generator // better optimization inside JS generator
_this5.scriptUpdated(); _this5.scriptUpdated();
return _this5.attributes[v] || ''; var result = _this5.attributes[v] || '';
return Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isArray"])(result) || _babel_runtime_helpers_typeof__WEBPACK_IMPORTED_MODULE_0___default()(result) == 'object' ? JSON.stringify(result) : result;
}); });
return scr; return scr;
}, },
@ -30236,7 +30250,7 @@ var Component = backbone__WEBPACK_IMPORTED_MODULE_4___default.a.Model.extend(dom
* }) * })
*/ */
onAll: function onAll(clb) { onAll: function onAll(clb) {
if (Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isFunction"])(clb)) { if (Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isFunction"])(clb)) {
clb(this); clb(this);
this.components().forEach(function (model) { this.components().forEach(function (model) {
return model.onAll(clb); return model.onAll(clb);
@ -30356,7 +30370,7 @@ var Component = backbone__WEBPACK_IMPORTED_MODULE_4___default.a.Model.extend(dom
checkId: function checkId(components) { checkId: function checkId(components) {
var styles = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; var styles = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
var list = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var list = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var comps = Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isArray"])(components) ? components : [components]; var comps = Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isArray"])(components) ? components : [components];
comps.forEach(function (comp) { comps.forEach(function (comp) {
var _comp$attributes = comp.attributes, var _comp$attributes = comp.attributes,
attributes = _comp$attributes === void 0 ? {} : _comp$attributes, attributes = _comp$attributes === void 0 ? {} : _comp$attributes,
@ -30367,7 +30381,7 @@ var Component = backbone__WEBPACK_IMPORTED_MODULE_4___default.a.Model.extend(dom
var newId = Component.getIncrementId(id, list); var newId = Component.getIncrementId(id, list);
attributes.id = newId; // Update passed styles attributes.id = newId; // Update passed styles
Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isArray"])(styles) && styles.forEach(function (style) { Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isArray"])(styles) && styles.forEach(function (style) {
var selectors = style.selectors; var selectors = style.selectors;
selectors.forEach(function (sel, idx) { selectors.forEach(function (sel, idx) {
if (sel === "#".concat(id)) selectors[idx] = "#".concat(newId); if (sel === "#".concat(id)) selectors[idx] = "#".concat(newId);
@ -32354,13 +32368,14 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
var compProt = _ComponentView__WEBPACK_IMPORTED_MODULE_2__["default"].prototype;
/* harmony default export */ __webpack_exports__["default"] = (_ComponentView__WEBPACK_IMPORTED_MODULE_2__["default"].extend({ /* harmony default export */ __webpack_exports__["default"] = (_ComponentView__WEBPACK_IMPORTED_MODULE_2__["default"].extend({
events: { events: {
dblclick: 'onActive', dblclick: 'onActive',
input: 'onInput' input: 'onInput'
}, },
initialize: function initialize(o) { initialize: function initialize(o) {
_ComponentView__WEBPACK_IMPORTED_MODULE_2__["default"].prototype.initialize.apply(this, arguments); compProt.initialize.apply(this, arguments);
this.disableEditing = this.disableEditing.bind(this); this.disableEditing = this.disableEditing.bind(this);
var model = this.model; var model = this.model;
var em = this.em; var em = this.em;
@ -32514,7 +32529,18 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
mixins[method](elDocs, 'mousedown', this.disableEditing); // Avoid closing edit mode on component click mixins[method](elDocs, 'mousedown', this.disableEditing); // Avoid closing edit mode on component click
this.$el.off('mousedown', this.disablePropagation); this.$el.off('mousedown', this.disablePropagation);
this.$el[method]('mousedown', this.disablePropagation); this.$el[method]('mousedown', this.disablePropagation); // Fixes #2210 but use this also as a replacement
// of this fix: bd7b804f3b46eb45b4398304b2345ce870f232d2
if (this.config.draggableComponents) {
var el = this.el;
while (el) {
el.draggable = enable ? !1 : !0;
el = el.parentNode;
el.tagName == 'BODY' && (el = 0);
}
}
} }
})); }));
@ -32695,6 +32721,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var _model_Components__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../model/Components */ "./src/dom_components/model/Components.js"); /* harmony import */ var _model_Components__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../model/Components */ "./src/dom_components/model/Components.js");
/* harmony import */ var _ComponentsView__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./ComponentsView */ "./src/dom_components/view/ComponentsView.js"); /* harmony import */ var _ComponentsView__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./ComponentsView */ "./src/dom_components/view/ComponentsView.js");
/* harmony import */ var selector_manager_model_Selectors__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! selector_manager/model/Selectors */ "./src/selector_manager/model/Selectors.js"); /* harmony import */ var selector_manager_model_Selectors__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! selector_manager/model/Selectors */ "./src/selector_manager/model/Selectors.js");
/* harmony import */ var utils_dom__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! utils/dom */ "./src/utils/dom.js");
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
@ -32706,6 +32733,7 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
/* harmony default export */ __webpack_exports__["default"] = (backbone__WEBPACK_IMPORTED_MODULE_1___default.a.View.extend({ /* harmony default export */ __webpack_exports__["default"] = (backbone__WEBPACK_IMPORTED_MODULE_1___default.a.View.extend({
className: function className() { className: function className() {
return this.getClasses(); return this.getClasses();
@ -32748,7 +32776,17 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
dragstart: 'handleDragStart' dragstart: 'handleDragStart'
}); });
this.delegateEvents(); this.delegateEvents();
!modelOpt.temporary && this.init(); !modelOpt.temporary && this.init(this._clbObj());
},
_clbObj: function _clbObj() {
var em = this.em,
model = this.model,
el = this.el;
return {
editor: em && em.getEditor(),
model: model,
el: el
};
}, },
/** /**
@ -32756,10 +32794,20 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
*/ */
init: function init() {}, init: function init() {},
/**
* Remove callback
*/
removed: function removed() {},
/** /**
* Callback executed when the `active` event is triggered on component * Callback executed when the `active` event is triggered on component
*/ */
onActive: function onActive() {}, onActive: function onActive() {},
remove: function remove() {
backbone__WEBPACK_IMPORTED_MODULE_1___default.a.View.prototype.remove.apply(this, arguments);
this.removed(this._clbObj());
return this;
},
handleDragStart: function handleDragStart(event) { handleDragStart: function handleDragStart(event) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
@ -33069,7 +33117,7 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
model: model, model: model,
collection: collection collection: collection
}); });
el.replaceWith(this.el); Object(utils_dom__WEBPACK_IMPORTED_MODULE_6__["replaceWith"])(el, this.el);
this.render(); this.render();
}, },
@ -33111,7 +33159,7 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
modelOpt = this.modelOpt; modelOpt = this.modelOpt;
if (!modelOpt.temporary) { if (!modelOpt.temporary) {
this.onRender(); this.onRender(this._clbObj());
em && em.trigger('component:mount', model); em && em.trigger('component:mount', model);
} }
}, },
@ -34370,7 +34418,7 @@ __webpack_require__.r(__webpack_exports__);
// Show the wrapper component in the final code, eg. in editor.getHtml() // Show the wrapper component in the final code, eg. in editor.getHtml()
exportWrapper: 0, exportWrapper: 0,
// The wrapper, if visible, will be shown as a `<body>` // The wrapper, if visible, will be shown as a `<body>`
wrappesIsBody: 1, wrapperIsBody: 1,
// Usually when you update the `style` of the component this changes the // Usually when you update the `style` of the component this changes the
// element's `style` attribute. Unfortunately, inline styling doesn't allow // element's `style` attribute. Unfortunately, inline styling doesn't allow
// use of media queries (@media) or even pseudo selectors (eg. :hover). // use of media queries (@media) or even pseudo selectors (eg. :hover).
@ -34379,7 +34427,7 @@ __webpack_require__.r(__webpack_exports__);
// Avoid default properties from storable JSON data, like `components` and `styles`. // Avoid default properties from storable JSON data, like `components` and `styles`.
// With this option enabled your data will be smaller (usefull if need to // With this option enabled your data will be smaller (usefull if need to
// save some storage space) // save some storage space)
avoidDefaults: 0, avoidDefaults: 1,
// (experimental) // (experimental)
// The structure of components is always on the screen but it's not the same // 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 // for style rules. When you delete a component you might leave a lot of styles
@ -34408,7 +34456,7 @@ __webpack_require__.r(__webpack_exports__);
//Configurations for Storage Manager //Configurations for Storage Manager
storageManager: {}, storageManager: {},
//Configurations for Rich Text Editor //Configurations for Rich Text Editor
rte: {}, richTextEditor: {},
//Configurations for DomComponents //Configurations for DomComponents
domComponents: {}, domComponents: {},
//Configurations for Modal Dialog //Configurations for Modal Dialog
@ -35710,12 +35758,12 @@ var logs = {
getHtml: function getHtml() { getHtml: function getHtml() {
var config = this.config; var config = this.config;
var exportWrapper = config.exportWrapper; var exportWrapper = config.exportWrapper;
var wrappesIsBody = config.wrappesIsBody; var wrapperIsBody = config.wrapperIsBody;
var js = config.jsInHtml ? this.getJs() : ''; var js = config.jsInHtml ? this.getJs() : '';
var wrp = this.get('DomComponents').getComponent(); var wrp = this.get('DomComponents').getComponent();
var html = this.get('CodeManager').getCode(wrp, 'html', { var html = this.get('CodeManager').getCode(wrp, 'html', {
exportWrapper: exportWrapper, exportWrapper: exportWrapper,
wrappesIsBody: wrappesIsBody wrapperIsBody: wrapperIsBody
}); });
html += js ? "<script>".concat(js, "</script>") : ''; html += js ? "<script>".concat(js, "</script>") : '';
return html; return html;
@ -35730,7 +35778,7 @@ var logs = {
getCss: function getCss() { getCss: function getCss() {
var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var config = this.config; var config = this.config;
var wrappesIsBody = config.wrappesIsBody; var wrapperIsBody = config.wrapperIsBody;
var avoidProt = opts.avoidProtected; var avoidProt = opts.avoidProtected;
var keepUnusedStyles = !Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isUndefined"])(opts.keepUnusedStyles) ? opts.keepUnusedStyles : config.keepUnusedStyles; var keepUnusedStyles = !Object(underscore__WEBPACK_IMPORTED_MODULE_1__["isUndefined"])(opts.keepUnusedStyles) ? opts.keepUnusedStyles : config.keepUnusedStyles;
var cssc = this.get('CssComposer'); var cssc = this.get('CssComposer');
@ -35738,7 +35786,7 @@ var logs = {
var protCss = !avoidProt ? config.protectedCss : ''; var protCss = !avoidProt ? config.protectedCss : '';
return protCss + this.get('CodeManager').getCode(wrp, 'css', { return protCss + this.get('CodeManager').getCode(wrp, 'css', {
cssc: cssc, cssc: cssc,
wrappesIsBody: wrappesIsBody, wrapperIsBody: wrapperIsBody,
keepUnusedStyles: keepUnusedStyles keepUnusedStyles: keepUnusedStyles
}); });
}, },
@ -36113,7 +36161,7 @@ var defaultConfig = {
editors: editors, editors: editors,
plugins: plugins, plugins: plugins,
// Will be replaced on build // Will be replaced on build
version: '0.15.3', version: '0.15.5',
/** /**
* Initialize the editor with passed options * Initialize the editor with passed options
@ -39436,9 +39484,17 @@ __webpack_require__.r(__webpack_exports__);
"use strict"; "use strict";
__webpack_require__.r(__webpack_exports__); __webpack_require__.r(__webpack_exports__);
/* harmony import */ var _model_RichTextEditor__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./model/RichTextEditor */ "./src/rich_text_editor/model/RichTextEditor.js"); /* harmony import */ var _babel_runtime_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/defineProperty */ "./node_modules/@babel/runtime/helpers/defineProperty.js");
/* harmony import */ var utils_mixins__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! utils/mixins */ "./src/utils/mixins.js"); /* harmony import */ var _babel_runtime_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _config_config__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./config/config */ "./src/rich_text_editor/config/config.js"); /* harmony import */ var _model_RichTextEditor__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./model/RichTextEditor */ "./src/rich_text_editor/model/RichTextEditor.js");
/* harmony import */ var utils_mixins__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! utils/mixins */ "./src/utils/mixins.js");
/* harmony import */ var _config_config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./config/config */ "./src/rich_text_editor/config/config.js");
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _babel_runtime_helpers_defineProperty__WEBPACK_IMPORTED_MODULE_0___default()(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
/** /**
* This module allows to customize the built-in toolbar of the Rich Text Editor and use commands from the [HTML Editing APIs](https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand). * This module allows to customize the built-in toolbar of the Rich Text Editor and use commands from the [HTML Editing APIs](https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand).
* It's highly recommended to keep this toolbar as small as possible, especially from styling commands (eg. 'fontSize') and leave this task to the Style Manager * It's highly recommended to keep this toolbar as small as possible, especially from styling commands (eg. 'fontSize') and leave this task to the Style Manager
@ -39446,7 +39502,7 @@ __webpack_require__.r(__webpack_exports__);
* You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object](https://github.com/artf/grapesjs/blob/master/src/rich_text_editor/config/config.js) * You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object](https://github.com/artf/grapesjs/blob/master/src/rich_text_editor/config/config.js)
* ```js * ```js
* const editor = grapesjs.init({ * const editor = grapesjs.init({
* rte: { * richTextEditor: {
* // options * // options
* } * }
* }) * })
@ -39490,6 +39546,9 @@ __webpack_require__.r(__webpack_exports__);
* @private * @private
*/ */
name: 'RichTextEditor', name: 'RichTextEditor',
getConfig: function getConfig() {
return config;
},
/** /**
* Initialize module. Automatically called with a new instance of the editor * Initialize module. Automatically called with a new instance of the editor
@ -39498,14 +39557,7 @@ __webpack_require__.r(__webpack_exports__);
*/ */
init: function init() { init: function init() {
var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
config = opts; config = _objectSpread({}, _config_config__WEBPACK_IMPORTED_MODULE_3__["default"], {}, opts);
for (var name in _config_config__WEBPACK_IMPORTED_MODULE_2__["default"]) {
if (!(name in config)) {
config[name] = _config_config__WEBPACK_IMPORTED_MODULE_2__["default"][name];
}
}
var ppfx = config.pStylePrefix; var ppfx = config.pStylePrefix;
if (ppfx) { if (ppfx) {
@ -39518,7 +39570,7 @@ __webpack_require__.r(__webpack_exports__);
toolbar.className = "".concat(ppfx, "rte-toolbar ").concat(ppfx, "one-bg"); toolbar.className = "".concat(ppfx, "rte-toolbar ").concat(ppfx, "one-bg");
globalRte = this.initRte(document.createElement('div')); //Avoid closing on toolbar clicking globalRte = this.initRte(document.createElement('div')); //Avoid closing on toolbar clicking
Object(utils_mixins__WEBPACK_IMPORTED_MODULE_1__["on"])(toolbar, 'mousedown', function (e) { Object(utils_mixins__WEBPACK_IMPORTED_MODULE_2__["on"])(toolbar, 'mousedown', function (e) {
return e.stopPropagation(); return e.stopPropagation();
}); });
return this; return this;
@ -39552,7 +39604,7 @@ __webpack_require__.r(__webpack_exports__);
button: "".concat(pfx, "action"), button: "".concat(pfx, "action"),
active: "".concat(pfx, "active") active: "".concat(pfx, "active")
}; };
var rte = new _model_RichTextEditor__WEBPACK_IMPORTED_MODULE_0__["default"]({ var rte = new _model_RichTextEditor__WEBPACK_IMPORTED_MODULE_1__["default"]({
el: el, el: el,
classes: classes, classes: classes,
actions: actions, actions: actions,
@ -41908,6 +41960,7 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
* @param {Array<Object>} [property.properties=[]] Nested properties for composite and stack type * @param {Array<Object>} [property.properties=[]] Nested properties for composite and stack type
* @param {Array<Object>} [property.layers=[]] Layers for stack properties * @param {Array<Object>} [property.layers=[]] Layers for stack properties
* @param {Array<Object>} [property.list=[]] List of possible options for radio and select types * @param {Array<Object>} [property.list=[]] List of possible options for radio and select types
* @param {Object} [options={}] Options
* @return {Property|null} Added Property or `null` in case sector doesn't exist * @return {Property|null} Added Property or `null` in case sector doesn't exist
* @example * @example
* var property = styleManager.addProperty('mySector',{ * var property = styleManager.addProperty('mySector',{
@ -41922,14 +41975,16 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
* value: '200px', * value: '200px',
* name: '200', * name: '200',
* }], * }],
* }); * }, { at: 0 });
* // With `at: 0` we place the new property at the beginning of the collection
*/ */
addProperty: function addProperty(sectorId, property) { addProperty: function addProperty(sectorId, property) {
var opts = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var sector = this.getSector(sectorId, { var sector = this.getSector(sectorId, {
warn: 1 warn: 1
}); });
var prop = null; var prop = null;
if (sector) prop = sector.get('properties').add(property); if (sector) prop = sector.get('properties').add(property, opts);
return prop; return prop;
}, },
@ -42724,12 +42779,13 @@ var Property = backbone__WEBPACK_IMPORTED_MODULE_1___default.a.Model.extend({
getFullValue: function getFullValue(val) { getFullValue: function getFullValue(val) {
var fn = this.get('functionName'); var fn = this.get('functionName');
var value = Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isUndefined"])(val) ? this.get('value') : val; var value = Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isUndefined"])(val) ? this.get('value') : val;
var hasValue = !Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isUndefined"])(value);
if (fn && !Object(underscore__WEBPACK_IMPORTED_MODULE_2__["isUndefined"])(value)) { if (fn && hasValue) {
value = "".concat(fn, "(").concat(value, ")"); value = "".concat(fn, "(").concat(value, ")");
} }
if (this.get('important')) { if (hasValue && this.get('important')) {
value = "".concat(value, " !important"); value = "".concat(value, " !important");
} }
@ -44480,6 +44536,8 @@ __webpack_require__.r(__webpack_exports__);
__webpack_require__.r(__webpack_exports__); __webpack_require__.r(__webpack_exports__);
/* harmony import */ var backbone__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! backbone */ "./node_modules/backbone/backbone.js"); /* harmony import */ var backbone__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! backbone */ "./node_modules/backbone/backbone.js");
/* harmony import */ var backbone__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(backbone__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var backbone__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(backbone__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var utils_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! utils/dom */ "./src/utils/dom.js");
/* harmony default export */ __webpack_exports__["default"] = (backbone__WEBPACK_IMPORTED_MODULE_0___default.a.View.extend({ /* harmony default export */ __webpack_exports__["default"] = (backbone__WEBPACK_IMPORTED_MODULE_0___default.a.View.extend({
initialize: function initialize(o) { initialize: function initialize(o) {
@ -44495,10 +44553,12 @@ __webpack_require__.r(__webpack_exports__);
this.listenTo(coll, 'add', this.addTo); this.listenTo(coll, 'add', this.addTo);
this.listenTo(coll, 'reset', this.render); this.listenTo(coll, 'reset', this.render);
}, },
addTo: function addTo(model) { addTo: function addTo(model, coll, opts) {
this.add(model); this.add(model, null, opts);
}, },
add: function add(model, frag) { add: function add(model, frag) {
var opts = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var appendTo = frag || this.el;
var view = new model.typeView({ var view = new model.typeView({
model: model, model: model,
name: model.get('name'), name: model.get('name'),
@ -44515,14 +44575,9 @@ __webpack_require__.r(__webpack_exports__);
} }
view.render(); view.render();
var el = view.el; var rendered = view.el;
this.properties.push(view); this.properties.push(view);
Object(utils_dom__WEBPACK_IMPORTED_MODULE_1__["appendAtIndex"])(appendTo, rendered, opts.at);
if (frag) {
frag.appendChild(el);
} else {
this.el.appendChild(el);
}
}, },
render: function render() { render: function render() {
var _this = this; var _this = this;
@ -47127,7 +47182,7 @@ var $ = backbone__WEBPACK_IMPORTED_MODULE_2___default.a.$;
getInputElem: function getInputElem() { getInputElem: function getInputElem() {
var input = this.input, var input = this.input,
$input = this.$input; $input = this.$input;
return input || $input && $input.get(0) || this.getElInput(); return input || $input && $input.get && $input.get(0) || this.getElInput();
}, },
getModelValue: function getModelValue() { getModelValue: function getModelValue() {
var value; var value;
@ -52362,12 +52417,13 @@ var $ = backbone__WEBPACK_IMPORTED_MODULE_1___default.a.$;
/*!**************************!*\ /*!**************************!*\
!*** ./src/utils/dom.js ***! !*** ./src/utils/dom.js ***!
\**************************/ \**************************/
/*! exports provided: empty, appendAtIndex, appendVNodes */ /*! exports provided: empty, replaceWith, appendAtIndex, appendVNodes */
/***/ (function(module, __webpack_exports__, __webpack_require__) { /***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict"; "use strict";
__webpack_require__.r(__webpack_exports__); __webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "empty", function() { return empty; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "empty", function() { return empty; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "replaceWith", function() { return replaceWith; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "appendAtIndex", function() { return appendAtIndex; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "appendAtIndex", function() { return appendAtIndex; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "appendVNodes", function() { return appendVNodes; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "appendVNodes", function() { return appendVNodes; });
/* harmony import */ var underscore__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! underscore */ "./node_modules/underscore/underscore.js"); /* harmony import */ var underscore__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! underscore */ "./node_modules/underscore/underscore.js");
@ -52382,6 +52438,9 @@ var empty = function empty(node) {
node.removeChild(node.firstChild); node.removeChild(node.firstChild);
} }
}; };
var replaceWith = function replaceWith(oldEl, newEl) {
oldEl.parentNode.replaceChild(newEl, oldEl);
};
var appendAtIndex = function appendAtIndex(parent, child, index) { var appendAtIndex = function appendAtIndex(parent, child, index) {
var childNodes = parent.childNodes; var childNodes = parent.childNodes;
var total = childNodes.length; var total = childNodes.length;

8
dist/grapes.min.js

File diff suppressed because one or more lines are too long

2
dist/grapes.min.js.map

File diff suppressed because one or more lines are too long

622
docs/modules/Components-new.md

@ -13,6 +13,9 @@ This guide is referring to GrapesJS v0.14.67 or higher
[[toc]] [[toc]]
## How Components work? ## How Components work?
Let's see in detail how components work by looking at all steps from adding an HTML string to the editor. Let's see in detail how components work by looking at all steps from adding an HTML string to the editor.
@ -46,6 +49,8 @@ component.append('<div>...', { at: parseInt(length / 2, 10) })
``` ```
::: :::
### Component Definition ### Component Definition
In the first step the HTML string is parsed and trasformed to what is called **Component Definition**, so the result of the input would be: In the first step the HTML string is parsed and trasformed to what is called **Component Definition**, so the result of the input would be:
@ -76,9 +81,11 @@ You can notice the result is similar to what is generally called a **Virtual DOM
The meaning of properties like `tagName`, `attributes` and `components` are quite obvious, but what about `type`?! This particular property specifies the actual **Component** of our **Component Definition** (you check the list of default components [below](#built-in-components)) and if it's omitted, the default one will be used `type: 'default'`. The meaning of properties like `tagName`, `attributes` and `components` are quite obvious, but what about `type`?! This particular property specifies the actual **Component** of our **Component Definition** (you check the list of default components [below](#built-in-components)) and if it's omitted, the default one will be used `type: 'default'`.
At this point, a good question would be, how the editor assignes those types by starting from a simple HTML string? This step is identified as **Component Recognition** and it's explained in detail in the next paragraph. At this point, a good question would be, how the editor assignes those types by starting from a simple HTML string? This step is identified as **Component Recognition** and it's explained in detail in the next paragraph.
### Component Recognition and Component Type Stack ### Component Recognition and Component Type Stack
As we said before, when you pass an HTML string as a component to the editor, that string is parsed and compiled to the [Component Definition](#component-definition) with a new `type` property. To understand what `type` should be assigned, for each parsed HTML Element, the editor iterates over all the defined components, called **Component Type Stack**, and checks via `isComponent` method (we will see it later) if that component type is appropriate for that element. The Component Type Stack is just a simple array of components but the important part is the order of those components. Any new added Custom Component (we'll see later how to create them) goes on top of the Component Type Stack and each element returned from the parser iterates the stack from top to bottom (the last element of the stack is the `default` one), the iteration stops once one of the component returns a truthy value from the `isComponent` method. As we said before, when you pass an HTML string as a component to the editor, that string is parsed and compiled to the [Component Definition](#component-definition) with a new `type` property. To understand what `type` should be assigned, for each parsed HTML Element, the editor iterates over all the defined components, called **Component Type Stack**, and checks via `isComponent` method (we will see it later) if that component type is appropriate for that element. The Component Type Stack is just a simple array of component types but what is matter is the order of those types. Any new added custom **Component Type** (we'll see later how to create them) goes on top of the Component Type Stack and each element returned from the parser iterates the stack from top to bottom (the last element of the stack is the `default` one), the iteration stops once one of the component returns a truthy value from the `isComponent` method.
SVG - ComponentTypeStack SVG - ComponentTypeStack
@ -87,7 +94,8 @@ If you're importing big chunks of HTML code you might want to improve the perfor
::: :::
### Component Creation
### Component instance
Once the **Component Definition** is ready and the type is assigned, the [Component](api/component) instance can be created (known also as the **Model**). Let's step back to our previous example with the HTML string, the result of the `append` method is an array of added components. Once the **Component Definition** is ready and the type is assigned, the [Component](api/component) instance can be created (known also as the **Model**). Let's step back to our previous example with the HTML string, the result of the `append` method is an array of added components.
@ -135,15 +143,16 @@ JSON.stringify(component)
For storing/loading all the components you should rely on the [Storage Manager](modules/storage) For storing/loading all the components you should rely on the [Storage Manager](modules/storage)
::: :::
The Component instance is responable for the **final data** (eg. HTML, JSON) of your templates, so if you need, for example, to update/add some attribute in the HTML you need to update its component (eg. `component.addAttributes({ title: 'Title added' })`), so the Component/Model is your **Source of Truth**. So, the **Component instance** is responable for the **final data** (eg. HTML, JSON) of your templates. If you need, for example, to update/add some attribute in the HTML you need to update its component (eg. `component.addAttributes({ title: 'Title added' })`), so the Component/Model is your **Source of Truth**.
### Component Rendering ### Component rendering
Another important thing of components is how they are rendered in the **canvas**, this aspect is handled by the **View** of the component. It has nothing to do with the **final data**, you can return a big `<div>...</div>` string as HTML of your component but render it as a simple image in the canvas (think about placeholders for complex/dynamic data). Another important part of components is how they are rendered in the **canvas**, this aspect is handled by the **View** of the component. It has nothing to do with the **final data**, you can return a big `<div>...</div>` string as HTML of your component but render it as a simple image in the canvas (think about placeholders for complex/dynamic data).
So, by default, the view of components is automatically synced with the data of its models (you can't have a View without a Model). If you update the attribute of the component or append a new one as a child, the view will render it in the canvas. So, by default, the view of components is automatically synced with the data of its models (you can't have a View without a Model). If you update the attribute of the component or append a new one as a child, the view will render it in the canvas.
Unfotunatelly, sometimes, you might need some additional logic to handle better the component result. Think about allowing a user build its `<table>` element, for this specific case you might want to add custom buttons in the canvas, so it'd be easier adding/removing columns/rows. To handle those cases you can rely on the View, where you can add additional DOM component, attach events, etc. All of this will be completely unrelated with the final HTML of the `<table>` (the result the user would expect) as it handled by the Model. Unfotunatelly, sometimes, you might need some additional logic to handle better the component result. Think about allowing a user build its `<table>` element, for this specific case you might want to add custom buttons in the canvas, so it'd be easier adding/removing columns/rows. To handle those cases you can rely on the View, where you can add additional DOM component, attach events, etc. All of this will be completely unrelated with the final HTML of the `<table>` (the result the user would expect) as it handled by the Model.
Once the component is rendered (when you actually see it in the canvas) you can always access its View and the DOM element. Once the component is rendered (when you actually see it in the canvas) you can always access its View and the DOM element.
@ -155,22 +164,24 @@ const view = component.getView();
const el = component.getEl(); const el = component.getEl();
``` ```
So generally, the View is something you wouldn't need to change as the default one handles already the sync with the Model but in case you'd need more control over elements (eg. custom UI in canvas) you'll probably need to create a custom component and extend the default View with your logic. We'll see later how to create custom components. So, generally, the View is something you wouldn't need to change as the default one handles already the sync with the Model but in case you'd need more control over elements (eg. custom UI in canvas) you'll probably need to create a custom component type and extend the default View with your logic. We'll see later how to create custom Component Types.
So far we have seen the core concept behind Components and how they work. The **Model/Component** is the **source of truth** for the final code of templates (eg. the HTML export relies on it) and the *View/ComponentView* is what is used by the editor to **preview our components** to users in the canvas. So far we have seen the core concept behind Components and how they work. The **Model/Component** is the **source of truth** for the final code of templates (eg. the HTML export relies on it) and the *View/ComponentView* is what is used by the editor to **preview our components** to users in the canvas.
<!--
TODO TODO
A more advanced use case of custom components is an implementation of a custom renderer inside of them A more advanced use case of custom components is an implementation of a custom renderer inside of them
-->
## Built-in Components ## Built-in Component Types
Here below you can see the list of built-in components, ordered by their position in the Component Type Stack Here below you can see the list of built-in component types, ordered by their position in the **Component Type Stack**
* [`cell`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/ComponentTableCell.js) - Component for handle `<td>` and `<th>` elements * [`cell`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/ComponentTableCell.js) - Component for handle `<td>` and `<th>` elements
* [`row`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/ComponentTableRow.js) - Component for handle `<tr>` elements * [`row`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/ComponentTableRow.js) - Component for handle `<tr>` elements
@ -195,298 +206,430 @@ Here below you can see the list of built-in components, ordered by their positio
## Define new Component ## Define new Component Type
Now that we know how components work, we can start exploring the process of creating new **Component Types**.
<u>The first rule of defining new component types is to place the code inside a plugin</u>. This is necessary if you want to load your custom types at the beginning, before any component initialization (eg. a template loaded from DB). The plugin is loaded before component fetch (eg. in case of Storage use) so it's a perfect place to define component types.
```js
const myNewComponentTypes = editor => {
editor.DomComponents.addType(/* API for component type definition */);
};
const editor = grapesjs.init({
container : '#gjs',
// ...
plugins: [ myNewComponentTypes ],
});
```
Let's say we want to make the editor understand and handle better `<input>` elements. This is how we would start defining our new component type
```js
editor.DomComponents.addType('my-input-type', {
// Make the editor understand when to bind `my-input-type`
isComponent: el => el.tagName === 'INPUT',
Now that we know how components work, we can start exploring the process of creating new **Custom Components**. // Model definition
model: {
defaults: {
tagName: 'input',
draggable: 'form, form *', // Can be dropped only inside `form` elements
droppable: false, // Can't drop other elements inside it
attributes: { // Default attributes
type: 'text',
name: 'default-name',
placeholder: 'Insert text here',
},
traits: [
'name',
'placeholder',
{ type: 'checkbox', name: 'required' },
],
}
}
});
```
Let's say we want to make the editor understand and handle better `<input>` elements With this code the editor will be able to understand simple text `<input>`s, assign default attributes and show some trait for a better attribute handling.
First of all, place your components inside a plugin ::: tip
To understand better how Traits work you should read its [dedicated page](Traits.html) but we highly sugggest to read it after you've finished reading this one
:::
### isComponent
--- OLD Let's see in detail what we have done so far. The first thing to notice is the `isComponent` function, we have mentioned it already in [this](#component-recognition-and-component-type-stack) section and we need it to make the editor understand `<input>` during the component recognition step.
It receives only the `el` argument, which is the parsed HTMLElement node and expects a truthy value in case the element satisfies your logic condition. So, if we add this HTML string as component
## Component recognition ```js
// ...after editor initialization
editor.addComponents(`<input name="my-test" title="hello"/>`)
```
But now, how does the editor recognize which Component to bind to the `img` element and what to do with the `span` one? The resultant Component Definition will be
Each Component inherits, from the base one, a particular static method
```js ```js
/** {
* @param {HTMLElement} el type: 'my-input-type',
* @return {Object} attributes: {
*/ name: 'my-test',
isComponent: function(el) { title: 'hello',
... },
} }
``` ```
This method gives us the possibility to recognize and bind component types to each HTMLElement (div, img, iframe, etc.). Each **HTML string/element** introduced inside the canvas will be processed by `isComponent` of all available types and if it matches, the object represented the type should be returned. The method `isComponent` **is skipped** if you add the component object (`{ type: 'my-custom-type', tagName: 'div', attribute: {...}, ...}`) or declare the type explicitly on the element (`<div data-gjs-type="my-custom-type">...</div>`) If you need you can also customize the resultant Component Definition by returning an object as the result:
For example, with the image component this method looks like:
```js ```js
// Image component editor.DomComponents.addType('my-input-type', {
isComponent: function(el) { isComponent: el => {
if(el.tagName == 'IMG') if (el.tagName === 'INPUT') {
return {type: 'image'}; // You should explicitly declare the type of your resultant
} // object, otherwise the `default` one will be used
const result = { type: 'my-input-type' };
if (/* some other condition */) {
result.attributes = { title: 'Hi' };
}
return result;
}
},
// ...
});
``` ```
Let's try with something that might look a little bit tricky. What about a Google Map?!? Google Maps are generally embedded as `iframe`s, but the template can be composed by a lot of different `iframe`s. How can I tell the editor that a particular iframe is actually a Google's Map? Well, you'll have to figure out the right pattern, you have the `HTMLElement` so you can make all the checks you want. In this particular case this pattern is used: **Be aware** that this method will probably receive ANY parsed element from your canvas (eg. on load or on add) and not all the nodes have the same interface (eg. properties/methods).
If you do this:
```js ```js
// Map component // ...
isComponent: function(el) { // Print elements
if(el.tagName == 'IFRAME' && /maps\.google\.com/.test(el.src)) { isComponent: el => {
return {type: 'map', src: el.src}; console.log(el);
} return el.tagName === 'INPUT';
}, },
// ...
editor.addComponents(`<div>
I'm a text node
<!-- I'm a comment node -->
<img alt="Image here"/>
<input/>
</div>`);
``` ```
In addition to `tagName` check, we also used the `src` property, but you can actually override it with your own logic by extending the built-in component. You will see printing all the nodes, so doing something like this `el.getAttribute('...')` (which will work on the div but not on the text node), without an appropriate check, will break the code.
It's also important to understand that `isComponent` is executed only if the parsing is required (eg. by adding components as HTML string or initializing the editor with `fromElement`). In case the type is already defined, there is no need for the `isComponent` to be executed.
Let's see some examples:
```js
// isComponent will be executed on some-element
editor.addComponents('<some-element>...</some-element>');
// isComponent WON'T be executed on ANY provided object
// If the object without `type`, the `default` one will be used
editor.addComponents({
type: 'some-component',
});
// isComponent WON'T be executed as we're forcing the type
editor.addComponents('<some-element data-gjs-type="some-component">...');
```
One more tip, if you define a component type without the `isComponent`, the only way for the editor to see that component will be with a declared type (via object like `{ type: '...' }` or using `data-gjs-type`)
## Define new Component
Let's see an example with another HTML element that is not handled by default Component types. What about `input` elements?
With the default GrapesJS configuration `input`s are treated like any other element; you can move it around, style it, etc. However, we'd like to handle this type of element more specifically. In this case, we have to create a new Component type. ### Model
Let's define few specs for our new *Input* type: Now that we got how `isComponent` works we can start to explore the `model` property.
The `model` is probably the one you'll use the most as is what is used for the description of your component and the first thing you can see is its `defaults` key which just stands for *default component properties* and it reflects the already described [Component Definition](#component-definition)
* Can be dropped only inside `form` elements The model defines also what you will see as the resultant HTML (the export code) and you've probably noticed the use of `tagName` (if not specified the `div` will be used) and `attributes` properties on the model.
* Can't drop other elements inside it
* Can change the type of the input (text, password, email, etc.)
* Can make it required for the form
To define a new Component type you need to choose from which built-in Component inherit its properties, in our case we just gonna choose the default one. Let's see a complete example of the new type definition One another important property (not used because `<input/>` doesn't need it) might be `components`, which defines default internal components
```js ```js
// Get DomComponents module defaults: {
var comps = editor.DomComponents; tagName: 'div',
attributes: { title: 'Hello' },
// Get the model and the view from the default Component type // Can be a string
var defaultType = comps.getType('default'); components: `
var defaultModel = defaultType.model; <h1>Header test</h1>
var defaultView = defaultType.view; <p>Paragraph test</p>
`,
var inputTypes = [ // A component definiton
{value: 'text', name: 'Text'}, components: {
{value: 'email', name: 'Email'}, tagName: 'h1',
{value: 'password', name: 'Password'}, components: 'Header test',
{value: 'number', name: 'Number'},
];
// The `input` will be the Component type ID
comps.addType('input', {
// Define the Model
model: defaultModel.extend({
// Extend default properties
defaults: Object.assign({}, defaultModel.prototype.defaults, {
// Can be dropped only inside `form` elements
draggable: 'form, form *',
// Can't drop other elements inside it
droppable: false,
// Traits (Settings)
traits: ['name', 'placeholder', {
// Change the type of the input (text, password, email, etc.)
type: 'select',
label: 'Type',
name: 'type',
options: inputTypes,
},{
// Can make it required for the form
type: 'checkbox',
label: 'Required',
name: 'required',
}],
}),
}, },
// The second argument of .extend are static methods and we'll put inside our // Array of strings/component definitons
// isComponent() method. As you're putting a new Component type on top of the stack, components: [
// not declaring isComponent() might probably break stuff, especially if you extend {
// the default one. tagName: 'h1',
{ components: 'Header test',
isComponent: function(el) {
if(el.tagName == 'INPUT'){
return {type: 'input'};
}
}, },
}), '<p>Paragraph test</p>',
],
// Or a function, which get as an argument the current
// model and expects as the return one of the possible
// values described above
components: model => {
return `<h1>Header test: ${model.get('type')}</h1>`;
},
}
```
// Define the View #### Read and update the model
view: defaultType.view,
You can read and update the model properties wherever you have the reference to it. Here some references to the most useful API
```js
// let's use the selected component
const modelComponent = editor.getSelected();
// All model props
const props = modelComponent.props();
// Get single property
const tagName = modelComponent.get('tagName');
// Update single property
modelComponent.set('tagName', '...');
// Update multiple properties
modelComponent.set({
tagName: '...',
// ...
}); });
// Some helpers
// Get all attributes
const attrs = modelComponent.getAttributes();
// Add attributes
modelComponent.addAttributes({ title: 'Test' });
// Replace all attributes
modelComponent.setAttributes({ title: 'Test' });
// Get the collection of all inner components
modelComponent.components().forEach(
inner => console.log(inner.props())
);
// Update the inner content with an HTML string/Component Definitions
const addedComponents = modelComponent.components(`<div>...</div>`);
// Find components by query string
modelComponent.find(`.query-string[example=value]`).forEach(
inner => console.log(inner.props())
);
``` ```
The code above is pretty much self-explanatory and as you see a lot of work is basically done on top of the Model properties. You'll notice that, on any change, the component in the canvas and its export code are changing accordingly
The *View* is just extending the default one, so to cover also this part let's add some random behavior.
:::tip
To know all the available methods/properties check the [Component API](/api/component.html)
:::
#### Listen to property changes
If you need to accomplish some kind of action on some property change you can setup listeners in the `init` method
```js ```js
comps.addType('input', { editor.DomComponents.addType('my-input-type', {
model: {...}, // ...
view: defaultType.view.extend({ model: {
// Bind events defaults: {
events: { // ...
// If you want to bind the event to children elements someprop: 'initial value',
// 'click .someChildrenClass': 'methodName',
click: 'handleClick',
dblclick: function(){
alert('Hi!');
}
}, },
// It doesn't make too much sense this method inside the component init() {
// but it's ok as an example this.on('change:someprop', this.handlePropChange);
randomHex: function() { // Listen to any attribute change
return '#' + Math.floor(Math.random()*16777216).toString(16); this.on('change:attributes', this.handleAttrChange);
// Listen to title attribute change
this.on('change:attributes:title', this.handleTitleChange);
},
handlePropChange() {
const { someprop } = this.props();
console.log('New value of someprop: ', someprop);
}, },
handleClick: function(e) { handleAttrChange() {
this.model.set('style', {color: this.randomHex()}); // <- Affects the final HTML code console.log('Attributes updated: ', this.getAttributes());
this.el.style.backgroundColor = this.randomHex(); // <- Doesn't affect the final HTML code
// Tip: updating the model will reflect the changes to the view, so, in this case,
// if you put the model change after the DOM one this will override the backgroundColor
// change made before
}, },
// The render() should return 'this' handleTitleChange() {
render: function () { console.log('Attribute title updated: ', this.getAttributes().title);
// Extend the original render method
defaultType.view.prototype.render.apply(this, arguments);
this.el.placeholder = 'Text here'; // <- Doesn't affect the final HTML code
return this;
}, },
}), }
}); });
``` ```
From the example above you can notice few interesting things: how to bind events, how to update directly the DOM and how to update the model. The difference between updating the DOM and the model is that the HTML code (the one you get with `editor.getHtml()`) is generated from the *Model* so updating directly the DOM will not affect it, it's just the change for the canvas. You'll find other lifecycle methods, like `init`, [below](#lifecycle-hooks)
Now let's go back to our input component integration and see another useful part for the component customization
### View
## Update Component type Generally, when you create a component in GrapesJS you expect to see in the canvas the preview of what you've defined in the model. Indeed, by default, the editor does the exact thing and updates the element in the canvas when something in the model changes (eg. attributes, tag, etc.) to obtain the classic WYSIWYG (What You See Is What You Get) experience. Unfortunately, not always the simpliest thing is the right one, by building components for the builder you will notice that sometimes you'll need something more:
* You want to improve the experience of editing of the component.
A perfect example is the TextComponent, its view is enriched with a built-in RTE (Rich Text Editor) which enables the user to edit the text faster by double clicking on it.
So you'll probably feel a need adding actions to react on some DOM events or even custom UI elements (eg. buttons) around the component.
* The DOM representation of the component acts differently from what you expect, so you need to change some behaviour.
An example could be a VideoComponent which, for example, is loaded from Youtube via iframe. Once the iframe is loaded, everything inside it is in a different context, the editor is not able to see it, indeed if you point your cursor on the iframe you'll interact with the video and not the editor, so you can't event select your component. To workaround this "issue", in the render, we disabled the pointer interaction with the iframe and wrapped it with another element (without the wrapper the editor would select the parent component). Obviosly, all of this changes has nothing to do with the final code, the result will always be a simple iframe
* You need to customize the content or fill it with some data from the server
Here an example of how easily you can update/override the component For all of this cases you can use the `view` in your component type defintion. The input component is probably not the best use case for this scenario but we'll try to cover most of the cases with an example below
```js ```js
var originalMap = comps.getType('map'); editor.DomComponents.addType('my-input-type', {
// ...
model: {
// ...
},
view: {
// Be default, the tag of the element is the same of the model
tagName: 'div',
comps.addType('map', { // Add easily component specific listeners with `events`
model: originalMap.model.extend({ // Being component specific (eg. you can't attach here listeners to window)
// Override how the component is rendered to HTML // you don't need to care about removing them when the component is removed,
toHTML: function() { // they will be managed automatically by the editor
return '<div>My Custom Map</div>'; events: {
click: 'clickOnElement',
// You can also make use of event delegation
// and listen to events bubbled from some inner element
'dblclick .inner-el': 'innerElClick',
}, },
}, {
isComponent: function(el) {
// ... new logic for isComponent
},
}),
view: originalMap.view
});
```
## Improvement over addType <Badge text="0.14.50+"/> innerElClick(ev) {
ev.stopPropagation();
// ...
Now, with the [0.14.50](https://github.com/artf/grapesjs/releases/tag/v0.14.50) release, defining new components or extending them is a bit easier (without breaking the old process) // If you need you can access the model from any function in the view
this.model.components('Update inner components');
},
* If you don't specify the type to extend, the `default` one will be used. In that case, you just // On init you can create listeners, like in the model, or start some other
use objects for `model` and `view` // function at the beginning
* The `defaults` property, in the `model`, will be merged automatically with defaults of the parent component init({ model }) {
* If you use an object in `model` you can specify `isComponent` outside or omit it. In this case, // Do something in view on model property change
the `isComponent` is not mandatory but without it means the parser won't be able to identify the component this.listenTo(model, 'change:prop', this.handlePropChange);
if not explicitly declared (eg. `<div data-gjs-type="new-component">...</div>`)
**Before** // If you attach listeners on outside objects remember to unbind
```js // them in `removed` function in order to avoid memory leaks
const defaultType = comps.getType('default'); this.onDocClick = this.onDocClick.bind(this);
document.addEventListener('click', this.onDocClick)
},
comps.addType('new-component', { // Callback triggered when the element is removed from the canvas
model: defaultType.model.extend({ removed() {
defaults: { document.removeEventListener('click', this.onDocClick)
...defaultType.model.prototype.defaults,
someprop: 'somevalue',
}, },
...
}, { // Do something with the content once the element is rendered.
// Even if it returns false, declaring isComponent is mandatory // The DOM element is passed as `el` in the argument object,
isComponent(el) { // but you can access it from any function via `this.el`
return false; onRender({ el }) {
const btn = document.createElement('button');
btn.value = '+';
// Avoid adding events on inner elements, use `events`
btn.addEventListener('click', () => {});
el.appendChild(btn);
}, },
}),
view: defaultType.view.extend({ ... }); // Example of async content
async onRender({ el, model }) {
const asyncContent = await fetchSomething({
someDataFromModel: model.get('someData'),
});
// Rememebr that this changes exist only inside the editor canvas
// None of the DOM change is stored in your template data,
// if you need to store something, use the model properties
el.appendChild(asyncContent);
}
},
}); });
``` ```
**After**
## Update Component Type
Updating component types is quite easy, let's see how:
```js ```js
comps.addType('new-component', { const domc = editor.DomComponents;
// We can even omit isComponent here, as `false` return will be the default behavior
isComponent: el => false, domc.addType('some-component', {
// You can update the isComponent logic or leave the one from `some-component`
// isComponent: (el) => false,
// Update the model, if you need
model: { model: {
// The `defaults` property is handled differently
// and will be merged with the old `defaults`
defaults: { defaults: {
someprop: 'somevalue', tagName: '...', // Override an old one
someNewProp: 'Hello', // Add new property
}, },
... init() {
// Ovverride `init` function in `some-component`
}
}, },
view: { ... };
});
```
* If you need to extend some component, you can use `extend` and `extendView` property.
* You can now omit `view` property if you don't need to change it
**Before** // Update the view, if you need
```js view: {},
const originalMap = comps.getType('map');
comps.addType('map', {
model: originalMap.model.extend({
...
}, {
isComponent(el) {
// ... usually, you'd reuse the same logic
},
}),
// Even if I do nothing in view, I have to specify it
view: originalMap.view
}); });
``` ```
**After**
The `map` type is already defined, so it will be used as a base for the model and view.
We can skip `isComponent` if the recognition logic is the same of the extended component.
```js ### Extend Component Type
comps.addType('map', {
model: { ... }, Sometimes you would need to create a new type by extending another one. Just use `extend` and `extendView` indicating the component to extend.
});
```
Extend the `model` and `view` with some other, already defined, components.
```js ```js
comps.addType('map', { comps.addType('my-new-component', {
isComponent: el => {/* ... */},
extend: 'other-defined-component', extend: 'other-defined-component',
model: { ... }, // Will extend 'other-defined-component' model: { ... }, // Will extend the model from 'other-defined-component'
view: { ... }, // Will extend 'other-defined-component' view: { ... }, // Will extend the view from 'other-defined-component'
// `isComponent` will be taken from `map`
}); });
``` ```
```js ```js
comps.addType('map', { comps.addType('my-new-component', {
isComponent: el => {/* ... */},
extend: 'other-defined-component', extend: 'other-defined-component',
model: { ... }, // Will extend 'other-defined-component' model: { ... }, // Will extend the model from 'other-defined-component'
extendView: 'other-defined-component-2', extendView: 'other-defined-component-2',
view: { ... }, // Will extend 'other-defined-component-2' view: { ... }, // Will extend the view from 'other-defined-component-2'
// `isComponent` will be taken from `map`
}); });
``` ```
### Extend parent functions <Badge text="0.14.60+"/>
When you need to reuse functions, of the parent you're extending, you can avoid writing something like this in any function:
### Extend parent functions
When you need to reuse functions, of the parent you're extending, you can avoid writing something like this:
```js ```js
domc.getType('parent-type').model.prototype.init.apply(this, arguments); domc.getType('parent-type').model.prototype.init.apply(this, arguments);
``` ```
@ -494,7 +637,7 @@ by using `extendFn` and `extendFnView` arrays:
```js ```js
domc.addType('new-type', { domc.addType('new-type', {
extend: 'parent-type', extend: 'parent-type',
extendFn: ['init'], // array of model functions to extend extendFn: ['init'], // array of model functions to extend from `parent-type`
model: { model: {
init() { init() {
// do something; // do something;
@ -505,6 +648,16 @@ domc.addType('new-type', {
The same would be for the view by using `extendFnView` The same would be for the view by using `extendFnView`
:::tip
If you need you can also read all the current component types by using `getTypes`
```js
editor.DomComponents.getTypes().forEach(compType => console.log(compType.id))
```
:::
## Lifecycle Hooks ## Lifecycle Hooks
@ -582,47 +735,4 @@ editor.on(`component:remove`, model => console.log('Global hook: component:remov
## Components & JS ## Components & JS
If you want to know how to create Components with javascript attached (eg. counters, galleries, slideshows, etc.) check the dedicated page If you want to know how to create Components with javascript attached (eg. counters, galleries, slideshows, etc.) check the dedicated page
[Components & JS](Components-js.html) [Components & JS](Components-js.html)
## Hints
```html
<div id="gjs">
...
<cutom-element></cutom-element>
...
</div>
<script>
var editor = grapesjs.init({
container : '#gjs',
fromElement: true,
});
editor.DomComponents.addType('cutom-element-type', {...});
</script>
```
In the example above the editor will not get the new type from the HTML because the content is already parsed and appended, so it'll get it only with new components (eg. from Blocks)
Solution 1: turn off `autorender`
```html
<script>
var editor = grapesjs.init({
autorender: 0,
container : '#gjs',
fromElement: true,
});
editor.DomComponents.addType('cutom-element-type', {...});
// after all new types
editor.render();
</script>
```
Solution 2: put all the stuff inside a plugin ([Creating plugins](Plugins.html))

2
package-lock.json

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

8
package.json

@ -1,12 +1,16 @@
{ {
"name": "grapesjs", "name": "grapesjs",
"description": "Free and Open Source Web Builder Framework", "description": "Free and Open Source Web Builder Framework",
"version": "0.15.3", "version": "0.15.5",
"author": "Artur Arseniev", "author": "Artur Arseniev",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"homepage": "http://grapesjs.com", "homepage": "http://grapesjs.com",
"main": "dist/grapes.min.js", "main": "dist/grapes.min.js",
"sideEffects": [ "*.vue", "*.css", "*.scss" ], "sideEffects": [
"*.vue",
"*.css",
"*.scss"
],
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/artf/grapesjs.git" "url": "https://github.com/artf/grapesjs.git"

11
src/asset_manager/config/config.js

@ -100,5 +100,14 @@ export default {
modalTitle: 'Select Image', modalTitle: 'Select Image',
//Default placeholder for input //Default placeholder for input
inputPlaceholder: 'http://path/to/the/image.jpg' inputPlaceholder: 'http://path/to/the/image.jpg',
//method called before upload, on return false upload is canceled.
// @example
// beforeUpload: (files) => {
// // logic...
// var stopUpload = true;
// if(stopUpload) return false;
// }
beforeUpload: null
}; };

5
src/asset_manager/view/FileUploader.js

@ -108,6 +108,11 @@ export default Backbone.View.extend(
uploadFile(e, clb) { uploadFile(e, clb) {
const files = e.dataTransfer ? e.dataTransfer.files : e.target.files; const files = e.dataTransfer ? e.dataTransfer.files : e.target.files;
const { config } = this; const { config } = this;
const { beforeUpload } = config;
const beforeUploadResponse = beforeUpload && beforeUpload(files);
if (beforeUploadResponse === false) return;
const body = new FormData(); const body = new FormData();
const { params, customFetch } = config; const { params, customFetch } = config;

4
src/code_manager/model/CssGenerator.js

@ -20,7 +20,7 @@ export default Backbone.Model.extend({
const avoidInline = em && em.getConfig('avoidInlineStyle'); const avoidInline = em && em.getConfig('avoidInlineStyle');
const style = model.styleToString(); const style = model.styleToString();
const classes = model.get('classes'); const classes = model.get('classes');
const wrappesIsBody = opts.wrappesIsBody; const wrapperIsBody = opts.wrapperIsBody;
const isWrapper = model.get('wrapper'); const isWrapper = model.get('wrapper');
this.ids.push(`#${model.getId()}`); this.ids.push(`#${model.getId()}`);
@ -29,7 +29,7 @@ export default Backbone.Model.extend({
if (!avoidInline && style) { if (!avoidInline && style) {
let selector = `#${model.getId()}`; let selector = `#${model.getId()}`;
selector = wrappesIsBody && isWrapper ? 'body' : selector; selector = wrapperIsBody && isWrapper ? 'body' : selector;
code = `${selector}{${style}}`; code = `${selector}{${style}}`;
} }

2
src/code_manager/model/HtmlGenerator.js

@ -5,7 +5,7 @@ export default Backbone.Model.extend({
const models = model.get('components'); const models = model.get('components');
if (opts.exportWrapper) { if (opts.exportWrapper) {
return opts.wrappesIsBody return opts.wrapperIsBody
? `<body>${this.buildModels(models)}</body>` ? `<body>${this.buildModels(models)}</body>`
: model.toHTML(); : model.toHTML();
} }

8
src/commands/view/SelectComponent.js

@ -455,12 +455,8 @@ export default {
const style = modelToStyle.getStyle(); const style = modelToStyle.getStyle();
if (!onlyHeight) { if (!onlyHeight) {
const padding = 10; const bodyw = canvas.getBody().offsetWidth;
const frameOffset = canvas.getCanvasView().getFrameOffset(); const width = rect.w < bodyw ? rect.w : bodyw;
const width =
rect.w < frameOffset.width - padding
? rect.w
: frameOffset.width - padding;
style[keyWidth] = autoWidth ? 'auto' : `${width}${unitWidth}`; style[keyWidth] = autoWidth ? 'auto' : `${width}${unitWidth}`;
} }

2
src/css_composer/model/CssRule.js

@ -78,7 +78,7 @@ export default Backbone.Model.extend(Styleable).extend({
const state = this.get('state'); const state = this.get('state');
const wrapper = this.get('wrapper'); const wrapper = this.get('wrapper');
const addSelector = this.get('selectorsAdd'); const addSelector = this.get('selectorsAdd');
const isBody = wrapper && em && em.getConfig('wrappesIsBody'); const isBody = wrapper && em && em.getConfig('wrapperIsBody');
const selectors = isBody ? 'body' : this.get('selectors').getFullString(); const selectors = isBody ? 'body' : this.get('selectors').getFullString();
const stateStr = state ? `:${state}` : ''; const stateStr = state ? `:${state}` : '';
selectors && result.push(`${selectors}${stateStr}`); selectors && result.push(`${selectors}${stateStr}`);

2
src/dom_components/config/config.js

@ -41,7 +41,7 @@ export default {
* It might be useful to transform custom objects (es. some framework specific JSX) to GrapesJS component one. * It might be useful to transform custom objects (es. some framework specific JSX) to GrapesJS component one.
* This custom function will be executed on ANY new added component to the editor so make smart checks/conditions * This custom function will be executed on ANY new added component to the editor so make smart checks/conditions
* to avoid doing useless executions * to avoid doing useless executions
* By default, GrapesJS supports already React Element * By default, GrapesJS supports already elements generated from React JSX preset
* @example * @example
* processor: (obj) => { * processor: (obj) => {
* if (obj.$$typeof) { // eg. this is a React Element * if (obj.$$typeof) { // eg. this is a React Element

5
src/dom_components/model/Component.js

@ -1074,7 +1074,10 @@ const Component = Backbone.Model.extend(Styleable).extend(
// If at least one match is found I have to track this change for a // If at least one match is found I have to track this change for a
// better optimization inside JS generator // better optimization inside JS generator
this.scriptUpdated(); this.scriptUpdated();
return this.attributes[v] || ''; const result = this.attributes[v] || '';
return isArray(result) || typeof result == 'object'
? JSON.stringify(result)
: result;
}); });
return scr; return scr;

16
src/dom_components/view/ComponentTextView.js

@ -1,6 +1,8 @@
import { on, off } from 'utils/mixins'; import { on, off } from 'utils/mixins';
import ComponentView from './ComponentView'; import ComponentView from './ComponentView';
const compProt = ComponentView.prototype;
export default ComponentView.extend({ export default ComponentView.extend({
events: { events: {
dblclick: 'onActive', dblclick: 'onActive',
@ -8,7 +10,7 @@ export default ComponentView.extend({
}, },
initialize(o) { initialize(o) {
ComponentView.prototype.initialize.apply(this, arguments); compProt.initialize.apply(this, arguments);
this.disableEditing = this.disableEditing.bind(this); this.disableEditing = this.disableEditing.bind(this);
const model = this.model; const model = this.model;
const em = this.em; const em = this.em;
@ -151,5 +153,17 @@ export default ComponentView.extend({
// Avoid closing edit mode on component click // Avoid closing edit mode on component click
this.$el.off('mousedown', this.disablePropagation); this.$el.off('mousedown', this.disablePropagation);
this.$el[method]('mousedown', this.disablePropagation); this.$el[method]('mousedown', this.disablePropagation);
// Fixes #2210 but use this also as a replacement
// of this fix: bd7b804f3b46eb45b4398304b2345ce870f232d2
if (this.config.draggableComponents) {
let { el } = this;
while (el) {
el.draggable = enable ? !1 : !0;
el = el.parentNode;
el.tagName == 'BODY' && (el = 0);
}
}
} }
}); });

27
src/dom_components/view/ComponentView.js

@ -3,6 +3,7 @@ import { isArray, isEmpty, each, keys } from 'underscore';
import Components from '../model/Components'; import Components from '../model/Components';
import ComponentsView from './ComponentsView'; import ComponentsView from './ComponentsView';
import Selectors from 'selector_manager/model/Selectors'; import Selectors from 'selector_manager/model/Selectors';
import { replaceWith } from 'utils/dom';
export default Backbone.View.extend({ export default Backbone.View.extend({
className() { className() {
@ -46,7 +47,16 @@ export default Backbone.View.extend({
...(draggableComponents && { dragstart: 'handleDragStart' }) ...(draggableComponents && { dragstart: 'handleDragStart' })
}; };
this.delegateEvents(); this.delegateEvents();
!modelOpt.temporary && this.init(); !modelOpt.temporary && this.init(this._clbObj());
},
_clbObj() {
const { em, model, el } = this;
return {
editor: em && em.getEditor(),
model,
el
};
}, },
/** /**
@ -54,11 +64,22 @@ export default Backbone.View.extend({
*/ */
init() {}, init() {},
/**
* Remove callback
*/
removed() {},
/** /**
* Callback executed when the `active` event is triggered on component * Callback executed when the `active` event is triggered on component
*/ */
onActive() {}, onActive() {},
remove() {
Backbone.View.prototype.remove.apply(this, arguments);
this.removed(this._clbObj());
return this;
},
handleDragStart(event) { handleDragStart(event) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
@ -354,7 +375,7 @@ export default Backbone.View.extend({
this.el = ''; this.el = '';
this._ensureElement(); this._ensureElement();
this.$el.data({ model, collection }); this.$el.data({ model, collection });
el.replaceWith(this.el); replaceWith(el, this.el);
this.render(); this.render();
}, },
@ -399,7 +420,7 @@ export default Backbone.View.extend({
const { em, model, modelOpt } = this; const { em, model, modelOpt } = this;
if (!modelOpt.temporary) { if (!modelOpt.temporary) {
this.onRender(); this.onRender(this._clbObj());
em && em.trigger('component:mount', model); em && em.trigger('component:mount', model);
} }
}, },

6
src/editor/config/config.js

@ -117,7 +117,7 @@ export default {
exportWrapper: 0, exportWrapper: 0,
// The wrapper, if visible, will be shown as a `<body>` // The wrapper, if visible, will be shown as a `<body>`
wrappesIsBody: 1, wrapperIsBody: 1,
// Usually when you update the `style` of the component this changes the // Usually when you update the `style` of the component this changes the
// element's `style` attribute. Unfortunately, inline styling doesn't allow // element's `style` attribute. Unfortunately, inline styling doesn't allow
@ -128,7 +128,7 @@ export default {
// Avoid default properties from storable JSON data, like `components` and `styles`. // Avoid default properties from storable JSON data, like `components` and `styles`.
// With this option enabled your data will be smaller (usefull if need to // With this option enabled your data will be smaller (usefull if need to
// save some storage space) // save some storage space)
avoidDefaults: 0, avoidDefaults: 1,
// (experimental) // (experimental)
// The structure of components is always on the screen but it's not the same // The structure of components is always on the screen but it's not the same
@ -166,7 +166,7 @@ export default {
storageManager: {}, storageManager: {},
//Configurations for Rich Text Editor //Configurations for Rich Text Editor
rte: {}, richTextEditor: {},
//Configurations for DomComponents //Configurations for DomComponents
domComponents: {}, domComponents: {},

8
src/editor/model/Editor.js

@ -417,12 +417,12 @@ export default Backbone.Model.extend({
getHtml() { getHtml() {
const config = this.config; const config = this.config;
const exportWrapper = config.exportWrapper; const exportWrapper = config.exportWrapper;
const wrappesIsBody = config.wrappesIsBody; const wrapperIsBody = config.wrapperIsBody;
const js = config.jsInHtml ? this.getJs() : ''; const js = config.jsInHtml ? this.getJs() : '';
var wrp = this.get('DomComponents').getComponent(); var wrp = this.get('DomComponents').getComponent();
var html = this.get('CodeManager').getCode(wrp, 'html', { var html = this.get('CodeManager').getCode(wrp, 'html', {
exportWrapper, exportWrapper,
wrappesIsBody wrapperIsBody
}); });
html += js ? `<script>${js}</script>` : ''; html += js ? `<script>${js}</script>` : '';
return html; return html;
@ -436,7 +436,7 @@ export default Backbone.Model.extend({
*/ */
getCss(opts = {}) { getCss(opts = {}) {
const config = this.config; const config = this.config;
const wrappesIsBody = config.wrappesIsBody; const wrapperIsBody = config.wrapperIsBody;
const avoidProt = opts.avoidProtected; const avoidProt = opts.avoidProtected;
const keepUnusedStyles = !isUndefined(opts.keepUnusedStyles) const keepUnusedStyles = !isUndefined(opts.keepUnusedStyles)
? opts.keepUnusedStyles ? opts.keepUnusedStyles
@ -449,7 +449,7 @@ export default Backbone.Model.extend({
protCss + protCss +
this.get('CodeManager').getCode(wrp, 'css', { this.get('CodeManager').getCode(wrp, 'css', {
cssc, cssc,
wrappesIsBody, wrapperIsBody,
keepUnusedStyles keepUnusedStyles
}) })
); );

18
src/rich_text_editor/index.js

@ -5,7 +5,7 @@
* You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object](https://github.com/artf/grapesjs/blob/master/src/rich_text_editor/config/config.js) * You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object](https://github.com/artf/grapesjs/blob/master/src/rich_text_editor/config/config.js)
* ```js * ```js
* const editor = grapesjs.init({ * const editor = grapesjs.init({
* rte: { * richTextEditor: {
* // options * // options
* } * }
* }) * })
@ -52,20 +52,20 @@ export default () => {
*/ */
name: 'RichTextEditor', name: 'RichTextEditor',
getConfig() {
return config;
},
/** /**
* Initialize module. Automatically called with a new instance of the editor * Initialize module. Automatically called with a new instance of the editor
* @param {Object} opts Options * @param {Object} opts Options
* @private * @private
*/ */
init(opts = {}) { init(opts = {}) {
config = opts; config = {
...defaults,
for (let name in defaults) { ...opts
if (!(name in config)) { };
config[name] = defaults[name];
}
}
const ppfx = config.pStylePrefix; const ppfx = config.pStylePrefix;
if (ppfx) { if (ppfx) {

8
src/style_manager/index.js

@ -174,6 +174,7 @@ export default () => {
* @param {Array<Object>} [property.properties=[]] Nested properties for composite and stack type * @param {Array<Object>} [property.properties=[]] Nested properties for composite and stack type
* @param {Array<Object>} [property.layers=[]] Layers for stack properties * @param {Array<Object>} [property.layers=[]] Layers for stack properties
* @param {Array<Object>} [property.list=[]] List of possible options for radio and select types * @param {Array<Object>} [property.list=[]] List of possible options for radio and select types
* @param {Object} [options={}] Options
* @return {Property|null} Added Property or `null` in case sector doesn't exist * @return {Property|null} Added Property or `null` in case sector doesn't exist
* @example * @example
* var property = styleManager.addProperty('mySector',{ * var property = styleManager.addProperty('mySector',{
@ -188,12 +189,13 @@ export default () => {
* value: '200px', * value: '200px',
* name: '200', * name: '200',
* }], * }],
* }); * }, { at: 0 });
* // With `at: 0` we place the new property at the beginning of the collection
*/ */
addProperty(sectorId, property) { addProperty(sectorId, property, opts = {}) {
const sector = this.getSector(sectorId, { warn: 1 }); const sector = this.getSector(sectorId, { warn: 1 });
let prop = null; let prop = null;
if (sector) prop = sector.get('properties').add(property); if (sector) prop = sector.get('properties').add(property, opts);
return prop; return prop;
}, },

5
src/style_manager/model/Property.js

@ -162,12 +162,13 @@ const Property = Backbone.Model.extend(
getFullValue(val) { getFullValue(val) {
const fn = this.get('functionName'); const fn = this.get('functionName');
let value = isUndefined(val) ? this.get('value') : val; let value = isUndefined(val) ? this.get('value') : val;
const hasValue = !isUndefined(value);
if (fn && !isUndefined(value)) { if (fn && hasValue) {
value = `${fn}(${value})`; value = `${fn}(${value})`;
} }
if (this.get('important')) { if (hasValue && this.get('important')) {
value = `${value} !important`; value = `${value} !important`;
} }

18
src/style_manager/view/PropertiesView.js

@ -1,4 +1,5 @@
import Backbone from 'backbone'; import Backbone from 'backbone';
import { appendAtIndex } from 'utils/dom';
export default Backbone.View.extend({ export default Backbone.View.extend({
initialize(o) { initialize(o) {
@ -15,12 +16,13 @@ export default Backbone.View.extend({
this.listenTo(coll, 'reset', this.render); this.listenTo(coll, 'reset', this.render);
}, },
addTo(model) { addTo(model, coll, opts) {
this.add(model); this.add(model, null, opts);
}, },
add(model, frag) { add(model, frag, opts = {}) {
var view = new model.typeView({ const appendTo = frag || this.el;
const view = new model.typeView({
model, model,
name: model.get('name'), name: model.get('name'),
id: this.pfx + model.get('property'), id: this.pfx + model.get('property'),
@ -36,14 +38,10 @@ export default Backbone.View.extend({
} }
view.render(); view.render();
const el = view.el; const rendered = view.el;
this.properties.push(view); this.properties.push(view);
if (frag) { appendAtIndex(appendTo, rendered, opts.at);
frag.appendChild(el);
} else {
this.el.appendChild(el);
}
}, },
render() { render() {

2
src/styles/scss/_gjs_selectors.scss

@ -3,7 +3,7 @@
} }
.#{$clm-prefix}select { .#{$clm-prefix}select {
@extend .#{$sm-prefix}field.#{$sm-prefix}select; @extend .#{$sm-prefix}field, .#{$sm-prefix}select;
} }
.#{$clm-prefix}tags { .#{$clm-prefix}tags {

4
src/trait_manager/view/TraitView.js

@ -181,7 +181,9 @@ export default Backbone.View.extend({
getInputElem() { getInputElem() {
const { input, $input } = this; const { input, $input } = this;
return input || ($input && $input.get(0)) || this.getElInput(); return (
input || ($input && $input.get && $input.get(0)) || this.getElInput()
);
}, },
getModelValue() { getModelValue() {

4
src/utils/dom.js

@ -9,6 +9,10 @@ export const empty = node => {
while (node.firstChild) node.removeChild(node.firstChild); while (node.firstChild) node.removeChild(node.firstChild);
}; };
export const replaceWith = (oldEl, newEl) => {
oldEl.parentNode.replaceChild(newEl, oldEl);
};
export const appendAtIndex = (parent, child, index) => { export const appendAtIndex = (parent, child, index) => {
const { childNodes } = parent; const { childNodes } = parent;
const total = childNodes.length; const total = childNodes.length;

19
test/specs/css_composer/e2e/CssComposer.js

@ -130,7 +130,6 @@ module.exports = {
// ruleOut is a Model // ruleOut is a Model
ruleOut = JSON.parse(JSON.stringify(ruleOut)); ruleOut = JSON.parse(JSON.stringify(ruleOut));
var ruleResult = { var ruleResult = {
mediaText: '',
selectors: [ selectors: [
{ {
active: true, active: true,
@ -141,12 +140,6 @@ module.exports = {
protected: false protected: false
} }
], ],
atRuleType: '',
singleAtRule: 0,
important: 0,
selectorsAdd: '',
state: '',
stylable: true,
style: { style: {
color: 'red', color: 'red',
width: '10px' width: '10px'
@ -181,28 +174,16 @@ module.exports = {
rule1Out = JSON.parse(JSON.stringify(rule1Out)); rule1Out = JSON.parse(JSON.stringify(rule1Out));
rule2Out = JSON.parse(JSON.stringify(rule2Out)); rule2Out = JSON.parse(JSON.stringify(rule2Out));
var rule1Result = { var rule1Result = {
atRuleType: '',
singleAtRule: 0,
important: 0,
mediaText: '',
selectors: [], selectors: [],
selectorsAdd: '*', selectorsAdd: '*',
state: '',
stylable: true,
style: { style: {
color: 'red', color: 'red',
width: '10px' width: '10px'
} }
}; };
var rule2Result = { var rule2Result = {
atRuleType: '',
singleAtRule: 0,
important: 0,
mediaText: '',
selectors: [], selectors: [],
selectorsAdd: 'p', selectorsAdd: 'p',
state: '',
stylable: true,
style: { style: {
height: '20px', height: '20px',
width: '20px' width: '20px'

Loading…
Cancel
Save