Editor.jsについての備忘録3 インラインツールの作成
Editor.jsのインラインツールのカスタマイズ方法について備忘録としてまとめる。
インラインの取り込み
外部のインラインツールを取り込むのは、通常のブロックツールを取り込むときと同じ。(事前にscriptのsrcを読み込んでおく。)
const editor = new EditorJS({
holder: 'editor',
tools: {
paragraph: {
class: Paragraph,
inlineToolbar: true
},
p: {
class: CustParagraph,
inlineToolbar: true
} ,
Marker: { //マーカーツールを読み込む。
class: MarkerTool,
shortcut: 'CMD+SHIFT+M',
}
}
});
こんな感じにインポートできる。
インラインツールの作成
チュートリアルを参考に作成。
isInline、render, surround, checkstateが必要。例はマーカーツール。
class MarkerTool {
static get isInline() {
return true;
}
constructor() {
this.button = null;
this.state = false;
}
render() {
this.button = document.createElement('button');
this.button.type = 'button';
this.button.textContent = 'M';
return this.button;
}
surround(range) {
if (this.state) {
// If highlights is already applied, do nothing for now
return;
}
const selectedText = range.extractContents();
// Create MARK element
const mark = document.createElement('MARK');
// Append to the MARK element selected TextNode
mark.appendChild(selectedText);
// Insert new element
range.insertNode(mark);
}
checkState(selection) {
const text = selection.anchorNode;
if (!text) {
return;
}
const anchorElement = text instanceof Element ? text : text.parentElement;
this.state = !!anchorElement.closest('MARK');
}
}
isInlineでインラインツールかどうかを確認する。renderでボタンの外観処理、surroundで選択範囲の処理、checkStateで状態確認を行う。
出力に含めるためにはsanitaizeで通すものを指定する。
static get sanitize() {
return {
mark: {
class: 'cdx-marker'
}
};
}
sanitizeで通すタグを指定する。これを指定しないと保存時に反映されない。
基本的にはこれだけでも実装可能だが、取り消し処理やパラメータの設定などを追加すると使いやすくなる。特に取り消し処理は必須。
パラメータの設定は、選択するとinputが出てくるようにしてその値を読みだして実装する感じ。
renderActions() {
this.colorPicker = document.createElement('input');
this.colorPicker.type = 'color';
this.colorPicker.value = '#f5f1cc';
this.colorPicker.hidden = true;
return this.colorPicker;
}
showActions(mark) {
this.colorPicker.value = mark.style.backgroundColor || '#f5f1cc';
this.colorPicker.onchange = () => {
mark.style.backgroundColor = this.colorPicker.value;
};
this.colorPicker.hidden = false;
}
hideActions() {
this.colorPicker.onchange = null;
this.colorPicker.hidden = true;
}
checkState() {
const mark = this.api.selection.findParentTag(this.tag);
this.state = !!mark;
if (this.state) {
this.showActions(mark);
} else {
this.hideActions();
}
}
完成形
class MarkerTool {
static get isInline() {
return true;
}
get state() {
return this._state;
}
set state(state) {
this._state = state;
this.button.classList.toggle(this.api.styles.inlineToolButtonActive, state);
}
constructor({api}) {
this.api = api;
this.button = null;
this._state = false;
this.tag = 'MARK';
this.class = 'cdx-marker';
}
render() {
this.button = document.createElement('button');
this.button.type = 'button';
this.button.innerHTML = '<svg width="20" height="18"><path d="M10.458 12.04l2.919 1.686-.781 1.417-.984-.03-.974 1.687H8.674l1.49-2.583-.508-.775.802-1.401zm.546-.952l3.624-6.327a1.597 1.597 0 0 1 2.182-.59 1.632 1.632 0 0 1 .615 2.201l-3.519 6.391-2.902-1.675zm-7.73 3.467h3.465a1.123 1.123 0 1 1 0 2.247H3.273a1.123 1.123 0 1 1 0-2.247z"/></svg>';
this.button.classList.add(this.api.styles.inlineToolButton);
return this.button;
}
surround(range) {
if (this.state) {
this.unwrap(range);
return;
}
this.wrap(range);
}
wrap(range) {
const selectedText = range.extractContents();
const mark = document.createElement(this.tag);
mark.classList.add(this.class);
mark.appendChild(selectedText);
range.insertNode(mark);
this.api.selection.expandToTag(mark);
}
unwrap(range) {
const mark = this.api.selection.findParentTag(this.tag, this.class);
const text = range.extractContents();
mark.remove();
range.insertNode(text);
}
checkState() {
const mark = this.api.selection.findParentTag(this.tag);
this.state = !!mark;
if (this.state) {
this.showActions(mark);
} else {
this.hideActions();
}
}
renderActions() {
this.colorPicker = document.createElement('input');
this.colorPicker.type = 'color';
this.colorPicker.value = '#f5f1cc';
this.colorPicker.hidden = true;
return this.colorPicker;
}
showActions(mark) {
const {backgroundColor} = mark.style;
this.colorPicker.value = backgroundColor ? this.convertToHex(backgroundColor) : '#f5f1cc';
this.colorPicker.onchange = () => {
mark.style.backgroundColor = this.colorPicker.value;
};
this.colorPicker.hidden = false;
}
hideActions() {
this.colorPicker.onchange = null;
this.colorPicker.hidden = true;
}
convertToHex(color) {
const rgb = color.match(/(\d+)/g);
let hexr = parseInt(rgb[0]).toString(16);
let hexg = parseInt(rgb[1]).toString(16);
let hexb = parseInt(rgb[2]).toString(16);
hexr = hexr.length === 1 ? '0' + hexr : hexr;
hexg = hexg.length === 1 ? '0' + hexg : hexg;
hexb = hexb.length === 1 ? '0' + hexb : hexb;
return '#' + hexr + hexg + hexb;
}
static get sanitize() {
return {
mark: {
class: 'cdx-marker'
}
};
}
}
これでマーカーツールの出来上がり。
パラメータをいくつか設定するともっと柔軟に対応できそう。
カスタムBlockを作るよりは楽かもしれない。
良ければサポートお願いします。サポート費用はサーバー維持などの開発費に使わせていただきます。