# Content Editor - adding your own plug-in

## Introduction

The Content Editor [component](https://petal.build/components/content-editor) is currently based on [Editor.js](https://editorjs.io/). Editor.js is based on a plug-in eco-system. Petal Pro comes pre-installed with a [set of plug-ins](https://docs.petal.build/petal-pro-documentation/v2.2.0/fundamentals/blog-cms#pre-installed-editor.js-plug-ins) that allow you to do common activities in a blog post (such as editing text, creating lists and tables, inserting images, etc).

However, what if you wanted to install your own Editor.js plug-in? There's nothing stopping you! There's only one extra thing to consider - the renderer needs to be adjusted to allow for the data of the new plug-in.

Though this may seem daunting. Updating the renderer is surprisingly trivial! In fact, creating your own Editor.js plug-in is pretty easy too. The rest of this guide will show you how to create your own Editor.js plug-in and adjust the Content Editor renderer.

## The Idea

To start we need an idea for our plug-in.  How about we build Markdown plug-in? Let's come up with some requirements:

* User can paste their markdown text into a textarea
* Markdown text is saved unaltered in the Editor.js json
* Update the renderer to show html instead of markdown (using [Earmark](https://hexdocs.pm/earmark))

## The Folder Structure

Before we start, it may help to provide an overview of the Petal Pro folder structure with regards to Editor.js and the Content Editor component:

```
├── assets
│   ├── css
│   │   ├── editorjs.css
│   ├── js
│   │   ├── editorjs
│   │   │   ├── petal-image.js
│   │   ├── hooks
│   │   │   ├── editorjs-hook.js
│   ├── package.json
├── lib
│   ├── petal_pro_web
│   │   ├── components
│   │   │   ├── pro_components
│   │   │   │   ├── content_editor.ex
```

Moving through the files, top to bottom:

* `editorjs.css` is for Petal Pro styles that apply to Editor.js
* `petal-image.js` is an Editor.js plug-in that we wrote for the [File Browser](https://docs.petal.build/petal-pro-documentation/v2.2.0/fundamentals/blog-cms#file-browser)
* `editorjs-hook.js` is the JavaScript hook
* The `assets` folder is where `package.json` is kept. This is where you would install your Editor.js plug-ins
* `content-editor.ex` is the component that uses the hook and where the renderer is

The plug-in that we're going to create will be placed adjacent to the `petal-image.js` file.

## Creating our Plug-in

Create a file called `/assets/js/editorjs/petal-markdown.js`:

{% code title="petal-markdown.js" %}

```javascript
export default class PetalMarkdown {
  static get toolbox() {
    return {
      title: "Markdown",
      icon: '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"><path stroke-linecap="round" stroke-linejoin="round" d="M5.25 8.25h15m-16.5 7.5h15m-1.8-13.5-3.9 19.5m-2.1-19.5-3.9 19.5" /></svg>',
    };
  }

  constructor({ data, block }) {
    this.data = data;
    this.block = block;
    this.wrapper = undefined;
  }

  render() {
    this.wrapper = document.createElement("div");

    const markdown_input = document.createElement("textarea");
    markdown_input.placeholder = "Paste your markdown here...";
    markdown_input.value = this.data.markdown || "";
    markdown_input.classList.add("petal-markdown");

    this.wrapper.append(markdown_input);

    return this.wrapper;
  }

  save(blockContent) {
    const markdown_input = blockContent.querySelector("textarea");

    return {
      markdown: markdown_input ? markdown_input.value : "",
    };
  }
}
```

{% endcode %}

We won't go into detail about the ins and outs of Editor.js. If you want to learn more, head on over to their [documentation](https://editorjs.io/the-first-plugin/). We'll just highlight the parts that are important to our new markdown feature.

The `toolbox()` static getter is used by Editor.js when showing the menu:

<figure><img src="https://2964479083-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fjybnmx3gX5MSuPHDwIJB%2Fuploads%2FnetlCklOqdf5ZFBL8znV%2FXnapper-2024-12-06-16.21.27.png?alt=media&#x26;token=7dbf7030-5c56-4bc7-89c7-8320cda4e6d5" alt=""><figcaption><p>Editor.js menu</p></figcaption></figure>

The `render()` method is used to generate the `textarea` that's displayed in the editor. Note that the `petal-markdown` class is added to the `textarea` (this is referenced in the CSS below).&#x20;

Finally, the `save()` method is used to generate the output for the json document. It's an object with a single `markdown` property.

Add the following class to `/assets/css/editorjs.css`:

{% code title="editorjs.css" %}

```css
/* Add this to the bottom */

.codex-editor textarea.petal-markdown {
  @apply w-full rounded-md min-h-32 bg-gray-100 dark:bg-gray-950 dark:text-gray-400 border-gray-200 dark:border-gray-600 ring-0;
}

```

{% endcode %}

The last step is to configure our new plug-in within the js hook. Most of the code from this file has been cut to keep the contents short. But you only need to do two things - add the `import` and add the `tool`:

{% code title="editorjs-hook.js" %}

```javascript
import EditorJS from "@editorjs/editorjs";

// Add import
import PetalMarkdown from "../editorjs/petal-markdown";

const EditorJsHook = {
  mounted() {
    this.editor = new EditorJS({
      tools: {
        // Add tool
        petalMarkdown: PetalMarkdown,
      },
    });
  },
};

export default EditorJsHook;
```

{% endcode %}

Now in the editor, once you add a Markdown block, this is what our data entry looks like:

<figure><img src="https://2964479083-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fjybnmx3gX5MSuPHDwIJB%2Fuploads%2FEffnPmOJra7I7vTOWjgF%2FXnapper-2024-12-06-16.22.44.png?alt=media&#x26;token=6f047909-3b0a-4694-adb2-97e8e9b65d1e" alt=""><figcaption><p>Data entry for markdown/plain text</p></figcaption></figure>

Now our plug-in is complete and the first two requirements are met:&#x20;

* The user can paste markdown text into a `textarea`; and&#x20;
* The data is saved in the output for the json.

## Updating the Renderer

The final step is to update the Content Editor so that it renders the markdown as HTML. If you look in:

```
/lib/petal_pro_web/components/pro_components/content_editor.js
```

You'll see that there's a function component called `content`:

```elixir
attr :json, :string, required: true

def content(assigns) do
  json_object = decode_json(assigns.json)

  blocks =
    case json_object do
      %{"blocks" => blocks} -> MapExt.atomize_keys(blocks)
      _ -> []
    end

  assigns = assign(assigns, :blocks, blocks)

  ~H"""
  <.block :for={block <- @blocks} block={block} />
  """
end
```

It decodes the Editor.js json and generates a list of `blocks`. It then calls the `.block` function component for each item in the array. The `.block` function uses pattern matching to render content for a plug-in. Here's what the `.block` function looks like for our new plug-in:

```elixir
defp block(%{block: %{type: "petalMarkdown", data: %{markdown: markdown}}} = assigns) do
  assigns = assign(assigns, :markdown, markdown)

  ~H"""
  <PetalProWeb.Markdown.pretty_markdown content={@markdown} />
  """
end
```

In `content_editor.ex`  this needs to be placed above the "catch all" `.block` function:

{% code title="content\_editor.ex" %}

```elixir
...

def content(assigns) do
  ...
end

attr :block, :map, required: true

...

defp block(%{block: %{type: "petalMarkdown", data: %{markdown: markdown}}} = assigns) do
  assigns = assign(assigns, :markdown, markdown)

  ~H"""
  <PetalProWeb.Markdown.pretty_markdown content={@markdown} />
  """
end

defp block(assigns) do
  ~H""
end

...
```

{% endcode %}

And with that, we're done! The third requirement is met:

* The Content Editor renderer has been adjusted for our new plug-in

## Installing a Third Party Plug-in

You can choose from any of the [available plug-ins.](https://github.com/editor-js/awesome-editorjs) Choose a plug-in and look for the npm command in the relevant README. For example, if you were to install the [checklist plug-in](https://github.com/editor-js/checklist), this is what you would do at the command line:

```shell
cd assets
npm add @editorjs/checklist
```

Then you'd have to configure the Editor.js hook:

```javascript
import EditorJS from "@editorjs/editorjs";

// Add import
import CheckList from '@editorjs/checklist';

const EditorJsHook = {
  mounted() {
    this.editor = new EditorJS({
      tools: {
        // Add tool
        checkList: CheckList,
      },
    });
  },
};

export default EditorJsHook;
```

However, you'd still have to update the Content Editor renderer.
