evilstreak / markdown-js

A Markdown parser for javascript
7.7k stars 861 forks source link

Mathjax? #129

Open asselinpaul opened 11 years ago

asselinpaul commented 11 years ago

Has anyone gotten this to work with MathJax? http://www.mathjax.org/

kasperpeulen commented 11 years ago

I got it to work. But there are some issues. Markdown sometimes alters the TeX code. For example with $a^*=b^*$ or with a lot of subscript (_) in the tex code. What I do now is: 1.placing math in <code> blocks, 2. allowing mathjax processing in code blocks, and 3. restyling those code blocks It's a simple javascript code, if you are intrested let me now. However, I would rather not have to (and let my user need to) put all the latex in code blocks. I've adressed this issue with the mathjax community, and they responded with:

that seems to be a Markdown issue here: Markdown should probably have a new syntax to include math formulas or at least some tag to indicate a section that should not be processed. Otherwise, if the TeX source is altered, MathJax can not do anything with it.

ashb commented 11 years ago

Cool! Glad you found some working solution.

So they are probably right - a proper solution might be to have $ open a new inline element that stops processing until the closing $ is seen and just passes the entire thing through as is.

We haven't gotten around to writing docs on how to extend/create dialects but if you have a look at how the Maruku dialect to inherit from the Gruber dialect hopefully that should give you some pointers.

kasperpeulen commented 10 years ago

@asselinpaul I've got it to work now. Are you still interested ?

I use this:

Discourse.Dialect.inlineBetween({
  start: '\\(',
  stop: '\\)',
  rawContents: true,
  emitter: function(contents) { return '\\('+contents+'\\)'; }
});

Discourse.Dialect.inlineRegexp({
start: '$',
matcher: /(\$)([\S\s]+)(\$)/,
emitter: function(matches) {return matches[0];}
});

Discourse.Dialect.replaceBlock({
  start: /(\\[)([\s\S]*)/,
  stop: '\\]',
  rawContents: true,
  emitter: function(contents) { return '\\['+contents+'\\]'; }
});

Discourse.Dialect.inlineRegexp({
  start: '\\begin',
  matcher: /(\\begin{[\S\s]+})([\S\s]*)(\\end{[\S\s]+})/,
  emitter: function(matches) { return matches[0]; }
});

Look at this, for how those functions are defined: https://github.com/discourse/discourse/blob/549060867dd1a94d3c74ca61c0c9be7bd37c035b/app/assets/javascripts/discourse/dialects/dialect.js

You will also need this pull request: https://github.com/evilstreak/markdown-js/pull/154

If you are a little experienced with javascript, this is probably not that difficult to get this right. If you are like me, it will probably take some time, but it must be doable.

meghprkh commented 10 years ago

can you pls explain for beginner how to integrate in browser

joosti commented 10 years ago

Clone the repository, then include a file with the following code in src/dialects.; Include the filename in src/markdown.js (so that it is compiled); grunt all and then you can use the compiled js in your web page. Apply text as console.log( md.toHTML( "this text *also* has $ MathJax$ " , "Ti" ) );

    if (typeof define !== 'function') { var define = require('amdefine')(module) }
define(['../markdown_helpers', './dialect_helpers', './maruku', '../parser'], function (MarkdownHelpers, DialectHelpers, Maruku, Markdown) {

  var Ti = DialectHelpers.subclassDialect( Maruku ),
      extract_attr = MarkdownHelpers.extract_attr,
      forEach = MarkdownHelpers.forEach;

  // helper function
  function invalidBoundary(args, prev) {
      if (!args.wordBoundary && !args.spaceBoundary) { return; }
      var last = prev[prev.length - 1];
      if (typeof last !== "string") { return; }
      if (args.wordBoundary && (last.match(/(\w|\/)$/))) { return true; }
      if (args.spaceBoundary && (!last.match(/\s$/))) { return true; }
    }

  Ti.registerInline = function(start, fn) { this.inline[start] = fn; };

  Ti.inlineRegexp = function(args) {
    this.registerInline(args.start, function(text, match, prev) {
      if (invalidBoundary(args, prev)) { return; }
      args.matcher.lastIndex = 0;
      var m = args.matcher.exec(text);
      if (m) {
        var result = args.emitter.call(this, m);
        if (result) {
          return [m[0].length, result];
        }
      }
    });
  };

  Ti.inlineRegexp({
    start: '$',
    matcher: /(\$)([\S\s]+)(\$)/,
    emitter: function(matches) { return matches[0]; }
  });
  Ti.inlineRegexp({
    start: '\\begin',
    matcher: /(\\begin{[\S\s]+})([\S\s]*)(\\end{[\S\s]+})/,
    emitter: function(matches) { return matches[0]; }
  });

  Markdown.dialects.Ti = Ti;
  Markdown.dialects.Ti.inline.__escape__ = /^\\[\\`\*_{}\[\]()#\+.!\-|:]/;
  Markdown.buildBlockOrder ( Markdown.dialects.Ti.block );
  Markdown.buildInlinePatterns( Markdown.dialects.Ti.inline );

  return Ti;
});
qbolec commented 9 years ago

While I think that extending a dialect is the best way to approach this problem, a quick and dirty solution is to split the text into parts of text and latex and then apply mardown.toHTML only on text parts. The problem with this is that you have to remove the wrapping <p></p> and once you do that you may end up with some leading/trailing whitespace missing on boundaries between text and latex. What I mean is something like:

"Inline TeX: $a_5=a_3$ and *display* in separate line $$a^*=b^*$$".replace(/([^$]*)([$]+[^$]*[$]+)?/g,function(a,text,code){
  return markdown.toHTML(text).replace(/^<p>|<\/p>$/g,'') + (code?_.escape(code):'');
})

Another problem with this approach is that it's conceptually backwards (it treats markdown not latex as small injected parts) so if you have some larger structure of Markdown (say, a list) it will probably be broken into multiple parts.

Personally I just needed ability to make some parts of the text bold, so I went with the following oneliner:

return str.replace(/([^$]*)([$]+[^$]*[$]+)?/g,(a,text,code) => (_.escape(text).replace(/[*]([^*]*)[*]/g,'<strong>$1</strong>')+(code?_.escape(code):'') ));