Implementing GFM Strikethrough on Markdown

I am trying to implement the strike-through but can’t get it to work.
Following this lezer-markdown - npm, i imported Strikethrough

import { Strikethrough } from 'lezer-markdown';

and added it as an extension

EditorState.create({
  doc: '',
  extensions: [
    ...
    markdown({
      base: markdownLanguage,
      extensions: [
        Strikethrough
      ]
    }),
  ],
});

still doesn’t work as if i never added it as an extension.

This is what the html looks like for the supposedly strike-through line:

<div class="cm-line">
  <span class="ͼp ͼ11">~~</span>
  <span class="ͼp">test</span>
  <span class="ͼp ͼ11">~~</span>
</div>

also i have no idea where or why the classes has a prefix of ͼ

other references:

So apparently GFM doesn’t actually render line-through to its strike-through elements. Correct me if I’m wrong, one reason why is because they felt it should be done manually through CSS styling.

Anyways i have this working by implementing my own Markdown inline parser:

Create a markdown extension and export your custom tags:

// strikethrough.js

import { styleTags, Tag, tags } from "@codemirror/highlight"

export const strikethroughTags = {
    strikethrough: Tag.define(),
};

const StrikethroughDelim = { resolve: "Strikethrough", mark: "StrikethroughMark" };

export const Strikethrough = {
    defineNodes: ["Strikethrough", "StrikethroughMark"],
    parseInline: [{
        name: "Strikethrough",
        parse(cx, next, pos) {
            if (next != 126 /* '~' */ || cx.char(pos + 1) != 126) {
                return -1;
            }
            return cx.addDelimiter(StrikethroughDelim, pos, pos + 2, true, true);
        },
        after: "Emphasis"
    }],
    props: [
        styleTags({
            StrikethroughMark: tags.processingInstruction,
            'Strikethrough/...': strikethroughTags.strikethrough
        })
    ]
}

Add the custom tags to your highlight styles

// highlight.js

import { HighlightStyle, tags } from '@codemirror/highlight';
import { strikethroughTags } from './strikethrough';

export const myHighlightStyle = HighlightStyle.define([
  ...,
  { tag: strikethroughTags.strikethrough, textDecoration: 'line-through' }
]);

Finally add your custom strikethrough custom extension

import { Strikethrough } from './strikethroughHighlight';
...

const initialState = EditorState.create({
  doc: '',
  extensions: [
   ...,
    markdown({
      base: markdownLanguage,
      extensions: [
        Strikethrough,
      ]
    }),
    myHighlightStyle,
  ],
});

Honestly it’s hard to follow the docs alone, but fortunately with the active help of the maintainer and others in answering to all of the questions in the forum, it can guide me to the right direction.

Add’tl references: https://www.npmjs.com/package/lezer-markdown

Reimplementing the strikethrough extension should definitely not be necessary for this. You should be able to just add your custom style tag for the existing extenion’s node type (just the {props: [styleTags({"Strikethrough/...": strikethroughTags.strikethrough})]} part).

The current setup assigns the tags.deleted style tag to strike-through text, so you could also just style that. But I guess, since none of the current themes style that with strike-through, I guess that is a bit confusing. Maybe a separate style tag for strike-through would be worthwhile.

Never knew i could extend an existing node type with this setup:

export const Strikethrough = {
    defineNodes: ["Strikethrough"],
    props: [
        styleTags({
            'Strikethrough/...': tags.deleted
        })
    ]
}

and

{
    tag: tags.deleted,
    textDecoration: 'line-through',
},

this works but is this the correct way of extending a node type?

Maybe a separate style tag for strike-through would be worthwhile.

I can vouch for this.

You don’t need the defineNodes part (the node is already defined by the strikethrough extension in the library).

What other use does deleted serve? If there is a potential conflict where applying a strikethrough doesn’t make sense, I’d also opt for a separate tag.

Right, i also don’t know other use cases for having deleted and strikethrough tag, in HTML since del tag performs a strikethrough.

But i also think its better to have a separate tag since CM 5 does it, and for future use cases.

Got it, works now. I misunderstood earlier how this thing works.

export const Strikethrough = {
    props: [
        styleTags({
            'Strikethrough/...': tags.deleted
        })
    ]
}

The deleted style tag mostly exists for things like a diff/patch language mode, where you’d used it for the removed parts. I feel that styling all of those with strikethrough would get annoying/unreadable.

I’ve added a separate strikethrough tag, similar to emphasis/strong/link, to @codemirror/highlight to make this easier in the future.

1 Like

That makes sense. Thanks!

I just tested this out, and it’s working as expected. When will the next version of @codemirror/lang-markdown be released?

I’ve tagged `lang-markdown` 0.18.3 which includes this.

1 Like