Wysiwyg-editor: MathJax fails inside froala box

Created on 25 Mar 2015  路  8Comments  路  Source: froala/wysiwyg-editor

Any MathJax notation inside of froala editing box,
for instance, [\frac{a}b] causes [MathJax Processing Error].

Outside froala, MathJax renders with no error.

Check here
https://jsfiddle.net/Marxy/hxxn9ftq/22/

Most helpful comment

Took a little bit of work, but I was able to get MathJax to work using an img. I used the approach from:

http://stackoverflow.com/questions/34924033/convert-latex-mathml-to-svg-or-image-with-mathjax-or-similar

I came up with:

In the index:

<script>
    window.MathJax = {
        jax: ["input/TeX", "output/SVG"],
        extensions: ["tex2jax.js", "MathMenu.js", "MathZoom.js"],
        showMathMenu: false,
        showProcessingMessages: false,
        messageStyle: "none",
        SVG: {
            useGlobalCache: false
        },
        TeX: {
            extensions: ["AMSmath.js", "AMSsymbols.js", "autoload-all.js"]
        }
    };
</script>

<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js"></script>

In Froala Editor Plugin:

function insertMathJaxImage(mathText) {
    var wrapper = document.createElement("div");
    wrapper.innerHTML = wrapper.innerHTML = '\\['+ mathText +'\\]';
    MathJax.Hub.Queue(["Typeset", MathJax.Hub, wrapper]);
    MathJax.Hub.Queue(function () {
        var mathjaxSVG = wrapper.getElementsByTagName("svg")[0];
        mathjaxSVG.setAttribute("xmlns", "http://www.w3.org/2000/svg");
        var svg = mathjaxSVG.parentElement.innerHTML;
        var image = new Image();
        image.src = 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(svg)));
        image.onload = function () {
            var canvas = document.createElement('canvas');
            canvas.width = image.width;
            canvas.height = image.height;
            var context = canvas.getContext('2d');
            context.drawImage(image, 0, 0);
            var imgSrc = canvas.toDataURL('image/png');
            options.editor.selection.restore();
            options.editor.html.insert('<img src="' + imgSrc + '"></img>');
            wrapper = mathjaxSVG = svg = image = canvas = context = imgSrc = null;
        };
    });
}

we convert the contents to strip out the img itself and replace with a data div on save and replace back on load. (keeps server side and other usages simple)

<div data-math-text="2^3=y"></div>

Hope this helps others!

All 8 comments

Sorry, but MathJax is not compatible with our editor. The way MathJax creates the output makes it impossible to render fine in Froala Editor.

It has been a year so I figured I would check, has anyone figured a way to do this? We have a need for mathjax and the Froala editor is perfect for all our needs minus this. Even if its not officially supported has there been anyone who has a workaround?

Took a little bit of work, but I was able to get MathJax to work using an img. I used the approach from:

http://stackoverflow.com/questions/34924033/convert-latex-mathml-to-svg-or-image-with-mathjax-or-similar

I came up with:

In the index:

<script>
    window.MathJax = {
        jax: ["input/TeX", "output/SVG"],
        extensions: ["tex2jax.js", "MathMenu.js", "MathZoom.js"],
        showMathMenu: false,
        showProcessingMessages: false,
        messageStyle: "none",
        SVG: {
            useGlobalCache: false
        },
        TeX: {
            extensions: ["AMSmath.js", "AMSsymbols.js", "autoload-all.js"]
        }
    };
</script>

<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js"></script>

In Froala Editor Plugin:

function insertMathJaxImage(mathText) {
    var wrapper = document.createElement("div");
    wrapper.innerHTML = wrapper.innerHTML = '\\['+ mathText +'\\]';
    MathJax.Hub.Queue(["Typeset", MathJax.Hub, wrapper]);
    MathJax.Hub.Queue(function () {
        var mathjaxSVG = wrapper.getElementsByTagName("svg")[0];
        mathjaxSVG.setAttribute("xmlns", "http://www.w3.org/2000/svg");
        var svg = mathjaxSVG.parentElement.innerHTML;
        var image = new Image();
        image.src = 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(svg)));
        image.onload = function () {
            var canvas = document.createElement('canvas');
            canvas.width = image.width;
            canvas.height = image.height;
            var context = canvas.getContext('2d');
            context.drawImage(image, 0, 0);
            var imgSrc = canvas.toDataURL('image/png');
            options.editor.selection.restore();
            options.editor.html.insert('<img src="' + imgSrc + '"></img>');
            wrapper = mathjaxSVG = svg = image = canvas = context = imgSrc = null;
        };
    });
}

we convert the contents to strip out the img itself and replace with a data div on save and replace back on load. (keeps server side and other usages simple)

<div data-math-text="2^3=y"></div>

Hope this helps others!

Also safari is angry but I found a fix at stack overflow safari ns1

move the image.src below the image.onload declaration

modified to add:

$svg.find('use').each(function() {
    var $use = $(this);
    if ($use.attr('xmlns:xlink')) {
        return false; //we dont need to modify it
    }
    $use.attr('xmlns:xlink', 'http://www.w3.org/1999/xlink'); //add these for safari

});
var svgHtml = $svg.prop('outerHTML');
//firefox fix
svgHtml = svgHtml.replace(' xlink=', ' xmlns:xlink=');
// Safari xlink ns issue fix
svgHtml = svgHtml.replace(/ ns\d+:href/g, ' xlink:href');
image.src = 'data:image/svg+xml;base64,' + window.btoa(svgHtml);

@zalos do you have a working demo of this working somewhere? I'm not totally clear what this is doing.

We're looking at copy and pasting a formula into the Froala editor, and having it render nicely. I guess converting to an image on paste, and then replacing the math formula text with that?

@magician11 I do not have a demo as it's pretty involved with our internal software. But I can get some more of the relevant code and an explanation when I get into the office in a few hours

@magician11 first we include mathjax in our index with:

<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.6.1/MathJax.js"></script>

below is service that we migrated the above code to (we use angular, but i removed all angular stuff to make it simplier

var service = {
            renderMathjaxAsPNG: renderMathjaxAsPNG,
            renderMathjax: renderMathjax
        };

        return service;

        function renderMathjaxAsPNG($html, renderConfig) {
            return $q(function (resolve, reject) {
                if (typeof $html != 'object' || !$html.html) {
                    $html = $('<div>' + $html + '</div>');
                }

                renderConfig = renderConfig ? renderConfig : {};
                //our container is such: <div class="equation-container" data-eq="2^3=y"></div>
                renderConfig.mathContainer = renderConfig.mathContainer ? renderConfig.mathContainer : '.equation-container';
                renderConfig.mathDataAttr = renderConfig.mathDataAttr ? renderConfig.mathDataAttr : 'data-eq';
                var $mathJaxElements = $html.find(renderConfig.mathContainer);
                var currentReturn = 0;
                var numberOfReturns = $mathJaxElements.length;

                if (numberOfReturns == 0) {
                    return resolve($html.html());
                }
                for (var i = 0; i < numberOfReturns; i++) {
                    var $mathjaxElement = $($mathJaxElements[i]),
                        mathContent = decodeURIComponent($mathjaxElement.attr(renderConfig.mathDataAttr));//this is the "2^3=y"

                    renderMathjax(mathContent, $mathjaxElement).then(function (result) {
                        result.element.replaceWith(result.imgHtml);
                    }, function (err) {
                        console.log(err);
                    }).finally(function () {
                        if (++currentReturn >= numberOfReturns) {
                            resolve($html.html());
                        }
                    });
                }


            });

        }

        function renderMathjax(mathContent, $mathjaxElement) {
            return $q(function (resolve, reject) {
                var wrapper = document.createElement("div");
                wrapper.style.fontSize = "400%";//may not need this
                wrapper.style['text-rendering'] = "optimizeLegibility";
                wrapper.innerHTML = '\\[' + mathContent + '\\]';

                //let mathjax do its thing and make the svg
                mathjax.Hub.Queue(["Typeset", MathJax.Hub, wrapper]);
                mathjax.Hub.Queue(function renderMathjaxToImg() {
                    try {
                        //start the conversion from svg to a base64 image source
                        var mathjaxSVG = wrapper.getElementsByTagName("svg")[0];
                        var svg = (new XMLSerializer()).serializeToString(mathjaxSVG);
                        var image = new Image();
                        var canvas = document.createElement('canvas');

                        var mycanvas = document.createElement('canvas');

                        //modify the svg for safari and IE/Edge for proper rendering
                        svg = svg.replace('xmlns:NS1=""', 'xmlns:xlink="http://www.w3.org/1999/xlink"');
                        svg = svg.replace('NS1:xmlns:xlink="http://www.w3.org/1999/xlink"', '');
                        var imgSrc = 'data:image/svg+xml;base64,' +window.btoa(svg);
                        return  resolve({
                            element: $mathjaxElement,
                            imgHtml: '<img src="' + imgSrc + '"></img>',
                            imgSrc: imgSrc
                        });

                    } catch (ex) {
                        reject(ex);
                    }
                });
            });
        }

All you need to do now, as with what we do, is write the mathjax LATex and have it insert into a container, then run the render and replace the html in the editor.

we use this:

 function replaceOrInsertFormula(options) {
            adeMathjaxRenderService.renderMathjax(
                options.mathText
            ).then(function (result) {
                    $(result.element).html(result.imgHtml);
                    if (options.replace) {
                        options.editor.events.focus();
                        options.editor.selection.restore();
                        //options.editor.selection.setAfter(options.node);
                        /*options.editor.cursor.backspace();//remove the space from the setAfter the node
                         options.editor.cursor.backspace();//remove the other image node and replace with the new one*/
                        options.editor.image.remove($(options.node));
                    } else {
                        options.editor.events.focus();
                        options.editor.selection.restore();
                    }
                    var guid = utilities.newGUID();
                    options.editor.html.insert('<img class="rendered-math-jax" id="' + guid + '" data-eq="' + encodeURIComponent(options.mathText) + '" contenteditable="false" src="' + result.imgSrc + '"></img>');
                    options.editor.events.trigger('contentChanged');
                }, function (err) {
                    console.log(err);
                }
            );
        }

(you can ignore my comments on trying to get chrome cursor focus position after image working, another issue for another time)

we open a popup to allow the user to type latex and see a preview before calling the insert method above:

<div class="row">
    <div class="col-xs-6">
        <div class="eq-preview-editor">
            <textarea class="math-jax-editor-input k-input form-control" style="width:100%; height:55px" ng-model="mathText" ng-model-options="{ debounce: 650 }"></textarea>
        </div>
    </div>
    <div class="col-xs-6" ng-show="mathText">
        <math-editor-preview id="math-jax-editor-output" math-content="mathText"></math-editor-preview>
    </div>
    <div class="col-xs-6" ng-show="!mathText">
        <div class="center-block center-text" style="color:lightgrey">type formula to left...</div>
    </div>
</div>

directive as follows. (you can steal the content and use in any framework or just jquery it

angular
        .module('editor.wysiwyg')
        .directive('mathEditorPreview', mathEditorPreview);

    mathEditorPreview.$inject = ['adeMathjaxRenderService'];

    function mathEditorPreview(adeMathjaxRenderService) {


        return {
            restrict: 'E',
            scope: {
                mathContent: '='
            },
            template: '<div eq-preview-wrapper-container"><div id="math-jax-instance-{{$id}}" class="ade-eq-preview-wrapper"></div></div>',
            link: function (scope, element, attrs) {

                scope.isVisible = false;
                scope.innerContent = '';
                scope.$mathJaxPreview = $('#math-jax-instance-' + scope.$id);
                scope.$watch('mathContent', function (newVal, oldVal) {
                    if (newVal === oldVal)
                        return;

                    scope.isVisible = false;
                    scope.$mathJaxPreview.hide();
                    MathjaxRenderService.renderMathjax(
                        scope.mathContent,
                        element.find('.eq-preview-wrapper')[0]
                    ).then(function (result) {
                            $(result.element).html(result.imgHtml)
                        }, function (err) {
                            $(element.find('.eq-preview-wrapper')[0]).html('<div class="alert alert-danger">Failed to render Mathjax</div>');
                        }
                    );
                });
            }
        }
    }

You can then use the replaceOrInsertFormula that has a reference to your editor (ours is options.editor) to insert or replace the mathjax that you clicked to edit.

Hope this helps and let me know if you have any other questions.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

studiotemple picture studiotemple  路  3Comments

DerekJDev picture DerekJDev  路  3Comments

adilsonb picture adilsonb  路  3Comments

kikeso77 picture kikeso77  路  3Comments

lohiaad picture lohiaad  路  4Comments