add docs to the class methods and extend utils

This commit is contained in:
Murod Khaydarov 2017-12-19 01:11:10 +03:00
commit 7b1ddc944b
5 changed files with 592 additions and 147 deletions

View file

@ -244,6 +244,20 @@ var Util = function () {
return Array.prototype.slice.call(collection);
}
/**
* Checks if object is empty
*
* @param {Object} object
* @return {boolean}
*/
}, {
key: "isEmpty",
value: function isEmpty(object) {
return Object.keys(object).length === 0 && object.constructor === Object;
}
}]);
return Util;
@ -471,7 +485,7 @@ var _createClass = function () { function defineProperties(target, props) { for
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var modules = ["blockManager.js","events.js","renderer.js","toolbar.js","tools.js","ui.js"].map(function (module) {
var modules = ["blockManager.js","events.js","renderer.js","sanitizer.js","toolbar.js","tools.js","ui.js"].map(function (module) {
return __webpack_require__(4)("./" + module);
});
@ -813,11 +827,12 @@ module.exports = function () {
var map = {
"./blockManager.js": 5,
"./events.js": 7,
"./renderer.js": 8,
"./toolbar.js": 9,
"./tools.js": 10,
"./ui.js": 11
"./events.js": 6,
"./renderer.js": 7,
"./sanitizer.js": 8,
"./toolbar.js": 10,
"./tools.js": 11,
"./ui.js": 12
};
function webpackContext(req) {
return __webpack_require__(webpackContextResolve(req));
@ -847,7 +862,7 @@ var _createClass = function () { function defineProperties(target, props) { for
* @classdesc Manage editor`s blocks storage and appearance
*/
var _block = __webpack_require__(6);
var _block = __webpack_require__(15);
var _block2 = _interopRequireDefault(_block);
@ -1294,94 +1309,6 @@ module.exports = BlockManager;
/* 6 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* WEBPACK VAR INJECTION */(function($) {
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
*
* @class Block
* @classdesc This class describes editor`s block, including block`s HTMLElement, data and tool
*
* @property {Tool} tool current block tool (Paragraph, for example)
* @property {Object} CSS block`s css classes
*
*/
var Block = function () {
/**
* @constructor
*
* @param {Object} tool current block plugin`s instance
*/
function Block(tool) {
_classCallCheck(this, Block);
this.tool = tool;
this.CSS = {
wrapper: 'ce-block',
content: 'ce-block__content'
};
this._html = this.compose();
}
/**
* Make default block wrappers and put tool`s content there
*
* @returns {HTMLDivElement}
* @private
*/
_createClass(Block, [{
key: 'compose',
value: function compose() {
var wrapper = $.make('div', this.CSS.wrapper),
content = $.make('div', this.CSS.content);
content.appendChild(this.tool.html);
wrapper.appendChild(content);
return wrapper;
}
/**
* Get block`s HTML
*
* @returns {HTMLDivElement}
*/
}, {
key: 'html',
get: function get() {
return this._html;
}
}]);
return Block;
}();
Block.displayName = 'Block';
exports.default = Block;
module.exports = exports['default'];
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)))
/***/ }),
/* 7 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* WEBPACK VAR INJECTION */(function(Module) {
@ -1483,7 +1410,7 @@ module.exports = exports["default"];
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(0)))
/***/ }),
/* 8 */
/* 7 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@ -1787,10 +1714,376 @@ module.exports = Renderer;
// })({});
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1)))
/***/ }),
/* 8 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* WEBPACK VAR INJECTION */(function(Module, _) {
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
/**
* Codex Sanitizer
*
* @module Sanitizer module
* Clears HTML from taint tags
*
* @version 2.0.0
*
* @usage
* Module can be used within two ways:
* 1) When you have an instance
* - this.moduleInstance['Sanitizer'].clean(yourDirtyString);
* 2) As static method
* - CodexEditor.Sanitizer.clean(yourDirtyString, yourCustomConfiguration);
*
*
* Look up the SanitizerConfig object as example to make your custom restrictions
*/
/**
* @typedef {Object} SanitizerConfig
* @property {Object} tags - define tags restrictions
*
* @example
*
* tags : {
* p: true,
* a: {
* href: true,
* rel: "nofollow",
* target: "_blank"
* }
* }
*/
var Sanitizer = function (_Module) {
_inherits(Sanitizer, _Module);
/**
* Initializes Sanitizer module
* Sets default configuration if custom not exists
*
* @property {HTMLJanitor} this.janitor - Sanitizer library
*
* @param {SanitizerConfig} config
*/
function Sanitizer(config) {
_classCallCheck(this, Sanitizer);
// default config
var _this = _possibleConstructorReturn(this, (Sanitizer.__proto__ || Object.getPrototypeOf(Sanitizer)).call(this, config));
_this.instantConfig = {};
_this.janitorInstance = null;
/** Custom configuration */
_this.sanitizerConfig = config.settings ? config.settings.sanitizer : {};
/** HTML Janitor library */
_this.sanitizerInstance = __webpack_require__(9);
return _this;
}
/**
* If developer uses editor's API, then he can customize sanitize restrictions.
* Or, sanitizing config can be defined globally in editors initialization. That config will be used everywhere
* At least, if there is no config overrides, that API uses Default configuration
*
* @uses https://www.npmjs.com/package/html-janitor
*
* @param {HTMLJanitor} library - sanitizer extension
*/
_createClass(Sanitizer, [{
key: 'clean',
/**
* Cleans string from unwanted tags
* @param {String} taintString - HTML string
*
* @return {String} clean HTML
*/
value: function clean(taintString) {
return this.janitorInstance.clean(taintString);
}
/**
* Cleans string from unwanted tags
* @static
*
* Method allows to use default config
*
* @param {String} taintString - taint string
* @param {SanitizerConfig} customConfig - allowed tags
*
* @return {String} clean HTML
*/
}, {
key: 'sanitizerInstance',
set: function set(library) {
this.janitorInstance = new library(this.instanceConfig);
}
/**
* Sets sanitizer configuration. Uses default config if user didn't pass the restriction
* @param {SanitizerConfig} config
*/
}, {
key: 'sanitizerConfig',
set: function set(config) {
if (_.isEmpty(config)) {
this.instanceConfig = {
tags: {
p: {},
a: {
href: true,
target: '_blank',
rel: 'nofollow'
}
}
};
} else {
this.instanceConfig = config;
}
}
}], [{
key: 'clean',
value: function clean(taintString, customConfig) {
var newInstance = Sanitizer(customConfig);
return newInstance.clean(taintString);
}
}]);
return Sanitizer;
}(Module);
Sanitizer.displayName = 'Sanitizer';
exports.default = Sanitizer;
module.exports = exports['default'];
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(0), __webpack_require__(1)))
/***/ }),
/* 9 */
/***/ (function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;(function (root, factory) {
if (true) {
!(__WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
(__WEBPACK_AMD_DEFINE_FACTORY__.call(exports, __webpack_require__, exports, module)) :
__WEBPACK_AMD_DEFINE_FACTORY__),
__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
root.HTMLJanitor = factory();
}
}(this, function () {
/**
* @param {Object} config.tags Dictionary of allowed tags.
* @param {boolean} config.keepNestedBlockElements Default false.
*/
function HTMLJanitor(config) {
var tagDefinitions = config['tags'];
var tags = Object.keys(tagDefinitions);
var validConfigValues = tags
.map(function(k) { return typeof tagDefinitions[k]; })
.every(function(type) { return type === 'object' || type === 'boolean' || type === 'function'; });
if(!validConfigValues) {
throw new Error("The configuration was invalid");
}
this.config = config;
}
// TODO: not exhaustive?
var blockElementNames = ['P', 'LI', 'TD', 'TH', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'PRE'];
function isBlockElement(node) {
return blockElementNames.indexOf(node.nodeName) !== -1;
}
var inlineElementNames = ['A', 'B', 'STRONG', 'I', 'EM', 'SUB', 'SUP', 'U', 'STRIKE'];
function isInlineElement(node) {
return inlineElementNames.indexOf(node.nodeName) !== -1;
}
HTMLJanitor.prototype.clean = function (html) {
var sandbox = document.createElement('div');
sandbox.innerHTML = html;
this._sanitize(sandbox);
return sandbox.innerHTML;
};
HTMLJanitor.prototype._sanitize = function (parentNode) {
var treeWalker = createTreeWalker(parentNode);
var node = treeWalker.firstChild();
if (!node) { return; }
do {
// Ignore nodes that have already been sanitized
if (node._sanitized) {
continue;
}
if (node.nodeType === Node.TEXT_NODE) {
// If this text node is just whitespace and the previous or next element
// sibling is a block element, remove it
// N.B.: This heuristic could change. Very specific to a bug with
// `contenteditable` in Firefox: http://jsbin.com/EyuKase/1/edit?js,output
// FIXME: make this an option?
if (node.data.trim() === ''
&& ((node.previousElementSibling && isBlockElement(node.previousElementSibling))
|| (node.nextElementSibling && isBlockElement(node.nextElementSibling)))) {
parentNode.removeChild(node);
this._sanitize(parentNode);
break;
} else {
continue;
}
}
// Remove all comments
if (node.nodeType === Node.COMMENT_NODE) {
parentNode.removeChild(node);
this._sanitize(parentNode);
break;
}
var isInline = isInlineElement(node);
var containsBlockElement;
if (isInline) {
containsBlockElement = Array.prototype.some.call(node.childNodes, isBlockElement);
}
// Block elements should not be nested (e.g. <li><p>...); if
// they are, we want to unwrap the inner block element.
var isNotTopContainer = !! parentNode.parentNode;
var isNestedBlockElement =
isBlockElement(parentNode) &&
isBlockElement(node) &&
isNotTopContainer;
var nodeName = node.nodeName.toLowerCase();
var allowedAttrs = getAllowedAttrs(this.config, nodeName, node);
var isInvalid = isInline && containsBlockElement;
// Drop tag entirely according to the whitelist *and* if the markup
// is invalid.
if (isInvalid || shouldRejectNode(node, allowedAttrs)
|| (!this.config.keepNestedBlockElements && isNestedBlockElement)) {
// Do not keep the inner text of SCRIPT/STYLE elements.
if (! (node.nodeName === 'SCRIPT' || node.nodeName === 'STYLE')) {
while (node.childNodes.length > 0) {
parentNode.insertBefore(node.childNodes[0], node);
}
}
parentNode.removeChild(node);
this._sanitize(parentNode);
break;
}
// Sanitize attributes
for (var a = 0; a < node.attributes.length; a += 1) {
var attr = node.attributes[a];
if (shouldRejectAttr(attr, allowedAttrs, node)) {
node.removeAttribute(attr.name);
// Shift the array to continue looping.
a = a - 1;
}
}
// Sanitize children
this._sanitize(node);
// Mark node as sanitized so it's ignored in future runs
node._sanitized = true;
} while ((node = treeWalker.nextSibling()));
};
function createTreeWalker(node) {
return document.createTreeWalker(node,
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT,
null, false);
}
function getAllowedAttrs(config, nodeName, node){
if (typeof config.tags[nodeName] === 'function') {
return config.tags[nodeName](node);
} else {
return config.tags[nodeName];
}
}
function shouldRejectNode(node, allowedAttrs){
if (typeof allowedAttrs === 'undefined') {
return true;
} else if (typeof allowedAttrs === 'boolean') {
return !allowedAttrs;
}
return false;
}
function shouldRejectAttr(attr, allowedAttrs, node){
var attrName = attr.name.toLowerCase();
if (allowedAttrs === true){
return false;
} else if (typeof allowedAttrs[attrName] === 'function'){
return !allowedAttrs[attrName](attr.value, node);
} else if (typeof allowedAttrs[attrName] === 'undefined'){
return true;
} else if (allowedAttrs[attrName] === false) {
return true;
} else if (typeof allowedAttrs[attrName] === 'string') {
return (allowedAttrs[attrName] !== attr.value);
}
return false;
}
return HTMLJanitor;
}));
/***/ }),
/* 10 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* WEBPACK VAR INJECTION */(function(Module, $) {
@ -2008,7 +2301,7 @@ module.exports = exports['default'];
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(0), __webpack_require__(2)))
/***/ }),
/* 10 */
/* 11 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@ -2271,7 +2564,7 @@ module.exports = exports['default'];
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(0), __webpack_require__(1)))
/***/ }),
/* 11 */
/* 12 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@ -2446,7 +2739,7 @@ var UI = function (_Module) {
/**
* Load CSS
*/
var styles = __webpack_require__(12);
var styles = __webpack_require__(13);
/**
* Make tag
@ -2735,10 +3028,10 @@ module.exports = exports['default'];
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(0), __webpack_require__(2)))
/***/ }),
/* 12 */
/* 13 */
/***/ (function(module, exports, __webpack_require__) {
exports = module.exports = __webpack_require__(13)(undefined);
exports = module.exports = __webpack_require__(14)(undefined);
// imports
@ -2749,7 +3042,7 @@ exports.push([module.i, ":root {\n\n /**\n * Toolbar buttons\n */\n\n
/***/ }),
/* 13 */
/* 14 */
/***/ (function(module, exports) {
/*
@ -2830,6 +3123,94 @@ function toComment(sourceMap) {
}
/***/ }),
/* 15 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* WEBPACK VAR INJECTION */(function($) {
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
*
* @class Block
* @classdesc This class describes editor`s block, including block`s HTMLElement, data and tool
*
* @property {Tool} tool current block tool (Paragraph, for example)
* @property {Object} CSS block`s css classes
*
*/
var Block = function () {
/**
* @constructor
*
* @param {Object} tool current block plugin`s instance
*/
function Block(tool) {
_classCallCheck(this, Block);
this.tool = tool;
this.CSS = {
wrapper: 'ce-block',
content: 'ce-block__content'
};
this._html = this.compose();
}
/**
* Make default block wrappers and put tool`s content there
*
* @returns {HTMLDivElement}
* @private
*/
_createClass(Block, [{
key: 'compose',
value: function compose() {
var wrapper = $.make('div', this.CSS.wrapper),
content = $.make('div', this.CSS.content);
content.appendChild(this.tool.html);
wrapper.appendChild(content);
return wrapper;
}
/**
* Get block`s HTML
*
* @returns {HTMLDivElement}
*/
}, {
key: 'html',
get: function get() {
return this._html;
}
}]);
return Block;
}();
Block.displayName = 'Block';
exports.default = Block;
module.exports = exports['default'];
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)))
/***/ })
/******/ ]);
//# sourceMappingURL=codex-editor.js.map

File diff suppressed because one or more lines are too long

34
package-lock.json generated
View file

@ -2986,6 +2986,14 @@
}
}
},
"string_decoder": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"requires": {
"safe-buffer": "5.0.1"
}
},
"string-width": {
"version": "1.0.2",
"bundled": true,
@ -2996,14 +3004,6 @@
"strip-ansi": "3.0.1"
}
},
"string_decoder": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"requires": {
"safe-buffer": "5.0.1"
}
},
"stringstream": {
"version": "0.0.5",
"bundled": true,
@ -7994,6 +7994,15 @@
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
"dev": true
},
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
"dev": true,
"requires": {
"safe-buffer": "5.1.1"
}
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
@ -8021,15 +8030,6 @@
}
}
},
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
"dev": true,
"requires": {
"safe-buffer": "5.1.1"
}
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",

View file

@ -2,9 +2,36 @@
* Codex Sanitizer
*
* @module Sanitizer module
* Clears HTML from dirty tags
* Clears HTML from taint tags
*
* @version 2.0.0
*
* @usage
* Module can be used within two ways:
* 1) When you have an instance
* - this.moduleInstance['Sanitizer'].clean(yourDirtyString);
* 2) As static method
* - CodexEditor.Sanitizer.clean(yourDirtyString, yourCustomConfiguration);
*
*
* Look up the SanitizerConfig object as example to make your custom restrictions
*/
/**
* @typedef {Object} SanitizerConfig
* @property {Object} tags - define tags restrictions
*
* @example
*
* tags : {
* p: true,
* a: {
* href: true,
* rel: "nofollow",
* target: "_blank"
* }
* }
*/
export default class Sanitizer extends Module {
@ -14,55 +41,76 @@ export default class Sanitizer extends Module {
*
* @property {HTMLJanitor} this.janitor - Sanitizer library
*
* @param config
* @param {SanitizerConfig} config
*/
constructor(config) {
super(config);
// default config
this._config = {};
this._janitor = null;
this.instantConfig = {};
this.janitorInstance = null;
/** Custom configuration */
this.sanitizerConfig = config.settings.sanitizer;
this.sanitizerConfig = config.settings ? config.settings.sanitizer : {};
/** HTML Janitor library */
this.sanitizerInstance = require('html-janitor');
}
/**
* @param library
* If developer uses editor's API, then he can customize sanitize restrictions.
* Or, sanitizing config can be defined globally in editors initialization. That config will be used everywhere
* At least, if there is no config overrides, that API uses Default configuration
*
* @description If developer uses editor's API, then he can customize sane restrictions.
* Or, sane config can be defined globally in editors initialization. That config will be used everywhere
* At least, if there is no config overrides, that API uses BASIC Default configation
* @uses https://www.npmjs.com/package/html-janitor
*
* @param {HTMLJanitor} library - sanitizer extension
*/
set sanitizerInstance(library) {
this._janitor = new library(this._config);
this.janitorInstance = new library(this.instanceConfig);
}
/**
* Sets sanitizer configuration. Uses default config if user didn't pass the restriction
* @param {SanitizerConfig} config
*/
set sanitizerConfig(config) {
this._config = {
tags: {
p: {},
a: {
href: true,
target: '_blank',
rel: 'nofollow'
if (_.isEmpty(config)) {
this.instanceConfig = {
tags: {
p: {},
a: {
href: true,
target: '_blank',
rel: 'nofollow'
}
}
}
};
};
} else {
this.instanceConfig = config;
}
}
/**
* Cleans string from unwanted tags
* @param dirtyString
* @return {*}
* @param {String} taintString - HTML string
*
* @return {String} clean HTML
*/
clean(dirtyString) {
return this._janitor.clean(dirtyString);
clean(taintString) {
return this.janitorInstance.clean(taintString);
}
/**
@ -71,13 +119,17 @@ export default class Sanitizer extends Module {
*
* Method allows to use default config
*
* @param {String} dirtyString - taint string
* @param {Object} customConfig - allowed tags
* @param {String} taintString - taint string
* @param {SanitizerConfig} customConfig - allowed tags
*
* @return {String} clean HTML
*/
static clean(dirtyString, customConfig) {
static clean(taintString, customConfig) {
let newInstance = Sanitizer(customConfig);
return newInstance.clean(dirtyString);
return newInstance.clean(taintString);
}
}

View file

@ -97,4 +97,16 @@ export default class Util {
}
/**
* Checks if object is empty
*
* @param {Object} object
* @return {boolean}
*/
static isEmpty(object) {
return Object.keys(object).length === 0 && object.constructor === Object;
}
};