diff options
author | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2008-10-27 11:08:29 +0000 |
---|---|---|
committer | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2008-10-27 11:08:29 +0000 |
commit | a3b9a5aa5fe344cf33a47ccbf1d26a95fbe4e255 (patch) | |
tree | 6b19387c81e2d865a57cf3357340deffdbc5e858 /lib | |
parent | 9b624fd1d6f66bbe60d738522d9fc5fd547ef3af (diff) | |
download | redmine-a3b9a5aa5fe344cf33a47ccbf1d26a95fbe4e255.tar.gz redmine-a3b9a5aa5fe344cf33a47ccbf1d26a95fbe4e255.zip |
Makes wiki text formatter pluggable.
Original patch #2025 by Yuki Sonoda slightly edited.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@1955 e93f8b46-1217-0410-a6f0-8f06a7374b81
Diffstat (limited to 'lib')
-rw-r--r-- | lib/redmine.rb | 5 | ||||
-rw-r--r-- | lib/redmine/plugin.rb | 10 | ||||
-rw-r--r-- | lib/redmine/wiki_formatting.rb | 193 | ||||
-rw-r--r-- | lib/redmine/wiki_formatting/textile/formatter.rb | 183 | ||||
-rw-r--r-- | lib/redmine/wiki_formatting/textile/helper.rb | 43 |
5 files changed, 282 insertions, 152 deletions
diff --git a/lib/redmine.rb b/lib/redmine.rb index 1503c1d41..eeb12e00a 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -6,6 +6,7 @@ require 'redmine/core_ext' require 'redmine/themes' require 'redmine/hook' require 'redmine/plugin' +require 'redmine/wiki_formatting' begin require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick) @@ -150,3 +151,7 @@ Redmine::Activity.map do |activity| activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false activity.register :messages, :default => false end + +Redmine::WikiFormatting.map do |format| + format.register :textile, Redmine::WikiFormatting::Textile::Formatter, Redmine::WikiFormatting::Textile::Helper +end diff --git a/lib/redmine/plugin.rb b/lib/redmine/plugin.rb index 8123003cc..87a6ce4d3 100644 --- a/lib/redmine/plugin.rb +++ b/lib/redmine/plugin.rb @@ -148,6 +148,16 @@ module Redmine #:nodoc: def activity_provider(*args) Redmine::Activity.register(*args) end + + # Registers a wiki formatter. + # + # Parameters: + # * +name+ - human-readable name + # * +formatter+ - formatter class, which should have an instance method +to_html+ + # * +helper+ - helper module, which will be included by wiki pages + def wiki_format_provider(name, formatter, helper) + Redmine::WikiFormatting.register(name, formatter, helper) + end # Returns +true+ if the plugin can be configured. def configurable? diff --git a/lib/redmine/wiki_formatting.rb b/lib/redmine/wiki_formatting.rb index 7dffff492..1525dbc19 100644 --- a/lib/redmine/wiki_formatting.rb +++ b/lib/redmine/wiki_formatting.rb @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2007 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2008 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 @@ -15,176 +15,65 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -require 'redcloth3' -require 'coderay' - module Redmine module WikiFormatting - - private - - class TextileFormatter < RedCloth3 - - # auto_link rule after textile rules so that it doesn't break !image_url! tags - RULES = [:textile, :block_markdown_rule, :inline_auto_link, :inline_auto_mailto, :inline_toc, :inline_macros] + @@formatters = {} + + class << self + def map + yield self + end - def initialize(*args) - super - self.hard_breaks=true - self.no_span_caps=true + def register(name, formatter, helper) + raise ArgumentError, "format name '#{name}' is already taken" if @@formatters[name] + @@formatters[name.to_sym] = {:formatter => formatter, :helper => helper} end - def to_html(*rules, &block) - @toc = [] - @macros_runner = block - super(*RULES).to_s + def formatter_for(name) + entry = @@formatters[name.to_sym] + (entry && entry[:formatter]) || Redmine::WikiFormatting::NullFormatter::Formatter end - - private - - # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet. - # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a> - def hard_break( text ) - text.gsub!( /(.)\n(?!\n|\Z|>| *(>? *[#*=]+(\s|$)|[{|]))/, "\\1<br />\n" ) if hard_breaks + + def helper_for(name) + entry = @@formatters[name.to_sym] + (entry && entry[:helper]) || Redmine::WikiFormatting::NullFormatter::Helper end - # Patch to add code highlighting support to RedCloth - def smooth_offtags( text ) - unless @pre_list.empty? - ## replace <pre> content - text.gsub!(/<redpre#(\d+)>/) do - content = @pre_list[$1.to_i] - if content.match(/<code\s+class="(\w+)">\s?(.+)/m) - content = "<code class=\"#{$1} CodeRay\">" + - CodeRay.scan($2, $1.downcase).html(:escape => false, :line_numbers => :inline) - end - content - end - end + def format_names + @@formatters.keys.map end - # Patch to add 'table of content' support to RedCloth - def textile_p_withtoc(tag, atts, cite, content) - # removes wiki links from the item - toc_item = content.gsub(/(\[\[|\]\])/, '') - # removes styles - # eg. %{color:red}Triggers% => Triggers - toc_item.gsub! %r[%\{[^\}]*\}([^%]+)%], '\\1' + def to_html(format, text, options = {}, &block) + formatter_for(format).new(text).to_html(&block) + end + end + + # Default formatter module + module NullFormatter + class Formatter + include ActionView::Helpers::TagHelper + include ActionView::Helpers::TextHelper - # replaces non word caracters by dashes - anchor = toc_item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') - - unless anchor.blank? - if tag =~ /^h(\d)$/ - @toc << [$1.to_i, anchor, toc_item] - end - atts << " id=\"#{anchor}\"" - content = content + "<a href=\"##{anchor}\" class=\"wiki-anchor\">¶</a>" + def initialize(text) + @text = text end - textile_p(tag, atts, cite, content) - end - - alias :textile_h1 :textile_p_withtoc - alias :textile_h2 :textile_p_withtoc - alias :textile_h3 :textile_p_withtoc - - def inline_toc(text) - text.gsub!(/<p>\{\{([<>]?)toc\}\}<\/p>/i) do - div_class = 'toc' - div_class << ' right' if $1 == '>' - div_class << ' left' if $1 == '<' - out = "<ul class=\"#{div_class}\">" - @toc.each do |heading| - level, anchor, toc_item = heading - out << "<li class=\"heading#{level}\"><a href=\"##{anchor}\">#{toc_item}</a></li>\n" - end - out << '</ul>' - out + + def to_html(*args) + simple_format(auto_link(CGI::escapeHTML(@text))) end end - MACROS_RE = / - (!)? # escaping - ( - \{\{ # opening tag - ([\w]+) # macro name - (\(([^\}]*)\))? # optional arguments - \}\} # closing tag - ) - /x unless const_defined?(:MACROS_RE) - - def inline_macros(text) - text.gsub!(MACROS_RE) do - esc, all, macro = $1, $2, $3.downcase - args = ($5 || '').split(',').each(&:strip) - if esc.nil? - begin - @macros_runner.call(macro, args) - rescue => e - "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>" - end || all - else - all - end + module Helper + def wikitoolbar_for(field_id) end - end - AUTO_LINK_RE = %r{ - ( # leading text - <\w+.*?>| # leading HTML tag, or - [^=<>!:'"/]| # leading punctuation, or - ^ # beginning of line - ) - ( - (?:https?://)| # protocol spec, or - (?:ftp://)| - (?:www\.) # www.* - ) - ( - (\S+?) # url - (\/)? # slash - ) - ([^\w\=\/;\(\)]*?) # post - (?=<|\s|$) - }x unless const_defined?(:AUTO_LINK_RE) - - # Turns all urls into clickable links (code from Rails). - def inline_auto_link(text) - text.gsub!(AUTO_LINK_RE) do - all, leading, proto, url, post = $&, $1, $2, $3, $6 - if leading =~ /<a\s/i || leading =~ /![<>=]?/ - # don't replace URL's that are already linked - # and URL's prefixed with ! !> !< != (textile images) - all - else - # Idea below : an URL with unbalanced parethesis and - # ending by ')' is put into external parenthesis - if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) ) - url=url[0..-2] # discard closing parenth from url - post = ")"+post # add closing parenth to post - end - %(#{leading}<a class="external" href="#{proto=="www."?"http://www.":proto}#{url}">#{proto + url}</a>#{post}) - end + def heads_for_wiki_formatter end - end - - # Turns all email addresses into clickable links (code from Rails). - def inline_auto_mailto(text) - text.gsub!(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do - mail = $1 - if text.match(/<a\b[^>]*>(.*)(#{Regexp.escape(mail)})(.*)<\/a>/) - mail - else - %{<a href="mailto:#{mail}" class="email">#{mail}</a>} - end + + def initial_page_content(page) + page.pretty_title.to_s end end end - - public - - def self.to_html(text, options = {}, &block) - TextileFormatter.new(text).to_html(&block) - end end end diff --git a/lib/redmine/wiki_formatting/textile/formatter.rb b/lib/redmine/wiki_formatting/textile/formatter.rb new file mode 100644 index 000000000..c936803d5 --- /dev/null +++ b/lib/redmine/wiki_formatting/textile/formatter.rb @@ -0,0 +1,183 @@ +# Redmine - project management software +# Copyright (C) 2006-2008 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 'redcloth3' +require 'coderay' + +module Redmine + module WikiFormatting + module Textile + class Formatter < RedCloth3 + + # auto_link rule after textile rules so that it doesn't break !image_url! tags + RULES = [:textile, :block_markdown_rule, :inline_auto_link, :inline_auto_mailto, :inline_toc, :inline_macros] + + def initialize(*args) + super + self.hard_breaks=true + self.no_span_caps=true + end + + def to_html(*rules, &block) + @toc = [] + @macros_runner = block + super(*RULES).to_s + end + + private + + # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet. + # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a> + def hard_break( text ) + text.gsub!( /(.)\n(?!\n|\Z|>| *(>? *[#*=]+(\s|$)|[{|]))/, "\\1<br />\n" ) if hard_breaks + end + + # Patch to add code highlighting support to RedCloth + def smooth_offtags( text ) + unless @pre_list.empty? + ## replace <pre> content + text.gsub!(/<redpre#(\d+)>/) do + content = @pre_list[$1.to_i] + if content.match(/<code\s+class="(\w+)">\s?(.+)/m) + content = "<code class=\"#{$1} CodeRay\">" + + CodeRay.scan($2, $1.downcase).html(:escape => false, :line_numbers => :inline) + end + content + end + end + end + + # Patch to add 'table of content' support to RedCloth + def textile_p_withtoc(tag, atts, cite, content) + # removes wiki links from the item + toc_item = content.gsub(/(\[\[|\]\])/, '') + # removes styles + # eg. %{color:red}Triggers% => Triggers + toc_item.gsub! %r[%\{[^\}]*\}([^%]+)%], '\\1' + + # replaces non word caracters by dashes + anchor = toc_item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') + + unless anchor.blank? + if tag =~ /^h(\d)$/ + @toc << [$1.to_i, anchor, toc_item] + end + atts << " id=\"#{anchor}\"" + content = content + "<a href=\"##{anchor}\" class=\"wiki-anchor\">¶</a>" + end + textile_p(tag, atts, cite, content) + end + + alias :textile_h1 :textile_p_withtoc + alias :textile_h2 :textile_p_withtoc + alias :textile_h3 :textile_p_withtoc + + def inline_toc(text) + text.gsub!(/<p>\{\{([<>]?)toc\}\}<\/p>/i) do + div_class = 'toc' + div_class << ' right' if $1 == '>' + div_class << ' left' if $1 == '<' + out = "<ul class=\"#{div_class}\">" + @toc.each do |heading| + level, anchor, toc_item = heading + out << "<li class=\"heading#{level}\"><a href=\"##{anchor}\">#{toc_item}</a></li>\n" + end + out << '</ul>' + out + end + end + + MACROS_RE = / + (!)? # escaping + ( + \{\{ # opening tag + ([\w]+) # macro name + (\(([^\}]*)\))? # optional arguments + \}\} # closing tag + ) + /x unless const_defined?(:MACROS_RE) + + def inline_macros(text) + text.gsub!(MACROS_RE) do + esc, all, macro = $1, $2, $3.downcase + args = ($5 || '').split(',').each(&:strip) + if esc.nil? + begin + @macros_runner.call(macro, args) + rescue => e + "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>" + end || all + else + all + end + end + end + + AUTO_LINK_RE = %r{ + ( # leading text + <\w+.*?>| # leading HTML tag, or + [^=<>!:'"/]| # leading punctuation, or + ^ # beginning of line + ) + ( + (?:https?://)| # protocol spec, or + (?:ftp://)| + (?:www\.) # www.* + ) + ( + (\S+?) # url + (\/)? # slash + ) + ([^\w\=\/;\(\)]*?) # post + (?=<|\s|$) + }x unless const_defined?(:AUTO_LINK_RE) + + # Turns all urls into clickable links (code from Rails). + def inline_auto_link(text) + text.gsub!(AUTO_LINK_RE) do + all, leading, proto, url, post = $&, $1, $2, $3, $6 + if leading =~ /<a\s/i || leading =~ /![<>=]?/ + # don't replace URL's that are already linked + # and URL's prefixed with ! !> !< != (textile images) + all + else + # Idea below : an URL with unbalanced parethesis and + # ending by ')' is put into external parenthesis + if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) ) + url=url[0..-2] # discard closing parenth from url + post = ")"+post # add closing parenth to post + end + %(#{leading}<a class="external" href="#{proto=="www."?"http://www.":proto}#{url}">#{proto + url}</a>#{post}) + end + end + end + + # Turns all email addresses into clickable links (code from Rails). + def inline_auto_mailto(text) + text.gsub!(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do + mail = $1 + if text.match(/<a\b[^>]*>(.*)(#{Regexp.escape(mail)})(.*)<\/a>/) + mail + else + %{<a href="mailto:#{mail}" class="email">#{mail}</a>} + end + end + end + end + end + end +end diff --git a/lib/redmine/wiki_formatting/textile/helper.rb b/lib/redmine/wiki_formatting/textile/helper.rb new file mode 100644 index 000000000..af021f990 --- /dev/null +++ b/lib/redmine/wiki_formatting/textile/helper.rb @@ -0,0 +1,43 @@ +# Redmine - project management software +# Copyright (C) 2006-2008 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 Textile + module Helper + def wikitoolbar_for(field_id) + help_link = l(:setting_text_formatting) + ': ' + + link_to(l(:label_help), compute_public_path('wiki_syntax', 'help', 'html'), + :onclick => "window.open(\"#{ compute_public_path('wiki_syntax', 'help', 'html') }\", \"\", \"resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes\"); return false;") + + javascript_include_tag('jstoolbar/jstoolbar') + + javascript_include_tag('jstoolbar/textile') + + javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language}") + + javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.setHelpLink('#{help_link}'); toolbar.draw();") + end + + def initial_page_content(page) + "h1. #{@page.pretty_title}" + end + + def heads_for_wiki_formatter + stylesheet_link_tag 'jstoolbar' + end + end + end + end +end |