Simplistic Texteditor for Your Next Ruby On Rails Webapplication

Introduction

Do you want to come to the metaparty? Okay it is not a party tool it is a political site in German where the party member itself create the party platform. The vision is: if you vote for metapartei you will get representative candidates which supports the oppinion of the nation – not of a specific lobby. As a member of Metapartei you can create concepts (ideas) and vote for them. At some day the set of all voted concepts will be our party platform.

Metapartei.de was written entirely with rails and with the help NetBeans as IDE (I could displace aptana ;-)). It is and it was really fun. Especially the database migrations in rails are fantastic for production! Okay sometimes ruby wasn’t that much fun. For example the following passes (very strange!):

assert_equal 2, "ÄÄa__".mb_chars.index("a", 4)

In the meanwhile we have to decide how the user can input some more than only text. If you would allow pure html then you would introduce a big security hole! For example someone could then insert any javascript function he likes with e.g. the <script> tag. So we need a rich text editor. But which one? There are a lot of editors out to allow the user to mark up the text. See the appendix for some we had evaluated. But we finally choose

Control.TextArea

from Ryan Johnson, because it is very small and highly customizable. See here for a life example. The small trade-off we had to pay was that we had to implement the conversation from wiki text to html. But this was relative easy. See The ruby code (under public domain) for the WikiText class, which implements the following behaviour:

  • ”bold” to bold
  • ”’italic”’ to italic
  • line breaks to <br />
  • and links like [http://www.metapartei.de Metapartei – a great idea] to Metapartei – a great idea

You can simply add other things (just let me know if you implement lists ;-)). But how you can embed the javascript with ruby? Here is the answer:

TextArea within Rails

Get the textarea.js from the TextArea bundle and put it under public/javascripts. Then create a file textarea_init.js from the code in the The javascript code section. Then insert

<%= javascript_include_tag 'textarea' %>

into app/views/layouts/application.html.erb (<head>HERE</head>). Then directly before the closing </body> tag write

<%= javascript_include_tag 'textarea_init' %>

Additionally you have to add a mark_down specific css file get it from the TextArea project too.

Now you can use it where you like. E.g. in the new template of the comments controller. Within new.html.erb add the following line as the very last:

<div id="markdown_formatted"></div>

But be sure that you specify at least one id for the text content (e.g. like we did: comment_content). See the javascript code section.

That’s it. Hope you enjoy it!

The ruby code

class WikiText
  def initialize(str)
    # process umlaute correctly
    @text = str.mb_chars
  end

  def to_html
    str, ii = process_sub_tag(nil, 0)
    str
  end  

  # returns the resulting string and the processed characters
  def process_sub_tag(until_tag_name, from_index)
    ii = from_index
    if(until_tag_name != nil)
      found_tag_index = @text.index(until_tag_name, from_index)
      if(found_tag_index != nil)
        until_index = found_tag_index + until_tag_name.length
      else
        return "", 0
      end
    end
    until_index = @text.length if until_index == nil

    result = ""
    while(ii < until_index)
      # F... ruby: '\n' != "\n"
      if (@text.at(ii) == "\n")
        result << "\n<br />"
        ii += 1

      elsif check_chars(ii, "'''")
        ii += 3
        if(until_tag_name == "'''")
          break
        end
        str, p_chars = process_sub_tag("'''", ii)
        result << "<b>" + str + "</b>"
        ii += p_chars

      elsif check_chars(ii, "''")
        ii += 2
        # closing tag
        if(until_tag_name == "''")
          break
        end
        last_index = @text.index("''", ii)
        if(last_index != nil)
          str, p_chars = process_sub_tag("''", ii)
          result << "<i>" + str + "</i>"
          ii += p_chars
        end

      elsif check_chars(ii, "]")
        ii += 1
        break;

      elsif check_chars(ii, "[")
        ii += 1
        last_index = @text.index("]", ii)
        if(last_index != nil)
          res = @text[ii, last_index-1].split(' ')
          if(res.length > 1 && !res[1].blank?)
            ii += res[0].length+1
            str, p_chars = process_sub_tag("]", ii)
            result << '<a target="_blank" href="'+ res[0]+'">'+str+"</a>"
            ii += p_chars
          end
        end
      else
        result << @text.at(ii)
        ii += 1
      end
    end
    return result, ii - from_index
  end

  # returns true if the specified chars match directly from the current position
  def check_chars(index, chars)
    len = chars.length
    #if (index > 0 && @text[index-1, 1] == chars.at(0) ||
    #      index + len < @text.length && @text[index+len, 1] == chars.at(0))
    #  return false
    #end
    @text[index, len] == chars
  end
end
# WARNING: with unicode it will not work 100%. See the strange assertion at the beginning of the blog post

The JavaScript code

/**
 * @author Ryan Johnson <http://saucytiger.com/>
 * @copyright 2008 PersonalGrid Corporation <http://personalgrid.com/>
 * @package LivePipe UI
 * @license MIT
 * @url http://livepipe.net/control/textarea
 * @require prototype.js, livepipe.js, textarea.js
 */

if(typeof(Control.TextArea) == "undefined")
    throw "Initialization requires Control.TextArea to be loaded.";

var idOfComponent = 'comment_content';
if($(idOfComponent) == null) {
    idOfComponent = 'concept_content';
}

if($(idOfComponent) != null) {
    var textarea = new Control.TextArea(idOfComponent);

    var toolbar = new Control.TextArea.ToolBar(textarea);

    //for css styles
    toolbar.container.id = 'markdown_toolbar';

    //buttons
    toolbar.addButton('Italics',function(){
        this.wrapSelection("''","''");
    },{
        id: 'markdown_italics_button'
    });

    toolbar.addButton('Bold',function(){
        this.wrapSelection("'''","'''");
    },{
        id: 'markdown_bold_button'
    });

    toolbar.addButton('Link',function(){
        var selection = this.getSelection();
        var response = prompt('Enter Link URL','');
        if(response == null)
            return;
        this.replaceSelection('[' + (response == '' ? 'http://link_url/' : response).
            replace(/^(?!(f|ht)tps?:\/\/)/,'http://') + ' ' + (selection == '' ? 'Link Text' : selection) + ']');
    },{
        id: 'markdown_link_button'
    });

    toolbar.addButton('Help',function(){
        window.open('/documents/wiki_text');
    },{
        id: 'markdown_help_button'
    });
}

Appendix

For more editors look here.

One thought on “Simplistic Texteditor for Your Next Ruby On Rails Webapplication

Comments are closed.