ruby-on-railsrubymarkdownredcarpet

Implementing preview for markdown text


I am working on Ruby on Rails project and I have implemented markdown syntax for some text descriptions in my project using redcarpet gem.

It works like charm allowing to convert markdown text to HTML as simply as

<%= markdown some_text_variable %>

But now I want to implement preview feature rendering just small part of the full text.

The following naive construction

<%= markdown some_text_variable[0..preview_length] %>

will not work because it can easily break down MD syntax resulting in confusing constructions (imagine, for example, spliting original string on the half of image link).

I came up with

<%= markdown some_text_variable[0..preview_length].split(/\r?\n/)[0..-2].join("\r\n")) %>

but it does not deal, for example, with code blocks.

Is there any way to implement such kind of preview for MD text?


Solution

  • Using markdown.js and / or showdown should work. Here's a StackO with the same question and answer. I personally have used showdown in an Ember app before to render a live preview of the text as it's being typed (via 2-way data binding), and it worked flawlessly.

    In the fiddle below, I wrote a little Showdown parser that takes in a string of markdown, splits it on a newline (returns an array of tags), and iterates through the array. On each iteration, it removes the tags, checks the length of the resulting string, and then compares it to the max character count for the preview. Once the next iteration surpasses the max character count, it returns the preview. The do loop ensures that you will always get at least one blob of html as a preview.

    Fiddle

    $(function() {
      var converter = new Showdown.converter();
      var previewMax = 200;
    
      $('button').click(function() {        
        var content = $('#markdown').val(),
            charCount = 0,
            i = 0,
            output = '';
    
        if (!content) {
          return $('div.preview').html("Please enter some text.");
        }
    
        var mark = converter.makeHtml(content);
        var mark_arr = mark.split('\n');
    
        while (charCount < previewMax) {
          var html = mark_arr[i];
          var text = htmlStrip(html);            
    
          if ((charCount + text.length) > previewMax) {
            var overflow = (charCount + text.length) - previewMax;
            var clipAmount = text.length - overflow;
            html = jQuery.truncate(mark_arr[i], { length: clipAmount });
            }
    
          output += html;
          charCount += text.length;
          i++;
        };
    
        $('div.preview').html(output);
        $('div.full').html(mark);
      });
    
      function htmlStrip (html) {
        var div = document.createElement('div');
        div.innerHTML = html;
        var text = div.textContent || div.innerText || "";
        return text;
      }
    });
    

    REVISION

    I updated the function using jQuery Truncate to cut the final string into an elipses so that all your previews are the same length as the others. Also, I realized that the original function returned a long string of undefined' over and over when no text was entered, so there is a check to eliminate that. Since this loop will always return at least one html item now, I changed the do loop to a while loop for easier reading. Finally, if you want your truncation to always end at a word boundary, pass the words: true option when you call it. Obviously, this will not give you the same level of truncation for every preview, but it will improve legibility. That's it!