/**
* @typedef {Object} HeaderData
* @description Tool's input and output data format
* @property {String} text — Header's content
* @property {number} level - Header's level from 1 to 3
*/
/**
* @typedef {Object} HeaderConfig
* @description Tool's config from Editor
* @property {string} placeholder — Block's placeholder
*/
/**
* Header block for the CodeX Editor.
*
* @author CodeX Team (team@ifmo.su)
* @copyright CodeX Team 2018
* @license The MIT License (MIT)
* @version 2.0.0
*/
class Header {
/**
* Should this tools be displayed at the Editor's Toolbox
* @returns {boolean}
* @public
*/
static get displayInToolbox() {
return true;
}
/**
* Class for the Toolbox icon
* @returns {string}
* @public
*/
static get iconClassName() {
return 'cdx-header-icon';
}
/**
* Render plugin`s main Element and fill it with saved data
* @param {HeaderData} blockData - previously saved data
* @param {HeaderConfig} blockConfig - Tool's config from Editor
*/
constructor(blockData = {}, blockConfig = {}) {
/**
* Styles
* @type {Object}
*/
this._CSS = {
wrapper: 'ce-header',
settingsButton: 'ce-settings__button',
settingsSelected: 'ce-settings__button--selected',
};
/**
* Tool's settings passed from Editor
* @type {HeaderConfig}
* @private
*/
this._settings = blockConfig;
/**
* Block's data
* @type {HeaderData}
* @private
*/
this._data = blockData || {};
/**
* List of settings buttons
* @type {HTMLElement[]}
*/
this.settingsButtons = [];
/**
* Main Block wrapper
* @type {HTMLElement}
* @private
*/
this._element = this.getTag();
}
/**
* Return Tool's view
* @returns {HTMLHeadingElement}
* @public
*/
render() {
return this._element;
}
/**
* Create Block's settings block
*
* @return {HTMLElement}
*/
renderSettings() {
let holder = document.createElement('DIV');
/** Add type selectors */
this.levels.forEach( level => {
let selectTypeButton = document.createElement('SPAN');
selectTypeButton.classList.add(this._CSS.settingsButton);
/**
* Highlight current level button
*/
if (this.currentLevel.number === level.number) {
selectTypeButton.classList.add(this._CSS.settingsSelected);
}
/**
* Add SVG icon
*/
selectTypeButton.innerHTML = level.svg;
/**
* Save level to its button
*/
selectTypeButton.dataset.level = level.number;
/**
* Set up click handler
*/
selectTypeButton.addEventListener('click', () => {
this.setLevel(level.number);
});
/**
* Append settings button to holder
*/
holder.appendChild(selectTypeButton);
/**
* Save settings buttons
*/
this.settingsButtons.push(selectTypeButton);
});
return holder;
}
/**
* Callback for Block's settings buttons
* @param level
*/
setLevel(level) {
this.data = {
level: level
};
/**
* Highlight button by selected level
*/
this.settingsButtons.forEach(button => {
button.classList.toggle(this._CSS.settingsSelected, parseInt(button.dataset.level) === level);
});
}
/**
* Method that specified how to merge two Text blocks.
* Called by CodeX Editor by backspace at the beginning of the Block
* @param {HeaderData} data
* @public
*/
merge(data) {
let newData = {
text: this.data.text + data.text,
level: this.data.level
};
this.data = newData;
}
/**
* Validate Text block data:
* - check for emptiness
*
* @param {HeaderData} blockData — data received after saving
* @returns {boolean} false if saved data is not correct, otherwise true
* @public
*/
validate(blockData) {
return blockData.text.trim() !== '';
}
/**
* Extract Tool's data from the view
* @param {HTMLHeadingElement} toolsContent - Text tools rendered view
* @returns {HeaderData} - saved data
* @public
*/
save(toolsContent) {
/**
* @todo sanitize data
*/
return {
text: toolsContent.innerHTML,
level: this.currentLevel.number
};
}
/**
* Get current Tools`s data
* @returns {HeaderData} Current data
* @private
*/
get data() {
this._data.text = this._element.innerHTML;
this._data.level = this.currentLevel.number;
return this._data;
}
/**
* Store data in plugin:
* - at the this._data property
* - at the HTML
*
* @param {HeaderData} data — data to set
* @private
*/
set data(data) {
this._data = data || {};
/**
* If level is set and block in DOM
* then replace it to a new block
*/
if (data.level !== undefined && this._element.parentNode) {
/**
* Create a new tag
* @type {HTMLHeadingElement}
*/
let newHeader = this.getTag();
/**
* Save Block's content
*/
newHeader.innerHTML = this._element.innerHTML;
/**
* Replace blocks
*/
this._element.parentNode.replaceChild(newHeader, this._element);
/**
* Save new block to private variable
* @type {HTMLHeadingElement}
* @private
*/
this._element = newHeader;
}
/**
* If data.text was passed then update block's content
*/
if (data.text !== undefined) {
this._element.innerHTML = this._data.text || '';
}
}
/**
* Get tag for target level
* By default returns second-leveled header
* @return {HTMLElement}
*/
getTag() {
/**
* Create element for current Block's level
*/
let tag = document.createElement(this.currentLevel.tag);
/**
* Add text to block
*/
tag.innerHTML = this._data.text || '';
/**
* Add styles class
*/
tag.classList.add(this._CSS.wrapper);
/**
* Make tag editable
*/
tag.contentEditable = 'true';
/**
* Add Placeholder
*/
tag.dataset.placeholder = this._settings.placeholder || '';
return tag;
}
/**
* Get current level
* @return {level}
*/
get currentLevel() {
let level = this.levels.find( level => level.number === this._data.level);
if (!level) {
level = this.levels[0];
}
return level;
}
/**
* @typedef {object} level
* @property {number} number - level number
* @property {string} tag - tag correspondes with level number
* @property {string} svg - icon
*/
/**
* Available header levels
* @return {level[]}
*/
get levels() {
return [
{
number: 2,
tag: 'H2',
svg: ''
},
{
number: 3,
tag: 'H3',
svg: ''
},
{
number: 4,
tag: 'H4',
svg: ''
}
];
}
}