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
This commit is contained in:
parent
9b624fd1d6
commit
a3b9a5aa5f
|
@ -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
|
||||
|
|
|
@ -17,10 +17,14 @@
|
|||
|
||||
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
|
||||
|
|
|
@ -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%' ); }
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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]
|
||||
|
||||
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
|
||||
@@formatters = {}
|
||||
|
||||
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
|
||||
class << self
|
||||
def map
|
||||
yield self
|
||||
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 register(name, formatter, helper)
|
||||
raise ArgumentError, "format name '#{name}' is already taken" if @@formatters[name]
|
||||
@@formatters[name.to_sym] = {:formatter => formatter, :helper => helper}
|
||||
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
|
||||
def formatter_for(name)
|
||||
entry = @@formatters[name.to_sym]
|
||||
(entry && entry[:formatter]) || Redmine::WikiFormatting::NullFormatter::Formatter
|
||||
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
|
||||
def helper_for(name)
|
||||
entry = @@formatters[name.to_sym]
|
||||
(entry && entry[:helper]) || Redmine::WikiFormatting::NullFormatter::Helper
|
||||
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
|
||||
def format_names
|
||||
@@formatters.keys.map
|
||||
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
|
||||
|
||||
def to_html(format, text, options = {}, &block)
|
||||
formatter_for(format).new(text).to_html(&block)
|
||||
end
|
||||
end
|
||||
|
||||
public
|
||||
|
||||
def self.to_html(text, options = {}, &block)
|
||||
TextileFormatter.new(text).to_html(&block)
|
||||
# Default formatter module
|
||||
module NullFormatter
|
||||
class Formatter
|
||||
include ActionView::Helpers::TagHelper
|
||||
include ActionView::Helpers::TextHelper
|
||||
|
||||
def initialize(text)
|
||||
@text = text
|
||||
end
|
||||
|
||||
def to_html(*args)
|
||||
simple_format(auto_link(CGI::escapeHTML(@text)))
|
||||
end
|
||||
end
|
||||
|
||||
module Helper
|
||||
def wikitoolbar_for(field_id)
|
||||
end
|
||||
|
||||
def heads_for_wiki_formatter
|
||||
end
|
||||
|
||||
def initial_page_content(page)
|
||||
page.pretty_title.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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("!", "!") }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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("!", "!") }
|
||||
}
|
||||
}
|
|
@ -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 = ''
|
||||
|
|
Loading…
Reference in New Issue