Header plugin (#281)

* header initial

* fix styles

* eslint fix

* add appendCallback

* add comments

* update styles

* add svgs

* highlight settings buttons

* do not show text plugin in the toolbar

* remove svg

* Fixing caret behaviour. (#282)

Plugins can change their state so that affect on Block's pluginsContent property which is in memory.

* remove useless code

* fix merge
This commit is contained in:
Taly 2018-07-16 18:51:41 +03:00 committed by GitHub
commit 3d03461dc0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 3481 additions and 7259 deletions

View file

@ -1,41 +1,24 @@
/**
* Plugin styles
*/
* Plugin styles
*/
.ce-header {
padding: .7em 0;
margin: 0;
line-height: 1.4em;
padding: .7em 0;
margin: 0;
line-height: 1.4em;
outline: none;
}
.ce-header p,
.ce-header div{
.ce-header p,
.ce-header div{
padding: 0 !important;
margin: 0 !important;
}
/** H e a d e r - settings */
.ce_plugin_header--select_button{
display: block;
color: #306ac7;
cursor: pointer;
line-height: 1.3em;
}
.ce_plugin_header--select_button:not(:last-of-type){
margin-bottom: 1.5em;
}
.ce_plugin_header--select_button:hover{
color: #a1b4ec;
}
}
/**
* Empty header placeholder
*/
.ce-header:empty::before{
content : attr(data-placeholder);
color: #818BA1;
opacity: .7;
transition: opacity 200ms ease;
}
.ce-header:focus::before{
opacity: .1;
* Styles for Plugin icon in Toolbar
*/
.cdx-header-icon {
background-image: url('icon.svg');
background-position: center center;
background-repeat: no-repeat;
}

View file

@ -1,150 +1,324 @@
/**
* Example of making plugin
* H e a d e r
*/
* @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
*/
var header = (function (header_plugin) {
/**
* 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 {
/**
* @private
*/
var methods_ = {
/**
* Binds click event to passed button
*/
addSelectTypeClickListener : function (el, type) {
el.addEventListener('click', function () {
methods_.selectTypeClicked(type);
}, false);
},
/**
* Replaces old header with new type
* @params {string} type - new header tagName: H1H6
*/
selectTypeClicked : function (type) {
var old_header, new_header;
/** Now current header stored as a currentNode */
old_header = codex.editor.content.currentNode.querySelector('[contentEditable]');
/** Making new header */
new_header = codex.editor.draw.node(type, [ 'ce-header' ], { innerHTML : old_header.innerHTML });
new_header.contentEditable = true;
new_header.setAttribute('data-placeholder', 'Заголовок');
new_header.dataset.headerData = type;
codex.editor.content.switchBlock(old_header, new_header, 'header');
/** Close settings after replacing */
codex.editor.toolbar.settings.close();
}
};
/**
* @private
*
* Make initial header block
* @param {object} JSON with block data
* @return {Element} element to append
*/
var make_ = function (data) {
var availableTypes = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
tag,
headerType = 'h2';
if ( data && data['heading-styles'] && availableTypes.includes(data['heading-styles']) ) {
headerType = data['heading-styles'];
}
tag = document.createElement(headerType);
/**
* Save header type in data-attr.
* We need it in save method to extract type from HTML to JSON
*/
tag.dataset.headerData = headerType;
if (data && data.text) {
tag.textContent = data.text;
}
if (!tag.dataset.headerData) {
tag.dataset.headerData = 'h2';
}
tag.classList.add('ce-header');
tag.setAttribute('data-placeholder', 'Заголовок');
tag.contentEditable = true;
return tag;
};
header_plugin.prepareDataForSave = function (data) {
};
* Should this tools be displayed at the Editor's Toolbox
* @returns {boolean}
* @public
*/
static get displayInToolbox() {
return true;
}
/**
* Method to render HTML block from JSON
*/
header_plugin.render = function (data) {
return make_(data);
};
* Class for the Toolbox icon
* @returns {string}
* @public
*/
static get iconClassName() {
return 'cdx-header-icon';
}
/**
* Method to extract JSON data from HTML block
* Render plugin`s main Element and fill it with saved data
* @param {HeaderData} savedData previously saved data
*/
constructor(savedData = {}) {
/**
* Styles
* @type {Object}
*/
header_plugin.save = function (blockContent) {
var data = {
'heading-styles': blockContent.dataset.headerData,
'format': 'html',
'text': blockContent.textContent || ''
this._CSS = {
wrapper: 'ce-header',
settingsButton: 'ce-settings__button',
settingsSelected: 'ce-settings__button--selected',
};
return data;
};
/**
* Block's data
* @type {Object}
*/
this._data = savedData || {};
/**
* List of settings buttons
* @type {HTMLElement[]}
*/
this.settingsButtons = [];
/**
* Main Block wrapper
* @type {HTMLElement}
*/
this._element = this.getTag();
}
/**
* Settings panel content
* - - - - - - - - - - - - -
* | настройки H1 H2 H3 |
* - - - - - - - - - - - - -
* @return {Element} element contains all settings
*/
header_plugin.makeSettings = function () {
var holder = codex.editor.draw.node('DIV', [ 'cdx-plugin-settings--horisontal' ], {} ),
types = {
h2: 'H2',
h3: 'H3',
h4: 'H4'
},
selectTypeButton;
* Return Tool's view
* @returns {HTMLHeadingElement}
* @public
*/
render() {
return this._element;
}
/** Now add type selectors */
for (var type in types) {
selectTypeButton = codex.editor.draw.node('SPAN', [ 'cdx-plugin-settings__item' ], { textContent : types[type] });
methods_.addSelectTypeClickListener(selectTypeButton, type);
/**
* Create Block's settings block
*
* @return {HTMLElement}
*/
makeSettings() {
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;
};
}
header_plugin.validate = function (data) {
if (data.text.trim() === '' || data['heading-styles'].trim() === '') {
return false;
/**
* 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 {TextData} 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 {TextData} savedData data received after saving
* @returns {boolean} false if saved data is not correct, otherwise true
* @public
*/
validate(savedData) {
return savedData.text.trim() !== '';
}
/**
* Extract Tool's data from the view
* @param {HTMLHeadingElement} toolsContent - Text tools rendered view
* @returns {TextData} - saved data
* @public
*/
save(toolsContent) {
/**
* @todo sanitize data
*/
return {
text: toolsContent.innerHTML,
level: this.currentLevel.number
};
}
/**
* Get current Tools`s data
* @returns {TextData} 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;
}
return true;
};
/**
* If data.text was passed then update block's content
*/
if (data.text !== undefined) {
this._element.innerHTML = this._data.text || '';
}
}
header_plugin.destroy = function () {
header = null;
};
/**
* 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);
return header_plugin;
})({});
/**
* Add text to block
*/
tag.innerHTML = this._data.text || '';
/**
* Add styles class
*/
tag.classList.add(this._CSS.wrapper);
/**
* Make tag editable
*/
tag.contentEditable = 'true';
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: '<svg width="18" height="14" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M2.152 1.494V4.98h4.646V1.494c0-.498.097-.871.293-1.12A.934.934 0 0 1 7.863 0c.324 0 .586.123.786.37.2.246.301.62.301 1.124v9.588c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378c-.194-.251-.29-.626-.29-1.124V6.989H2.152v4.093c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378C.097 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.286.374A.929.929 0 0 1 1.066 0c.323 0 .585.123.786.37.2.246.3.62.3 1.124zm10.99 9.288h3.527c.351 0 .62.072.804.216.185.144.277.34.277.588 0 .22-.073.408-.22.56-.146.154-.368.23-.665.23h-4.972c-.338 0-.601-.093-.79-.28a.896.896 0 0 1-.284-.659c0-.162.06-.377.182-.645s.255-.478.399-.631a38.617 38.617 0 0 1 1.621-1.598c.482-.444.827-.735 1.034-.875.369-.261.676-.523.922-.787.245-.263.432-.534.56-.81.129-.278.193-.549.193-.815 0-.288-.069-.546-.206-.773a1.428 1.428 0 0 0-.56-.53 1.618 1.618 0 0 0-.774-.19c-.59 0-1.054.26-1.392.777-.045.068-.12.252-.226.554-.106.302-.225.534-.358.696-.133.162-.328.243-.585.243a.76.76 0 0 1-.56-.223c-.149-.148-.223-.351-.223-.608 0-.31.07-.635.21-.972.139-.338.347-.645.624-.92a3.093 3.093 0 0 1 1.054-.665c.426-.169.924-.253 1.496-.253.69 0 1.277.108 1.764.324.315.144.592.343.83.595.24.252.425.544.558.875.133.33.2.674.2 1.03 0 .558-.14 1.066-.416 1.523-.277.457-.56.815-.848 1.074-.288.26-.771.666-1.45 1.22-.677.554-1.142.984-1.394 1.29a3.836 3.836 0 0 0-.331.44z"/></svg>'
},
{
number: 3,
tag: 'H3',
svg: '<svg width="18" height="14" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M2.152 1.494V4.98h4.646V1.494c0-.498.097-.871.293-1.12A.934.934 0 0 1 7.863 0c.324 0 .586.123.786.37.2.246.301.62.301 1.124v9.588c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378c-.194-.251-.29-.626-.29-1.124V6.989H2.152v4.093c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378C.097 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.286.374A.929.929 0 0 1 1.066 0c.323 0 .585.123.786.37.2.246.3.62.3 1.124zm11.61 4.919c.418 0 .778-.123 1.08-.368.301-.245.452-.597.452-1.055 0-.35-.12-.65-.36-.902-.241-.252-.566-.378-.974-.378-.277 0-.505.038-.684.116a1.1 1.1 0 0 0-.426.306 2.31 2.31 0 0 0-.296.49c-.093.2-.178.388-.255.565a.479.479 0 0 1-.245.225.965.965 0 0 1-.409.081.706.706 0 0 1-.5-.22c-.152-.148-.228-.345-.228-.59 0-.236.071-.484.214-.745a2.72 2.72 0 0 1 .627-.746 3.149 3.149 0 0 1 1.024-.568 4.122 4.122 0 0 1 1.368-.214c.44 0 .842.06 1.205.18.364.12.679.294.947.52.267.228.47.49.606.79.136.3.204.622.204.967 0 .454-.099.843-.296 1.168-.198.324-.48.64-.848.95.354.19.653.408.895.653.243.245.426.516.548.813.123.298.184.619.184.964 0 .413-.083.812-.248 1.198-.166.386-.41.73-.732 1.031a3.49 3.49 0 0 1-1.147.708c-.443.17-.932.256-1.467.256a3.512 3.512 0 0 1-1.464-.293 3.332 3.332 0 0 1-1.699-1.64c-.142-.314-.214-.573-.214-.777 0-.263.085-.475.255-.636a.89.89 0 0 1 .637-.242c.127 0 .25.037.367.112a.53.53 0 0 1 .232.27c.236.63.489 1.099.759 1.405.27.306.65.46 1.14.46a1.714 1.714 0 0 0 1.46-.824c.17-.273.256-.588.256-.947 0-.53-.145-.947-.436-1.249-.29-.302-.694-.453-1.212-.453-.09 0-.231.01-.422.028-.19.018-.313.027-.367.027-.25 0-.443-.062-.579-.187-.136-.125-.204-.299-.204-.521 0-.218.081-.394.245-.528.163-.134.406-.2.728-.2h.28z"/></svg>'
},
{
number: 4,
tag: 'H4',
svg: '<svg width="20" height="14" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M2.152 1.494V4.98h4.646V1.494c0-.498.097-.871.293-1.12A.934.934 0 0 1 7.863 0c.324 0 .586.123.786.37.2.246.301.62.301 1.124v9.588c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378c-.194-.251-.29-.626-.29-1.124V6.989H2.152v4.093c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378C.097 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.286.374A.929.929 0 0 1 1.066 0c.323 0 .585.123.786.37.2.246.3.62.3 1.124zm13.003 10.09v-1.252h-3.38c-.427 0-.746-.097-.96-.29-.213-.193-.32-.456-.32-.788 0-.085.016-.171.048-.259.031-.088.078-.18.141-.276.063-.097.128-.19.195-.28.068-.09.15-.2.25-.33l3.568-4.774a5.44 5.44 0 0 1 .576-.683.763.763 0 0 1 .542-.212c.682 0 1.023.39 1.023 1.171v5.212h.29c.346 0 .623.047.832.142.208.094.313.3.313.62 0 .26-.086.45-.256.568-.17.12-.427.179-.768.179h-.41v1.252c0 .346-.077.603-.23.771-.152.168-.356.253-.612.253a.78.78 0 0 1-.61-.26c-.154-.173-.232-.427-.232-.764zm-2.895-2.76h2.895V4.91L12.26 8.823z"/></svg>'
}
];
}
}

View file

@ -0,0 +1 @@
<svg width="11" height="14" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M7.6 8.15H2.25v4.525a1.125 1.125 0 0 1-2.25 0V1.125a1.125 1.125 0 1 1 2.25 0V5.9H7.6V1.125a1.125 1.125 0 0 1 2.25 0v11.55a1.125 1.125 0 0 1-2.25 0V8.15z"/></svg>

After

Width:  |  Height:  |  Size: 277 B

View file

@ -8,7 +8,7 @@
* @version 2.0.1
*/
/**
/**
* @typedef {Object} TextData
* @description Tool's input and output data format
* @property {String} text Paragraph's content. Can include HTML tags: <a><b><i>
@ -20,7 +20,7 @@ class Text {
* @public
*/
static get displayInToolbox() {
return true;
return false;
}
/**
@ -107,11 +107,13 @@ class Text {
* @public
*/
save(toolsContent) {
let toolData = {
/**
* @todo sanitize data
*/
return {
text: toolsContent.innerHTML
};
return toolData;
}
/**
@ -122,10 +124,6 @@ class Text {
get data() {
let text = this._element.innerHTML;
/**
* @todo sanitize data
*/
this._data.text = text;
return this._data;