# Writer marks/nodes

****

The Writer is our rich-text editor powering the <a href="https://getkirby.com/docs/reference/panel/fields/writer">writer field</a> and <a href="https://getkirby.com/docs/reference/panel/fields/list">list field</a> but also the <a href="https://getkirby.com/docs/reference/panel/blocks/text">text</a> and <a href="https://getkirby.com/docs/reference/panel/blocks/heading">heading blocks</a>, among others. In your plugin, you can add custom marks and nodes to enhance and customize the editing experience of those fields and blocks.

<info>
The Writer is built on top of <a href="https://prosemirror.net/">ProseMirror</a>. When diving deeper into your project, you will probably also need to consult the ProseMirror documentation for all details.
</info>

## Custom marks
A mark is a "a piece of information that can be attached to a node, such as it being emphasized, in code font, or a link" – e.g. our default marks include bold, strike, sub, sup etc.

A custom mark automatically extends our <a href="https://github.com/getkirby/kirby/blob/main/panel/src/components/Forms/Writer/Mark.js">JavaScript `Mark` class</a> that functions as thin layer over the raw ProseMirror implementation. You can start building your mark from there:

```js "/site/plugins/your-plugin/index.js"
window.panel.plugin("your/plugin", {
  writerMarks: {
    highlight: {
      get button() {
        return {
          icon: "palette",
          label: window.panel.$t("color")
        }
      },

      commands() {
        return () => this.toggle()
      },

      get name() {
        return "highlight"
      },

      get schema() {
        return {
          parseDOM: [{ tag: "mark" }],
          toDOM: () => ["mark", 0]
        }
      }
    }
  }
});
```

You can also take a look at the <a href="https://github.com/getkirby/kirby/blob/main/panel/src/components/Forms/Writer/Marks">implementation of the default marks</a> as well as <a href="https://prosemirror.net/docs/ref/#model.MarkSpec">ProseMirror&#039;s mark docs</a>.

## Custom nodes
A node represents some structured content in ProseMirror's document tree. Often this is rather seen as blocks, where marks are rather inline. Our default marks include the normal text paragraph, unordered/ordered lists, headings etc.

A custom node automatically extends our <a href="https://github.com/getkirby/kirby/blob/main/panel/src/components/Forms/Writer/Node.js">JavaScript `Node` class</a> that functions as thin layer over the raw ProseMirror implementation. You can start building your node from there:

```js "/site/plugins/your-plugin/index.js"
window.panel.plugin("your/plugin", {
  writerNodes: {
    quote: {
      get button() {
        return {
          icon: "quote",
          label: window.panel.$t("field.blocks.quote.name"),
        };
      },

      commands({ type, utils }) {
        return () => utils.toggleWrap(type);
      },

      get name() {
        return "quote";
      },

      get schema() {
        return {
          content: "block+",
          group: "block",
          defining: true,
          draggable: false,
          parseDOM: [
            {
              tag: "blockquote"
            }
          ],
          toDOM: () => ["blockquote", 0]
        };
      }
    }
  }
});
```

<since v="5.0.0">
Writer nodes with `inline: true` in their button definition are displayed as inline buttons in toolbar (instead inside the dropdown).
</since>

You can also take a look at the <a href="https://github.com/getkirby/kirby/blob/main/panel/src/components/Forms/Writer/Nodes">implementation of the default nodes</a> as well as <a href="https://prosemirror.net/docs/ref/#model.NodeSpec">ProseMirror&#039;s node docs</a>.

## Using custom marks/nodes in your fields

Learn how to <a href="https://getkirby.com/docs/reference/panel/fields/writer#choosing-available-marks">activate your custom mark or node</a>. This also works for blocks, when you extend or overwrite e.g. the <a href="https://getkirby.com/docs/reference/panel/blocks/text#default-files__blueprint">text blocks&#039; blueprint</a> to add the right options to the block's text field (which is a writer type field by default).

## Troubleshooting

### HTML tags/attributes gets removed when saving

If you run into issues where HTML tags/attributes get removed when saving, it is likely due to them not being allowed by our HTML sanitizer by default.

To allow your specific tags/attributes, you can modify the list of allowed tags/attributes in the [`Kirby\Sane\Html` class](https://github.com/getkirby/kirby/blob/main/src/Sane/Html.php), e.g.

```php "/site/config/config.php"
use Kirby\Sane\Html;

Html::$allowedTags['span'] = ['title', 'class'];
```

### Competing with other nodes/mark

When implementing a custom node/mark, your `parseDOM` rules might compete with rules from other nodes/marks. E.g. your custom node targets a `<p>` tag with a specific class, while the default paragraph node targets all `<p>` tags. This can lead to problems, when the broader `parseDOM` rule gets checked before the more specific one.

If you run into issues here, you can use the <a href="https://prosemirror.net/docs/ref/#model.GenericParseRule.priority">`priority` option</a> and choose a value higher than 50 (which is the default) to ensure your `parseDOM` rule gets checked first.