2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WYSIWYGエディターをTinyMCEからJoditに移行した

Posted at

TinyMCEクラウドがマンスリー固定プランだったのだが、2024/5/14から従量課金制に変わったので無料のエディタに切り替えた。そのログを残しておく🦆🦆🦆🦆🦆
詳しく言えないが、従量課金制に変わったことでより高額なプランに移らないといけない状況になったが、そこまでしてTinyMCEを使いたくなかった。

困っている人が海外にもいた
https://iwasherefirst2.medium.com/how-to-selfhost-tinymce-in-vue-270bb53cb4d8

TinyMCE has recently updated their policy. The free cloud version is now only free for the first 1,000 page loads, starting from May 14, 2024. Below is the email I received:

Xにも同様のポスト

代替エディタ候補

  • Quill
  • Lexical
  • Froala
  • Editor.js
  • Toast.ui

移行要件

  • TinyMCE同等の機能がある
    • テーブル
    • 画像アップロード
    • ソースコード編集
    • undo, redo
    • etc
  • 無料で上記機能が使える
  • メンテされてる

検討

  • Quill
    • テーブルが使えん
      • あるけど5年以上メンテされてなくてvu3でバグ起こして使えない
  • Lexical
    • オプション機能の実装ハードルが高い
  • Froala
    • 高い
  • Editor.js
    • ブロックエディタスタイルなので違う
  • Toast.ui
    • あくまでマークダウンエディタなので左右中央寄せができない

ということで。全然見つからなかったのだが、1つ探し出した。Jodit
https://xdsoft.net/jodit/

全然聞いたことなかったがFreeプランで一通りやりたいことできるし、定期的にリリースされているので大丈夫そう。
全然ドキュメント充実してないけど笑

実装

vue3にSFCで書いています。あと、jodit v3でやってます。v4だとテーブルのポップアップツールが表示されなかった。

<script setup lang="ts">
import { Jodit } from "jodit";
import { ref, onMounted } from "vue";
import "jodit/build/jodit.min.css";


const editor = ref("editor");
const body = computed({
  get: () => props.modelValue,
  set: (value) => emit("update:model-value", value),
});

const s3Endpoint = runtimeConfig.public.S3_ENDPOINT;
const joditConfig = {
  uploader: {
    insertImageAsBase64URI: false,
    imagesExtensions: ["jpg", "JPG", "jpeg", "JPEG", "png", "PNG", "webp", "WEBP"],
    url: <s3Endpoint>,
    method: "POST",
    credentials: "include",
    buildData: function (formData: FormData) {
      return new Promise((resolve, reject) => {
        (async function () {
          try {
            const file = formData.get("files[0]") as File;
            if (!file) {
              throw new Error("ファイルが選択されていません");
            }
            const contentType = file.type;
            Object.assign(presignedInput, await fetchPresignedURL(contentType.split("/")[1]));
            const fields = presignedInput.fields;

            // 不要なパラメータを削除
            formData.delete("files[0]");
            formData.delete("path");
            formData.delete("source");

            // 署名情報を追加
            Object.keys(fields).forEach((key) => {
              formData.append(key, fields[key]);
            });
            formData.append("Content-Type", contentType);
            // 最後にファイルを追加
            formData.append("file", file);

            resolve(formData);
          } catch (error) {
            reject(error);
          }
        })();
      });
    },
    isSuccess: function () {
      return true;
    },
    process: function () {
      const res = getPostInput();
      return {
        files: [`/${res.fields.key}`],
        isImages: [true],
        baseurl: s3Endpoint,
      };
    },
  },
};

onMounted(() => {
  const joditInstance = Jodit.make(editor.value, joditConfig);
});
</script>

<template>
  <div class="container">
    <label for="editor"></label>
      <textarea ref="editor"></textarea>
  </div>
</template>
  • fetchPresignedURL
  • buildDataで送信用のデータを用意してる
  • isSuccessで成功判定してる
    • 署名付きpostだとコンテンツ返ってこないからとりあえずなんでも成功にしてる
    • ほんとはもっといい方法あるかも
  • processでエディタに表示する画像を返してる

あとは作ったコンポーネントをほかで参照してあげるだけ
出来上がりはこんな感じ。
ええやんええやん
image.png

細かいoptionはここを参照しておくれやんす
https://xdsoft.net/jodit/docs/options.html

以上。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?