Tue, 22 Mar 2011 18:42 CET in Diem
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.
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.

Let's add some buttons on this one.
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);
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
![]()
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.
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
Get Geshi from sourceforge and put it in lib/vendor/ folder in your application.
in config/dm/services.yml
parameters:
markdown.class: myGeshiMarkdown
services:
markdown:
class: %markdown.class%
shared: true
arguments: [ @helper, %markdown.options% ]
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.
Comments for this post
David J said - March 23, 2011permalink
Sorry for the commented lines in myGeshiMarkdown. I'm still trying to know if they are necessary.
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,
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.