-  -

Insert beautiful code in your Diem posts with Mardown and Geshi

It's nice to present code in our posts, it's better with indentation and syntax highlight. This is possible in Diem with the use of Markdown and Geshi library.

As I needeed a starting point for this article, I decided to have a look at Diem's website code (can be downloaded here.
And there I found clues. I just need to hack Markdown editor and use Geshi highliter.

Setup for John Gruber's Markdown Editor

With Diem, if you declare a table field as 'markdown' in your model, the Markdown editor is automagically added to the edition form for your object.

Diem's Markdown editor default buttons

Let's add some buttons on this one.

Javascript code

Markdown's setup takes place in web/dmCorePlugin/lib/dmMarkitup/sets/markdown/set.js.
But we cannot touch this file if we want to survive next Diem upgrade.

So, we'll override this setup in our web/js/admin.js file.

(function($)
{  
    // Markdown editor setup  
    dmMarkitupMarkdown = {  
        previewParserPath:  '',  
        onShiftEnter:    {keepDefault:false, openWith:'\n\n'},  
        markupSet: [  
            {name:'Heading 2', className: 'markitup_heading_2', key:'2', openWith:'## ', placeHolder:'Your title here...' },  
            {name:'Heading 3', className: 'markitup_heading_3', key:'3', openWith:'### ', placeHolder:'Your title here...' },  
            {name:'Heading 4', className: 'markitup_heading_4', key:'4', openWith:'#### ', placeHolder:'Your title here...' },  
            {separator:'---------------' },  
            {name:'Bold', className: 'markitup_bold', key:'B', openWith:'**', closeWith:'**'},  
            {name:'Italic', className: 'markitup_italic', key:'I', openWith:'_', closeWith:'_'},  
            {separator:'---------------' },  
            {name:'Bulleted List', className: 'markitup_ul', openWith:'- ' },  
            {name:'Numeric List', className: 'markitup_ol', openWith:function(markItUp) { return markItUp.line+'. '; }},  
            {separator:'---------------' },  
            {name:'Link', className: 'markitup_link', key:'L', openWith:'[', closeWith:']([![Url:!:http://]!] "[![Title]!]")', placeHolder:'Your text to link here...' },  
            {separator:'---------------'},  
            {name:'Quotes', className: 'markitup_quote', openWith:'> '},  
            {separator:'---------------' },  
            {name:'Code', className: 'markitup_code', openWith:'[code]\n', closeWith:'\n[/code]'},  
            {name:'CodePhp', className: 'markitup_code_php', openWith:'[code PHP]\n', closeWith:'\n[/code]'},  
            {name:'CodeJs', className: 'markitup_code_js', openWith:'[code JAVASCRIPT]\n', closeWith:'\n[/code]'},  
            {name:'CodeCss', className: 'markitup_code_css', openWith:'[code CSS]\n', closeWith:'\n[/code]'},  
            {name:'CodeSql', className: 'markitup_code_sql', openWith:'[code SQL]\n', closeWith:'\n[/code]'},  
            {name:'CodeHtml', className: 'markitup_code_html', openWith:'[code html4strict]\n', closeWith:'\n[/code]'}  
        ],  
        resizeHandle:    false  
    }  
 
})(jQuery);  

CSS code

To render our new buttons, we need two things :
- a cascading style sheet
- icons for our buttons

My choice is to group icons (sprites) in a single file as advised in Yahoos Best Practices for Speeding Up Your Web Site.

So, the css rules, that overrides web/dmCorePlugin/lib/dmMarkitup/sets/markdown/style.css has to be stored in web/themeAdmin/css/markdown.css.

/*
styling markdown editor buttons this stylesheet overrides styles in  
web/dmCorePlugin/lib/dmMarkitup/sets/markdown/styles.css  
*/  
div.markItUp li a{ background-image:url(../images/markdown-sprites.png) !important; }  
div.markItUp li.markitup_heading_2 a, div.markItUp li.markitup_heading_3 a, div.markItUp li.markitup_heading_4 a, div.markItUp li.markitup_bold a,  
div.markItUp li.markitup_italic a, div.markItUp li.markitup_ul a, div.markItUp li.markitup_ol a, div.markItUp li.markitup_link a,  
div.markItUp li.markitup_quote a, div.markItUp li.markitup_code a, div.markItUp li.markitup_code_php a, div.markItUp li.markitup_code_js a,  
div.markItUp li.markitup_code_css a, div.markItUp li.markitup_code_sql a, div.markItUp li.markitup_code_html a, div.markItUp li.markitup_full_screen a  
{   padding: 0px !important;   margin: 3px; }  
div.markItUp li.markitup_heading_2 a { background-position: -16px 0; }  
div.markItUp li.markitup_heading_3 a { background-position: -32px 0; }  
div.markItUp li.markitup_heading_4 a { background-position: -48px 0; }  
div.markItUp li.markitup_bold a { background-position: -96px 0; }  
div.markItUp li.markitup_italic a { background-position: -112px 0; }  
div.markItUp li.markitup_ul a { background-position: -128px 0; }  
div.markItUp li.markitup_ol a { background-position: -144px 0; }  
div.markItUp li.markitup_link a { background-position: 0 -16px; }  
div.markItUp li.markitup_quote a { background-position: -16px -16px; }  
div.markItUp li.markitup_full_screen a { background-position: -144px -16px; }  
div.markItUp li.markitup_code a{ background-position: -16px -32px; }  
div.markItUp li.markitup_code_css a { background-position: 0 -48px; }  
div.markItUp li.markitup_code_php a { background-position: -16px -48px; }  
div.markItUp li.markitup_code_js a { background-position: -32px -48px; }  
div.markItUp li.markitup_code_sql a { background-position: -48px -48px; }  
div.markItUp li.markitup_code_html a { background-position: -64px -48px; }  

Here is the sprite used
download sprites file for markdown editor buttons here

It has to be placed in web/themeAdmin/images.

And now we are done with Markdown Editor hack, we now have new buttons on our toolbar.

Setup for Geshi highlighter

To be able to use Geshi in the rendering process, we need to do few things :
- install Geshi
- declare a new service in Diem
- use this service to render our code nicely

Installing Geshi

Get Geshi from sourceforge and put it in lib/vendor/ folder in your application.

declare a new markdown service

in config/dm/services.yml

parameters:  
  markdown.class:             myGeshiMarkdown  
  
services:  
  markdown:  
    class:                    %markdown.class%  
    shared:                   true  
    arguments:                [ @helper, %markdown.options% ]  

create our custom geshi class

Create a file lib/myGeshiMarkdown.php

 
/**  
 * adapted from http://github.com/ornicar/diem-project  
 */  
class myGeshiMarkdown extends dmMarkdown  
{  
    public function __construct( dmHelper $helper, array $options = array() )  
    {  
        parent::__construct($helper, $options);  
    }  
 
    protected function preTransform($text)  
    {  
        $text = parent::preTransform($text);  
 
        if (strpos($text, '[/code]'))  
        {  
            $text = preg_replace_callback(  
                    '#\[code\s?(\w*)\]((?:\n|.)*)\n\[/code\]#uU',  
                    array($this, 'formatCode'),  
                    $text  
            );  
        }  
 
        return $text;  
    }  
 
    protected function formatCode(array $matches)  
    {  
        $language = $matches[1];  
 
        // no language specified  
        if (!$matches[1])  
        {  
            $html = '<pre><code>'.$matches[2].'</code></pre>';  
 
            $html = dmString::str_replace_once("\n", '', $html);  
 
            $html = dmString::str_replace_once('  ', '', $html);  
 
            return $html;  
        }  
        else  
        {  
            return $this->formatGeshiCode($matches);  
        }  
    }  
 
    protected function formatGeshiCode(array $matches)  
    {  
        $code = $matches[2];  
        $language = $matches[1];  
 
        $cacheKey = md5($code.$language);  
 
        if ( $this->getOption('use_cache')  
                && $cache = $this->cacheManager->getCache('markdown')->get($cacheKey) )  
        {  
          return $cache;  
        }  
 
        $code = html_entity_decode($code);  
 
        require_once(dmOs::join(sfConfig::get('sf_lib_dir'), 'vendor/geshi/geshi.php'));  
 
        $geshi = new GeSHi($code, $language);  
 
        $geshi->enable_classes( true );  
 
        $html = $geshi->parse_code();  
 
        $html = dmString::str_replace_once('> ', '>', $html);  
 
        $html = dmString::str_replace_once("\n<span class=\"kw2\"><?php</span>", '', $html);  
 
        $html = dmString::str_replace_once("\n", '', $html);  
 
        $html = dmString::str_replace_once('  ', '', $html);  
 
        /** THIS IS UGLY - css are not supposed to be in the document body - Any hint ? **/  
        $cssCode = "<style>" . $geshi->get_stylesheet() . "</style>";  
 
        if ($this->getOption('use_cache'))  
        {  
           $this->cacheManager->getCache('markdown')->set($cacheKey, $html);  
        }  
 
        return $cssCode . $html;  
    }  
 
}  

Here you are ! With the same code highliting you can see in Diem website or on Pygmeeweb.

Leave a comment

Your email will never be published

Comments for this post

  1. David J said - March 23, 2011permalink

    Sorry for the commented lines in myGeshiMarkdown. I'm still trying to know if they are necessary.

  2. Stéphane Erard said - March 24, 2011permalink

    Hello :)

    This is really nice !
    What about making a plugin with this embedded ? :)

    Just some two three little things:

    require_once(dmOs::join(sfConfig::get('sf_lib_dir'), 'vendor/geshi/geshi.php'));

    Put this on the top of your class, not within methods !

    About the commented lines:
    You might look at the parent class, if it is looking into cache before calling methods format* (I'm guessing here, I don't really know how it works). In this case these might be usefull. Perhaps I'm wrong :)

    Thank you !

    Regards,

  3. David J said - March 25, 2011permalink

    @Stéphane
    Thank you for encouraging.
    - do you really think I have to move this include, it is a conditional include. If we use cache = no include
    - I have uncommented the use of cache : it works fine (the original code is from Diem project / Ornicar)
    - Making a plugin : yes certainly soon or with the help of someone interested in.

    By the way, if any of you knows how to inject the generated geshi css in document head, this will help to get a clean document.