📝

Editor.jsでインラインのマーカーツールで作成する

2024/04/19に公開

概要

Editor.jsでインラインのマーカーツールを作成する方法の備忘録です。

参考

以下のページが参考になりました。

https://editorjs.io/creating-an-inline-tool/

https://note.com/eveningmoon_lab/n/n638b9541c47c

TypeScriptでの記述にあたっては、以下が参考になりました。

https://github.com/codex-team/editor.js/issues/900

実装

Nuxtで実装します。以下のmarker.tsを作成します。

/utils/tools/marker.ts
import type { API } from "@editorjs/editorjs";

class MarkerTool {
  button: null | HTMLButtonElement;
  state: boolean;
  api: API;
  tag: string;
  class: string;

  // 静的メソッドで許可されるHTMLタグと属性を指定
  static get sanitize() {
    return {
      mark: {
        class: "cdx-marker",
      },
    };
  }

  // インラインツールとしての振る舞いを定義
  static get isInline() {
    return true;
  }

  constructor({ api }: { api: API }) {
    this.api = api;
    this.button = null;
    this.state = false;
    this.tag = "MARK";
    this.class = "cdx-marker";
  }

  // ボタン要素を作成し、SVGアイコンを設定
  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;
  }

  // 選択されたテキストを <mark> タグで囲む
  surround(range: Range) {
    if (this.state) {
      this.unwrap(range);
      return;
    }

    this.wrap(range);
  }

  // テキストを <mark> タグでラップ
  wrap(range: Range) {
    const selectedText = range.extractContents();
    const mark = document.createElement(this.tag);
    mark.className = this.class; // class 属性の追加
    mark.appendChild(selectedText);
    range.insertNode(mark);

    this.api.selection.expandToTag(mark);
  }

  // <mark> タグを解除
  unwrap(range: Range) {
    const mark = this.api.selection.findParentTag(this.tag);
    const text = range.extractContents();

    mark?.remove();

    range.insertNode(text);
  }

  // ツールの状態をチェック
  checkState() {
    const mark = this.api.selection.findParentTag(this.tag, this.class);
    this.state = !!mark;
    if (this.state) {
      this.button?.classList.add("cdx-marker--active");
    } else {
      this.button?.classList.remove("cdx-marker--active");
    }
  }
}

export default MarkerTool;

上記を以下のように呼び出します。

<script setup lang="ts">
import EditorJS from '@editorjs/editorjs';
import type { OutputData } from '@editorjs/editorjs';
import MarkerTool from '@/utils/tools/marker';

// Editor.jsの初期データを設定
const blocks = ref<OutputData>({
  time: new Date().getTime(),
  blocks: [
    {
      type: 'paragraph',
      data: {
        text: '大明副使蒋 承奉すらく 、 欽差督察総制提督浙江等処軍務各衙門 、近年以来、日本各島小民、仮るに買売を以て名と為し 、 しばしば中国辺境を犯し、居民を刼掠するを因となし、旨を奉じて 、 浙江等処承宣布政使司 に議行し、本職に転行して 、 親しく貴国に詣り面議せしめん等の因あり。',
      },
    },
  ],
});

// Editor.jsを初期化し、ページに組み込む
const initEditor = () => {
  new EditorJS({
    holder: 'editorjs', // Editor.jsを表示する要素のID
    data: blocks.value,
    onChange: async (api) => {
      blocks.value = await api.saver.save(); // 編集内容が変更された時にデータを更新
    },
    tools: {
      Marker: {
        class: MarkerTool,
        shortcut: 'CMD+SHIFT+M', // マーカーツールのショートカットキー
      },
    },
  });
};

// エディタの初期化を実行
initEditor();
</script>

<template>
  <div style="background-color: aliceblue">
    <div id="editorjs"></div>
    <hljson :content="blocks" />
  </div>
</template>

<style>
pre {
  background-color: #f4f4f4;
  border: 1px solid #ccc;
  padding: 10px;
  white-space: pre-wrap; /* 自動的に折り返し */
}

/* エディタ関連のスタイリング調整 */
.ce-block__content,
.ce-toolbar__content {
  max-width: calc(100% - 80px) !important; /* エディタの最大幅を調整 */
}
.cdx-block {
  max-width: 100% !important; /* ブロック要素の最大幅を設定 */
}

/* アクティブなマーカーのスタイル */
.cdx-marker--active {
  color: #388ae5;
}
</style>

動作確認

マーカーアイコンをクリックすると、青色に変わり、<mark class=\"cdx-marker\">xxx</mark>というタグが挿入されます。

まとめ

参考になりましたら幸いです。

Discussion