diff --git a/src/css_composer/config/config.js b/src/css_composer/config/config.js index fac604c5a..625191d18 100644 --- a/src/css_composer/config/config.js +++ b/src/css_composer/config/config.js @@ -4,19 +4,4 @@ export default { // Default CSS style rules: [], - - /** - * Adjust style object before creation/update. - * @example - * onBeforeStyle(style) { - * const padValue = style.padding; - * if (padValue === '10px') { - * delete style.padding; - * style['padding-top'] = padValue; - * // ... - * } - * return style; - * } - */ - onBeforeStyle: null, }; diff --git a/src/dom_components/index.ts b/src/dom_components/index.ts index 6876afdfc..5dc428173 100644 --- a/src/dom_components/index.ts +++ b/src/dom_components/index.ts @@ -442,19 +442,23 @@ export default class ComponentManager extends ItemManagerModule { // If the model/view is a simple object I need to extend it if (typeof model === 'object') { + const defaults = result(model, 'defaults'); + delete model.defaults; methods.model = modelToExt.extend( { ...model, ...getExtendedObj(extendFn, model, modelToExt), - defaults: { - ...(result(modelToExt.prototype, 'defaults') || {}), - ...(result(model, 'defaults') || {}), - }, }, { isComponent: compType && !extendType && !isComponent ? modelToExt.isComponent : isComponent || (() => 0), } ); + Object.defineProperty(methods.model.prototype, 'defaults', { + value: { + ...(result(modelToExt.prototype, 'defaults') || {}), + ...(defaults || {}), + }, + }); } if (typeof view === 'object') { diff --git a/src/dom_components/model/Component.js b/src/dom_components/model/Component.js index 213d06af1..432fa7fbc 100644 --- a/src/dom_components/model/Component.js +++ b/src/dom_components/model/Component.js @@ -94,6 +94,51 @@ export const keyUpdateInside = `${keyUpdate}-inside`; * @module docsjs.Component */ export default class Component extends StyleableModel { + get defaults() { + return { + tagName: 'div', + type: '', + name: '', + removable: true, + draggable: true, + droppable: true, + badgable: true, + stylable: true, + 'stylable-require': '', + 'style-signature': '', + unstylable: '', + highlightable: true, + copyable: true, + resizable: false, + editable: false, + layerable: true, + selectable: true, + hoverable: true, + locked: false, + void: false, + state: '', // Indicates if the component is in some CSS state like ':hover', ':active', etc. + status: '', // State, eg. 'selected' + content: '', + icon: '', + style: '', + styles: '', // Component related styles + classes: '', // Array of classes + script: '', + 'script-props': '', + 'script-export': '', + attributes: '', + traits: ['id', 'title'], + propagate: '', + dmode: '', + toolbar: null, + [keySymbol]: 0, + [keySymbols]: 0, + [keySymbolOvrd]: 0, + _undo: true, + _undoexc: ['status', 'open'], + }; + } + /** * Hook method, called once the model is created */ @@ -1253,7 +1298,6 @@ export default class Component extends StyleableModel { /** * Override original clone method - * @private */ clone(opt = {}) { const em = this.em; @@ -1485,7 +1529,6 @@ export default class Component extends StyleableModel { * Return a shallow copy of the model's attributes for JSON * stringification. * @return {Object} - * @private */ toJSON(opts = {}) { const obj = Model.prototype.toJSON.call(this, opts); @@ -1838,6 +1881,10 @@ export default class Component extends StyleableModel { } } +Component.getDefaults = function () { + return result(this.prototype, 'defaults'); +}; + /** * Detect if the passed element is a valid component. * In case the element is valid an object abstracted @@ -1965,50 +2012,3 @@ Component.checkId = (components, styles = [], list = {}, opts = {}) => { components && Component.checkId(components, styles, list, opts); }); }; - -Component.getDefaults = function () { - return result(this.prototype, 'defaults'); -}; - -Component.prototype.defaults = { - tagName: 'div', - type: '', - name: '', - removable: true, - draggable: true, - droppable: true, - badgable: true, - stylable: true, - 'stylable-require': '', - 'style-signature': '', - unstylable: '', - highlightable: true, - copyable: true, - resizable: false, - editable: false, - layerable: true, - selectable: true, - hoverable: true, - locked: false, - void: false, - state: '', // Indicates if the component is in some CSS state like ':hover', ':active', etc. - status: '', // State, eg. 'selected' - content: '', - icon: '', - style: '', - styles: '', // Component related styles - classes: '', // Array of classes - script: '', - 'script-props': '', - 'script-export': '', - attributes: '', - traits: ['id', 'title'], - propagate: '', - dmode: '', - toolbar: null, - [keySymbol]: 0, - [keySymbols]: 0, - [keySymbolOvrd]: 0, - _undo: true, - _undoexc: ['status', 'open'], -}; diff --git a/src/dom_components/model/ComponentComment.js b/src/dom_components/model/ComponentComment.js index 020e86b92..15bfb1a05 100644 --- a/src/dom_components/model/ComponentComment.js +++ b/src/dom_components/model/ComponentComment.js @@ -1,24 +1,21 @@ -import Component from './ComponentTextNode'; +import ComponentTextNode from './ComponentTextNode'; -export default Component.extend( - { - defaults: { - ...Component.prototype.defaults, - }, +export default class ComponentComment extends ComponentTextNode { + get defaults() { + return { ...super.defaults }; + } + + toHTML() { + return ``; + } +} - toHTML() { - return ``; - }, - }, - { - isComponent(el) { - if (el.nodeType == 8) { - return { - tagName: 'NULL', - type: 'comment', - content: el.textContent, - }; - } - }, +ComponentComment.isComponent = el => { + if (el.nodeType == 8) { + return { + tagName: 'NULL', + type: 'comment', + content: el.textContent, + }; } -); +}; diff --git a/src/dom_components/model/ComponentFrame.js b/src/dom_components/model/ComponentFrame.js index 82bb0cc9f..35e445200 100644 --- a/src/dom_components/model/ComponentFrame.js +++ b/src/dom_components/model/ComponentFrame.js @@ -3,21 +3,18 @@ import { toLowerCase } from 'utils/mixins'; const type = 'iframe'; -export default Component.extend( - { - defaults() { - return { - ...Component.prototype.defaults, - type, - tagName: type, - droppable: false, - resizable: true, - traits: ['id', 'title', 'src'], - attributes: { frameborder: '0' }, - }; - }, - }, - { - isComponent: el => toLowerCase(el.tagName) === type, +export default class ComponentFrame extends Component { + get defaults() { + return { + ...super.defaults, + type, + tagName: type, + droppable: false, + resizable: true, + traits: ['id', 'title', 'src'], + attributes: { frameborder: '0' }, + }; } -); +} + +ComponentFrame.isComponent = el => toLowerCase(el.tagName) === type; diff --git a/src/dom_components/model/ComponentImage.js b/src/dom_components/model/ComponentImage.js index 5a9f52bcc..61da75a97 100644 --- a/src/dom_components/model/ComponentImage.js +++ b/src/dom_components/model/ComponentImage.js @@ -5,10 +5,10 @@ import { toLowerCase, buildBase64UrlFromSvg, hasWin } from '../../utils/mixins'; const svgAttrs = 'xmlns="http://www.w3.org/2000/svg" width="100" viewBox="0 0 24 24" style="fill: rgba(0,0,0,0.15); transform: scale(0.75)"'; -export default Component.extend( - { - defaults: { - ...Component.prototype.defaults, +export default class ComponentImage extends Component { + get defaults() { + return { + ...super.defaults, type: 'image', tagName: 'img', void: true, @@ -30,134 +30,132 @@ export default Component.extend( // File to load asynchronously once the model is rendered file: '', - }, + }; + } - initialize(o, opt) { - Component.prototype.initialize.apply(this, arguments); - const { src } = this.get('attributes'); - if (src && buildBase64UrlFromSvg(result(this, 'defaults').src) !== src) { - this.set('src', src, { silent: 1 }); - } - }, - - initToolbar(...args) { - Component.prototype.initToolbar.apply(this, args); - const em = this.em; - - if (em) { - var cmd = em.get('Commands'); - var cmdName = 'image-editor'; - - // Add Image Editor button only if the default command exists - if (cmd.has(cmdName)) { - let hasButtonBool = false; - var tb = this.get('toolbar'); - - for (let i = 0; i < tb.length; i++) { - if (tb[i].command === 'image-editor') { - hasButtonBool = true; - break; - } - } + initialize(o, opt) { + Component.prototype.initialize.apply(this, arguments); + const { src } = this.get('attributes'); + if (src && buildBase64UrlFromSvg(result(this, 'defaults').src) !== src) { + this.set('src', src, { silent: 1 }); + } + } + + initToolbar(...args) { + Component.prototype.initToolbar.apply(this, args); + const em = this.em; + + if (em) { + var cmd = em.get('Commands'); + var cmdName = 'image-editor'; - if (!hasButtonBool) { - tb.push({ - attributes: { class: 'fa fa-pencil' }, - command: cmdName, - }); - this.set('toolbar', tb); + // Add Image Editor button only if the default command exists + if (cmd.has(cmdName)) { + let hasButtonBool = false; + var tb = this.get('toolbar'); + + for (let i = 0; i < tb.length; i++) { + if (tb[i].command === 'image-editor') { + hasButtonBool = true; + break; } } - } - }, - - /** - * Returns object of attributes for HTML - * @return {Object} - * @private - */ - getAttrToHTML(...args) { - const attr = Component.prototype.getAttrToHTML.apply(this, args); - const src = this.getSrcResult(); - if (src) attr.src = src; - return attr; - }, - - getSrcResult(opt = {}) { - const src = this.get(opt.fallback ? 'fallback' : 'src') || ''; - let result = src; - - if (src && src.substr(0, 4) === ' { - const query = {}; - const qrs = search.substring(1).split('&'); - - for (let i = 0; i < qrs.length; i++) { - const pair = qrs[i].split('='); - const name = decodeURIComponent(pair[0]); - if (name) query[name] = decodeURIComponent(pair[1] || ''); - } + /** + * Returns object of attributes for HTML + * @return {Object} + * @private + */ + getAttrToHTML(...args) { + const attr = Component.prototype.getAttrToHTML.apply(this, args); + const src = this.getSrcResult(); + if (src) attr.src = src; + return attr; + } + + getSrcResult(opt = {}) { + const src = this.get(opt.fallback ? 'fallback' : 'src') || ''; + let result = src; + + if (src && src.substr(0, 4) === ' { + const query = {}; + const qrs = search.substring(1).split('&'); + + for (let i = 0; i < qrs.length; i++) { + const pair = qrs[i].split('='); + const name = decodeURIComponent(pair[0]); + if (name) query[name] = decodeURIComponent(pair[1] || ''); } - return { - hostname: result.hostname || '', - pathname: result.pathname || '', - protocol: result.protocol || '', - search: result.search || '', - hash: result.hash || '', - port: result.port || '', - query: getQueryObject(result.search), - }; - }, - }, - { - isComponent: el => toLowerCase(el.tagName) === 'img', + return query; + }; + + if (hasWin()) { + result = document.createElement('a'); + result.href = uri; + } else if (typeof URL !== 'undefined') { + try { + result = new URL(uri); + } catch (e) {} + } + + return { + hostname: result.hostname || '', + pathname: result.pathname || '', + protocol: result.protocol || '', + search: result.search || '', + hash: result.hash || '', + port: result.port || '', + query: getQueryObject(result.search), + }; } -); +} + +ComponentImage.isComponent = el => toLowerCase(el.tagName) === 'img'; diff --git a/src/dom_components/model/ComponentLabel.js b/src/dom_components/model/ComponentLabel.js index 39d28e3f7..9b0f4121e 100644 --- a/src/dom_components/model/ComponentLabel.js +++ b/src/dom_components/model/ComponentLabel.js @@ -1,18 +1,17 @@ -import Component from './ComponentText'; +import ComponentText from './ComponentText'; import { toLowerCase } from 'utils/mixins'; const type = 'label'; -export default Component.extend( - { - defaults: { - ...Component.prototype.defaults, +export default class ComponentLabel extends ComponentText { + get defaults() { + return { + ...super.defaults, type, tagName: type, traits: ['id', 'title', 'for'], - }, - }, - { - isComponent: el => toLowerCase(el.tagName) === type, + }; } -); +} + +ComponentLabel.isComponent = el => toLowerCase(el.tagName) === type; diff --git a/src/dom_components/model/ComponentLink.js b/src/dom_components/model/ComponentLink.js index 4e5bd8c52..ba0bb0735 100644 --- a/src/dom_components/model/ComponentLink.js +++ b/src/dom_components/model/ComponentLink.js @@ -4,14 +4,16 @@ import ComponentText from './ComponentText'; const type = 'link'; -export default class ComponentLink extends ComponentText {} - -ComponentLink.prototype.defaults = { - ...ComponentText.getDefaults(), - type, - tagName: 'a', - traits: ['title', 'href', 'target'], -}; +export default class ComponentLink extends ComponentText { + get defaults() { + return { + ...super.defaults, + type, + tagName: 'a', + traits: ['title', 'href', 'target'], + }; + } +} ComponentLink.isComponent = (el, opts = {}) => { let result; diff --git a/src/dom_components/model/ComponentMap.js b/src/dom_components/model/ComponentMap.js index 05896b3fc..ba97d9b96 100644 --- a/src/dom_components/model/ComponentMap.js +++ b/src/dom_components/model/ComponentMap.js @@ -1,11 +1,10 @@ -import Component from './ComponentImage'; -import OComponent from './Component'; +import ComponentImage from './ComponentImage'; import { toLowerCase } from 'utils/mixins'; -export default Component.extend( - { - defaults: { - ...Component.prototype.defaults, +export default class ComponentMap extends ComponentImage { + get defaults() { + return { + ...super.defaults, type: 'map', src: '', void: 0, @@ -15,7 +14,7 @@ export default Component.extend( address: '', zoom: '1', attributes: { frameborder: 0 }, - toolbar: OComponent.prototype.defaults.toolbar, + toolbar: super.defaults.toolbar, traits: [ { label: 'Address', @@ -42,65 +41,64 @@ export default Component.extend( changeProp: 1, }, ], - }, + }; + } + + initialize(o, opt) { + if (this.get('src')) this.parseFromSrc(); + else this.updateSrc(); + Component.prototype.initialize.apply(this, arguments); + this.listenTo(this, 'change:address change:zoom change:mapType', this.updateSrc); + } - initialize(o, opt) { - if (this.get('src')) this.parseFromSrc(); - else this.updateSrc(); - Component.prototype.initialize.apply(this, arguments); - this.listenTo(this, 'change:address change:zoom change:mapType', this.updateSrc); - }, + updateSrc() { + this.set('src', this.getMapUrl()); + } - updateSrc() { - this.set('src', this.getMapUrl()); - }, + /** + * Returns url of the map + * @return {string} + * @private + */ + getMapUrl() { + var md = this; + var addr = md.get('address'); + var zoom = md.get('zoom'); + var type = md.get('mapType'); + var size = ''; + addr = addr ? '&q=' + addr : ''; + zoom = zoom ? '&z=' + zoom : ''; + type = type ? '&t=' + type : ''; + var result = md.get('mapUrl') + '?' + addr + zoom + type; + result += '&output=embed'; + return result; + } - /** - * Returns url of the map - * @return {string} - * @private - */ - getMapUrl() { - var md = this; - var addr = md.get('address'); - var zoom = md.get('zoom'); - var type = md.get('mapType'); - var size = ''; - addr = addr ? '&q=' + addr : ''; - zoom = zoom ? '&z=' + zoom : ''; - type = type ? '&t=' + type : ''; - var result = md.get('mapUrl') + '?' + addr + zoom + type; - result += '&output=embed'; - return result; - }, + /** + * Set attributes by src string + * @private + */ + parseFromSrc() { + var uri = this.parseUri(this.get('src')); + var qr = uri.query; + if (qr.q) this.set('address', qr.q); + if (qr.z) this.set('zoom', qr.z); + if (qr.t) this.set('mapType', qr.t); + } +} - /** - * Set attributes by src string - * @private - */ - parseFromSrc() { - var uri = this.parseUri(this.get('src')); - var qr = uri.query; - if (qr.q) this.set('address', qr.q); - if (qr.z) this.set('zoom', qr.z); - if (qr.t) this.set('mapType', qr.t); - }, - }, - { - /** - * Detect if the passed element is a valid component. - * In case the element is valid an object abstracted - * from the element will be returned - * @param {HTMLElement} - * @return {Object} - * @private - */ - isComponent(el) { - var result = ''; - if (toLowerCase(el.tagName) == 'iframe' && /maps\.google\.com/.test(el.src)) { - result = { type: 'map', src: el.src }; - } - return result; - }, +/** + * Detect if the passed element is a valid component. + * In case the element is valid an object abstracted + * from the element will be returned + * @param {HTMLElement} + * @return {Object} + * @private + */ +ComponentMap.isComponent = el => { + var result = ''; + if (toLowerCase(el.tagName) == 'iframe' && /maps\.google\.com/.test(el.src)) { + result = { type: 'map', src: el.src }; } -); + return result; +}; diff --git a/src/dom_components/model/ComponentScript.js b/src/dom_components/model/ComponentScript.js index 57f767d75..508ffc5d7 100644 --- a/src/dom_components/model/ComponentScript.js +++ b/src/dom_components/model/ComponentScript.js @@ -3,29 +3,28 @@ import { toLowerCase } from 'utils/mixins'; const type = 'script'; -export default Component.extend( - { - defaults: { - ...Component.prototype.defaults, +export default class ComponentScript extends Component { + get defaults() { + return { + ...super.defaults, type, tagName: type, droppable: false, draggable: false, layerable: false, - }, - }, - { - isComponent(el) { - if (toLowerCase(el.tagName) == type) { - const result = { type }; + }; + } +} + +ComponentScript.isComponent = el => { + if (toLowerCase(el.tagName) == type) { + const result = { type }; - if (el.src) { - result.src = el.src; - result.onload = el.onload; - } + if (el.src) { + result.src = el.src; + result.onload = el.onload; + } - return result; - } - }, + return result; } -); +}; diff --git a/src/dom_components/model/ComponentSvg.js b/src/dom_components/model/ComponentSvg.js index 862c0ca4f..eadf9e719 100644 --- a/src/dom_components/model/ComponentSvg.js +++ b/src/dom_components/model/ComponentSvg.js @@ -3,24 +3,23 @@ import { toLowerCase } from 'utils/mixins'; const type = 'svg'; -export default Component.extend( - { - defaults: { - ...Component.prototype.defaults, +export default class ComponentSvg extends Component { + get defaults() { + return { + ...super.defaults, type, tagName: type, highlightable: 0, resizable: { ratioDefault: 1 }, - }, + }; + } - getName() { - let name = this.get('tagName'); - let customName = this.get('custom-name'); - name = name.charAt(0).toUpperCase() + name.slice(1); - return customName || name; - }, - }, - { - isComponent: el => toLowerCase(el.tagName) === type, + getName() { + let name = this.get('tagName'); + let customName = this.get('custom-name'); + name = name.charAt(0).toUpperCase() + name.slice(1); + return customName || name; } -); +} + +ComponentSvg.isComponent = el => toLowerCase(el.tagName) === type; diff --git a/src/dom_components/model/ComponentSvgIn.js b/src/dom_components/model/ComponentSvgIn.js index 72778c459..8d6891ab8 100644 --- a/src/dom_components/model/ComponentSvgIn.js +++ b/src/dom_components/model/ComponentSvgIn.js @@ -1,18 +1,17 @@ -import Component from './ComponentSvg'; +import ComponentSvg from './ComponentSvg'; /** * Component for inner SVG elements */ -export default Component.extend( - { - defaults: { - ...Component.prototype.defaults, +export default class ComponentSvgln extends ComponentSvg { + get defaults() { + return { + ...super.defaults, selectable: false, hoverable: false, layerable: false, - }, - }, - { - isComponent: (el, opts = {}) => !!opts.inSvg, + }; } -); +} + +ComponentSvgln.isComponent = (el, opts = {}) => !!opts.inSvg; diff --git a/src/dom_components/model/ComponentTable.js b/src/dom_components/model/ComponentTable.js index 733e2d637..5cef52650 100644 --- a/src/dom_components/model/ComponentTable.js +++ b/src/dom_components/model/ComponentTable.js @@ -3,22 +3,21 @@ import { toLowerCase } from 'utils/mixins'; const type = 'table'; -export default Component.extend( - { - defaults: { - ...Component.prototype.defaults, +export default class ComponentTable extends Component { + get defaults() { + return { + ...super.defaults, type, tagName: type, droppable: ['tbody', 'thead', 'tfoot'], - }, + }; + } - initialize(o, opt) { - Component.prototype.initialize.apply(this, arguments); - const components = this.get('components'); - !components.length && components.add({ type: 'tbody' }); - }, - }, - { - isComponent: el => toLowerCase(el.tagName) === type, + initialize(o, opt) { + Component.prototype.initialize.apply(this, arguments); + const components = this.get('components'); + !components.length && components.add({ type: 'tbody' }); } -); +} + +ComponentTable.isComponent = el => toLowerCase(el.tagName) === type; diff --git a/src/dom_components/model/ComponentTableBody.js b/src/dom_components/model/ComponentTableBody.js index 2f13a836e..8e9a0df7e 100644 --- a/src/dom_components/model/ComponentTableBody.js +++ b/src/dom_components/model/ComponentTableBody.js @@ -3,51 +3,49 @@ import { toLowerCase } from 'utils/mixins'; const type = 'tbody'; -export default Component.extend( - { - defaults: { - ...Component.prototype.defaults, +export default class ComponentTableBody extends Component { + get defaults() { + return { + ...super.defaults, type, tagName: type, draggable: ['table'], droppable: ['tr'], columns: 1, rows: 1, - }, - - initialize(o, opt) { - Component.prototype.initialize.apply(this, arguments); - const components = this.get('components'); - let columns = this.get('columns'); - let rows = this.get('rows'); - - // Init components if empty - if (!components.length) { - const rowsToAdd = []; - - while (rows--) { - const columnsToAdd = []; - let clm = columns; - - while (clm--) { - columnsToAdd.push({ - type: 'cell', - classes: ['cell'], - }); - } - - rowsToAdd.push({ - type: 'row', - classes: ['row'], - components: columnsToAdd, + }; + } + + initialize(o, opt) { + Component.prototype.initialize.apply(this, arguments); + const components = this.get('components'); + let columns = this.get('columns'); + let rows = this.get('rows'); + + // Init components if empty + if (!components.length) { + const rowsToAdd = []; + + while (rows--) { + const columnsToAdd = []; + let clm = columns; + + while (clm--) { + columnsToAdd.push({ + type: 'cell', + classes: ['cell'], }); } - components.add(rowsToAdd); + rowsToAdd.push({ + type: 'row', + classes: ['row'], + components: columnsToAdd, + }); } - }, - }, - { - isComponent: el => toLowerCase(el.tagName) === type, + + components.add(rowsToAdd); + } } -); +} +ComponentTableBody.isComponent = el => toLowerCase(el.tagName) === type; diff --git a/src/dom_components/model/ComponentTableCell.js b/src/dom_components/model/ComponentTableCell.js index 20209b482..3f6b4cf52 100644 --- a/src/dom_components/model/ComponentTableCell.js +++ b/src/dom_components/model/ComponentTableCell.js @@ -1,16 +1,15 @@ import Component from './Component'; import { toLowerCase } from 'utils/mixins'; -export default Component.extend( - { - defaults: { - ...Component.prototype.defaults, +export default class ComponentTableCell extends Component { + get defaults() { + return { + ...super.defaults, type: 'cell', tagName: 'td', draggable: ['tr'], - }, - }, - { - isComponent: el => ['td', 'th'].indexOf(toLowerCase(el.tagName)) >= 0, + }; } -); +} + +ComponentTableCell.isComponent = el => ['td', 'th'].indexOf(toLowerCase(el.tagName)) >= 0; diff --git a/src/dom_components/model/ComponentTableFoot.js b/src/dom_components/model/ComponentTableFoot.js index 8171a53b3..2f31aa167 100644 --- a/src/dom_components/model/ComponentTableFoot.js +++ b/src/dom_components/model/ComponentTableFoot.js @@ -3,15 +3,14 @@ import { toLowerCase } from 'utils/mixins'; const type = 'tfoot'; -export default ComponentTableBody.extend( - { - defaults: { - ...ComponentTableBody.prototype.defaults, +export default class ComponentTableFoot extends ComponentTableBody { + get defaults() { + return { + ...super.defaults, type, tagName: type, - }, - }, - { - isComponent: el => toLowerCase(el.tagName) === type, + }; } -); +} + +ComponentTableFoot.isComponent = el => toLowerCase(el.tagName) === type; diff --git a/src/dom_components/model/ComponentTableHead.js b/src/dom_components/model/ComponentTableHead.js index e3b19ce76..1452ce3a0 100644 --- a/src/dom_components/model/ComponentTableHead.js +++ b/src/dom_components/model/ComponentTableHead.js @@ -3,15 +3,14 @@ import { toLowerCase } from 'utils/mixins'; const type = 'thead'; -export default ComponentTableBody.extend( - { - defaults: { - ...ComponentTableBody.prototype.defaults, +export default class ComponentTableHead extends ComponentTableBody { + get defaults() { + return { + ...super.defaults, type, tagName: type, - }, - }, - { - isComponent: el => toLowerCase(el.tagName) === type, + }; } -); +} + +ComponentTableHead.isComponent = el => toLowerCase(el.tagName) === type; diff --git a/src/dom_components/model/ComponentTableRow.js b/src/dom_components/model/ComponentTableRow.js index fd862f313..f4bf81aaf 100644 --- a/src/dom_components/model/ComponentTableRow.js +++ b/src/dom_components/model/ComponentTableRow.js @@ -3,16 +3,15 @@ import { toLowerCase } from 'utils/mixins'; const tagName = 'tr'; -export default Component.extend( - { - defaults: { - ...Component.prototype.defaults, +export default class ComponentTableRow extends Component { + get defaults() { + return { + ...super.defaults, tagName, draggable: ['thead', 'tbody', 'tfoot'], droppable: ['th', 'td'], - }, - }, - { - isComponent: el => toLowerCase(el.tagName) === tagName, + }; } -); +} + +ComponentTableRow.isComponent = el => toLowerCase(el.tagName) === tagName; diff --git a/src/dom_components/model/ComponentText.js b/src/dom_components/model/ComponentText.js index 7b0f6e4c8..e01811079 100644 --- a/src/dom_components/model/ComponentText.js +++ b/src/dom_components/model/ComponentText.js @@ -1,10 +1,12 @@ import Component from './Component'; -export default class ComponentText extends Component {} - -ComponentText.prototype.defaults = { - ...Component.getDefaults(), - type: 'text', - droppable: false, - editable: true, -}; +export default class ComponentText extends Component { + get defaults() { + return { + ...super.defaults, + type: 'text', + droppable: false, + editable: true, + }; + } +} diff --git a/src/dom_components/model/ComponentTextNode.js b/src/dom_components/model/ComponentTextNode.js index 8ee5589d9..02837b12d 100644 --- a/src/dom_components/model/ComponentTextNode.js +++ b/src/dom_components/model/ComponentTextNode.js @@ -1,33 +1,32 @@ import Component from './Component'; import { escape } from 'utils/mixins'; -export default Component.extend( - { - defaults: { - ...Component.prototype.defaults, +export default class ComponentTextNode extends Component { + get defaults() { + return { + ...super.defaults, tagName: '', droppable: false, layerable: false, selectable: false, editable: true, - }, + }; + } + + toHTML() { + const parent = this.parent(); + const cnt = this.get('content'); + return parent && parent.is('script') ? cnt : escape(cnt); + } +} - toHTML() { - const parent = this.parent(); - const cnt = this.get('content'); - return parent && parent.is('script') ? cnt : escape(cnt); - }, - }, - { - isComponent(el) { - var result = ''; - if (el.nodeType === 3) { - result = { - type: 'textnode', - content: el.textContent, - }; - } - return result; - }, +ComponentTextNode.isComponent = el => { + var result = ''; + if (el.nodeType === 3) { + result = { + type: 'textnode', + content: el.textContent, + }; } -); + return result; +}; diff --git a/src/dom_components/model/ComponentVideo.js b/src/dom_components/model/ComponentVideo.js index 586799b67..faf2e9f8d 100644 --- a/src/dom_components/model/ComponentVideo.js +++ b/src/dom_components/model/ComponentVideo.js @@ -1,4 +1,4 @@ -import Component from './ComponentImage'; +import ComponentImage from './ComponentImage'; import { toLowerCase } from 'utils/mixins'; const type = 'video'; @@ -8,10 +8,10 @@ const ytnc = 'ytnc'; const hasParam = value => value && value !== '0'; -export default Component.extend( - { - defaults: { - ...Component.prototype.defaults, +export default class ComponentVideo extends ComponentImage { + get defaults() { + return { + ...super.defaults, type, tagName: type, videoId: '', @@ -31,332 +31,330 @@ export default Component.extend( modestbranding: 0, // YT modest branding sources: [], attributes: { allowfullscreen: 'allowfullscreen' }, - }, - - initialize(o, opt) { - this.em = opt.em; - if (this.get('src')) this.parseFromSrc(); - this.updateTraits(); - this.listenTo(this, 'change:provider', this.updateTraits); - this.listenTo(this, 'change:videoId change:provider', this.updateSrc); - Component.prototype.initialize.apply(this, arguments); - }, - - /** - * Update traits by provider - * @private - */ - updateTraits() { - const { em } = this; - const prov = this.get('provider'); - let tagName = 'iframe'; - let traits; + }; + } - switch (prov) { - case yt: - case ytnc: - traits = this.getYoutubeTraits(); - break; - case vi: - traits = this.getVimeoTraits(); - break; - default: - tagName = 'video'; - traits = this.getSourceTraits(); - } + initialize(o, opt) { + this.em = opt.em; + if (this.get('src')) this.parseFromSrc(); + this.updateTraits(); + this.listenTo(this, 'change:provider', this.updateTraits); + this.listenTo(this, 'change:videoId change:provider', this.updateSrc); + ComponentImage.prototype.initialize.apply(this, arguments); + } - this.set({ tagName }, { silent: 1 }); // avoid break in view - this.set({ traits }); - em.get('ready') && em.trigger('component:toggled'); - }, + /** + * Update traits by provider + * @private + */ + updateTraits() { + const { em } = this; + const prov = this.get('provider'); + let tagName = 'iframe'; + let traits; - /** - * Set attributes by src string - */ - parseFromSrc() { - const prov = this.get('provider'); - const uri = this.parseUri(this.get('src')); - const qr = uri.query; - switch (prov) { - case yt: - case ytnc: - case vi: - this.set('videoId', uri.pathname.split('/').pop()); - qr.list && this.set('list', qr.list); - hasParam(qr.autoplay) && this.set('autoplay', 1); - hasParam(qr.loop) && this.set('loop', 1); - parseInt(qr.controls) === 0 && this.set('controls', 0); - hasParam(qr.color) && this.set('color', qr.color); - qr.rel === '0' && this.set('rel', 0); - qr.modestbranding === '1' && this.set('modestbranding', 1); - break; - default: - } - }, + switch (prov) { + case yt: + case ytnc: + traits = this.getYoutubeTraits(); + break; + case vi: + traits = this.getVimeoTraits(); + break; + default: + tagName = 'video'; + traits = this.getSourceTraits(); + } - /** - * Update src on change of video ID - * @private - */ - updateSrc() { - const prov = this.get('provider'); - let src = ''; + this.set({ tagName }, { silent: 1 }); // avoid break in view + this.set({ traits }); + em.get('ready') && em.trigger('component:toggled'); + } - switch (prov) { - case yt: - src = this.getYoutubeSrc(); - break; - case ytnc: - src = this.getYoutubeNoCookieSrc(); - break; - case vi: - src = this.getVimeoSrc(); - break; - } + /** + * Set attributes by src string + */ + parseFromSrc() { + const prov = this.get('provider'); + const uri = this.parseUri(this.get('src')); + const qr = uri.query; + switch (prov) { + case yt: + case ytnc: + case vi: + this.set('videoId', uri.pathname.split('/').pop()); + qr.list && this.set('list', qr.list); + hasParam(qr.autoplay) && this.set('autoplay', 1); + hasParam(qr.loop) && this.set('loop', 1); + parseInt(qr.controls) === 0 && this.set('controls', 0); + hasParam(qr.color) && this.set('color', qr.color); + qr.rel === '0' && this.set('rel', 0); + qr.modestbranding === '1' && this.set('modestbranding', 1); + break; + default: + } + } - this.set({ src }); - }, + /** + * Update src on change of video ID + * @private + */ + updateSrc() { + const prov = this.get('provider'); + let src = ''; - /** - * Returns object of attributes for HTML - * @return {Object} - * @private - */ - getAttrToHTML(...args) { - var attr = Component.prototype.getAttrToHTML.apply(this, args); - var prov = this.get('provider'); - switch (prov) { - case yt: - case ytnc: - case vi: - break; - default: - if (this.get('loop')) attr.loop = 'loop'; - if (this.get('autoplay')) attr.autoplay = 'autoplay'; - if (this.get('controls')) attr.controls = 'controls'; - } - return attr; - }, + switch (prov) { + case yt: + src = this.getYoutubeSrc(); + break; + case ytnc: + src = this.getYoutubeNoCookieSrc(); + break; + case vi: + src = this.getVimeoSrc(); + break; + } - // Listen provider change and switch traits, in TraitView listen traits change + this.set({ src }); + } - /** - * Return the provider trait - * @return {Object} - * @private - */ - getProviderTrait() { - return { - type: 'select', - label: 'Provider', - name: 'provider', - changeProp: 1, - options: [ - { value: 'so', name: 'HTML5 Source' }, - { value: yt, name: 'Youtube' }, - { value: ytnc, name: 'Youtube (no cookie)' }, - { value: vi, name: 'Vimeo' }, - ], - }; - }, + /** + * Returns object of attributes for HTML + * @return {Object} + * @private + */ + getAttrToHTML(...args) { + var attr = Component.prototype.getAttrToHTML.apply(this, args); + var prov = this.get('provider'); + switch (prov) { + case yt: + case ytnc: + case vi: + break; + default: + if (this.get('loop')) attr.loop = 'loop'; + if (this.get('autoplay')) attr.autoplay = 'autoplay'; + if (this.get('controls')) attr.controls = 'controls'; + } + return attr; + } - /** - * Return traits for the source provider - * @return {Array} - * @private - */ - getSourceTraits() { - return [ - this.getProviderTrait(), - { - label: 'Source', - name: 'src', - placeholder: 'eg. ./media/video.mp4', - changeProp: 1, - }, - { - label: 'Poster', - name: 'poster', - placeholder: 'eg. ./media/image.jpg', - // changeProp: 1 - }, - this.getAutoplayTrait(), - this.getLoopTrait(), - this.getControlsTrait(), - ]; - }, - /** - * Return traits for the source provider - * @return {Array} - * @private - */ - getYoutubeTraits() { - return [ - this.getProviderTrait(), - { - label: 'Video ID', - name: 'videoId', - placeholder: 'eg. jNQXAC9IVRw', - changeProp: 1, - }, - this.getAutoplayTrait(), - this.getLoopTrait(), - this.getControlsTrait(), - { - type: 'checkbox', - label: 'Related', - name: 'rel', - changeProp: 1, - }, - { - type: 'checkbox', - label: 'Modest', - name: 'modestbranding', - changeProp: 1, - }, - ]; - }, + // Listen provider change and switch traits, in TraitView listen traits change - /** - * Return traits for the source provider - * @return {Array} - * @private - */ - getVimeoTraits() { - return [ - this.getProviderTrait(), - { - label: 'Video ID', - name: 'videoId', - placeholder: 'eg. 123456789', - changeProp: 1, - }, - { - label: 'Color', - name: 'color', - placeholder: 'eg. FF0000', - changeProp: 1, - }, - this.getAutoplayTrait(), - this.getLoopTrait(), - ]; - }, + /** + * Return the provider trait + * @return {Object} + * @private + */ + getProviderTrait() { + return { + type: 'select', + label: 'Provider', + name: 'provider', + changeProp: 1, + options: [ + { value: 'so', name: 'HTML5 Source' }, + { value: yt, name: 'Youtube' }, + { value: ytnc, name: 'Youtube (no cookie)' }, + { value: vi, name: 'Vimeo' }, + ], + }; + } - /** - * Return object trait - * @return {Object} - * @private - */ - getAutoplayTrait() { - return { + /** + * Return traits for the source provider + * @return {Array} + * @private + */ + getSourceTraits() { + return [ + this.getProviderTrait(), + { + label: 'Source', + name: 'src', + placeholder: 'eg. ./media/video.mp4', + changeProp: 1, + }, + { + label: 'Poster', + name: 'poster', + placeholder: 'eg. ./media/image.jpg', + // changeProp: 1 + }, + this.getAutoplayTrait(), + this.getLoopTrait(), + this.getControlsTrait(), + ]; + } + /** + * Return traits for the source provider + * @return {Array} + * @private + */ + getYoutubeTraits() { + return [ + this.getProviderTrait(), + { + label: 'Video ID', + name: 'videoId', + placeholder: 'eg. jNQXAC9IVRw', + changeProp: 1, + }, + this.getAutoplayTrait(), + this.getLoopTrait(), + this.getControlsTrait(), + { type: 'checkbox', - label: 'Autoplay', - name: 'autoplay', + label: 'Related', + name: 'rel', changeProp: 1, - }; - }, - - /** - * Return object trait - * @return {Object} - * @private - */ - getLoopTrait() { - return { + }, + { type: 'checkbox', - label: 'Loop', - name: 'loop', + label: 'Modest', + name: 'modestbranding', changeProp: 1, - }; - }, + }, + ]; + } - /** - * Return object trait - * @return {Object} - * @private - */ - getControlsTrait() { - return { - type: 'checkbox', - label: 'Controls', - name: 'controls', + /** + * Return traits for the source provider + * @return {Array} + * @private + */ + getVimeoTraits() { + return [ + this.getProviderTrait(), + { + label: 'Video ID', + name: 'videoId', + placeholder: 'eg. 123456789', changeProp: 1, - }; - }, + }, + { + label: 'Color', + name: 'color', + placeholder: 'eg. FF0000', + changeProp: 1, + }, + this.getAutoplayTrait(), + this.getLoopTrait(), + ]; + } - /** - * Returns url to youtube video - * @return {string} - * @private - */ - getYoutubeSrc() { - const id = this.get('videoId'); - let url = this.get('ytUrl'); - const list = this.get('list'); - url += id + (id.indexOf('?') < 0 ? '?' : ''); - url += list ? `&list=${list}` : ''; - url += this.get('autoplay') ? '&autoplay=1' : ''; - url += !this.get('controls') ? '&controls=0&showinfo=0' : ''; - // Loop works only with playlist enabled - // https://stackoverflow.com/questions/25779966/youtube-iframe-loop-doesnt-work - url += this.get('loop') ? `&loop=1&playlist=${id}` : ''; - url += this.get('rel') ? '' : '&rel=0'; - url += this.get('modestbranding') ? '&modestbranding=1' : ''; - return url; - }, + /** + * Return object trait + * @return {Object} + * @private + */ + getAutoplayTrait() { + return { + type: 'checkbox', + label: 'Autoplay', + name: 'autoplay', + changeProp: 1, + }; + } - /** - * Returns url to youtube no cookie video - * @return {string} - * @private - */ - getYoutubeNoCookieSrc() { - let url = this.getYoutubeSrc(); - url = url.replace(this.get('ytUrl'), this.get('ytncUrl')); - return url; - }, + /** + * Return object trait + * @return {Object} + * @private + */ + getLoopTrait() { + return { + type: 'checkbox', + label: 'Loop', + name: 'loop', + changeProp: 1, + }; + } - /** - * Returns url to vimeo video - * @return {string} - * @private - */ - getVimeoSrc() { - var url = this.get('viUrl'); - url += this.get('videoId') + '?'; - url += this.get('autoplay') ? '&autoplay=1' : ''; - url += this.get('loop') ? '&loop=1' : ''; - url += !this.get('controls') ? '&title=0&portrait=0&badge=0' : ''; - url += this.get('color') ? '&color=' + this.get('color') : ''; - return url; - }, - }, - { - /** - * Detect if the passed element is a valid component. - * In case the element is valid an object abstracted - * from the element will be returned - * @param {HTMLElement} - * @return {Object} - * @private - */ - isComponent(el) { - let result = ''; - const { tagName, src } = el; - const isYtProv = /youtube\.com\/embed/.test(src); - const isYtncProv = /youtube-nocookie\.com\/embed/.test(src); - const isViProv = /player\.vimeo\.com\/video/.test(src); - const isExtProv = isYtProv || isYtncProv || isViProv; - if (toLowerCase(tagName) == type || (toLowerCase(tagName) == 'iframe' && isExtProv)) { - result = { type: 'video' }; - if (src) result.src = src; - if (isExtProv) { - if (isYtProv) result.provider = yt; - else if (isYtncProv) result.provider = ytnc; - else if (isViProv) result.provider = vi; - } - } - return result; - }, + /** + * Return object trait + * @return {Object} + * @private + */ + getControlsTrait() { + return { + type: 'checkbox', + label: 'Controls', + name: 'controls', + changeProp: 1, + }; + } + + /** + * Returns url to youtube video + * @return {string} + * @private + */ + getYoutubeSrc() { + const id = this.get('videoId'); + let url = this.get('ytUrl'); + const list = this.get('list'); + url += id + (id.indexOf('?') < 0 ? '?' : ''); + url += list ? `&list=${list}` : ''; + url += this.get('autoplay') ? '&autoplay=1' : ''; + url += !this.get('controls') ? '&controls=0&showinfo=0' : ''; + // Loop works only with playlist enabled + // https://stackoverflow.com/questions/25779966/youtube-iframe-loop-doesnt-work + url += this.get('loop') ? `&loop=1&playlist=${id}` : ''; + url += this.get('rel') ? '' : '&rel=0'; + url += this.get('modestbranding') ? '&modestbranding=1' : ''; + return url; + } + + /** + * Returns url to youtube no cookie video + * @return {string} + * @private + */ + getYoutubeNoCookieSrc() { + let url = this.getYoutubeSrc(); + url = url.replace(this.get('ytUrl'), this.get('ytncUrl')); + return url; + } + + /** + * Returns url to vimeo video + * @return {string} + * @private + */ + getVimeoSrc() { + var url = this.get('viUrl'); + url += this.get('videoId') + '?'; + url += this.get('autoplay') ? '&autoplay=1' : ''; + url += this.get('loop') ? '&loop=1' : ''; + url += !this.get('controls') ? '&title=0&portrait=0&badge=0' : ''; + url += this.get('color') ? '&color=' + this.get('color') : ''; + return url; + } +} +/** + * Detect if the passed element is a valid component. + * In case the element is valid an object abstracted + * from the element will be returned + * @param {HTMLElement} + * @return {Object} + * @private + */ +ComponentVideo.isComponent = el => { + let result = ''; + const { tagName, src } = el; + const isYtProv = /youtube\.com\/embed/.test(src); + const isYtncProv = /youtube-nocookie\.com\/embed/.test(src); + const isViProv = /player\.vimeo\.com\/video/.test(src); + const isExtProv = isYtProv || isYtncProv || isViProv; + if (toLowerCase(tagName) == type || (toLowerCase(tagName) == 'iframe' && isExtProv)) { + result = { type: 'video' }; + if (src) result.src = src; + if (isExtProv) { + if (isYtProv) result.provider = yt; + else if (isYtncProv) result.provider = ytnc; + else if (isViProv) result.provider = vi; + } } -); + return result; +}; diff --git a/src/dom_components/model/ComponentWrapper.js b/src/dom_components/model/ComponentWrapper.js index 6e4a17f51..74c74bfeb 100644 --- a/src/dom_components/model/ComponentWrapper.js +++ b/src/dom_components/model/ComponentWrapper.js @@ -3,7 +3,7 @@ import Component from './Component'; export default class ComponentWrapper extends Component { defaults() { return { - ...Component.getDefaults(), + ...super.defaults, tagName: 'body', removable: false, copyable: false, diff --git a/src/domain_abstract/model/StyleableModel.js b/src/domain_abstract/model/StyleableModel.js index c3b4e443e..593a42bff 100644 --- a/src/domain_abstract/model/StyleableModel.js +++ b/src/domain_abstract/model/StyleableModel.js @@ -123,22 +123,6 @@ export default class StyleableModel extends Model { } _validate(attr, opts) { - const { style } = attr; - const em = this.em || opts.em; - const onBeforeStyle = em?.get('CssComposer')?.getConfig().onBeforeStyle; - - if (style && onBeforeStyle) { - const newStyle = onBeforeStyle({ ...style }); - newStyle && - keys(style).map(prop => { - if (isUndefined(newStyle[prop])) delete attr.style[prop]; - }); - newStyle && - keys(newStyle).map(prop => { - attr.style[prop] = newStyle[prop]; - }); - } - return true; } } diff --git a/test/specs/dom_components/model/ComponentImage.js b/test/specs/dom_components/model/ComponentImage.js index 8d53ad7f2..935300fb6 100644 --- a/test/specs/dom_components/model/ComponentImage.js +++ b/test/specs/dom_components/model/ComponentImage.js @@ -25,9 +25,9 @@ describe('ComponentImage', () => { describe('.initialize', () => { test('when a base 64 default image is provided, it uses the default image', () => { - let imageUrl = buildBase64UrlFromSvg(ComponentImage.getDefaults().src); + let imageUrl = buildBase64UrlFromSvg(ComponentImage.prototype.defaults.src); let componentImage = new ComponentImage({ attributes: { src: imageUrl } }, { ...compOpts }); - expect(componentImage.get('src')).toEqual(ComponentImage.getDefaults().src); + expect(componentImage.get('src')).toEqual(ComponentImage.prototype.defaults.src); expect(componentImage.isDefaultSrc()).toBeTruthy(); }); diff --git a/test/specs/dom_components/model/ComponentTypes.js b/test/specs/dom_components/model/ComponentTypes.js index be2c46e0a..c9e93bdea 100644 --- a/test/specs/dom_components/model/ComponentTypes.js +++ b/test/specs/dom_components/model/ComponentTypes.js @@ -24,6 +24,9 @@ describe('Component Types', () => { afterEach(() => { wrapper.components().reset(); + editor = new Editor({ allowScripts: 1 }); + editor.getModel().get('PageManager').onLoad(); + wrapper = editor.getWrapper(); }); test(' is correctly recognized', () => {