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

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にも同様のポスト
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">TinyMCE 突然の有料化。しかも価格がエグい。</p>&mdash; ミクスローグの人 from キグルミタヌキ (@mixlogue) <a href="https://twitter.com/mixlogue/status/1780005410793267583?ref_src=twsrc%5Etfw">April 15, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## 代替エディタ候補
- Quill
- Lexical
- Froala
- Editor.js
- Toast.ui

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

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


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

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

## 実装

vue3にSFCで書いています。あと、jodit v3でやってます。v4だとテーブルのポップアップツールが表示されなかった。
```ts
<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
    - s3の署名付きpostするためのurlをとるやつ。自分で実装してね
    - https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-s3-presigned-post/
- buildDataで送信用のデータを用意してる
- isSuccessで成功判定してる
    - 署名付きpostだとコンテンツ返ってこないからとりあえずなんでも成功にしてる
    - ほんとはもっといい方法あるかも
- processでエディタに表示する画像を返してる


あとは作ったコンポーネントをほかで参照してあげるだけ
出来上がりはこんな感じ。
ええやんええやん
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2493126/4b28a25a-e049-2331-bfb1-8e0ecc138f7c.png)


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

以上。