Skip to content

Kirby 4.5.0

Writer marks/nodes

The Writer is our rich-text editor powering the writer field and list field but also the text and heading blocks, among others. In your plugin, you can add custom marks and nodes to enhance and customize the editing experience of those fields and blocks.

The Writer is built on top of ProseMirror. When diving deeper into your project, you will probably also need to consult the ProseMirror documentation for all details.

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 JavaScript Mark class that functions as thin layer over the raw ProseMirror implementation. You can start building your mark from there:

/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 implementation of the default marks as well as ProseMirror's mark docs.

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 JavaScript Node class that functions as thin layer over the raw ProseMirror implementation. You can start building your node from there:

/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]
        };
      }
    }
  }
});

You can also take a look at the implementation of the default nodes as well as ProseMirror's node docs.

Using custom marks/nodes in your fields

Learn how to activate your custom mark or node. This also works for blocks, when you extend or overwrite e.g. the text blocks' blueprint to add the right options to the block's text field (which is a writer type field by default).

Troubleshooting

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 priority option and choose a value higher than 50 (which is the default) to ensure your parseDOM rule gets checked first.