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?
|
@page.content = WikiContent.new(:page => @page) if @page.new_record?
|
||||||
|
|
||||||
@content = @page.content_for_version(params[:version])
|
@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
|
# don't keep previous comment
|
||||||
@content.comments = nil
|
@content.comments = nil
|
||||||
if request.get?
|
if request.get?
|
||||||
|
@ -208,4 +208,11 @@ private
|
||||||
def editable?(page = @page)
|
def editable?(page = @page)
|
||||||
page.editable_by?(User.current)
|
page.editable_by?(User.current)
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -17,10 +17,14 @@
|
||||||
|
|
||||||
require 'coderay'
|
require 'coderay'
|
||||||
require 'coderay/helpers/file_type'
|
require 'coderay/helpers/file_type'
|
||||||
|
require 'forwardable'
|
||||||
|
|
||||||
module ApplicationHelper
|
module ApplicationHelper
|
||||||
include Redmine::WikiFormatting::Macros::Definitions
|
include Redmine::WikiFormatting::Macros::Definitions
|
||||||
|
|
||||||
|
extend Forwardable
|
||||||
|
def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
|
||||||
|
|
||||||
def current_role
|
def current_role
|
||||||
@current_role ||= User.current.role_for_project(@project)
|
@current_role ||= User.current.role_for_project(@project)
|
||||||
end
|
end
|
||||||
|
@ -259,9 +263,7 @@ module ApplicationHelper
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
text = (Setting.text_formatting == 'textile') ?
|
text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text) { |macro, args| exec_macro(macro, obj, args) }
|
||||||
Redmine::WikiFormatting.to_html(text) { |macro, args| exec_macro(macro, obj, args) } :
|
|
||||||
simple_format(auto_link(h(text)))
|
|
||||||
|
|
||||||
# different methods for formatting wiki links
|
# different methods for formatting wiki links
|
||||||
case options[:wiki_links]
|
case options[:wiki_links]
|
||||||
|
@ -549,18 +551,6 @@ module ApplicationHelper
|
||||||
end
|
end
|
||||||
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)
|
def content_for(name, content = nil, &block)
|
||||||
@has_content ||= {}
|
@has_content ||= {}
|
||||||
@has_content[name] = true
|
@has_content[name] = true
|
||||||
|
@ -570,4 +560,12 @@ module ApplicationHelper
|
||||||
def has_content?(name)
|
def has_content?(name)
|
||||||
(@has_content && @has_content[name]) || false
|
(@has_content && @has_content[name]) || false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def wiki_helper
|
||||||
|
helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
|
||||||
|
extend helper
|
||||||
|
return self
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<meta name="keywords" content="issue,bug,tracker" />
|
<meta name="keywords" content="issue,bug,tracker" />
|
||||||
<%= stylesheet_link_tag 'application', :media => 'all' %>
|
<%= stylesheet_link_tag 'application', :media => 'all' %>
|
||||||
<%= javascript_include_tag :defaults %>
|
<%= javascript_include_tag :defaults %>
|
||||||
<%= stylesheet_link_tag 'jstoolbar' %>
|
<%= heads_for_wiki_formatter %>
|
||||||
<!--[if IE]>
|
<!--[if IE]>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
* html body{ width: expression( document.documentElement.clientWidth < 900 ? '900px' : '100%' ); }
|
* html body{ width: expression( document.documentElement.clientWidth < 900 ? '900px' : '100%' ); }
|
||||||
|
|
|
@ -32,6 +32,6 @@ hr {
|
||||||
<body>
|
<body>
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
<hr />
|
<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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
<%= select_tag 'settings[protocol]', options_for_select(['http', 'https'], Setting.protocol) %></p>
|
<%= select_tag 'settings[protocol]', options_for_select(['http', 'https'], Setting.protocol) %></p>
|
||||||
|
|
||||||
<p><label><%= l(:setting_text_formatting) %></label>
|
<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>
|
<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>
|
<%= 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/themes'
|
||||||
require 'redmine/hook'
|
require 'redmine/hook'
|
||||||
require 'redmine/plugin'
|
require 'redmine/plugin'
|
||||||
|
require 'redmine/wiki_formatting'
|
||||||
|
|
||||||
begin
|
begin
|
||||||
require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
|
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 :wiki_edits, :class_name => 'WikiContent::Version', :default => false
|
||||||
activity.register :messages, :default => false
|
activity.register :messages, :default => false
|
||||||
end
|
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)
|
def activity_provider(*args)
|
||||||
Redmine::Activity.register(*args)
|
Redmine::Activity.register(*args)
|
||||||
end
|
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.
|
# Returns +true+ if the plugin can be configured.
|
||||||
def configurable?
|
def configurable?
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# redMine - project management software
|
# Redmine - project management software
|
||||||
# Copyright (C) 2006-2007 Jean-Philippe Lang
|
# Copyright (C) 2006-2008 Jean-Philippe Lang
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or
|
# This program is free software; you can redistribute it and/or
|
||||||
# modify it under the terms of the GNU General Public License
|
# 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
|
# along with this program; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
require 'redcloth3'
|
|
||||||
require 'coderay'
|
|
||||||
|
|
||||||
module Redmine
|
module Redmine
|
||||||
module WikiFormatting
|
module WikiFormatting
|
||||||
|
@@formatters = {}
|
||||||
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
|
|
||||||
|
|
||||||
private
|
class << self
|
||||||
|
def map
|
||||||
# Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet.
|
yield self
|
||||||
# <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
|
end
|
||||||
|
|
||||||
# Patch to add code highlighting support to RedCloth
|
def register(name, formatter, helper)
|
||||||
def smooth_offtags( text )
|
raise ArgumentError, "format name '#{name}' is already taken" if @@formatters[name]
|
||||||
unless @pre_list.empty?
|
@@formatters[name.to_sym] = {:formatter => formatter, :helper => helper}
|
||||||
## 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
|
end
|
||||||
|
|
||||||
# Patch to add 'table of content' support to RedCloth
|
def formatter_for(name)
|
||||||
def textile_p_withtoc(tag, atts, cite, content)
|
entry = @@formatters[name.to_sym]
|
||||||
# removes wiki links from the item
|
(entry && entry[:formatter]) || Redmine::WikiFormatting::NullFormatter::Formatter
|
||||||
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
|
end
|
||||||
|
|
||||||
MACROS_RE = /
|
def helper_for(name)
|
||||||
(!)? # escaping
|
entry = @@formatters[name.to_sym]
|
||||||
(
|
(entry && entry[:helper]) || Redmine::WikiFormatting::NullFormatter::Helper
|
||||||
\{\{ # 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
|
end
|
||||||
|
|
||||||
AUTO_LINK_RE = %r{
|
def format_names
|
||||||
( # leading text
|
@@formatters.keys.map
|
||||||
<\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
|
end
|
||||||
|
|
||||||
# Turns all email addresses into clickable links (code from Rails).
|
def to_html(format, text, options = {}, &block)
|
||||||
def inline_auto_mailto(text)
|
formatter_for(format).new(text).to_html(&block)
|
||||||
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
|
||||||
|
|
||||||
public
|
# Default formatter module
|
||||||
|
module NullFormatter
|
||||||
def self.to_html(text, options = {}, &block)
|
class Formatter
|
||||||
TextileFormatter.new(text).to_html(&block)
|
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
|
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('mousemove', this.dragMoveHdlr, false);
|
||||||
document.removeEventListener('mouseup', this.dragStopHdlr, 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/)
|
assert textilizable(text).match(/Unknow project/)
|
||||||
end
|
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
|
def test_date_format_default
|
||||||
today = Date.today
|
today = Date.today
|
||||||
Setting.date_format = ''
|
Setting.date_format = ''
|
||||||
|
|
Loading…
Reference in New Issue