HTML card content corrupted after publishing

Issue Summary
I’m trying to use mermaid.js to embed charts and class diagrams and finding that the content of a HTML tag is getting messed up after publishing. It works fine in preview.

Steps to Reproduce

  1. Use ghost’s code injection to install mermaid in site footer:
<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.7.0/mermaid.min.js"></script>
<script>mermaid.initialize({startOnLoad:true});</script>
  1. Create a post with a HTML card and set the content to <div class="mermaid">. See below for the actual content I’m using.
  2. Preview the post and confirm the page is rendered correctly.
  3. Publish the post and note it doesn’t render correctly with mermaid complaining about syntax errors. View source shows the HTML card content is messed up. eg: errant </]> before the closing div.
<div class="mermaid">
flowchart TD
    if[if statement]
    cond[<]
    x[x]
    y[y]
    
    if --condition--> cond
    cond --> x
    cond --> y

    if --trueblock--> true
    true[codeblock] --statements--> meth1

    if --falseblock--> false
    false[codeblock] --statements--> retstmt

    meth1[method call]
    meth1--LHS-->id1
    meth1--parameters-->p1
    p1[expr literal 'less']
    id1[identifier 'print']
    
    retstmt[return] --value--> retval
    retval[expr literal 0]

</div>

Setup information

Ghost Version
docker image ghost:latest (5+)

Ghost-CLI version: 1.25.3
Ghost version: 5.75.3 (at /var/lib/ghost)

Node.js Version
18.19

How did you install Ghost?
Latest docker image from dockerhub

Provide details of your host & operating system
Docker running on ubuntu, Linux

Database type
MySQL 8

Browser & OS version
N/A

Relevant log / error output

Here’s the corrupted output of the above HTML card: (note the weird tag just before the closing div)

<div class="mermaid">
flowchart TD
    if[if statement]
    cond[<] x[x] y[y] if --condition--> cond
    cond --> x
    cond --> y

    if --trueblock--> true
    true[codeblock] --statements--> meth1

    if --falseblock--> false
    false[codeblock] --statements--> retstmt

    meth1[method call]
    meth1--LHS-->id1
    meth1--parameters-->p1
    p1[expr literal 'less']
    id1[identifier 'print']
    
    retstmt[return] --value--> retval
    retval[expr literal 0]

</]></div>

Example 2

Here’s a second, worse example where the rest of the post was cropped after the HTML tag.

(Again, this works fine in preview but is completely broken once published)

HTML Card Content:

<div class="mermaid">
classDiagram
    class AstElement{
        +CodePosition Position
    }
    AstElement <|-- AstStatement
    AstElement <|-- AstExprNode

    AstStatement <|-- all_statements
    AstExprNode <|-- all_exprnodes

    class all_statements["all statement types"]

    class all_exprnodes["all expression nodes types"]

</div>

Published Content:

<div class="mermaid">
classDiagram
    class AstElement{
        +CodePosition Position
    }
    AstElement <|-- aststatement astelement <|-- astexprnode all_statements all_exprnodes class all_statements["all statement types"] all_exprnodes["all expression nodes < div>

Are you viewing the “corrupted” content in web inspector or what’s actually served by Ghost? Mobile at the moment so haven’t had a chance to test but my guess is that the browser is getting confused by the HTML-like < chars in the content and attempting to fix the invalid markup. I would suggest first switching to valid HTML by using &lt; and &gt; in place of your < and > chars in the code examples

I’ve had a chance to test now and my guess wasn’t entirely correct. Using &lt; and &gt; will still be needed to avoid problems with invalid HTML but currently when the editor renders the content those get converted back to < and > which then breaks later HTML-parsing steps.

I’ve opened an issue to get it fixed. Thanks for the report.

1 Like

Hi Kevin,

Thanks for looking into this.

I’ve now found a solution which is to use ``` code blocks with language “mermaid” and some custom code to resolve conflicts between Prism and Mermaid. It’s a bit messy but works, and actually presents the mermaid code more cleanly in the editor.

Brad

<script>
// Prevent Prism auto highlighting
window.Prism = window.Prism || {};
Prism.manual = true;
</script>

<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/prism.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-css.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-csharp.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-markup.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-javascript.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-vhdl.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.7.0/mermaid.js"></script>
<script>
// Initialize mermaid and run on code blocks with language 'mermaid'
mermaid.initialize({  
    startOnLoad:false,
});
mermaid.run({
  nodes: document.querySelectorAll('.language-mermaid'),
});

// Apply syntax highlighting to all non-mermaid language blocks
let codeBlocks = document.querySelectorAll("[class^='language-']");
for (let cb of codeBlocks)
{
  if (!cb.matches(".language-mermaid"))
    Prism.highlightElement(cb);
}
   
</script>

Brad