ContentfulのUI ExtensionでエディタをWYSIWYGにカスタマイズしてみる

2021.03.05

ベルリンオフィスの小西です。

ヘッドレスCMSのContentfulではUI Extensionという拡張機能を使ってエディタやダッシュボードのカスタマイズが可能です。使いこなせればヘッドレスCMSで課題になるエディタ周りの柔軟性をカバーできます。

今回はUI Extensionの作成を通じてContentfulデフォルトのマークダウン式テキストエディタをAlloy Editorに置き換えてみます。

Alloy Editorとは

Alloy EditorはオープンソースのWYSIWYGなエディタです。

エディタ上での画像のドラッグ&ドロップ、スタイルの適用などが可能になり、マークダウンに慣れ親しんでいない人にとって直感的なエディタを提供できます。

contentful screenshot

前提

※本記事はContentfulをある程度利用されている方を想定しています。

UI Extensionを実装するにはソースコードをContentfulに読み込ませる必要があります。

ソースコードはGithubかローカルでホストする必要があり、今回はローカルでホストしてContentfulに読み込ませる形を取ります。

実装に先立って下記のインストールが必要になります。

早速実装してみる

ローカルでUI Extensionのアプリケーションを作成します。

% npx @contentful/create-contentful-extension alloy-editor-sample
npx: installed 47 in 9.615s
Creating a new Contentful extension in /Users/ryokonishi/Dev/alloy-editor-sample.

? Select type of extension: (Use arrow keys)
❯ Field extension 
  Sidebar extension 
  Entry editor extension 
  Page extension

-> Field extensionを選択します。

UI ExtensionはContentfulダッシュボード上での表示箇所を選ぶことができます。今回はエディタのフィールドを置き換えたいので、Field extensionを選択します。

? Select field types for this extension: 
 ◯ Assets
 ◯ Symbol
 ◯ Symbols
❯◉ Text
 ◯ RichText
 ◯ Integer
 ◯ Number

-> Textを選択します。

ちなみにちょっとわかりにくいのが、このTextはContentfulダッシュボードでいう[Text / Long text]フィールドを指します。[Short Text]に対してExtensionを作成したい場合はSymbolを選択します。

? What language you want to use to develop extension: (Use arrow keys)
❯ JavaScript 
  TypeScript

-> JavaScriptを選択。

インストールが始まります。

Success! Created alloy-editor-sample at /Users/ryokonishi/Dev/alloy-editor-sample

成功したらアプリケーションディレクトリに移動し、依存パッケージをインストールします。

% cd alloy-editor-sample
% npm install

次に、CLIからContentfulにログインします。
ブラウザが開き、CMA(Content Management API = コンテンツをContntfulに対してPostするトークン)をコピーして、CLIに貼り付けます。

% npm run login

> alloy-editor-sample@0.1.0 login /Users/ryokonishi/Dev/alloy-editor-sample
> contentful login

A browser window will open where you will log in (or sign up if you don’t have an account), authorize this CLI tool and paste your CMA token here:

? Open a browser window now? Yes
? Paste your token here: [hidden]

Great! Your CMA token is now stored on your system. (Located at /Users/ryokonishi/Dev/alloy-editor-sample/.contentfulrc.json)
You can always run contentful logout to remove it.

次に、どのContentfulのSpaceに連携するか設定します。

% npm run configure

> alloy-editor-sample@0.1.0 configure /Users/ryokonishi/Dev/alloy-editor-sample
> contentful space use && contentful space environment use


? Please select a space: space_name (XXXXXXXX)
Now using the 'master' Environment of Space bq (hgb7pexouhv0) when the `--environment-id` option is missing.

? Please select an environment: master (master)
Now using Environment master (master) in Space bq (hgb7pexouhv0) when the `--environment-id` option is missing.

準備が完了したので、アプリを素の状態で立ち上げてみます。

% npm run start

> alloy-editor-sample@0.1.0 start /Users/ryokonishi/Dev/alloy-editor-sample
> contentful-extension-scripts start

Installing extension in development mode...

✨  Successfully created extension:

Space: XXXXXXXX

Environment: master

Your extension: https://app.contentful.com/spaces/XXXXXXXX/settings/extensions/alloy-editor-sample

┌───────────────────────────────┬───────────────────────┐
│ Property                      │ Value                 │
├───────────────────────────────┼───────────────────────┤
│ ID                            │ alloy-editor-sample  │
├───────────────────────────────┼───────────────────────┤
│ Name                          │ alloy-editor-sample  │
├───────────────────────────────┼───────────────────────┤
│ Field types                   │ Text                  │
├───────────────────────────────┼───────────────────────┤
│ Src                           │ http://localhost:1234 │
├───────────────────────────────┼───────────────────────┤
│ Version                       │ 1                     │
├───────────────────────────────┼───────────────────────┤
│ Parameter definitions         │ Instance: 0           │
│                               │ Installation: 0       │
├───────────────────────────────┼───────────────────────┤
│ Installation parameter values │ 0                     │
└───────────────────────────────┴───────────────────────┘

これでローカル( http://localhost:1234 )でUI Extensionアプリがホストされた状態になり、かつ連携したContentfulのSpaceにもアプリがデプロイされました。

ちなみにcreate-contentful-extensionの各コマンドはこんな感じ。

npm run start
Developmentサーバーを立ち上げて開発モードでExtensionをデプロイします。

一度立ち上げると変更が自動的に反映されます。

npm run login
CLIツールにContentfulアカウントでログイン。作業に必要なCMA(コンテンツマネージメントAPI)が発行される。

npm run logout
CLIツールのセッションを切る。

npm run configure
ContentfulのSpaceとEnvironmentの設定。内容は.contentfulrc.jsonに保存される。これはリポジトリに上げてはいけない。

npm run build
Extensionをビルドして/buildフォルダにまとめる。依存関係をバンドルして最適化。デプロイは各自自由にやってねというスタンス。

デプロイされたUI Extensionを確認してみる

https://app.contentful.com/spaces/hgb7pexouhv0/settings/にアクセスすると、[alloy-editor-sample]という名前のUI Extensionが追加されてるのが確認できるはずです。

Contentful screenshot

詳細ページでは下記が確認できます。

  • [Field types]としてTextが選択されている
  • [Self-hosted]としてhttp://localhost:1234が指定されている

contentful screenshot

それではローカルでアプリの中身を変更していきます。

extension.json

{
  "id": "alloy",
  "name": "Alloy Text Editor",
  "srcdoc": "./index.html",
  "fieldTypes": ["Text"]
}

src/index.html

<!DOCTYPE html>
<head>
  <link href="https://contentful.github.io/ui-extensions-sdk/cf-extension.css" rel="stylesheet">
  <link href="https://contentful.github.io/extensions/libs/alloy-editor/assets/alloy-editor-ocean-min.css" rel="stylesheet">
  <script src="https://contentful.github.io/extensions/libs/alloy-editor/alloy-editor-all-min.js"></script>
  <script src="https://unpkg.com/contentful-ui-extensions-sdk@3"></script>
  <style>
    body {
      margin: 0;
      border: 1px solid #ccc;
      border-radius: 4px;
      overflow: hidden;
    }
    #content {
      outline: 0;
      padding: 1em;
      margin-left: 3em;
      margin-top: -1px;
      margin-bottom: -1px;
      min-height: 30em;
    }
  </style>
</head>
<body>
  <div id="content"></div>
  <script>
  var cfExt = window.contentfulExtension || window.contentfulWidget

  const AlloyEditor = window.AlloyEditor

  cfExt.init(function (ext) {
    var currentValue

    ext.window.startAutoResizer()

    const editor = AlloyEditor.editable('content')._editor
    editor.setData(ext.field.getValue())

    ext.field.onValueChanged(function(value) {
      if (value !== currentValue) {
        currentValue = value
        editor.setData(value)
      }
    })

    editor.on('change', function() {
      const value = editor.getData()

      if (currentValue !== value) {
        currentValue = value
        ext.field.setValue(value)
      }
    })
  })
  </script>
</body>

再度アプリを立ち上げます。

% npm run start

Contentful側にも再度リロードされ、アプリの準備は完了です。

Contentful側の設定

Content Modelがすでに作成されてる前提で、新しいフィールドを追加していきます。

[Add new field]からフィールドタイプで[Text]を選択

Contentful screenshot

[Appearance]から、先ほど作成した[alloy-editor-sample]が表示されているので選択

contentful screenshot

以上のフィールドを追加完了したら、新規投稿ページに遷移。
投稿フィールドを見てみると、Alloy Editorが適用されたエディタになっています。

contentful screenshot

Done!

さいごに

ContentfulにはAppsというより広範なカスタマイズを可能にする拡張機能もあります。今後統合されていきそうな気配ですが、今回はフィールド単位のカスタマイズだったのでUI Extensionsを使ってサクッと実装してみました。

参考URL

https://github.com/contentful/contentful-cli

https://www.contentful.com/developers/docs/extensibility/ui-extensions/