How to override MacroBrowser postPreview function?

Hi all, I am currently modifying the Mermaid Macro Plugin on Confluence Server such that users can input the diagram code directly in the macro browser dialog. I’ve accomplished this part but now the preview does not show the diagram.

I know I need to override the “postPreview” function but I can’t seem to find any examples on how. Would really appreciate any help. Thanks!

Hi @ChongJingHong ,

This depends on the current macro implementation details. I had a brief look at mermaid-js and your open source plugin code. It is currently implemented as a rich-text macro, whereby it is taking the macro body that the user enters, doing some slight cleanup/escaping and then rendering the html. Then on the front-end mermaid-js uses that initial html to render the diagram.

Therefore I believe that to support rendering from the markup provide in a macro parameter, you will need to modify MermaidMacro.java to handle the data coming from the parameter. The parameter content will be available as a string in the execute method. You will want to invoke renderDynamic with the parameter value instead of the body, and you may need to do some additional formatting to ensure that the final html is in the format that mermaid-js expects.

Regards,
Alex

Hi Alex, thanks for your reply.

I’ve done what you wrote above and the preview still shows the string instead of the diagram.

@Override
	public String execute(Map<String, String> parameters, String body, ConversionContext context)
			throws MacroExecutionException {

...

String code = null;
		if (parameters != null){
			code = parameters.get("Code");
		}
if (ConversionContextOutputType.PDF.value().equals(outputType)
				|| ConversionContextOutputType.WORD.value().equals(outputType)
				|| ConversionContextOutputType.FEED.value().equals(outputType)
				|| ConversionContextOutputType.EMAIL.value().equals(outputType)) {
			return renderImage(code);
		} else {
			pageBuilderService.assembler().resources().requireContext("mermaid-plugin");
			return renderDynamic(code, theme, width);
		}
}

This is the string that is returned at the end of the renderDynamic(code, theme, width) function:

[INFO] [talledLocalContainer] <div class=“mermaid” style=“overflow-x: auto; width: 100%”>
[INFO] [talledLocalContainer] %%{init: {‘theme’:‘default’}}%%
[INFO] [talledLocalContainer] sequenceDiagram
[INFO] [talledLocalContainer] Alice->>+John: Hello John, how are you?
[INFO] [talledLocalContainer] Alice->>+John: John, can you hear me?
[INFO] [talledLocalContainer] John–>>-Alice: Hi Alice, I can hear you!
[INFO] [talledLocalContainer] John–>>-Alice: I feel great!
[INFO] [talledLocalContainer] </div>

which is the same as when I try to render the diagram from the body.

Hey @ChongJingHong ,
If it is just showing the string directly and not a parsing error that might indicate that the mermaid javascript code is not running. You could try adding a breakpoint in your browser dev tools to the mermaid-plugin.js code and verify that it is running and not hitting any errors.

Could you also copy the exact html that is contained within the macro preview and share it?

Although when I test on the master branch with your sequence diagram code I am seeing a mermaid parsing error. Could you try with a very simple diagram such as:

sequenceDiagram
Alice->>John: Hello John, how are you?

If the macro execute method is returning the exact same thing for both the old version and new version, then I can’t see what would be the problem unless the javascript resources have somehow broken.
If you’d like you can push the branch to the repository and I can take a look as well.

Regards,
Alex

Hi @aknight,

You are right that mermaid-plugin.js is not executing. But I’m not sure why.

Here’s the html you asked for:

<div id="macro-browser-preview" class="macro-preview">
    <iframe src="/confluence/s/q86or5/8703/NOCACHE/_/blank.html" frameborder="0" name="macro-browser-preview-frame" id="macro-preview-iframe">
        #document
            <html class="js-focus-visible" data-js-focus-visible="">
                <head>
                    <title>Preview Macro</title>
                     ... <!-- meta tags here -->
               </head>
               <body id="com-atlassian-confluence" class="content-preview vsc-initialized" data-aui-version="9.2.0">
                   <div id="main">
                       <div id="content" class="page edit">
                           <div class="wiki-content">
                               <div class="mermaid" style="overflow-x: auto; width: 100%">
                                    %%{init: {'theme':'default'}}%%
                                    sequenceDiagram
                                        Alice->>+John: Hello John, how are you?
                               </div>
                           </div>
                       </div>
                   </div>
               </body>
           </html>
    </iframe>
</div>

I don’t have permission to create a branch on the original repository but here’s my github repo with my code.

Hi @ChongJingHong ,
I had a look at your version of the repo and confirmed that the mermaid js resources are no longer being loaded. This is because of the change to comment out the web resource context and change it to the editor context:

<!-- <context>mermaid-plugin</context> -->
<context>editor</context>

The way that the javascript resources are being loaded is with the following call in the MermaidMacro.java

pageBuilderService.assembler().resources().requireContext("mermaid-plugin");

Given that the macro preview is in a iframe it has a separate context to the editor. Also by changing it to the editor context you are limiting the macro from being able to display on the view page/blog. You should definitely revert that change and go back to requiring the context everywhere that the macro is rendered.

Just as an FYI, an alternative to loading via context is to load the web resource directly

pageBuilderService.assembler().resources().requireWebResource("mermaid-plugin-resources");

Screenshot below showing that the macro works after reverting back to loading the mermaid resources. The parameter is stored as a string in the macro xml and thus the macro also displays once the page is published.

Regards,
Alex

Hey @aknight, it works. Thank you so much! :grinning:

Hi @aknight , @ChongJingHong

I have a requirement to put some custom changes to Macro input fields. I tried to do in the same way as you mentioned . But it seems not working at all for me . Could you please tell me what is wrong in this code. ?
This is my custom-editor.js file code.

//reference: confluence-stash-macro/stash-macro.js at master · Scuilion/confluence-stash-macro · GitHub
// Documentation for AJS.MacroBrowser.setMacroJsOverride - #2 by aknight
// How to override MacroBrowser postPreview function? - #3 by ChongJingHong

(function($) {
var Helloworld = function(){
};
var originalCode;
console.log(“Inside custom-editor.js”);
//overrides the parameter field UI based on parameter type or parameter name
Helloworld.prototype.fields = {

    "string" : function(param, options){
    console.log("param:"+param);
        if(param.name == "Code"){

            // Confluence.Templates.MacroBrowser.macroParameter() returns a String:
            // <div class="macro-param-div"><label></label><input type="text" class="text"/></div>
            // paramDiv creates the above elements
            var paramDiv = AJS.$(Confluence.Templates.MacroBrowser.macroParameter());
            console.log("paramDiv:"+paramDiv);
            console.log("inside fields override");

            // $(selector, context)
            // selector - A string containing a selector expression i.e. id of tag
            // context - A DOM Element, Document, jQuery or selector to use as context
            // create paramDiv with id="macro-para-div-Code"
            var input = AJS.$("macro-param-div-Code", paramDiv);
            console.log("input:"+input);

            var textArea = document.createElement("textarea");
            textArea.style.resize = "none";
            textArea.style.overflow = "scroll";
            textArea.style.whiteSpace = "nowrap";
            textArea.setAttribute("rows", "12");
            textArea.setAttribute("cols", "28");
            textArea.setAttribute("id", "macro-param-Code");
            textArea.setAttribute("class", "macro-param-input");

            var label = document.createElement("label");
            label.innerHTML = "Code";
            label.setAttribute("for", "macro-param-Code");

            paramDiv.empty();
            paramDiv.append(label);
            paramDiv.append(textArea);

            return AJS.MacroBrowser.Field(paramDiv, input, options);
        }
    }
};

//a function to run before an existing macro is loaded into the parameter form fields
helloworld.prototype.beforeParamsSet = function(selectedParams, macroSelected){
    originalCode = selectedParams.code;
    $("#macro-param-Code").val(originalCode);
    console.log("Orignal Code : " + originalCode);
    return selectedParams;
};


//a function to run before the form fields are converted into a macro parameter string
Helloworld.prototype.beforeParamsRetrieved = function(params){
    params.code = $("#macro-param-Code").val();
    console.log("params.code : " + params.code);
    return params;
};

//called with the preview iframe element and macro metadata when the user previews the macro
// MermaidMacro.prototype.postPreview = function(iframe, macro){
//     console.log("postPreview iframe : " + iframe);

//     console.log($(AJS.$('.mermaid')));

//     console.log(typeof mermaid.init(undefined, $(AJS.$('.mermaid'))));
//     iframe.srcdoc = "hello";

//     return iframe;
// }

AJS.toInit(function(){
console.log("Inside ajs init");
    AJS.bind("init.rte", function(){
        console.log("inside init.rte");
        AJS.MacroBrowser.setMacroJsOverride('helloworld', new helloworld());
    })
})

})(AJS.$);

<web-resource key="myConfluenceMacro-resources" name="myConfluenceMacro Web Resources">
    <dependency>com.atlassian.auiplugin:ajs</dependency>
    
    <resource type="download" name="myConfluenceMacro.css" location="/css/myConfluenceMacro.css"/>
    <resource type="download" name="helloworld.js" location="/js/helloworld.js"/>
    <resource type="download" name="custom-editor.js" location="/js/custom-editor.js" />
    <resource type="download" name="images/" location="/images"/>

    <context>myConfluenceMacro</context>
  <!--  <context>atl.general</context>-->
</web-resource>

<xhtml-macro name="helloworld" class="com.atlassian.tutorial.macro.helloworld" key='helloworld-macro'>
    <description key="helloworld.macro.desc"/>
    <parameters>
        <parameter name="Name" type="string" />
        <parameter name="Code" type="string"/>
    </parameters>
</xhtml-macro>

I have also tried to put a debugger break points and found out control is not going inside “Helloworld.prototype.fields = {” function.

Please help to comment.

Thanks.