diff --git a/gulpfile.js b/gulpfile.js
index 105c245..ba9da19 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -85,7 +85,7 @@ gulp.task(`build-scripts`, () => {
json(),
babel({
babelrc: false,
- exclude: [`node_modules/**`, `js/tests/**`],
+ exclude: [`node_modules/**`, `src/tests/**`],
presets: [
[`@babel/preset-env`, {modules: false, useBuiltIns: `entry`}]
]
@@ -115,7 +115,7 @@ gulp.task(`build-prompt`, () => {
json(),
babel({
babelrc: false,
- exclude: [`node_modules/**`, `js/tests/**`],
+ exclude: [`node_modules/**`, `src/tests/**`],
presets: [
[`@babel/preset-env`, {modules: false}]
],
diff --git a/sass/console-blocks/c-html.scss b/sass/console-blocks/c-html.scss
new file mode 100644
index 0000000..3ee2b8a
--- /dev/null
+++ b/sass/console-blocks/c-html.scss
@@ -0,0 +1,31 @@
+.c-html {
+ &__tag {
+ color: #a894a6;
+ }
+
+ &__tag-name {
+ color: #881280;
+ }
+
+ &__attr-name {
+ color: #994500;
+ }
+
+ &__attr-value,
+ &__id {
+ color: #1a1aa6;
+ }
+
+ &__comment,
+ &__doctype {
+ color: slategray;
+ }
+
+ // &__punctuation {
+ // color: #999999;
+ // }
+
+ &__link {
+ color: #4855cc;
+ }
+}
diff --git a/sass/console-blocks/console.scss b/sass/console-blocks/console.scss
index a84e149..ed056ba 100644
--- a/sass/console-blocks/console.scss
+++ b/sass/console-blocks/console.scss
@@ -1,5 +1,5 @@
.console {
- font: 0.8em/normal "SFMono-Regular", "Consolas", "Liberation Mono", "Menlo", "Courier", "Courier New", monospace;
+ font: 0.8em/normal "Menlo", "Monaco", "Consolas", "Courier New", monospace;
line-height: 1.4;
cursor: default;
diff --git a/sass/style.scss b/sass/style.scss
index 4ab696e..802b5cd 100644
--- a/sass/style.scss
+++ b/sass/style.scss
@@ -31,6 +31,7 @@ __elem — element
@import "console-blocks/grey";
@import "console-blocks/error";
@import "console-blocks/nowrap";
+@import "console-blocks/c-html";
// Prompt blocks
@import "prompt-blocks/prompt";
diff --git a/src/array/array-view.js b/src/array/array-view.js
index 6c48ee1..da50c81 100644
--- a/src/array/array-view.js
+++ b/src/array/array-view.js
@@ -1,11 +1,14 @@
import TypeView from '../type-view';
+import EntryView from '../entry-view';
import MapEntryView from '../object/map-entry-view';
import {getElement} from '../utils';
-import {Mode, ViewType} from '../enums';
+import {Mode, ViewType, GET_STATE_DESCRIPTORS_KEY_NAME} from '../enums';
const EMPTY = `empty`;
const MULTIPLY_SIGN = `×`;
+const getStateDescriptorsKey = Symbol(GET_STATE_DESCRIPTORS_KEY_NAME);
+
export default class ArrayView extends TypeView {
constructor(params, cons) {
super(params, cons);
@@ -14,6 +17,8 @@ export default class ArrayView extends TypeView {
if (!params.parentView) {
this.rootView = this;
}
+
+ this._stateDescriptorsQueue.push(this[getStateDescriptorsKey]());
}
get template() {
@@ -42,7 +47,7 @@ export default class ArrayView extends TypeView {
this._state.isOpened = this.isOpeningAllowed;
}
- _getStateDescriptors() {
+ [getStateDescriptorsKey]() {
const self = this;
return {
set isHeadContentShowed(bool) {
@@ -139,7 +144,7 @@ export default class ArrayView extends TypeView {
}
if (inHead && emptyCount !== 0 && (hasKey || i === l - 1)) {
TypeView.appendEntryElIntoFragment(
- this._createEntryEl({key, el: getElement(`${EMPTY}${emptyCount > 1 ? ` ${MULTIPLY_SIGN} ${emptyCount}` : ``}`), withoutKey: true}),
+ new EntryView({key, entryEl: getElement(`${EMPTY}${emptyCount > 1 ? ` ${MULTIPLY_SIGN} ${emptyCount}` : ``}`), withoutKey: true}).el,
fragment
);
if (inHead && countEntriesWithoutKeys) {
@@ -150,9 +155,9 @@ export default class ArrayView extends TypeView {
if (hasKey) {
if (isMapEntriesSpecialValue) {
const pair = arr[i];
- const el = new MapEntryView({val: pair, mode, depth: this.nextNestingLevel, parentView: this, propKey: this._propKey}, this._console).el;
+ const entryEl = new MapEntryView({val: pair, mode, depth: this.nextNestingLevel, parentView: this, propKey: this._propKey}, this._console).el;
TypeView.appendEntryElIntoFragment(
- this._createEntryEl({key, el, withoutKey: inHead}),
+ new EntryView({key, entryEl, withoutKey: inHead}).el,
fragment
);
} else {
diff --git a/src/base-view.js b/src/base-view.js
new file mode 100644
index 0000000..429d5b6
--- /dev/null
+++ b/src/base-view.js
@@ -0,0 +1,285 @@
+import AbstractView from './abstract-view';
+import {Mode, GET_STATE_DESCRIPTORS_KEY_NAME} from './enums';
+
+const DEFAULT_DEPTH = 1;
+
+const getStateDescriptorsKey = Symbol(GET_STATE_DESCRIPTORS_KEY_NAME);
+
+export default class BaseView extends AbstractView {
+ constructor(params, cons) {
+ super();
+ if (params.parentView) {
+ this._parentView = params.parentView;
+ this.rootView = params.parentView.rootView;
+ }
+ this._viewTypeParams = void 0;
+ this._console = cons;
+ this._value = params.val;
+ this._mode = params.mode;
+ this._type = params.type;
+ this._currentDepth = params.depth ? params.depth : DEFAULT_DEPTH;
+
+ this._cache = {};
+
+ this._stateDescriptorsQueue = [];
+ this._stateDescriptorsQueue.push(this[getStateDescriptorsKey]());
+ }
+
+ /**
+ * @abstract
+ * @protected
+ */
+ _afterRender() {}
+
+ _bind() {
+ if (!this.viewType) {
+ throw new Error(`this.viewType must be specified`);
+ }
+ if (!this.rootView) {
+ throw new Error(`this.rootView must be specified`);
+ }
+
+ this._headEl = this.el.querySelector(`.head`);
+ this._headContentEl = this.el.querySelector(`.head__content`);
+ this._infoEl = this.el.querySelector(`.info`);
+ this._contentEl = this.el.querySelector(`.item__content`);
+
+ this._afterRender();
+ }
+
+ get protoConstructorName() {
+ if (!this._cache.protoConstructorName) {
+ const proto = Object.getPrototypeOf(this._value);
+ this._cache.protoConstructorName = proto && proto.hasOwnProperty(`constructor`) ?
+ proto.constructor.name : `Object`;
+ }
+ return this._cache.protoConstructorName;
+ }
+
+ get stringTagName() {
+ if (!this._cache.stringTagName) {
+ const stringTag = Object.prototype.toString.call(this._value);
+ this._cache.stringTagName = stringTag.substring(8, stringTag.length - 1);
+ }
+ return this._cache.stringTagName;
+ }
+
+ set stringTagName(val) {
+ if (!this._cache.stringTagName) {
+ this._cache.stringTagName = val;
+ }
+ }
+
+ get value() {
+ return this._value;
+ }
+
+ get parentView() {
+ return this._parentView;
+ }
+
+ /**
+ * Current state
+ * @type {{}}
+ * @param {{}} params — object with values which will be assigned throught setters
+ */
+ get _state() {
+ if (!this._viewState) {
+ this._viewState = {};
+ this._stateDescriptorsQueue.forEach((descriptorsObj) => {
+
+ Object.defineProperties(
+ this._viewState,
+ Object.getOwnPropertyDescriptors(descriptorsObj)
+ );
+ });
+ Object.seal(this._viewState);
+ }
+ return this._viewState;
+ }
+
+ /**
+ * @return {{}} — object that contains descriptors only
+ */
+ [getStateDescriptorsKey]() {
+ const self = this;
+ return {
+ set isShowInfo(bool) {
+ if (!self._infoEl) {
+ return;
+ }
+ if (bool && !self._infoEl.textContent) {
+ self._infoEl.textContent = self.info;
+ }
+ self._isShowInfo = self.toggleInfoShowed(bool);
+ },
+ get isShowInfo() {
+ return self._isShowInfo;
+ },
+ set isHeadContentShowed(bool) {
+ self.toggleHeadContentShowed(bool);
+ },
+ set isOpeningDisabled(bool) {
+ if (!bool && self._mode === Mode.PREVIEW) {
+ throw new Error(`Enabling opening object in preview mode is forbidden`);
+ }
+ if (self._isOpeningDisabled === bool) {
+ return;
+ }
+
+ self.toggleArrowPointer(!bool);
+ self._addOrRemoveHeadClickHandler(!bool);
+ self._isOpeningDisabled = bool;
+ },
+ get isOpeningDisabled() {
+ return self._isOpeningDisabled;
+ },
+ set isBraced(bool) {
+ self.toggleHeadContentBraced(bool);
+ },
+ set isOpened(bool) {
+ if (bool === self._isOpened) {
+ return;
+ }
+
+ self._isOpened = bool;
+ self.toggleArrowBottom(bool);
+ self._state.isContentShowed = bool;
+ },
+ get isOpened() {
+ return self._isOpened;
+ },
+ set isContentShowed(bool) {
+ if (bool === self._isContentShowed) {
+ return;
+ }
+ self._isContentShowed = self.toggleContentShowed(bool);
+ if (self._isContentShowed && self._contentEl.childElementCount === 0) {
+ self._contentEl.appendChild(self.createContent(self._value, false).fragment);
+ }
+ },
+ get isContentShowed() {
+ return self._isContentShowed;
+ },
+ set isOversized(bool) {
+ self.toggleHeadContentOversized(bool);
+ },
+ set isItalicEnabled(bool) {
+ self._isItalicEnabled = self.toggleItalic(bool);
+ },
+ get isItalicEnabled() {
+ return self._isItalicEnabled;
+ }
+ };
+ }
+
+ toggleHeadContentBraced(isEnable) {
+ return this._headContentEl.classList.toggle(`entry-container--braced`, isEnable);
+ }
+
+ toggleHeadContentOversized(isEnable) {
+ return this._headContentEl.classList.toggle(`entry-container--oversize`, isEnable);
+ }
+
+ toggleInfoShowed(isEnable) {
+ return !this._infoEl.classList.toggle(`hidden`, !isEnable);
+ }
+
+ toggleHeadContentShowed(isEnable) {
+ return !this._headContentEl.classList.toggle(`hidden`, !isEnable);
+ }
+
+ toggleContentShowed(isEnable) {
+ return !this._contentEl.classList.toggle(`hidden`, !isEnable);
+ }
+
+ toggleItalic(isEnable) {
+ return this._headEl.classList.toggle(`italic`, isEnable);
+ }
+
+ toggleArrowPointer(isEnable) {
+ return this._headEl.classList.toggle(`item__head--arrow-pointer`, isEnable);
+ }
+
+ toggleArrowBottom(isEnable) {
+ return this._headEl.classList.toggle(`item__head--arrow-bottom`, isEnable);
+ }
+
+ get isOpeningAllowed() {
+ return this._mode !== Mode.PREVIEW &&
+ !this._state.isOpeningDisabled &&
+ this.isAutoExpandNeeded;
+ }
+
+ get depth() {
+ if (!this._cache.depth) {
+ this._cache.depth = this._parentView ? this._parentView.depth + 1 : 1;
+ }
+ return this._cache.depth;
+ }
+
+ get nextNestingLevel() {
+ return this._currentDepth + 1;
+ }
+
+ /**
+ * Check if autoexpand needed
+ * Setter for force
+ * @type {boolean}
+ */
+ get isAutoExpandNeeded() {
+ if (!this._cache.isAutoExpandNeeded) {
+ this._cache.isAutoExpandNeeded = false;
+
+ const rootViewTypeParams = this._console.params[this.rootView.viewType];
+
+ if (this._currentDepth > rootViewTypeParams.expandDepth) {
+ return this._cache.isAutoExpandNeeded;
+ }
+
+ if (this._parentView) {
+ if (!(this._parentView.isAutoExpandNeeded &&
+ !rootViewTypeParams.excludeViewTypesFromAutoexpand.includes(this.viewType))) {
+ return this._cache.isAutoExpandNeeded;
+ }
+ }
+ }
+ return this._cache.isAutoExpandNeeded;
+ }
+
+ set isAutoExpandNeeded(bool) {
+ this._cache.isAutoExpandNeeded = bool;
+ }
+
+ _headClickHandler(evt) {
+ evt.preventDefault();
+ this._state.isOpened = !this._state.isOpened;
+ }
+
+ _addOrRemoveHeadClickHandler(bool) {
+ if (bool) {
+ if (!this._bindedHeadClickHandler) {
+ this._bindedHeadClickHandler = this._headClickHandler.bind(this);
+ }
+ this._headEl.addEventListener(`click`, this._bindedHeadClickHandler);
+ } else if (this._bindedHeadClickHandler) {
+ this._headEl.removeEventListener(`click`, this._bindedHeadClickHandler);
+ }
+ }
+
+ /**
+ * @param {HTMLElement|null} entryEl
+ * @param {DocumentFragment} fragment
+ */
+ static appendEntryElIntoFragment(entryEl, fragment) {
+ if (entryEl !== null) {
+ fragment.appendChild(entryEl);
+ }
+ }
+
+ static prependEntryElIntoFragment(entryEl, fragment) {
+ if (entryEl !== null) {
+ fragment.insertBefore(entryEl, fragment.firstElementChild);
+ }
+ }
+}
diff --git a/src/console.js b/src/console.js
index 15b30a5..4f68c77 100644
--- a/src/console.js
+++ b/src/console.js
@@ -3,9 +3,12 @@ import ObjectView from './object/object-view';
import MapSetView from './object/map-set-view';
import PromiseView from './object/promise-view';
import StringNumberView from './object/string-number-view';
+import ObjectNodeView from './object/object-node-view';
import ArrayView from './array/array-view';
import FunctionView from './function/function-view';
import PrimitiveView from './primitive/primitive-view';
+import NodeView from './node/node-view';
+// import NodeView from './object/node-view';
import mergeParams from './utils/merge-params';
import {getElement, checkObjectisPrototype, checkEnumContainsValue} from './utils';
import {Mode, ViewType, Env} from './enums';
@@ -52,7 +55,8 @@ export default class Console {
this.params = {
object: this._parseViewParams(ViewType.OBJECT, mergeParams([{}, mergedParams.common, mergedParams.object])),
array: this._parseViewParams(ViewType.ARRAY, mergeParams([{}, mergedParams.common, mergedParams.array])),
- function: this._parseViewParams(ViewType.FUNCTION, mergeParams([{}, mergedParams.common, mergedParams.function]))
+ function: this._parseViewParams(ViewType.FUNCTION, mergeParams([{}, mergedParams.common, mergedParams.function])),
+ node: this._parseViewParams(ViewType.NODE, mergeParams([{}, mergedParams.common, mergedParams.node]))
};
Object.assign(this.params, this._parseConsoleParams(mergedParams));
}
@@ -331,6 +335,7 @@ export default class Console {
const stringTagName = stringTag.substring(8, stringTag.length - 1);
const objectIsPrototype = checkObjectisPrototype(val);
+
if (stringTagName !== `Object` && (
Array.isArray(val) || (
!objectIsPrototype && (
@@ -345,10 +350,16 @@ export default class Console {
view = new ArrayView(params, this);
} else if (!objectIsPrototype && (this.checkInstanceOf(val, `Map`) || this.checkInstanceOf(val, `Set`))) {
view = new MapSetView(params, this);
- } else if (!objectIsPrototype && val instanceof Promise) {
+ } else if (!objectIsPrototype && this.checkInstanceOf(val, `Promise`)) {
view = new PromiseView(params, this);
} else if (!objectIsPrototype && (this.checkInstanceOf(val, `String`) || this.checkInstanceOf(val, `Number`))) {
view = new StringNumberView(params, this);
+ } else if (!objectIsPrototype && this.checkInstanceOf(val, `Node`)) {
+ if (mode === Mode.LOG || mode === Mode.LOG_HTML || mode === Mode.ERROR) {
+ view = new NodeView(params, this);
+ } else {
+ view = new ObjectNodeView(params, this);
+ }
} else {
view = new ObjectView(params, this);
}
diff --git a/src/entry-view.js b/src/entry-view.js
new file mode 100644
index 0000000..2570038
--- /dev/null
+++ b/src/entry-view.js
@@ -0,0 +1,53 @@
+import AbstractView from './abstract-view';
+import {escapeHTML} from './utils';
+
+export default class EntryView extends AbstractView {
+ constructor({key, entryEl, mode, withoutKey, withoutValue, getViewEl, isGrey}) {
+ super();
+ this._key = key;
+ this._entryEl = entryEl; // TODO не забыть поменять
+ this._mode = mode;
+ this._withoutKey = withoutKey;
+ this._withoutValue = withoutValue;
+ this._getViewEl = getViewEl;
+ this._isGrey = isGrey;
+ }
+
+ get template() {
+ return `
${this._withoutKey ? `` :
+ `
${escapeHTML(this._key.toString())}`
+ }${this._withoutValue ? `` :
+ `
${this._entryEl ? `` : `(...)`}
`
+ }
`;
+ }
+
+ _bind() {
+ if (this._withoutValue) {
+ return;
+ }
+ this._valueContEl = this._el.querySelector(`.entry-container__value-container`);
+
+ if (this._entryEl) {
+ this._valueContEl.appendChild(this._entryEl);
+ return;
+ }
+
+ this._valueContEl.addEventListener(`click`, this._onClickInsertEl.bind(this), {
+ once: true
+ });
+ }
+
+ _onClickInsertEl(evt) {
+ evt.preventDefault();
+
+ this._valueContEl.textContent = ``;
+ this._valueContEl.classList.remove(`entry-container__value-container--underscore`);
+
+ try {
+ const viewEl = this._getViewEl();
+ this._valueContEl.appendChild(viewEl);
+ } catch (err) {
+ this._valueContEl.textContent = `[Exception: ${err.stack}]`;
+ }
+ }
+}
diff --git a/src/enums.js b/src/enums.js
index adfe64a..e0c8239 100644
--- a/src/enums.js
+++ b/src/enums.js
@@ -19,7 +19,8 @@ export const ViewType = {
FUNCTION: `function`,
OBJECT: `object`,
ARRAY: `array`,
- PRIMITIVE: `primitive`
+ PRIMITIVE: `primitive`,
+ NODE: `node`
};
/**
@@ -47,3 +48,5 @@ export const WhereChangeHeaderOnExpand = {
ANY_DEPTH: `any-depth`,
ANY_DEPTH_EXCEPT_ROOT: `any-depth-except-root`
};
+
+export const GET_STATE_DESCRIPTORS_KEY_NAME = `get-state-descriptors`;
diff --git a/src/node/node-view.js b/src/node/node-view.js
new file mode 100644
index 0000000..c81da79
--- /dev/null
+++ b/src/node/node-view.js
@@ -0,0 +1,230 @@
+import {ViewType, GET_STATE_DESCRIPTORS_KEY_NAME, Mode} from '../enums';
+import BaseView from '../base-view';
+import EntryView from '../entry-view';
+import {getElement, escapeHTML} from '../utils';
+
+const ELS_WITHOUT_ENDING_TAG = [`input`, `br`, `hr`, `img`, `base`, `link`, `meta`, `wbr`, `source`, `embed`, `param`, `track`, `area`, `col`];
+
+const MAX_PREVIEW_CONTENT_LENGTH = 43;
+
+const getStateDescriptorsKey = Symbol(GET_STATE_DESCRIPTORS_KEY_NAME);
+
+const getElementAttributesStr = (el) => (
+ Array.from(el.attributes)
+ .map(({name, value}) => (
+ `${name}=\
+"${value}"`
+ ))
+ .join(` `)
+);
+
+const checkElementIsEmpty = (el) => !(
+ el.textContent.trim() ||
+ el.childNodes.length ||
+ el.content
+);
+
+export default class NodeView extends BaseView {
+ constructor(params, cons) {
+ super(params, cons);
+ this.viewType = ViewType.NODE;
+ this._viewTypeParams = this._console.params[this.viewType];
+ if (!params.parentView) {
+ this.rootView = this;
+ }
+
+ this._stateDescriptorsQueue.push(this[getStateDescriptorsKey]());
+ this._closingTag = this._getClosingTag();
+ }
+
+ get template() {
+ return `\
+
+
+
${this._getMainHeadContent()}${this._getRestHeadContent()}
\
+
+
+
`;
+ }
+
+ _afterRender() {
+ this._tagContentEl = this._headContentEl.querySelector(`.tag-pair__content`);
+ this._tagRestContentEl = this._headContentEl.querySelector(`.tag-pair__rest`);
+
+ this._state.isHeadContentShowed = true;
+ this._state.isRestHeadContentShowed = this.isShowRestHeadContent;
+ this._state.isOpeningDisabled = this.isDisableOpening;
+
+ this._state.isOpened = this.isOpeningAllowed;
+ }
+
+ [getStateDescriptorsKey]() {
+ const self = this;
+ return {
+ set isRestHeadContentShowed(bool) {
+ self._isRestHeadContentShowed = !self._tagRestContentEl.classList.toggle(`hidden`, !bool);
+ },
+ get isRestHeadContentShowed() {
+ return self._isRestHeadContentShowed;
+ },
+ set isErrorEnabled(bool) {
+ self._isErrorEnabled = self.toggleError(bool);
+ },
+ get isErrorEnabled() {
+ return self._isErrorEnabled;
+ },
+ set isOpened(bool) {
+ if (bool === self._isOpened) {
+ return;
+ }
+
+ self._isOpened = bool;
+ self.toggleArrowBottom(bool);
+ self._state.isContentShowed = bool;
+ self._state.isRestHeadContentShowed = self.isShowRestHeadContent;
+ },
+ get isOpened() {
+ return self._isOpened;
+ }
+ };
+ }
+
+ get isShowRestHeadContent() {
+ return this._mode !== Mode.PREVIEW && !this._state.isOpened;
+ }
+
+ get isDisableOpening() {
+ if (this._mode === Mode.PREVIEW) {
+ return true;
+ }
+
+ return !(
+ (this._value.nodeType === Node.ELEMENT_NODE && this._closingTag) ||
+ this._value.nodeType === Node.DOCUMENT_FRAGMENT_NODE ||
+ this._value.nodeType === Node.DOCUMENT_NODE
+ );
+ }
+
+ /**
+ * Возвращает строку контента ноды. Если это Элемент — первый, открывающий, тег.
+ * @return {string}
+ */
+ _getMainHeadContent() {
+ let val;
+ if (this._mode === Mode.PREVIEW) {
+ val = this._getHeadPlainContent(this._value);
+ } else {
+ val = this._getHeadNodesContent(this._value);
+ }
+ return val;
+ }
+
+ _getHeadNodesContent(v) {
+ let val;
+ if (v.nodeType === Node.ELEMENT_NODE) {
+ const attrs = getElementAttributesStr(v);
+ val = `<${v.tagName.toLowerCase()}${attrs ? ` ${attrs}` : ``}>`;
+ } else if (v.nodeType === Node.TEXT_NODE) {
+ val = v.data;
+ } else if (v.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
+ val = `#document-fragment`;
+ } else if (v.nodeType === Node.DOCUMENT_NODE) {
+ val = `#document`;
+ } else if (v.nodeType === Node.COMMENT_NODE) {
+ val = ``;
+ } else if (v.nodeType === Node.ATTRIBUTE_NODE) {
+ val = `${v.name}`;
+ } else if (v.nodeType === Node.DOCUMENT_TYPE_NODE) {
+ val = `<!DOCTYPE html>`;
+ } else {
+ val = `not implemented`;
+ }
+ return val;
+ }
+
+ _getHeadPlainContent(v) {
+ // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName#Value
+ let val;
+ if (v.nodeType === Node.ELEMENT_NODE) {
+ val = `${v.tagName.toLowerCase()}`;
+ if (v.id) {
+ val += `#${v.id}`;
+ }
+ if (v.classList.length) {
+ val += `.${Array.prototype.join.call(v.classList, `.`)}`;
+ }
+ } else {
+ val = `${v.nodeName}`;
+ }
+ return val;
+ }
+
+ /**
+ * Возвращает остальную строку контента ноды.
+ * Если это элемент — текст после открывающего тега (если там он есть) и закрывающий тег
+ * Если не элемент — пустую строку
+ * @param {ContentType} type
+ * @return {string}
+ */
+ _getRestHeadContent() {
+ if (this._value.nodeType === Node.ELEMENT_NODE) {
+ let content = ``;
+ if (!checkElementIsEmpty(this._value) && !this._console.checkInstanceOf(this._value, `HTMLTemplateElement`)) {
+ if (this._value.children.length || this._value.innerHTML.length > MAX_PREVIEW_CONTENT_LENGTH) {
+ content = `…`;
+ } else {
+ content = escapeHTML(this._value.innerHTML);
+ }
+ }
+
+ let closingTag = this._closingTag;
+ return `${content}${closingTag}`;
+ } else if (this._value.nodeType === Node.ATTRIBUTE_NODE) {
+ const attrValue = this._value.value;
+ return attrValue ? `="${attrValue}"` : ``;
+ }
+ return ``;
+ }
+
+ _getClosingTag() {
+ if (this._value.nodeType === Node.ELEMENT_NODE) {
+ const tagNameLower = this._value.tagName.toLowerCase();
+ if (!ELS_WITHOUT_ENDING_TAG.includes(tagNameLower)) {
+ return `</${tagNameLower}>`;
+ }
+ }
+ return ``;
+ }
+
+ createContent(node) {
+ const fragment = document.createDocumentFragment();
+
+ const mode = Mode.PROP;
+
+ if (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE || node.nodeType === Node.DOCUMENT_NODE) {
+ if (this._console.checkInstanceOf(node, `HTMLTemplateElement`)) {
+ const val = node.content;
+ const nodeView = new NodeView({val, mode, depth: this.nextNestingLevel, parentView: this}, this._console);
+ const entryEl = new EntryView({entryEl: nodeView.el, withoutKey: true}).el;
+ fragment.appendChild(entryEl);
+ }
+ for (let childNode of [...node.childNodes]) {
+ if (childNode.nodeType === Node.TEXT_NODE && !childNode.textContent.trim()) {
+ continue;
+ }
+ const nodeView = new NodeView({val: childNode, mode, depth: this.nextNestingLevel, parentView: this}, this._console);
+ const entryEl = new EntryView({entryEl: nodeView.el, withoutKey: true}).el;
+ fragment.appendChild(entryEl);
+ }
+ }
+
+ const closingTag = this._closingTag;
+ if (closingTag) {
+ const entryEl = getElement(closingTag);
+ const el = new EntryView({entryEl, withoutKey: true}).el;
+ fragment.appendChild(el);
+ }
+
+ return {fragment};
+ }
+}
diff --git a/src/object/map-entry-view.js b/src/object/map-entry-view.js
index 87794b4..56645c3 100644
--- a/src/object/map-entry-view.js
+++ b/src/object/map-entry-view.js
@@ -1,5 +1,8 @@
import TypeView from '../type-view';
-import {Mode, ViewType} from '../enums';
+import EntryView from '../entry-view';
+import {Mode, ViewType, GET_STATE_DESCRIPTORS_KEY_NAME} from '../enums';
+
+const getStateDescriptorsKey = Symbol(GET_STATE_DESCRIPTORS_KEY_NAME);
export default class MapEntryView extends TypeView {
constructor(params, cons) {
@@ -11,6 +14,8 @@ export default class MapEntryView extends TypeView {
this._pairKey = this._value[0];
this._pairValue = this._value[1];
+
+ this._stateDescriptorsQueue.push(this[getStateDescriptorsKey]());
}
get template() {
return `\
@@ -33,7 +38,7 @@ export default class MapEntryView extends TypeView {
this._state.isOpened = this.isOpeningAllowed;
}
- _getStateDescriptors() {
+ [getStateDescriptorsKey]() {
const self = this;
return {
set isHeadContentShowed(bool) {
@@ -65,11 +70,11 @@ export default class MapEntryView extends TypeView {
const valueEl = this._console.createTypedView(this._pairValue, this._mode, this.nextNestingLevel, this, this._propKey).el;
TypeView.appendEntryElIntoFragment(
- this._createEntryEl({key: `key`, el: keyEl, withoutKey: false}),
+ new EntryView({key: `key`, entryEl: keyEl, withoutKey: false}).el,
fragment
);
TypeView.appendEntryElIntoFragment(
- this._createEntryEl({key: `value`, el: valueEl, withoutKey: false}),
+ new EntryView({key: `value`, entryEl: valueEl, withoutKey: false}).el,
fragment
);
diff --git a/src/object/map-set-view.js b/src/object/map-set-view.js
index 731d94d..6007da1 100644
--- a/src/object/map-set-view.js
+++ b/src/object/map-set-view.js
@@ -1,4 +1,5 @@
import TypeView from '../type-view';
+import EntryView from '../entry-view';
import ObjectView from './object-view';
import MapEntryView from '../object/map-entry-view';
import {Mode} from '../enums';
@@ -28,9 +29,10 @@ export default class MapSetView extends ObjectView {
}
const entry = entriesArr[i];
let entryEl;
+
if (this._console.checkInstanceOf(this._value, `Map`)) {
const el = new MapEntryView({val: entry, mode, depth: this.nextNestingLevel, parentView: this, propKey: this._propKey}, this._console).el;
- entryEl = this._createEntryEl({key: i, el, withoutKey: true});
+ entryEl = new EntryView({key: i, entryEl: el, withoutKey: true}).el;
}
if (this._console.checkInstanceOf(this.value, `Set`)) {
entryEl = this._createTypedEntryEl({obj: entriesArr, key: i, mode, withoutKey: true, notCheckDescriptors: true});
@@ -46,7 +48,7 @@ export default class MapSetView extends ObjectView {
const entriesArrView = this._console.createTypedView(entriesArr, Mode.PROP, this.nextNestingLevel, this, `[[Entries]]`);
entriesArrView.isAutoExpandNeeded = true;
TypeView.appendEntryElIntoFragment(
- this._createEntryEl({key: `[[Entries]]`, el: entriesArrView.el, withoutKey: false}),
+ new EntryView({key: `[[Entries]]`, entryEl: entriesArrView.el, withoutKey: false}).el,
fragment
);
}
diff --git a/src/object/object-node-view.js b/src/object/object-node-view.js
new file mode 100644
index 0000000..046a99e
--- /dev/null
+++ b/src/object/object-node-view.js
@@ -0,0 +1,22 @@
+import ObjectView from './object-view';
+import {Mode} from '../enums';
+import NodeView from '../node/node-view';
+
+export default class ObjectNodeView extends ObjectView {
+ constructor(params, cons) {
+ super(params, cons);
+ }
+
+ createContent(obj, inHead) {
+ if (inHead) {
+ const nodeView = new NodeView({val: this._value, mode: Mode.PREVIEW, depth: this.nextNestingLevel, parentView: this, propKey: this._propKey}, this._console);
+
+ const fragment = document.createDocumentFragment();
+ fragment.appendChild(nodeView.el);
+ return {fragment};
+ } else {
+ const objContent = ObjectView.prototype.createContent.apply(this, [obj, inHead]);
+ return objContent;
+ }
+ }
+}
diff --git a/src/object/object-view.js b/src/object/object-view.js
index 4cace08..a746030 100644
--- a/src/object/object-view.js
+++ b/src/object/object-view.js
@@ -1,9 +1,11 @@
/* eslint guard-for-in: "off"*/
/* eslint no-empty: "off"*/
import TypeView from '../type-view';
-import {Mode, ViewType} from '../enums';
+import {Mode, ViewType, GET_STATE_DESCRIPTORS_KEY_NAME} from '../enums';
import {checkObjectisPrototype} from '../utils';
+const getStateDescriptorsKey = Symbol(GET_STATE_DESCRIPTORS_KEY_NAME);
+
export default class ObjectView extends TypeView {
constructor(params, cons) {
super(params, cons);
@@ -12,6 +14,7 @@ export default class ObjectView extends TypeView {
if (!params.parentView) {
this.rootView = this;
}
+ this._stateDescriptorsQueue.push(this[getStateDescriptorsKey]());
}
get template() {
@@ -41,7 +44,7 @@ export default class ObjectView extends TypeView {
this._state.isOpened = this.isOpeningAllowed;
}
- _getStateDescriptors() {
+ [getStateDescriptorsKey]() {
const self = this;
return {
set isHeadContentShowed(bool) {
@@ -212,7 +215,6 @@ export default class ObjectView extends TypeView {
return this.el.classList.toggle(`error`, isEnable);
}
-
get headContentClassName() {
if (this._console.checkInstanceOf(this._value, `RegExp`) && this._mode !== Mode.DIR) {
return `c-regexp`;
@@ -226,22 +228,24 @@ export default class ObjectView extends TypeView {
this.protoConstructorName === `Object`) {
return `…`;
}
-
- if (!Object.prototype.hasOwnProperty.call(this._value, `constructor`)) {
- if (this._console.checkInstanceOf(this._value, `Node`)) {
- if (this._console.checkInstanceOf(this._value, `HTMLElement`)) {
- let str = this._value.tagName.toLowerCase();
- if (this._value.id) {
- str += `#${this._value.id}`;
- }
- if (this._value.classList.length) {
- str += `.` + Array.prototype.join.call(this._value.classList, `.`);
- }
- return str;
- } else {
- return this._value.nodeName;
- }
- } else if (this._console.checkInstanceOf(this._value, `Error`)) {
+ if (!checkObjectisPrototype(this._value)) {
+ // Что пишем, если у нас элемент, отнаследованный от Node
+ // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName#Value
+ // if (this._console.checkInstanceOf(this._value, `Node`)) {
+ // if (this._console.checkInstanceOf(this._value, `HTMLElement`)) {
+ // let str = this._value.tagName.toLowerCase();
+ // if (this._value.id) {
+ // str += `#${this._value.id}`;
+ // }
+ // if (this._value.classList.length) {
+ // str += `.` + Array.prototype.join.call(this._value.classList, `.`);
+ // }
+ // return str;
+ // } else {
+ // return this._value.nodeName;
+ // }
+ // } else
+ if (this._console.checkInstanceOf(this._value, `Error`)) {
let str = this._value.name;
if (this._value.message) {
str += `: ${this._value.message}`;
diff --git a/src/object/promise-view.js b/src/object/promise-view.js
index ce1a73c..b35072d 100644
--- a/src/object/promise-view.js
+++ b/src/object/promise-view.js
@@ -1,4 +1,5 @@
import TypeView from '../type-view';
+import EntryView from '../entry-view';
import ObjectView from './object-view';
import {Mode, PromiseStatus} from '../enums';
@@ -37,27 +38,27 @@ export default class PromiseView extends ObjectView {
const mode = inHead ? Mode.PREVIEW : Mode.PROP;
if (inHead) {
TypeView.prependEntryElIntoFragment(
- this._createEntryEl({
+ new EntryView({
key: `<${this._promiseStatus}>`,
- el: this._console.createTypedView(this._promiseValue, mode, this.nextNestingLevel, this).el,
+ entryEl: this._console.createTypedView(this._promiseValue, mode, this.nextNestingLevel, this).el,
withoutValue: this._promiseStatus === PromiseStatus.pending,
isGrey: true
- }),
+ }).el,
fragment
);
} else {
TypeView.appendEntryElIntoFragment(
- this._createEntryEl({
+ new EntryView({
key: `[[PromiseStatus]]`,
- el: this._console.createTypedView(this._promiseStatus, mode, this.nextNestingLevel, this).el
- }),
+ entryEl: this._console.createTypedView(this._promiseStatus, mode, this.nextNestingLevel, this).el
+ }).el,
fragment
);
TypeView.appendEntryElIntoFragment(
- this._createEntryEl({
+ new EntryView({
key: `[[PromiseValue]]`,
- el: this._console.createTypedView(this._promiseValue, mode, this.nextNestingLevel, this).el
- }),
+ entryEl: this._console.createTypedView(this._promiseValue, mode, this.nextNestingLevel, this).el
+ }).el,
fragment
);
}
diff --git a/src/object/string-number-view.js b/src/object/string-number-view.js
index ef1ca14..3c32f35 100644
--- a/src/object/string-number-view.js
+++ b/src/object/string-number-view.js
@@ -1,4 +1,5 @@
import TypeView from '../type-view';
+import EntryView from '../entry-view';
import ObjectView from './object-view';
import {Mode} from '../enums';
@@ -30,15 +31,15 @@ export default class StringNumberView extends ObjectView {
const insertFn = inHead ? TypeView.prependEntryElIntoFragment : TypeView.appendEntryElIntoFragment;
const mode = inHead ? Mode.PREVIEW : Mode.PROP;
if (this._console.checkInstanceOf(obj, `String`)) {
- const el = this._console.createTypedView(this._value.toString(), mode, this.nextNestingLevel, this).el;
+ const entryEl = this._console.createTypedView(this._value.toString(), mode, this.nextNestingLevel, this).el;
insertFn(
- this._createEntryEl({key: `[[PrimitiveValue]]`, el, withoutKey: inHead}),
+ new EntryView({key: `[[PrimitiveValue]]`, entryEl, withoutKey: inHead}).el,
fragment
);
} else if (this._console.checkInstanceOf(obj, `Number`)) {
- const el = this._console.createTypedView(this._value * 1, mode, this.nextNestingLevel, this).el;
+ const entryEl = this._console.createTypedView(this._value * 1, mode, this.nextNestingLevel, this).el;
insertFn(
- this._createEntryEl({key: `[[PrimitiveValue]]`, el, withoutKey: inHead}),
+ new EntryView({key: `[[PrimitiveValue]]`, entryEl, withoutKey: inHead}).el,
fragment
);
}
diff --git a/src/type-view.js b/src/type-view.js
index c166306..00607d8 100644
--- a/src/type-view.js
+++ b/src/type-view.js
@@ -1,8 +1,9 @@
/* eslint no-empty: "off"*/
/* eslint no-unused-vars: "off"*/
-import AbstractView from './abstract-view';
+import BaseView from './base-view';
+import EntryView from './entry-view';
import {getElement, escapeHTML} from './utils';
-import {Mode, Env} from './enums';
+import {Mode, Env, GET_STATE_DESCRIPTORS_KEY_NAME} from './enums';
const isNativeFunction = (fn) => {
return /{\s*\[native code\]\s*}/g.test(fn);
@@ -28,45 +29,13 @@ const getFirstProtoContainingObject = (typeView) => {
return typeView.value;
};
-export default class TypeView extends AbstractView {
+const getStateDescriptorsKey = Symbol(GET_STATE_DESCRIPTORS_KEY_NAME);
+
+export default class TypeView extends BaseView {
constructor(params, cons) {
- super();
- if (params.parentView) {
- this._parentView = params.parentView;
- this.rootView = params.parentView.rootView;
- }
- /** @abstract must be overriden */
- this._viewTypeParams = void 0;
- this._console = cons;
- this._value = params.val;
- this._mode = params.mode;
- this._type = params.type;
+ super(params, cons);
this._propKey = params.propKey;
- this._currentDepth = typeof params.depth === `number` ? params.depth : 1;
-
- this._cache = {};
- }
-
- /**
- * @abstract
- * @protected
- */
- _afterRender() {}
-
- _bind() {
- if (!this.viewType) {
- throw new Error(`this.viewType must be specified`);
- }
- if (!this.rootView) {
- throw new Error(`this.rootView must be specified`);
- }
-
- this._headEl = this.el.querySelector(`.head`);
- this._headContentEl = this.el.querySelector(`.head__content`);
- this._infoEl = this.el.querySelector(`.info`);
- this._contentEl = this.el.querySelector(`.item__content`);
-
- this._afterRender();
+ // this._stateDescriptorsQueue.push(this[getStateDescriptorsKey]());
}
get protoConstructorName() {
@@ -104,109 +73,6 @@ export default class TypeView extends AbstractView {
return this._parentView;
}
- /**
- * Current state
- * @type {{}}
- * @param {{}} params — object with values which will be assigned throught setters
- */
- get _state() {
- if (!this._viewState) {
- this._viewState = {};
- Object.defineProperties(
- this._viewState,
- Object.getOwnPropertyDescriptors(this._getStateCommonDescriptors())
- );
- Object.defineProperties(
- this._viewState,
- Object.getOwnPropertyDescriptors(this._getStateDescriptors())
- );
- Object.seal(this._viewState);
- }
- return this._viewState;
- }
-
- /**
- * @abstract
- * @return {{}} if not overriden return object without descriptors
- */
- _getStateDescriptors() {
- return {};
- }
-
- /**
- * @return {{}} — object that contains descriptors only
- */
- _getStateCommonDescriptors() {
- const self = this;
- return {
- set isShowInfo(bool) {
- if (!self._infoEl) {
- return;
- }
- if (bool && !self._infoEl.textContent) {
- self._infoEl.textContent = self.info;
- }
- self._isShowInfo = self.toggleInfoShowed(bool);
- },
- get isShowInfo() {
- return self._isShowInfo;
- },
- set isHeadContentShowed(bool) {
- self.toggleHeadContentShowed(bool);
- },
- set isOpeningDisabled(bool) {
- if (!bool && self._mode === Mode.PREVIEW) {
- throw new Error(`Enabling opening object in preview mode is forbidden`);
- }
- if (self._isOpeningDisabled === bool) {
- return;
- }
- self.toggleArrowPointer(!bool);
- self._addOrRemoveHeadClickHandler(!bool);
- self._isOpeningDisabled = bool;
- },
- get isOpeningDisabled() {
- return self._isOpeningDisabled;
- },
- set isBraced(bool) {
- self.toggleHeadContentBraced(bool);
- },
- set isOpened(bool) {
- if (bool === self._isOpened) {
- return;
- }
-
- self._isOpened = bool;
- self.toggleArrowBottom(bool);
- self._state.isContentShowed = bool;
- },
- get isOpened() {
- return self._isOpened;
- },
- set isContentShowed(bool) {
- if (bool === self._isContentShowed) {
- return;
- }
- self._isContentShowed = self.toggleContentShowed(bool);
- if (self._isContentShowed && self._contentEl.childElementCount === 0) {
- self._contentEl.appendChild(self.createContent(self._value, false).fragment);
- }
- },
- get isContentShowed() {
- return self._isContentShowed;
- },
- set isOversized(bool) {
- self.toggleHeadContentOversized(bool);
- },
- set isItalicEnabled(bool) {
- self._isItalicEnabled = self.toggleItalic(bool);
- },
- get isItalicEnabled() {
- return self._isItalicEnabled;
- }
- };
- }
-
toggleHeadContentBraced(isEnable) {
return this._headContentEl.classList.toggle(`entry-container--braced`, isEnable);
}
@@ -475,7 +341,6 @@ export default class TypeView extends AbstractView {
get isChangeHeaderOnExpandNeeded() {
const typeParams = this._viewTypeParams;
-
}
get info() {
@@ -488,22 +353,6 @@ export default class TypeView extends AbstractView {
}
}
- _headClickHandler(evt) {
- evt.preventDefault();
- this._state.isOpened = !this._state.isOpened;
- }
-
- _addOrRemoveHeadClickHandler(bool) {
- if (bool) {
- if (!this._bindedHeadClickHandler) {
- this._bindedHeadClickHandler = this._headClickHandler.bind(this);
- }
- this._headEl.addEventListener(`click`, this._bindedHeadClickHandler);
- } else if (this._bindedHeadClickHandler) {
- this._headEl.removeEventListener(`click`, this._bindedHeadClickHandler);
- }
- }
-
_createGettersEntriesFragment() {
const fragment = document.createDocumentFragment();
const mode = Mode.PROP;
@@ -517,14 +366,14 @@ export default class TypeView extends AbstractView {
if (descriptor.get !== void 0) {
const getterEl = this._console.createTypedView(descriptor.get, mode, this.nextNestingLevel, this, key).el;
TypeView.appendEntryElIntoFragment(
- this._createEntryEl({key: `get ${key}`, el: getterEl, mode, isGrey: true}),
+ new EntryView({key: `get ${key}`, entryEl: getterEl, mode, isGrey: true}).el,
fragment
);
}
if (descriptor.set !== void 0) {
const setterEl = this._console.createTypedView(descriptor.set, mode, this.nextNestingLevel, this, key).el;
TypeView.appendEntryElIntoFragment(
- this._createEntryEl({key: `set ${key}`, el: setterEl, mode, isGrey: true}),
+ new EntryView({key: `set ${key}`, entryEl: setterEl, mode, isGrey: true}).el,
fragment
);
}
@@ -532,50 +381,6 @@ export default class TypeView extends AbstractView {
return fragment;
}
- /**
- * Create entry element
- * @protected
- * @param {{}} params
- * @param {string} params.key — key, index of array or field name
- * @param {HTMLSpanElement|undefined} params.el — HTML span element to append into container
- * @param {Mode} params.mode — log mode
- * @param {boolean} [params.withoutKey] — create entry without key element
- * @param {function} [params.getViewEl] — function to get element if so wasn't present while calling this method
- * @return {HTMLSpanElement}
- */
- _createEntryEl({key, el, mode, withoutKey, withoutValue, getViewEl, isGrey}) {
- const entryEl = getElement(`\
-\
-${withoutKey ? `` : `
${escapeHTML(key.toString())}`}${withoutValue ? `` : `
`}\
-
`);
- if (withoutValue) {
- return entryEl;
- }
- const valueContEl = entryEl.querySelector(`.entry-container__value-container`);
-
- if (el) {
- valueContEl.appendChild(el);
- } else {
- valueContEl.textContent = `(...)`;
- valueContEl.classList.add(`entry-container__value-container--underscore`);
- const insertEl = () => {
- valueContEl.textContent = ``;
- valueContEl.classList.remove(`entry-container__value-container--underscore`);
- let viewEl;
- try {
- viewEl = getViewEl();
- valueContEl.appendChild(viewEl);
- } catch (err) {
- valueContEl.textContent = `[Exception: ${err.stack}]`;
- }
- valueContEl.removeEventListener(`click`, insertEl);
- };
- valueContEl.addEventListener(`click`, insertEl);
- }
-
- return entryEl;
- }
-
/**
* Create typed entry element
* @protected
@@ -607,10 +412,10 @@ ${withoutKey ? `` : `