All threads / GoRails Markdown and Preview

Ask A Question

Notifications

You’re not receiving notifications from this thread.

GoRails Markdown and Preview

Ivor Padilla asked in Gems / Libraries

Hey Chris,

Just wondering what are you using to preview Markdown in forum posts? I watched the whole Forum series and I saw the other video about html-pipeline gem but you don't mention syntax highlight nor Markdown preview.

Thanks in advance.

I don't know which gem is GoRails using but I can recommend you a very good gem for that, it's called RedCarpet, it's what powers Github's markdown rendering, maybe that's the one... Check it out here.

I agree with Enrique. I've used RedCarpet as well, works great.

I'm using RedCarpet + Pygments for the final rendering with syntax highlighting and I'm using the marked Javascript library to render the previews when the text box content changes.

# Markdown previews to comments
class Comment
  constructor: (element) ->
    @element = $(element)
    @commentField = @element.find("[data-behavior='comment-body']")
    @previewArea = @element.find("[data-behavior='comment-preview']")
    @setEvents()

  setEvents: ->
    @commentField.on "change", @handlePreview

  handlePreview: =>
    html = marked @commentField.val()
    @previewArea.html html

jQuery ->
  $.each $("[data-behavior='comment-form']"), (i, element)->
    new Comment(element)

I don't mean to bring up a sort of old post, but wanted to post this here in case someone else was running into similar issues.

I was working on a form where I wanted a live preview of the parsed markdown on the same page and I like to see things in real time so I wanted to update the preview any time a key is pressed or I unfocus on the text field.. here's my coffeescript that I added to from what Chris posted.

Oh and I for whatever reason could not get @previewArea.html html to actually update the preview area so I changed it to select the ID of the div which I had set to preview.

class PostPreview
  constructor: (element) ->
    @element = $(element)
    @commentField = @element.find("[data-behavior~='post-content']")
    @previewArea = @element.find("[data-behavior='post-preview']")
    @setEvents()

  setEvents: ->
    @commentField.on "focusout", @handlePreview
    @commentField.on "change", @handlePreview
    @commentField.on "keyup", @handlePreview

  handlePreview: =>
    html = marked @commentField.val()
    document.getElementById('preview').innerHTML = html;

jQuery ->
  $.each $("[data-behavior='post-form']"), (i, element)->
    new PostPreview(element)

All in all basically the same as Chris. If there's a better way to do this or reasons I shouldn't I'd love to hear em.

In Turbolinks 5, the equivalent method of jQuery -> is

document.addEventListener("turbolinks:load", function() { ... }

However, from the docs:

When possible, avoid using the turbolinks:load event to add event listeners directly to elements on the page body. Instead, consider using event delegation to register event listeners once on document or window.

What this means is that events on a page bubble up the document tree. You can catch an event for a <li> on it's parent <ul>, and further than that -- all the way up to the document. So you could actually rewrite this Comment coffeescript class as:

$(document).on "change", "[data-behavior='post-form'] [data-behavior='post-body']", (event, object) ->
  html = marked $("[data-behavior='post-form'] [data-behavior='post-body']").val()
  $("[data-behavior='post-form'] [data-behavior='post-preview']").html html

However, I think this has reduced levels of developer clarity compared to the Comment implementation.

I think you could abstract out the selectors and make defining those communicate the intent clearly, as well as potentially let you drop in a bunch of different setups of selectors if you want the preview functionality in more places than one.

class Preview
  constructor: (element, inputField, previewField) ->
    @element =      element
    @inputField =   inputField
    @previewField = previewField

  inputSelector: =>
    @element + " " + @inputField

  previewSelector: =>
    @element + " " + @previewField

previews = [
  new Preview("[data-behavior='post-form']", "[data-behavior='post-body']", "[data-behavior='post-preview']")
]

for preview in previews
  do ->
    $(document).on "change", $(preview.inputSelector()), (event, object) ->
      html = marked $(preview.inputSelector()).val()
      $(preview.previewSelector()).html html

This would let you reuse "Previews" all over the place in a way thats turbolinks 5-friendly, and all you have to do is add in another Preview object with new selectors into the previews array. Total overkill right here, but interesting to think through.

Join the discussion

Want to stay up-to-date with Ruby on Rails?

Join 37,629+ developers who get early access to new tutorials, screencasts, articles, and more.

    We care about the protection of your data. Read our Privacy Policy.

    logo Created with Sketch.

    Ruby on Rails tutorials, guides, and screencasts for web developers learning Ruby, Rails, Javascript, Turbolinks, Stimulus.js, Vue.js, and more. Icons by Icons8

    © 2020 GoRails, LLC. All rights reserved.