Index: trunk/Gemfile
===================================================================
--- trunk/Gemfile	(revision 12451)
+++ trunk/Gemfile	(revision 12452)
@@ -5,6 +5,8 @@
 gem "coderay", "~> 1.1.0"
 gem "fastercsv", "~> 1.5.0", :platforms => [:mri_18, :mingw_18, :jruby]
 gem "builder", "3.0.0"
+# TODO: upgrade to redcarpet 3.x when ruby1.8 support is dropped
+gem "redcarpet", "~> 2.3.0"
 
 # Optional gem for LDAP authentication
 group :ldap do
Index: trunk/lib/redmine/wiki_formatting/markdown/formatter.rb
===================================================================
--- trunk/lib/redmine/wiki_formatting/markdown/formatter.rb	(nonexistent)
+++ trunk/lib/redmine/wiki_formatting/markdown/formatter.rb	(revision 12452)
@@ -0,0 +1,136 @@
+# Redmine - project management software
+# Copyright (C) 2006-2013  Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+require 'cgi'
+
+module Redmine
+  module WikiFormatting
+    module Markdown
+      class HTML < Redcarpet::Render::HTML
+        include ActionView::Helpers::TagHelper
+
+        def link(link, title, content)
+          css = nil
+          unless link && link.starts_with?('/')
+            css = 'external'
+          end
+          content_tag('a', content.html_safe, :href => link, :title => title, :class => css)
+        end
+
+        def block_code(code, language)
+          if language.present?
+            "<pre><code class=\"#{CGI.escapeHTML language} syntaxhl\">" +
+              Redmine::SyntaxHighlighting.highlight_by_language(code, language) +
+              "</code></pre>"
+          else
+            "<pre>" + CGI.escapeHTML(code) + "</pre>"
+          end
+        end
+      end
+
+      class Formatter
+        def initialize(text)
+          @text = text
+        end
+
+        def to_html(*args)
+          html = formatter.render(@text)
+          # restore wiki links eg. [[Foo]]
+          html.gsub!(%r{\[<a href="(.*?)">(.*?)</a>\]}) do
+            "[[#{$2}]]"
+          end
+          # restore Redmine links with double-quotes, eg. version:"1.0"
+          html.gsub!(/(\w):&quot;(.+?)&quot;/) do
+            "#{$1}:\"#{$2}\""
+          end
+          html
+        end
+
+        def get_section(index)
+          section = extract_sections(index)[1]
+          hash = Digest::MD5.hexdigest(section)
+          return section, hash
+        end
+
+        def update_section(index, update, hash=nil)
+          t = extract_sections(index)
+          if hash.present? && hash != Digest::MD5.hexdigest(t[1])
+            raise Redmine::WikiFormatting::StaleSectionError
+          end
+          t[1] = update unless t[1].blank?
+          t.reject(&:blank?).join "\n\n"
+        end
+
+        def extract_sections(index)
+          sections = ['', '', '']
+          offset = 0
+          i = 0
+          l = 1
+          inside_pre = false
+          @text.split(/(^(?:.+\r?\n\r?(?:\=+|\-+)|#+.+|~~~.*)\s*$)/).each do |part|
+            level = nil
+            if part =~ /\A~{3,}(\S+)?\s*$/
+              if $1
+                if !inside_pre
+                  inside_pre = true
+                end
+              else
+                inside_pre = !inside_pre
+              end
+            elsif inside_pre
+              # nop
+            elsif part =~ /\A(#+).+/
+              level = $1.size
+            elsif part =~ /\A.+\r?\n\r?(\=+|\-+)\s*$/
+              level = $1.include?('=') ? 1 : 2
+            end
+            if level
+              i += 1
+              if offset == 0 && i == index
+                # entering the requested section
+                offset = 1
+                l = level
+              elsif offset == 1 && i > index && level <= l
+                # leaving the requested section
+                offset = 2
+              end
+            end
+            sections[offset] << part
+          end
+          sections.map(&:strip)
+        end
+
+        private
+
+        def formatter
+          @@formatter ||= Redcarpet::Markdown.new(
+            Redmine::WikiFormatting::Markdown::HTML.new(
+              :filter_html => true,
+              :hard_wrap => true
+            ),
+            :autolink => true,
+            :fenced_code_blocks => true,
+            :space_after_headers => true,
+            :tables => true,
+            :strikethrough => true,
+            :superscript => true
+          )
+        end
+      end
+    end
+  end
+end

Property changes on: trunk/lib/redmine/wiki_formatting/markdown/formatter.rb
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: trunk/lib/redmine/wiki_formatting/markdown/helper.rb
===================================================================
--- trunk/lib/redmine/wiki_formatting/markdown/helper.rb	(nonexistent)
+++ trunk/lib/redmine/wiki_formatting/markdown/helper.rb	(revision 12452)
@@ -0,0 +1,45 @@
+# Redmine - project management software
+# Copyright (C) 2006-2013  Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+module Redmine
+  module WikiFormatting
+    module Markdown
+      module Helper
+        def wikitoolbar_for(field_id)
+          heads_for_wiki_formatter
+          javascript_tag("var wikiToolbar = new jsToolBar(document.getElementById('#{field_id}')); wikiToolbar.draw();")
+        end
+
+        def initial_page_content(page)
+          "# #{@page.pretty_title}"
+        end
+
+        def heads_for_wiki_formatter
+          unless @heads_for_wiki_formatter_included
+            content_for :header_tags do
+              javascript_include_tag('jstoolbar/jstoolbar') +
+              javascript_include_tag('jstoolbar/markdown') +
+              javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language.to_s.downcase}") +
+              stylesheet_link_tag('jstoolbar')
+            end
+            @heads_for_wiki_formatter_included = true
+          end
+        end
+      end
+    end
+  end
+end

Property changes on: trunk/lib/redmine/wiki_formatting/markdown/helper.rb
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: trunk/lib/redmine.rb
===================================================================
--- trunk/lib/redmine.rb	(revision 12451)
+++ trunk/lib/redmine.rb	(revision 12452)
@@ -267,6 +267,8 @@
 
 Redmine::WikiFormatting.map do |format|
   format.register :textile, Redmine::WikiFormatting::Textile::Formatter, Redmine::WikiFormatting::Textile::Helper
+  format.register :markdown, Redmine::WikiFormatting::Markdown::Formatter, Redmine::WikiFormatting::Markdown::Helper,
+    :label => 'Markdown (experimental)'
 end
 
 ActionView::Template.register_template_handler :rsb, Redmine::Views::ApiTemplateHandler
Index: trunk/public/javascripts/jstoolbar/markdown.js
===================================================================
--- trunk/public/javascripts/jstoolbar/markdown.js	(nonexistent)
+++ trunk/public/javascripts/jstoolbar/markdown.js	(revision 12452)
@@ -0,0 +1,194 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * This file is part of DotClear.
+ * Copyright (c) 2005 Nicolas Martin & Olivier Meunier and contributors. All
+ * rights reserved.
+ *
+ * DotClear is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * DotClear is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with DotClear; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * ***** END LICENSE BLOCK *****
+*/
+
+/* Modified by JP LANG for markdown formatting */
+
+// strong
+jsToolBar.prototype.elements.strong = {
+	type: 'button',
+	title: 'Strong',
+	fn: {
+		wiki: function() { this.singleTag('**') }
+	}
+}
+
+// em
+jsToolBar.prototype.elements.em = {
+	type: 'button',
+	title: 'Italic',
+	fn: {
+		wiki: function() { this.singleTag("*") }
+	}
+}
+
+// del
+jsToolBar.prototype.elements.del = {
+	type: 'button',
+	title: 'Deleted',
+	fn: {
+		wiki: function() { this.singleTag('~~') }
+	}
+}
+
+// code
+jsToolBar.prototype.elements.code = {
+	type: 'button',
+	title: 'Code',
+	fn: {
+		wiki: function() { this.singleTag('`') }
+	}
+}
+
+// spacer
+jsToolBar.prototype.elements.space1 = {type: 'space'}
+
+// headings
+jsToolBar.prototype.elements.h1 = {
+	type: 'button',
+	title: 'Heading 1',
+	fn: {
+		wiki: function() { 
+		  this.encloseLineSelection('# ', '',function(str) {
+		    str = str.replace(/^#+\s+/, '')
+		    return str;
+		  });
+		}
+	}
+}
+jsToolBar.prototype.elements.h2 = {
+	type: 'button',
+	title: 'Heading 2',
+	fn: {
+		wiki: function() { 
+		  this.encloseLineSelection('## ', '',function(str) {
+		    str = str.replace(/^#+\s+/, '')
+		    return str;
+		  });
+		}
+	}
+}
+jsToolBar.prototype.elements.h3 = {
+	type: 'button',
+	title: 'Heading 3',
+	fn: {
+		wiki: function() { 
+		  this.encloseLineSelection('### ', '',function(str) {
+		    str = str.replace(/^#+\s+/, '')
+		    return str;
+		  });
+		}
+	}
+}
+
+// spacer
+jsToolBar.prototype.elements.space2 = {type: 'space'}
+
+// ul
+jsToolBar.prototype.elements.ul = {
+	type: 'button',
+	title: 'Unordered list',
+	fn: {
+		wiki: function() {
+			this.encloseLineSelection('','',function(str) {
+				str = str.replace(/\r/g,'');
+				return str.replace(/(\n|^)[#-]?\s*/g,"$1* ");
+			});
+		}
+	}
+}
+
+// ol
+jsToolBar.prototype.elements.ol = {
+	type: 'button',
+	title: 'Ordered list',
+	fn: {
+		wiki: function() {
+			this.encloseLineSelection('','',function(str) {
+				str = str.replace(/\r/g,'');
+				return str.replace(/(\n|^)[*-]?\s*/g,"$11. ");
+			});
+		}
+	}
+}
+
+// spacer
+jsToolBar.prototype.elements.space3 = {type: 'space'}
+
+// bq
+jsToolBar.prototype.elements.bq = {
+	type: 'button',
+	title: 'Quote',
+	fn: {
+		wiki: function() {
+			this.encloseLineSelection('','',function(str) {
+				str = str.replace(/\r/g,'');
+				return str.replace(/(\n|^) *([^\n]*)/g,"$1> $2");
+			});
+		}
+	}
+}
+
+// unbq
+jsToolBar.prototype.elements.unbq = {
+	type: 'button',
+	title: 'Unquote',
+	fn: {
+		wiki: function() {
+			this.encloseLineSelection('','',function(str) {
+				str = str.replace(/\r/g,'');
+				return str.replace(/(\n|^) *[>]? *([^\n]*)/g,"$1$2");
+			});
+		}
+	}
+}
+
+// pre
+jsToolBar.prototype.elements.pre = {
+	type: 'button',
+	title: 'Preformatted text',
+	fn: {
+		wiki: function() { this.encloseLineSelection('~~~\n', '\n~~~') }
+	}
+}
+
+// spacer
+jsToolBar.prototype.elements.space4 = {type: 'space'}
+
+// wiki page
+jsToolBar.prototype.elements.link = {
+	type: 'button',
+	title: 'Wiki link',
+	fn: {
+		wiki: function() { this.encloseSelection("[[", "]]") }
+	}
+}
+// image
+jsToolBar.prototype.elements.img = {
+	type: 'button',
+	title: 'Image',
+	fn: {
+		wiki: function() { this.encloseSelection("![](", ")") }
+	}
+}
+
+// spacer
+jsToolBar.prototype.elements.space5 = {type: 'space'}

Property changes on: trunk/public/javascripts/jstoolbar/markdown.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: trunk/test/unit/lib/redmine/wiki_formatting/markdown_formatter.rb
===================================================================
--- trunk/test/unit/lib/redmine/wiki_formatting/markdown_formatter.rb	(nonexistent)
+++ trunk/test/unit/lib/redmine/wiki_formatting/markdown_formatter.rb	(revision 12452)
@@ -0,0 +1,62 @@
+# Redmine - project management software
+# Copyright (C) 2006-2013  Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+require File.expand_path('../../../../../test_helper', __FILE__)
+
+class Redmine::WikiFormatting::MarkdownFormatterTest < ActionView::TestCase
+  include ApplicationHelper
+
+  def setup
+    @formatter = Redmine::WikiFormatting::Markdown::Formatter
+  end
+
+  def test_inline_style
+    assert_equal "<p><strong>foo</strong></p>", @formatter.new("**foo**").to_html.strip
+  end
+
+  def test_wiki_links_should_be_preserved
+    text = 'This is a wiki link: [[Foo]]'
+    assert_include '[[Foo]]', @formatter.new(text).to_html
+  end
+
+  def test_redmine_links_with_double_quotes_should_be_preserved
+    text = 'This is a redmine link: version:"1.0"'
+    assert_include 'version:"1.0"', @formatter.new(text).to_html
+  end
+
+  def test_should_support_syntax_highligth
+    text = <<-STR
+~~~ruby
+def foo
+end
+~~~
+STR
+    assert_select_in @formatter.new(text).to_html, 'pre code.ruby.syntaxhl' do
+      assert_select 'span.keyword', :text => 'def'
+    end
+  end
+
+  def test_external_links_should_have_external_css_class
+    text = 'This is a [link](http://example.net/)'
+    assert_equal '<p>This is a <a class="external" href="http://example.net/">link</a></p>', @formatter.new(text).to_html.strip
+  end
+
+  def test_locals_links_should_not_have_external_css_class
+    text = 'This is a [link](/issues)'
+    assert_equal '<p>This is a <a href="/https/www.redmine.org/issues">link</a></p>', @formatter.new(text).to_html.strip
+  end
+end

Property changes on: trunk/test/unit/lib/redmine/wiki_formatting/markdown_formatter.rb
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
