Custom writer marks in Kirby 4

I’m trying to create a plugin for adding a custom mark to Kirby’s writer field with the help of the instructions from Kirby’s reference.

window.panel.plugin("my/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],
        };
      },
    },
  },
});

With the given code, a highlight button appears in the panel as expected and text can be highlighted.

But the text is not displayed on the website and also disappears from the panel field after the page is reloaded.

Interestingly, it works with tags other than ‘mark’, e.g. with ‘small’ or ‘sub’:

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

Can anyone help?

The docs should probably mention this. But “unknown” (to Kirby) tags are filtered out by the sanitizer.
You can allow tags in your plugin’s index.php

<?php 

Kirby\Sane\Html::$allowedTags['mark'] = true;

Kirby::plugin('mark/eg', [
    // ...
]);
1 Like

That did the trick. Thank you very much!

Please add this to the documentation, it seems like a must in order to create custom nodes/marks. thanks!

@rasteiner
@Erdgeschoss

I am having the same issue when creating custom nodes that adds a custom class to the p tag in the writer.

Everything works as expected until I refresh the page and the custom style is no longer applied in the panel, it only shows the p tags around the text.

The txt file saves the text like this:
"<p class=\"custom-class-name\">Selected text with custom node saved.</p>"

Is there an issue with the way it is saved to the text file—since it saves the html with the double quotation marks inside the json and also the escape slash?

This is fine on the front end, though it seems that the panel strips the custom class after refreshing.

Is there a way that the custom classes can be added to the sanitizer so they aren’t stripped out, like in the example with marks above?

My index.js for the plugin:

window.panel.plugin("my/plugin", {
  writerNodes: {
      customNode: {
          get button() {
              return {
                  id: this.name,
                  icon: 'text',
                  label: 'Custom Node',
                  name: this.name,
                  separator: true,
              }
          },

          commands({ utils, schema, type }) {
              return {
                customNode: () => {
                      return utils.setBlockType(type);
                  },
              }
          },

          get name() {
              return 'customNode'
          },

          get schema() {
              return {
                  content: 'inline*',
                  group: 'block',
                  draggable: false,
                  parseDOM: [
                      {
                          tag: 'p',
                          getAttrs: (node) => node.classList.contains('custom-class-name') ? {} : false,
                      },
                  ],
                  toDOM: () => ['p', { class: 'custom-class-name' }, 0],
              }
          },
      },
  },
})

AFAIK the sanitizer does not remove the class attribute.

I could imagine that it’s actually the “Writer” (the wysiwyg editor) that parses the HTML and simply prefers its own “paragraph” node over your custom node (maybe simply because it “comes first”), since both would match. The default paragraph node then renders without class attribute.
This is just speculation however, maybe someone with more expertise on the Writer component or Prosemirror should answer. (ping @distantnative)

Interesting, thanks for this input @rasteiner. Helpful to know that the sanitizer doesn’t strip the class from the paragraph tag.

It does then seem to be an issue with how the writer renders the the html that is saved in the txt file.

Hopefully there is there a hook to allow the writer to accept the class, or another way so that custom nodes can be added to the writer, like in the docs.

What do you think @distantnative ?

Are you sure/can you check if the class actually ends up in the content file in the first place? Or is it directly filtered by the input?

@distantnative
Yes, the class is saved in the txt content file like so:
"<p class=\"custom-class-name\">Selected text with custom node saved.</p>"

Also if you reload it in the Panel and save again?

Just trying to figure out which part exactly removes it (if somewhere in the JS and it’s removed from the content file again or if it’s some PHP part on the way to outputting it).

@distantnative
Thanks for thinking along.

Initially it saves the content to the txt file like this:
"<p class=\"custom-class-name\">Selected text with custom node saved.</p>"

Then if I refresh the page, the style is removed, only in the panel.

If I refresh without changing anything in that specific writer field, the content txt file remains unchanged.

If I edit the text in the writer to make text italic (for example) and save again, it won’t retain the custom class previously applied as a node, unless I apply the node again. Without applying the node again, it will only save the text as was edited with the italic or any other changes:
"<p>Selected text with <em>custom node</em> saved.</p>"

My suspicion is that ProseMirror re-renders it then and that these lines kirby/panel/src/components/Forms/Writer/Nodes/Paragraph.js at main · getkirby/kirby · GitHub don’t account for any attributes, thus omitting it.

I’d consider this a bug. Could you please open an issue?

Thanks!

ok, i’ll open a bug issue. thanks for the help

1 Like

For reference, the bug is reported here: Classes added by Custom Nodes are stripped out of the Writer field when refreshing the page/ProseMirror re-rendering the Writer · Issue #6587 · getkirby/kirby · GitHub

1 Like

For reference: We found the issue and I added a note in the docs about how to deal with it: Writer marks/nodes | Kirby CMS

1 Like

@distantnative Incredible! thanks for your support and adding this to the docs.

Hello, is the index.js file in site/plugins/myplugin/ truly all that is needed to add this as a plugin? I’ve done the same, but don’t see the additional mark showing up in the writer block. Any guidance is appreciated.

1 Like

yes, the index.js is all you need to create the plugin. Remember to activate the new node created by the plugin in your writer.
e.g.

fields:
  writer:
    type: writer
    nodes:
      - heading
      - bulletList
      - orderedList
      - customNode
1 Like