割烹エディター開発メモ9(カスタムパラグラフ)
次はデフォルトの段落を改良していきます。
デフォルトの段落も色々と機能が足りてないのでカスタマイズします。
class Paragraph2 {
static get enableLineBreaks(){
return true;
}
static get DEFAULT_PLACEHOLDER() {
return '';
}
constructor({data, config, api}) {
this.api = api;
this._CSS = {
block: this.api.styles.block,
wrapper: 'ce-paragraph'
};
this.onKeyUp = this.onKeyUp.bind(this);
this.onKeyDOWN = this.onKeyDOWN.bind(this);
this._placeholder = config.placeholder ? config.placeholder : Paragraph.DEFAULT_PLACEHOLDER;
this._data = {};
this._element = this.drawView();
this._setform=false;
this.data = {
text: data.text || '',
position:data.position !== undefined ? data.position : 'left',
size: data.size !== undefined ? data.size : '1',
withBorder: data.withBorder !== undefined ? data.withBorder : false,
color: data.color !== undefined ? data.color : '#000',
backcolor: data.backcolor !== undefined ? data.backcolor : '#fff'
};
this._changePosition(this.data.position);
if(this.data.withBorder){
this._element.classList.toggle('withBorder');
}
this.settings = [
{
name:'left',
icon:'左寄'
},
{
name:'center',
icon:'中央'
},
{
name:'right',
icon:'右寄'
},
{
name: 'withBorder',
icon:'枠線'
},
{
name: 'detail',
icon:'詳細'
}
]
}
onKeyUp(e) {
if (e.code !== 'Backspace' && e.code !== 'Delete') {
return;
}
const textblock = this._element.querySelector('[contenteditable]');
const {textContent} = textblock;
if (textContent === '') {
textblock.innerHTML = '';
}
}
onKeyDOWN(e){
if( e.key === 'Enter' && !e.shiftKey ){
/*
const textblock = this._element.querySelector('[contenteditable]');
let sel = document.getSelection();
const pos = sel.anchorOffset;
const str = textblock.innerHTML ;
textblock.innerHTML=str.slice(0, pos) + '<br>' + str.slice(pos);
console.log(textblock.innerHTML);
textblock.dispatchEvent( new KeyboardEvent( "keydown", { keyCode: 13 }));
*/
e.stopPropagation();
e.preventDefault();
let selection = window.getSelection(),
range = selection.getRangeAt(0),
newline = document.createTextNode('\r\n');
let del = document.createElement('br');
del.appendChild(range.extractContents());
//range.insertNode(del);
range.deleteContents();
//range.insertNode(newline);
range.insertNode(del);
//range.setStartAfter(newline);
//range.setEndAfter(newline);
range.setStartAfter(del);
range.setEndAfter(del);
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
}
}
drawView() {
let containr = document.createElement('DIV');
let textblock = document.createElement('DIV');
let setform = document.createElement('form');
containr.classList.add(this._CSS.wrapper, this._CSS.block);
textblock.contentEditable = true;
textblock.classList.add('p-textblock');
document.execCommand('defaultParagraphSeparator', false, 'div');
textblock.addEventListener('keyup', this.onKeyUp);
textblock.addEventListener('keydown', this.onKeyDOWN);
containr.appendChild(textblock);
containr.appendChild(setform);
return containr;
}
render() {
return this._element;
}
renderSettings(){
const wrapper = document.createElement('div');
this.settings.forEach(tune =>{
let button = document.createElement('div');
button.classList.add(this.api.styles.settingsButton);
button.innerHTML = tune.icon;
wrapper.appendChild(button);
if(tune.name === 'withBorder'){
button.classList.toggle(this.api.styles.settingsButtonActive, this.data[tune.name]);
}else if(tune.name === 'left' || tune.name === 'center' || tune.name === 'right'){
button.classList.toggle(this.api.styles.settingsButtonActive, tune.name === this.data.position);
}else{
if(this._setform){
button.classList.toggle(this.api.styles.settingsButtonActive);
}
}
button.addEventListener('click', ()=>{
if(tune.name === 'detail'){
button.classList.toggle(this.api.styles.settingsButtonActive);
if(button.classList.contains(this.api.styles.settingsButtonActive)){
this._showColorPicker();
this._setform=true;
}else{
this._hideColorPicker();
this._setform=false;
}
}else if(tune.name === 'withBorder'){
button.classList.toggle(this.api.styles.settingsButtonActive);
this._changewithborder(tune);
}else{
wrapper.querySelectorAll(`.${this.api.styles.settingsButton}`).forEach(el => {
if((el.innerHTML === '左寄' || el.innerHTML === '中央' || el.innerHTML === '右寄') ){
el.classList.remove(this.api.styles.settingsButtonActive);
}
});
button.classList.add(this.api.styles.settingsButtonActive);
this._changePosition(tune.name);
}
});
});
return wrapper;
}
merge(data) {
let newData = {
text : this.data.text + data.text
};
this.data = newData;
}
_changePosition(tune){
this.data.position=tune;
this._element.classList.remove('center');
this._element.classList.remove('right');
this._element.classList.remove('left');
this._element.classList.add(tune);
}
_changewithborder(tune){
this._element.classList.toggle('withBorder');
this.data.withBorder = !this.data.withBorder;
}
validate(savedData) {
if (savedData.text.trim() === '') {
return false;
}
return true;
}
save(toolsContent) {
return {
text: toolsContent.innerHTML,
position:this.data.position,
size:this.data.size,
color:this.data.color,
backcolor:this.data.backcolor
};
}
onPaste(event) {
const data = {
text: event.detail.data.innerHTML
};
this.data = data;
}
static get conversionConfig() {
return {
export: 'text', // to convert Paragraph to other block, use 'text' property of saved data
import: 'text' // to covert other block's exported string to Paragraph, fill 'text' property of tool data
};
}
static get sanitize() {
return {
text: {
br: true,
}
};
}
_showColorPicker(){
const setbox = this._element.querySelector('form');
const textcolor = document.createElement('input');
const backcolor = document.createElement('input');
const sizeblock = document.createElement('input');
textcolor.type = 'color';
textcolor.value = this.data.color;
backcolor.type = 'color';
backcolor.value = this.data.backcolor;
sizeblock.type = 'number';
sizeblock.step = '0.1';
sizeblock.min= "0.1" ;
sizeblock.max= "10";
sizeblock.value = this.data.size;
setbox.insertAdjacentHTML('beforeend', '文字色:');
setbox.appendChild(textcolor);
setbox.insertAdjacentHTML('beforeend', '<br>背景色:');
setbox.appendChild(backcolor);
setbox.insertAdjacentHTML('beforeend', '<br>サイズ:');
setbox.appendChild(sizeblock);
textcolor.onchange = () => {
const textblock = this._element.querySelector('[contenteditable]');
this.data.color = textcolor.value;
textblock.style.color=this.data.color;
};
backcolor.onchange = () => {
const textblock = this._element.querySelector('[contenteditable]');
this.data.backcolor = backcolor.value;
textblock.style.background=this.data.backcolor;
};
sizeblock.addEventListener('change', (event) => {
const textblock = this._element.querySelector('[contenteditable]');
this.data.size = sizeblock.value;
textblock.style.fontSize = this.data.size+'em';
});
sizeblock.addEventListener('keydown', (event) => {
if( event.key === 'Enter'){
const textblock = this._element.querySelector('[contenteditable]');
this.data.size = sizeblock.value;
textblock.style.fontSize = this.data.size+'em';
event.preventDefault();
}
});
}
_hideColorPicker(){
const setbox = this._element.querySelector('form');
setbox.innerHTML='';
}
get data() {
const textblock = this._element.querySelector('[contenteditable]');
let text = textblock.innerHTML;
this._data.text = text;
return this._data;
}
set data(data) {
this._data = data || {};
const textblock = this._element.querySelector('[contenteditable]');
textblock.innerHTML = this._data.text || '';
}
static get pasteConfig() {
return {
tags: [ 'P' ]
};
}
static get toolbox() {
return {
icon: '文字',
title: '段落の挿入'
};
}
}
主に頑張ったのは、エンターキーでbrタグを入れるようにしたところ。どこのエディターもそうなんですけど、contenteditableにするとエンターキーでデフォルトのだとdivブロックが挿入されます。
それをbrに変えてます。
で、あとは位置、背景、文字色、サイズかな。
こんな感じで最低限彩れるようになりました。
良ければサポートお願いします。サポート費用はサーバー維持などの開発費に使わせていただきます。