]> source.dussan.org Git - redmine.git/commitdiff
Makes wiki text formatter pluggable.
authorJean-Philippe Lang <jp_lang@yahoo.fr>
Mon, 27 Oct 2008 11:08:29 +0000 (11:08 +0000)
committerJean-Philippe Lang <jp_lang@yahoo.fr>
Mon, 27 Oct 2008 11:08:29 +0000 (11:08 +0000)
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

13 files changed:
app/controllers/wiki_controller.rb
app/helpers/application_helper.rb
app/views/layouts/base.rhtml
app/views/mailer/layout.text.html.rhtml
app/views/settings/_general.rhtml
lib/redmine.rb
lib/redmine/plugin.rb
lib/redmine/wiki_formatting.rb
lib/redmine/wiki_formatting/textile/formatter.rb [new file with mode: 0644]
lib/redmine/wiki_formatting/textile/helper.rb [new file with mode: 0644]
public/javascripts/jstoolbar/jstoolbar.js
public/javascripts/jstoolbar/textile.js [new file with mode: 0644]
test/unit/helpers/application_helper_test.rb

index 114010dff37bf3346daf4e909e34a67662f0048b..04bc33a82a6d884e18195cadd9339b4302af2413 100644 (file)
@@ -63,7 +63,7 @@ class WikiController < ApplicationController
     @page.content = WikiContent.new(:page => @page) if @page.new_record?
     
     @content = @page.content_for_version(params[:version])
-    @content.text = "h1. #{@page.pretty_title}" if @content.text.blank?
+    @content.text = initial_page_content(@page) if @content.text.blank?
     # don't keep previous comment
     @content.comments = nil
     if request.get?
@@ -208,4 +208,11 @@ private
   def editable?(page = @page)
     page.editable_by?(User.current)
   end
+
+  # Returns the default content of a new wiki page
+  def initial_page_content(page)
+    helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
+    extend helper unless self.instance_of?(helper)
+    helper.instance_method(:initial_page_content).bind(self).call(page)
+  end
 end
index c72763dbb0dbbed408f87d457c03361be8307459..aad2c5bec661c8cebaea8d7cf383b6a6e6abfcfa 100644 (file)
 
 require 'coderay'
 require 'coderay/helpers/file_type'
+require 'forwardable'
 
 module ApplicationHelper
   include Redmine::WikiFormatting::Macros::Definitions
 
+  extend Forwardable
+  def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
+
   def current_role
     @current_role ||= User.current.role_for_project(@project)
   end
@@ -259,9 +263,7 @@ module ApplicationHelper
       end
     end
     
-    text = (Setting.text_formatting == 'textile') ?
-      Redmine::WikiFormatting.to_html(text) { |macro, args| exec_macro(macro, obj, args) } :
-      simple_format(auto_link(h(text)))
+    text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text) { |macro, args| exec_macro(macro, obj, args) }
 
     # different methods for formatting wiki links
     case options[:wiki_links]
@@ -549,18 +551,6 @@ module ApplicationHelper
     end
   end
   
-  def wikitoolbar_for(field_id)
-    return '' unless Setting.text_formatting == 'textile'
-    
-    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/lang/jstoolbar-#{current_language}") +
-      javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.setHelpLink('#{help_link}'); toolbar.draw();")
-  end
-  
   def content_for(name, content = nil, &block)
     @has_content ||= {}
     @has_content[name] = true
@@ -570,4 +560,12 @@ module ApplicationHelper
   def has_content?(name)
     (@has_content && @has_content[name]) || false
   end
+
+  private
+  
+  def wiki_helper
+    helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
+    extend helper
+    return self
+  end
 end
index 86d23d62f28b02cc6acc46eaa484d50f3ebebd0b..8cdfcb8e66ef7618a51fa6223a6154d964fbfbec 100644 (file)
@@ -7,7 +7,7 @@
 <meta name="keywords" content="issue,bug,tracker" />
 <%= stylesheet_link_tag 'application', :media => 'all' %>
 <%= javascript_include_tag :defaults %>
-<%= stylesheet_link_tag 'jstoolbar' %>
+<%= heads_for_wiki_formatter %>
 <!--[if IE]>
     <style type="text/css">
       * html body{ width: expression( document.documentElement.clientWidth < 900 ? '900px' : '100%' ); }
index c95c945015e54f6780f049efb82dae57d5071a17..12fb654d6ec4f6effa9f0ab35ccf97be428c52fd 100644 (file)
@@ -32,6 +32,6 @@ hr {
 <body>
 <%= yield %>
 <hr />
-<span class="footer"><%= Redmine::WikiFormatting.to_html(Setting.emails_footer) %></span>
+<span class="footer"><%= Redmine::WikiFormatting.to_html(Setting.text_formatting, Setting.emails_footer) %></span>
 </body>
 </html>
index bb56c43dbcf916ed55cbd39679ac54ba4d58e567..f8d4e67fb2a31466aa4ae965d8afe7055bb8224f 100644 (file)
@@ -39,7 +39,7 @@
 <%= select_tag 'settings[protocol]', options_for_select(['http', 'https'], Setting.protocol) %></p>
 
 <p><label><%= l(:setting_text_formatting) %></label>
-<%= select_tag 'settings[text_formatting]', options_for_select([[l(:label_none), "0"], ["textile", "textile"]], Setting.text_formatting) %></p>
+<%= select_tag 'settings[text_formatting]', options_for_select([[l(:label_none), "0"], *Redmine::WikiFormatting.format_names.collect{|name| [name, name]} ], Setting.text_formatting.to_sym) %></p>
 
 <p><label><%= l(:setting_wiki_compression) %></label>
 <%= select_tag 'settings[wiki_compression]', options_for_select( [[l(:label_none), 0], ["gzip", "gzip"]], Setting.wiki_compression) %></p>
index 1503c1d410980b9ae399842201b7ee682569574e..eeb12e00a48c6e8058f5f46fe429372028563550 100644 (file)
@@ -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
index 8123003cc463a98cdfebcb6fdb9537a07a3f0bfa..87a6ce4d3db7c139880c2995afac9e8b6a005ab5 100644 (file)
@@ -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?
index 7dffff4923a6410e3716d8296f0c81ab4020ccb4..1525dbc1965e2ca078db05816bb666a481f49a60 100644 (file)
@@ -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
 # 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\">&para;</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 (file)
index 0000000..c936803
--- /dev/null
@@ -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\">&para;</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 (file)
index 0000000..af021f9
--- /dev/null
@@ -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
index 64c460217cbae1d2e24c976ec2c7dd100b30d6d4..66669ceee919750bde18551ba09825cc3e6b88b7 100644 (file)
@@ -378,182 +378,3 @@ jsToolBar.prototype.resizeDragStop = function(event) {
        document.removeEventListener('mousemove', this.dragMoveHdlr, false);
        document.removeEventListener('mouseup', this.dragStopHdlr, false);
 };
-
-// Elements definition ------------------------------------
-
-// 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("_") }
-       }
-}
-
-// ins
-jsToolBar.prototype.elements.ins = {
-       type: 'button',
-       title: 'Underline',
-       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('h1. ', '',function(str) {
-                   str = str.replace(/^h\d+\.\s+/, '')
-                   return str;
-                 });
-               }
-       }
-}
-jsToolBar.prototype.elements.h2 = {
-       type: 'button',
-       title: 'Heading 2',
-       fn: {
-               wiki: function() { 
-                 this.encloseLineSelection('h2. ', '',function(str) {
-                   str = str.replace(/^h\d+\.\s+/, '')
-                   return str;
-                 });
-               }
-       }
-}
-jsToolBar.prototype.elements.h3 = {
-       type: 'button',
-       title: 'Heading 3',
-       fn: {
-               wiki: function() { 
-                 this.encloseLineSelection('h3. ', '',function(str) {
-                   str = str.replace(/^h\d+\.\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,"$1# ");
-                       });
-               }
-       }
-}
-
-// 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('<pre>\n', '\n</pre>') }
-       }
-}
-
-// 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("!", "!") }
-       }
-}
diff --git a/public/javascripts/jstoolbar/textile.js b/public/javascripts/jstoolbar/textile.js
new file mode 100644 (file)
index 0000000..c461b9d
--- /dev/null
@@ -0,0 +1,200 @@
+/* ***** 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 textile 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("_") }
+       }
+}
+
+// ins
+jsToolBar.prototype.elements.ins = {
+       type: 'button',
+       title: 'Underline',
+       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('h1. ', '',function(str) {
+                   str = str.replace(/^h\d+\.\s+/, '')
+                   return str;
+                 });
+               }
+       }
+}
+jsToolBar.prototype.elements.h2 = {
+       type: 'button',
+       title: 'Heading 2',
+       fn: {
+               wiki: function() { 
+                 this.encloseLineSelection('h2. ', '',function(str) {
+                   str = str.replace(/^h\d+\.\s+/, '')
+                   return str;
+                 });
+               }
+       }
+}
+jsToolBar.prototype.elements.h3 = {
+       type: 'button',
+       title: 'Heading 3',
+       fn: {
+               wiki: function() { 
+                 this.encloseLineSelection('h3. ', '',function(str) {
+                   str = str.replace(/^h\d+\.\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,"$1# ");
+                       });
+               }
+       }
+}
+
+// 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('<pre>\n', '\n</pre>') }
+       }
+}
+
+// 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("!", "!") }
+       }
+}
index c3e8ca2d74133164ca7969f794b7c0c0d0851ef7..13fa262f121be22edef5033b5ac8118bcbd3c793 100644 (file)
@@ -350,6 +350,13 @@ EXPECTED
     assert textilizable(text).match(/Unknow project/)
   end
   
+  def test_default_formatter
+    Setting.text_formatting = 'unknown'
+    text = 'a *link*: http://www.example.net/'
+    assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
+    Setting.text_formatting = 'textile'
+  end
+  
   def test_date_format_default
     today = Date.today
     Setting.date_format = ''