@@ -0,0 +1,183 | |||||
|
1 | # Redmine - project management software | |||
|
2 | # Copyright (C) 2006-2008 Jean-Philippe Lang | |||
|
3 | # | |||
|
4 | # This program is free software; you can redistribute it and/or | |||
|
5 | # modify it under the terms of the GNU General Public License | |||
|
6 | # as published by the Free Software Foundation; either version 2 | |||
|
7 | # of the License, or (at your option) any later version. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU General Public License | |||
|
15 | # along with this program; if not, write to the Free Software | |||
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
|
17 | ||||
|
18 | require 'redcloth3' | |||
|
19 | require 'coderay' | |||
|
20 | ||||
|
21 | module Redmine | |||
|
22 | module WikiFormatting | |||
|
23 | module Textile | |||
|
24 | class Formatter < RedCloth3 | |||
|
25 | ||||
|
26 | # auto_link rule after textile rules so that it doesn't break !image_url! tags | |||
|
27 | RULES = [:textile, :block_markdown_rule, :inline_auto_link, :inline_auto_mailto, :inline_toc, :inline_macros] | |||
|
28 | ||||
|
29 | def initialize(*args) | |||
|
30 | super | |||
|
31 | self.hard_breaks=true | |||
|
32 | self.no_span_caps=true | |||
|
33 | end | |||
|
34 | ||||
|
35 | def to_html(*rules, &block) | |||
|
36 | @toc = [] | |||
|
37 | @macros_runner = block | |||
|
38 | super(*RULES).to_s | |||
|
39 | end | |||
|
40 | ||||
|
41 | private | |||
|
42 | ||||
|
43 | # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet. | |||
|
44 | # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a> | |||
|
45 | def hard_break( text ) | |||
|
46 | text.gsub!( /(.)\n(?!\n|\Z|>| *(>? *[#*=]+(\s|$)|[{|]))/, "\\1<br />\n" ) if hard_breaks | |||
|
47 | end | |||
|
48 | ||||
|
49 | # Patch to add code highlighting support to RedCloth | |||
|
50 | def smooth_offtags( text ) | |||
|
51 | unless @pre_list.empty? | |||
|
52 | ## replace <pre> content | |||
|
53 | text.gsub!(/<redpre#(\d+)>/) do | |||
|
54 | content = @pre_list[$1.to_i] | |||
|
55 | if content.match(/<code\s+class="(\w+)">\s?(.+)/m) | |||
|
56 | content = "<code class=\"#{$1} CodeRay\">" + | |||
|
57 | CodeRay.scan($2, $1.downcase).html(:escape => false, :line_numbers => :inline) | |||
|
58 | end | |||
|
59 | content | |||
|
60 | end | |||
|
61 | end | |||
|
62 | end | |||
|
63 | ||||
|
64 | # Patch to add 'table of content' support to RedCloth | |||
|
65 | def textile_p_withtoc(tag, atts, cite, content) | |||
|
66 | # removes wiki links from the item | |||
|
67 | toc_item = content.gsub(/(\[\[|\]\])/, '') | |||
|
68 | # removes styles | |||
|
69 | # eg. %{color:red}Triggers% => Triggers | |||
|
70 | toc_item.gsub! %r[%\{[^\}]*\}([^%]+)%], '\\1' | |||
|
71 | ||||
|
72 | # replaces non word caracters by dashes | |||
|
73 | anchor = toc_item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') | |||
|
74 | ||||
|
75 | unless anchor.blank? | |||
|
76 | if tag =~ /^h(\d)$/ | |||
|
77 | @toc << [$1.to_i, anchor, toc_item] | |||
|
78 | end | |||
|
79 | atts << " id=\"#{anchor}\"" | |||
|
80 | content = content + "<a href=\"##{anchor}\" class=\"wiki-anchor\">¶</a>" | |||
|
81 | end | |||
|
82 | textile_p(tag, atts, cite, content) | |||
|
83 | end | |||
|
84 | ||||
|
85 | alias :textile_h1 :textile_p_withtoc | |||
|
86 | alias :textile_h2 :textile_p_withtoc | |||
|
87 | alias :textile_h3 :textile_p_withtoc | |||
|
88 | ||||
|
89 | def inline_toc(text) | |||
|
90 | text.gsub!(/<p>\{\{([<>]?)toc\}\}<\/p>/i) do | |||
|
91 | div_class = 'toc' | |||
|
92 | div_class << ' right' if $1 == '>' | |||
|
93 | div_class << ' left' if $1 == '<' | |||
|
94 | out = "<ul class=\"#{div_class}\">" | |||
|
95 | @toc.each do |heading| | |||
|
96 | level, anchor, toc_item = heading | |||
|
97 | out << "<li class=\"heading#{level}\"><a href=\"##{anchor}\">#{toc_item}</a></li>\n" | |||
|
98 | end | |||
|
99 | out << '</ul>' | |||
|
100 | out | |||
|
101 | end | |||
|
102 | end | |||
|
103 | ||||
|
104 | MACROS_RE = / | |||
|
105 | (!)? # escaping | |||
|
106 | ( | |||
|
107 | \{\{ # opening tag | |||
|
108 | ([\w]+) # macro name | |||
|
109 | (\(([^\}]*)\))? # optional arguments | |||
|
110 | \}\} # closing tag | |||
|
111 | ) | |||
|
112 | /x unless const_defined?(:MACROS_RE) | |||
|
113 | ||||
|
114 | def inline_macros(text) | |||
|
115 | text.gsub!(MACROS_RE) do | |||
|
116 | esc, all, macro = $1, $2, $3.downcase | |||
|
117 | args = ($5 || '').split(',').each(&:strip) | |||
|
118 | if esc.nil? | |||
|
119 | begin | |||
|
120 | @macros_runner.call(macro, args) | |||
|
121 | rescue => e | |||
|
122 | "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>" | |||
|
123 | end || all | |||
|
124 | else | |||
|
125 | all | |||
|
126 | end | |||
|
127 | end | |||
|
128 | end | |||
|
129 | ||||
|
130 | AUTO_LINK_RE = %r{ | |||
|
131 | ( # leading text | |||
|
132 | <\w+.*?>| # leading HTML tag, or | |||
|
133 | [^=<>!:'"/]| # leading punctuation, or | |||
|
134 | ^ # beginning of line | |||
|
135 | ) | |||
|
136 | ( | |||
|
137 | (?:https?://)| # protocol spec, or | |||
|
138 | (?:ftp://)| | |||
|
139 | (?:www\.) # www.* | |||
|
140 | ) | |||
|
141 | ( | |||
|
142 | (\S+?) # url | |||
|
143 | (\/)? # slash | |||
|
144 | ) | |||
|
145 | ([^\w\=\/;\(\)]*?) # post | |||
|
146 | (?=<|\s|$) | |||
|
147 | }x unless const_defined?(:AUTO_LINK_RE) | |||
|
148 | ||||
|
149 | # Turns all urls into clickable links (code from Rails). | |||
|
150 | def inline_auto_link(text) | |||
|
151 | text.gsub!(AUTO_LINK_RE) do | |||
|
152 | all, leading, proto, url, post = $&, $1, $2, $3, $6 | |||
|
153 | if leading =~ /<a\s/i || leading =~ /![<>=]?/ | |||
|
154 | # don't replace URL's that are already linked | |||
|
155 | # and URL's prefixed with ! !> !< != (textile images) | |||
|
156 | all | |||
|
157 | else | |||
|
158 | # Idea below : an URL with unbalanced parethesis and | |||
|
159 | # ending by ')' is put into external parenthesis | |||
|
160 | if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) ) | |||
|
161 | url=url[0..-2] # discard closing parenth from url | |||
|
162 | post = ")"+post # add closing parenth to post | |||
|
163 | end | |||
|
164 | %(#{leading}<a class="external" href="#{proto=="www."?"http://www.":proto}#{url}">#{proto + url}</a>#{post}) | |||
|
165 | end | |||
|
166 | end | |||
|
167 | end | |||
|
168 | ||||
|
169 | # Turns all email addresses into clickable links (code from Rails). | |||
|
170 | def inline_auto_mailto(text) | |||
|
171 | text.gsub!(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do | |||
|
172 | mail = $1 | |||
|
173 | if text.match(/<a\b[^>]*>(.*)(#{Regexp.escape(mail)})(.*)<\/a>/) | |||
|
174 | ||||
|
175 | else | |||
|
176 | %{<a href="mailto:#{mail}" class="email">#{mail}</a>} | |||
|
177 | end | |||
|
178 | end | |||
|
179 | end | |||
|
180 | end | |||
|
181 | end | |||
|
182 | end | |||
|
183 | end |
@@ -0,0 +1,43 | |||||
|
1 | # Redmine - project management software | |||
|
2 | # Copyright (C) 2006-2008 Jean-Philippe Lang | |||
|
3 | # | |||
|
4 | # This program is free software; you can redistribute it and/or | |||
|
5 | # modify it under the terms of the GNU General Public License | |||
|
6 | # as published by the Free Software Foundation; either version 2 | |||
|
7 | # of the License, or (at your option) any later version. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU General Public License | |||
|
15 | # along with this program; if not, write to the Free Software | |||
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
|
17 | ||||
|
18 | module Redmine | |||
|
19 | module WikiFormatting | |||
|
20 | module Textile | |||
|
21 | module Helper | |||
|
22 | def wikitoolbar_for(field_id) | |||
|
23 | help_link = l(:setting_text_formatting) + ': ' + | |||
|
24 | link_to(l(:label_help), compute_public_path('wiki_syntax', 'help', 'html'), | |||
|
25 | :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;") | |||
|
26 | ||||
|
27 | javascript_include_tag('jstoolbar/jstoolbar') + | |||
|
28 | javascript_include_tag('jstoolbar/textile') + | |||
|
29 | javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language}") + | |||
|
30 | javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.setHelpLink('#{help_link}'); toolbar.draw();") | |||
|
31 | end | |||
|
32 | ||||
|
33 | def initial_page_content(page) | |||
|
34 | "h1. #{@page.pretty_title}" | |||
|
35 | end | |||
|
36 | ||||
|
37 | def heads_for_wiki_formatter | |||
|
38 | stylesheet_link_tag 'jstoolbar' | |||
|
39 | end | |||
|
40 | end | |||
|
41 | end | |||
|
42 | end | |||
|
43 | end |
@@ -0,0 +1,200 | |||||
|
1 | /* ***** BEGIN LICENSE BLOCK ***** | |||
|
2 | * This file is part of DotClear. | |||
|
3 | * Copyright (c) 2005 Nicolas Martin & Olivier Meunier and contributors. All | |||
|
4 | * rights reserved. | |||
|
5 | * | |||
|
6 | * DotClear is free software; you can redistribute it and/or modify | |||
|
7 | * it under the terms of the GNU General Public License as published by | |||
|
8 | * the Free Software Foundation; either version 2 of the License, or | |||
|
9 | * (at your option) any later version. | |||
|
10 | * | |||
|
11 | * DotClear is distributed in the hope that it will be useful, | |||
|
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
14 | * GNU General Public License for more details. | |||
|
15 | * | |||
|
16 | * You should have received a copy of the GNU General Public License | |||
|
17 | * along with DotClear; if not, write to the Free Software | |||
|
18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |||
|
19 | * | |||
|
20 | * ***** END LICENSE BLOCK ***** | |||
|
21 | */ | |||
|
22 | ||||
|
23 | /* Modified by JP LANG for textile formatting */ | |||
|
24 | ||||
|
25 | // strong | |||
|
26 | jsToolBar.prototype.elements.strong = { | |||
|
27 | type: 'button', | |||
|
28 | title: 'Strong', | |||
|
29 | fn: { | |||
|
30 | wiki: function() { this.singleTag('*') } | |||
|
31 | } | |||
|
32 | } | |||
|
33 | ||||
|
34 | // em | |||
|
35 | jsToolBar.prototype.elements.em = { | |||
|
36 | type: 'button', | |||
|
37 | title: 'Italic', | |||
|
38 | fn: { | |||
|
39 | wiki: function() { this.singleTag("_") } | |||
|
40 | } | |||
|
41 | } | |||
|
42 | ||||
|
43 | // ins | |||
|
44 | jsToolBar.prototype.elements.ins = { | |||
|
45 | type: 'button', | |||
|
46 | title: 'Underline', | |||
|
47 | fn: { | |||
|
48 | wiki: function() { this.singleTag('+') } | |||
|
49 | } | |||
|
50 | } | |||
|
51 | ||||
|
52 | // del | |||
|
53 | jsToolBar.prototype.elements.del = { | |||
|
54 | type: 'button', | |||
|
55 | title: 'Deleted', | |||
|
56 | fn: { | |||
|
57 | wiki: function() { this.singleTag('-') } | |||
|
58 | } | |||
|
59 | } | |||
|
60 | ||||
|
61 | // code | |||
|
62 | jsToolBar.prototype.elements.code = { | |||
|
63 | type: 'button', | |||
|
64 | title: 'Code', | |||
|
65 | fn: { | |||
|
66 | wiki: function() { this.singleTag('@') } | |||
|
67 | } | |||
|
68 | } | |||
|
69 | ||||
|
70 | // spacer | |||
|
71 | jsToolBar.prototype.elements.space1 = {type: 'space'} | |||
|
72 | ||||
|
73 | // headings | |||
|
74 | jsToolBar.prototype.elements.h1 = { | |||
|
75 | type: 'button', | |||
|
76 | title: 'Heading 1', | |||
|
77 | fn: { | |||
|
78 | wiki: function() { | |||
|
79 | this.encloseLineSelection('h1. ', '',function(str) { | |||
|
80 | str = str.replace(/^h\d+\.\s+/, '') | |||
|
81 | return str; | |||
|
82 | }); | |||
|
83 | } | |||
|
84 | } | |||
|
85 | } | |||
|
86 | jsToolBar.prototype.elements.h2 = { | |||
|
87 | type: 'button', | |||
|
88 | title: 'Heading 2', | |||
|
89 | fn: { | |||
|
90 | wiki: function() { | |||
|
91 | this.encloseLineSelection('h2. ', '',function(str) { | |||
|
92 | str = str.replace(/^h\d+\.\s+/, '') | |||
|
93 | return str; | |||
|
94 | }); | |||
|
95 | } | |||
|
96 | } | |||
|
97 | } | |||
|
98 | jsToolBar.prototype.elements.h3 = { | |||
|
99 | type: 'button', | |||
|
100 | title: 'Heading 3', | |||
|
101 | fn: { | |||
|
102 | wiki: function() { | |||
|
103 | this.encloseLineSelection('h3. ', '',function(str) { | |||
|
104 | str = str.replace(/^h\d+\.\s+/, '') | |||
|
105 | return str; | |||
|
106 | }); | |||
|
107 | } | |||
|
108 | } | |||
|
109 | } | |||
|
110 | ||||
|
111 | // spacer | |||
|
112 | jsToolBar.prototype.elements.space2 = {type: 'space'} | |||
|
113 | ||||
|
114 | // ul | |||
|
115 | jsToolBar.prototype.elements.ul = { | |||
|
116 | type: 'button', | |||
|
117 | title: 'Unordered list', | |||
|
118 | fn: { | |||
|
119 | wiki: function() { | |||
|
120 | this.encloseLineSelection('','',function(str) { | |||
|
121 | str = str.replace(/\r/g,''); | |||
|
122 | return str.replace(/(\n|^)[#-]?\s*/g,"$1* "); | |||
|
123 | }); | |||
|
124 | } | |||
|
125 | } | |||
|
126 | } | |||
|
127 | ||||
|
128 | // ol | |||
|
129 | jsToolBar.prototype.elements.ol = { | |||
|
130 | type: 'button', | |||
|
131 | title: 'Ordered list', | |||
|
132 | fn: { | |||
|
133 | wiki: function() { | |||
|
134 | this.encloseLineSelection('','',function(str) { | |||
|
135 | str = str.replace(/\r/g,''); | |||
|
136 | return str.replace(/(\n|^)[*-]?\s*/g,"$1# "); | |||
|
137 | }); | |||
|
138 | } | |||
|
139 | } | |||
|
140 | } | |||
|
141 | ||||
|
142 | // spacer | |||
|
143 | jsToolBar.prototype.elements.space3 = {type: 'space'} | |||
|
144 | ||||
|
145 | // bq | |||
|
146 | jsToolBar.prototype.elements.bq = { | |||
|
147 | type: 'button', | |||
|
148 | title: 'Quote', | |||
|
149 | fn: { | |||
|
150 | wiki: function() { | |||
|
151 | this.encloseLineSelection('','',function(str) { | |||
|
152 | str = str.replace(/\r/g,''); | |||
|
153 | return str.replace(/(\n|^) *([^\n]*)/g,"$1> $2"); | |||
|
154 | }); | |||
|
155 | } | |||
|
156 | } | |||
|
157 | } | |||
|
158 | ||||
|
159 | // unbq | |||
|
160 | jsToolBar.prototype.elements.unbq = { | |||
|
161 | type: 'button', | |||
|
162 | title: 'Unquote', | |||
|
163 | fn: { | |||
|
164 | wiki: function() { | |||
|
165 | this.encloseLineSelection('','',function(str) { | |||
|
166 | str = str.replace(/\r/g,''); | |||
|
167 | return str.replace(/(\n|^) *[>]? *([^\n]*)/g,"$1$2"); | |||
|
168 | }); | |||
|
169 | } | |||
|
170 | } | |||
|
171 | } | |||
|
172 | ||||
|
173 | // pre | |||
|
174 | jsToolBar.prototype.elements.pre = { | |||
|
175 | type: 'button', | |||
|
176 | title: 'Preformatted text', | |||
|
177 | fn: { | |||
|
178 | wiki: function() { this.encloseLineSelection('<pre>\n', '\n</pre>') } | |||
|
179 | } | |||
|
180 | } | |||
|
181 | ||||
|
182 | // spacer | |||
|
183 | jsToolBar.prototype.elements.space4 = {type: 'space'} | |||
|
184 | ||||
|
185 | // wiki page | |||
|
186 | jsToolBar.prototype.elements.link = { | |||
|
187 | type: 'button', | |||
|
188 | title: 'Wiki link', | |||
|
189 | fn: { | |||
|
190 | wiki: function() { this.encloseSelection("[[", "]]") } | |||
|
191 | } | |||
|
192 | } | |||
|
193 | // image | |||
|
194 | jsToolBar.prototype.elements.img = { | |||
|
195 | type: 'button', | |||
|
196 | title: 'Image', | |||
|
197 | fn: { | |||
|
198 | wiki: function() { this.encloseSelection("!", "!") } | |||
|
199 | } | |||
|
200 | } |
@@ -63,7 +63,7 class WikiController < ApplicationController | |||||
63 | @page.content = WikiContent.new(:page => @page) if @page.new_record? |
|
63 | @page.content = WikiContent.new(:page => @page) if @page.new_record? | |
64 |
|
64 | |||
65 | @content = @page.content_for_version(params[:version]) |
|
65 | @content = @page.content_for_version(params[:version]) | |
66 |
@content.text = |
|
66 | @content.text = initial_page_content(@page) if @content.text.blank? | |
67 | # don't keep previous comment |
|
67 | # don't keep previous comment | |
68 | @content.comments = nil |
|
68 | @content.comments = nil | |
69 | if request.get? |
|
69 | if request.get? | |
@@ -208,4 +208,11 private | |||||
208 | def editable?(page = @page) |
|
208 | def editable?(page = @page) | |
209 | page.editable_by?(User.current) |
|
209 | page.editable_by?(User.current) | |
210 | end |
|
210 | end | |
|
211 | ||||
|
212 | # Returns the default content of a new wiki page | |||
|
213 | def initial_page_content(page) | |||
|
214 | helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting) | |||
|
215 | extend helper unless self.instance_of?(helper) | |||
|
216 | helper.instance_method(:initial_page_content).bind(self).call(page) | |||
|
217 | end | |||
211 | end |
|
218 | end |
@@ -17,10 +17,14 | |||||
17 |
|
17 | |||
18 | require 'coderay' |
|
18 | require 'coderay' | |
19 | require 'coderay/helpers/file_type' |
|
19 | require 'coderay/helpers/file_type' | |
|
20 | require 'forwardable' | |||
20 |
|
21 | |||
21 | module ApplicationHelper |
|
22 | module ApplicationHelper | |
22 | include Redmine::WikiFormatting::Macros::Definitions |
|
23 | include Redmine::WikiFormatting::Macros::Definitions | |
23 |
|
24 | |||
|
25 | extend Forwardable | |||
|
26 | def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter | |||
|
27 | ||||
24 | def current_role |
|
28 | def current_role | |
25 | @current_role ||= User.current.role_for_project(@project) |
|
29 | @current_role ||= User.current.role_for_project(@project) | |
26 | end |
|
30 | end | |
@@ -259,9 +263,7 module ApplicationHelper | |||||
259 | end |
|
263 | end | |
260 | end |
|
264 | end | |
261 |
|
265 | |||
262 | text = (Setting.text_formatting == 'textile') ? |
|
266 | text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text) { |macro, args| exec_macro(macro, obj, args) } | |
263 | Redmine::WikiFormatting.to_html(text) { |macro, args| exec_macro(macro, obj, args) } : |
|
|||
264 | simple_format(auto_link(h(text))) |
|
|||
265 |
|
267 | |||
266 | # different methods for formatting wiki links |
|
268 | # different methods for formatting wiki links | |
267 | case options[:wiki_links] |
|
269 | case options[:wiki_links] | |
@@ -549,18 +551,6 module ApplicationHelper | |||||
549 | end |
|
551 | end | |
550 | end |
|
552 | end | |
551 |
|
553 | |||
552 | def wikitoolbar_for(field_id) |
|
|||
553 | return '' unless Setting.text_formatting == 'textile' |
|
|||
554 |
|
||||
555 | help_link = l(:setting_text_formatting) + ': ' + |
|
|||
556 | link_to(l(:label_help), compute_public_path('wiki_syntax', 'help', 'html'), |
|
|||
557 | :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;") |
|
|||
558 |
|
||||
559 | javascript_include_tag('jstoolbar/jstoolbar') + |
|
|||
560 | javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language}") + |
|
|||
561 | javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.setHelpLink('#{help_link}'); toolbar.draw();") |
|
|||
562 | end |
|
|||
563 |
|
||||
564 | def content_for(name, content = nil, &block) |
|
554 | def content_for(name, content = nil, &block) | |
565 | @has_content ||= {} |
|
555 | @has_content ||= {} | |
566 | @has_content[name] = true |
|
556 | @has_content[name] = true | |
@@ -570,4 +560,12 module ApplicationHelper | |||||
570 | def has_content?(name) |
|
560 | def has_content?(name) | |
571 | (@has_content && @has_content[name]) || false |
|
561 | (@has_content && @has_content[name]) || false | |
572 | end |
|
562 | end | |
|
563 | ||||
|
564 | private | |||
|
565 | ||||
|
566 | def wiki_helper | |||
|
567 | helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting) | |||
|
568 | extend helper | |||
|
569 | return self | |||
|
570 | end | |||
573 | end |
|
571 | end |
@@ -7,7 +7,7 | |||||
7 | <meta name="keywords" content="issue,bug,tracker" /> |
|
7 | <meta name="keywords" content="issue,bug,tracker" /> | |
8 | <%= stylesheet_link_tag 'application', :media => 'all' %> |
|
8 | <%= stylesheet_link_tag 'application', :media => 'all' %> | |
9 | <%= javascript_include_tag :defaults %> |
|
9 | <%= javascript_include_tag :defaults %> | |
10 | <%= stylesheet_link_tag 'jstoolbar' %> |
|
10 | <%= heads_for_wiki_formatter %> | |
11 | <!--[if IE]> |
|
11 | <!--[if IE]> | |
12 | <style type="text/css"> |
|
12 | <style type="text/css"> | |
13 | * html body{ width: expression( document.documentElement.clientWidth < 900 ? '900px' : '100%' ); } |
|
13 | * html body{ width: expression( document.documentElement.clientWidth < 900 ? '900px' : '100%' ); } |
@@ -32,6 +32,6 hr { | |||||
32 | <body> |
|
32 | <body> | |
33 | <%= yield %> |
|
33 | <%= yield %> | |
34 | <hr /> |
|
34 | <hr /> | |
35 | <span class="footer"><%= Redmine::WikiFormatting.to_html(Setting.emails_footer) %></span> |
|
35 | <span class="footer"><%= Redmine::WikiFormatting.to_html(Setting.text_formatting, Setting.emails_footer) %></span> | |
36 | </body> |
|
36 | </body> | |
37 | </html> |
|
37 | </html> |
@@ -39,7 +39,7 | |||||
39 | <%= select_tag 'settings[protocol]', options_for_select(['http', 'https'], Setting.protocol) %></p> |
|
39 | <%= select_tag 'settings[protocol]', options_for_select(['http', 'https'], Setting.protocol) %></p> | |
40 |
|
40 | |||
41 | <p><label><%= l(:setting_text_formatting) %></label> |
|
41 | <p><label><%= l(:setting_text_formatting) %></label> | |
42 |
<%= select_tag 'settings[text_formatting]', options_for_select([[l(:label_none), "0"], [ |
|
42 | <%= 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> | |
43 |
|
43 | |||
44 | <p><label><%= l(:setting_wiki_compression) %></label> |
|
44 | <p><label><%= l(:setting_wiki_compression) %></label> | |
45 | <%= select_tag 'settings[wiki_compression]', options_for_select( [[l(:label_none), 0], ["gzip", "gzip"]], Setting.wiki_compression) %></p> |
|
45 | <%= 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' | |||||
6 | require 'redmine/themes' |
|
6 | require 'redmine/themes' | |
7 | require 'redmine/hook' |
|
7 | require 'redmine/hook' | |
8 | require 'redmine/plugin' |
|
8 | require 'redmine/plugin' | |
|
9 | require 'redmine/wiki_formatting' | |||
9 |
|
10 | |||
10 | begin |
|
11 | begin | |
11 | require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick) |
|
12 | require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick) | |
@@ -150,3 +151,7 Redmine::Activity.map do |activity| | |||||
150 | activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false |
|
151 | activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false | |
151 | activity.register :messages, :default => false |
|
152 | activity.register :messages, :default => false | |
152 | end |
|
153 | end | |
|
154 | ||||
|
155 | Redmine::WikiFormatting.map do |format| | |||
|
156 | format.register :textile, Redmine::WikiFormatting::Textile::Formatter, Redmine::WikiFormatting::Textile::Helper | |||
|
157 | end |
@@ -149,6 +149,16 module Redmine #:nodoc: | |||||
149 | Redmine::Activity.register(*args) |
|
149 | Redmine::Activity.register(*args) | |
150 | end |
|
150 | end | |
151 |
|
151 | |||
|
152 | # Registers a wiki formatter. | |||
|
153 | # | |||
|
154 | # Parameters: | |||
|
155 | # * +name+ - human-readable name | |||
|
156 | # * +formatter+ - formatter class, which should have an instance method +to_html+ | |||
|
157 | # * +helper+ - helper module, which will be included by wiki pages | |||
|
158 | def wiki_format_provider(name, formatter, helper) | |||
|
159 | Redmine::WikiFormatting.register(name, formatter, helper) | |||
|
160 | end | |||
|
161 | ||||
152 | # Returns +true+ if the plugin can be configured. |
|
162 | # Returns +true+ if the plugin can be configured. | |
153 | def configurable? |
|
163 | def configurable? | |
154 | settings && settings.is_a?(Hash) && !settings[:partial].blank? |
|
164 | settings && settings.is_a?(Hash) && !settings[:partial].blank? |
@@ -1,5 +1,5 | |||||
1 |
# |
|
1 | # Redmine - project management software | |
2 |
# Copyright (C) 2006-200 |
|
2 | # Copyright (C) 2006-2008 Jean-Philippe Lang | |
3 | # |
|
3 | # | |
4 | # This program is free software; you can redistribute it and/or |
|
4 | # This program is free software; you can redistribute it and/or | |
5 | # modify it under the terms of the GNU General Public License |
|
5 | # modify it under the terms of the GNU General Public License | |
@@ -15,176 +15,65 | |||||
15 | # along with this program; if not, write to the Free Software |
|
15 | # along with this program; if not, write to the Free Software | |
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
17 |
|
17 | |||
18 | require 'redcloth3' |
|
|||
19 | require 'coderay' |
|
|||
20 |
|
||||
21 | module Redmine |
|
18 | module Redmine | |
22 | module WikiFormatting |
|
19 | module WikiFormatting | |
|
20 | @@formatters = {} | |||
23 |
|
21 | |||
24 | private |
|
22 | class << self | |
25 |
|
23 | def map | ||
26 | class TextileFormatter < RedCloth3 |
|
24 | yield self | |
27 |
|
||||
28 | # auto_link rule after textile rules so that it doesn't break !image_url! tags |
|
|||
29 | RULES = [:textile, :block_markdown_rule, :inline_auto_link, :inline_auto_mailto, :inline_toc, :inline_macros] |
|
|||
30 |
|
||||
31 | def initialize(*args) |
|
|||
32 | super |
|
|||
33 | self.hard_breaks=true |
|
|||
34 | self.no_span_caps=true |
|
|||
35 | end |
|
25 | end | |
36 |
|
26 | |||
37 | def to_html(*rules, &block) |
|
27 | def register(name, formatter, helper) | |
38 | @toc = [] |
|
28 | raise ArgumentError, "format name '#{name}' is already taken" if @@formatters[name] | |
39 | @macros_runner = block |
|
29 | @@formatters[name.to_sym] = {:formatter => formatter, :helper => helper} | |
40 | super(*RULES).to_s |
|
|||
41 | end |
|
30 | end | |
42 |
|
31 | |||
43 | private |
|
32 | def formatter_for(name) | |
44 |
|
33 | entry = @@formatters[name.to_sym] | ||
45 | # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet. |
|
34 | (entry && entry[:formatter]) || Redmine::WikiFormatting::NullFormatter::Formatter | |
46 | # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a> |
|
|||
47 | def hard_break( text ) |
|
|||
48 | text.gsub!( /(.)\n(?!\n|\Z|>| *(>? *[#*=]+(\s|$)|[{|]))/, "\\1<br />\n" ) if hard_breaks |
|
|||
49 | end |
|
35 | end | |
50 |
|
36 | |||
51 | # Patch to add code highlighting support to RedCloth |
|
37 | def helper_for(name) | |
52 | def smooth_offtags( text ) |
|
38 | entry = @@formatters[name.to_sym] | |
53 | unless @pre_list.empty? |
|
39 | (entry && entry[:helper]) || Redmine::WikiFormatting::NullFormatter::Helper | |
54 | ## replace <pre> content |
|
|||
55 | text.gsub!(/<redpre#(\d+)>/) do |
|
|||
56 | content = @pre_list[$1.to_i] |
|
|||
57 | if content.match(/<code\s+class="(\w+)">\s?(.+)/m) |
|
|||
58 | content = "<code class=\"#{$1} CodeRay\">" + |
|
|||
59 | CodeRay.scan($2, $1.downcase).html(:escape => false, :line_numbers => :inline) |
|
|||
60 | end |
|
|||
61 | content |
|
|||
62 |
|
|
40 | end | |
63 | end |
|
|||
64 | end |
|
|||
65 |
|
||||
66 | # Patch to add 'table of content' support to RedCloth |
|
|||
67 | def textile_p_withtoc(tag, atts, cite, content) |
|
|||
68 | # removes wiki links from the item |
|
|||
69 | toc_item = content.gsub(/(\[\[|\]\])/, '') |
|
|||
70 | # removes styles |
|
|||
71 | # eg. %{color:red}Triggers% => Triggers |
|
|||
72 | toc_item.gsub! %r[%\{[^\}]*\}([^%]+)%], '\\1' |
|
|||
73 |
|
|
41 | ||
74 | # replaces non word caracters by dashes |
|
42 | def format_names | |
75 | anchor = toc_item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') |
|
43 | @@formatters.keys.map | |
76 |
|
||||
77 | unless anchor.blank? |
|
|||
78 | if tag =~ /^h(\d)$/ |
|
|||
79 | @toc << [$1.to_i, anchor, toc_item] |
|
|||
80 | end |
|
|||
81 | atts << " id=\"#{anchor}\"" |
|
|||
82 | content = content + "<a href=\"##{anchor}\" class=\"wiki-anchor\">¶</a>" |
|
|||
83 | end |
|
|||
84 | textile_p(tag, atts, cite, content) |
|
|||
85 | end |
|
44 | end | |
86 |
|
45 | |||
87 | alias :textile_h1 :textile_p_withtoc |
|
46 | def to_html(format, text, options = {}, &block) | |
88 | alias :textile_h2 :textile_p_withtoc |
|
47 | formatter_for(format).new(text).to_html(&block) | |
89 | alias :textile_h3 :textile_p_withtoc |
|
|||
90 |
|
||||
91 | def inline_toc(text) |
|
|||
92 | text.gsub!(/<p>\{\{([<>]?)toc\}\}<\/p>/i) do |
|
|||
93 | div_class = 'toc' |
|
|||
94 | div_class << ' right' if $1 == '>' |
|
|||
95 | div_class << ' left' if $1 == '<' |
|
|||
96 | out = "<ul class=\"#{div_class}\">" |
|
|||
97 | @toc.each do |heading| |
|
|||
98 | level, anchor, toc_item = heading |
|
|||
99 | out << "<li class=\"heading#{level}\"><a href=\"##{anchor}\">#{toc_item}</a></li>\n" |
|
|||
100 | end |
|
|||
101 | out << '</ul>' |
|
|||
102 | out |
|
|||
103 |
|
|
48 | end | |
104 |
|
|
49 | end | |
105 |
|
|
50 | ||
106 | MACROS_RE = / |
|
51 | # Default formatter module | |
107 | (!)? # escaping |
|
52 | module NullFormatter | |
108 | ( |
|
53 | class Formatter | |
109 | \{\{ # opening tag |
|
54 | include ActionView::Helpers::TagHelper | |
110 | ([\w]+) # macro name |
|
55 | include ActionView::Helpers::TextHelper | |
111 | (\(([^\}]*)\))? # optional arguments |
|
|||
112 | \}\} # closing tag |
|
|||
113 | ) |
|
|||
114 | /x unless const_defined?(:MACROS_RE) |
|
|||
115 |
|
56 | |||
116 |
def in |
|
57 | def initialize(text) | |
117 | text.gsub!(MACROS_RE) do |
|
58 | @text = text | |
118 | esc, all, macro = $1, $2, $3.downcase |
|
|||
119 | args = ($5 || '').split(',').each(&:strip) |
|
|||
120 | if esc.nil? |
|
|||
121 | begin |
|
|||
122 | @macros_runner.call(macro, args) |
|
|||
123 | rescue => e |
|
|||
124 | "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>" |
|
|||
125 | end || all |
|
|||
126 | else |
|
|||
127 | all |
|
|||
128 | end |
|
|||
129 | end |
|
59 | end | |
130 | end |
|
|||
131 |
|
||||
132 | AUTO_LINK_RE = %r{ |
|
|||
133 | ( # leading text |
|
|||
134 | <\w+.*?>| # leading HTML tag, or |
|
|||
135 | [^=<>!:'"/]| # leading punctuation, or |
|
|||
136 | ^ # beginning of line |
|
|||
137 | ) |
|
|||
138 | ( |
|
|||
139 | (?:https?://)| # protocol spec, or |
|
|||
140 | (?:ftp://)| |
|
|||
141 | (?:www\.) # www.* |
|
|||
142 | ) |
|
|||
143 | ( |
|
|||
144 | (\S+?) # url |
|
|||
145 | (\/)? # slash |
|
|||
146 | ) |
|
|||
147 | ([^\w\=\/;\(\)]*?) # post |
|
|||
148 | (?=<|\s|$) |
|
|||
149 | }x unless const_defined?(:AUTO_LINK_RE) |
|
|||
150 |
|
60 | |||
151 | # Turns all urls into clickable links (code from Rails). |
|
61 | def to_html(*args) | |
152 | def inline_auto_link(text) |
|
62 | simple_format(auto_link(CGI::escapeHTML(@text))) | |
153 | text.gsub!(AUTO_LINK_RE) do |
|
|||
154 | all, leading, proto, url, post = $&, $1, $2, $3, $6 |
|
|||
155 | if leading =~ /<a\s/i || leading =~ /![<>=]?/ |
|
|||
156 | # don't replace URL's that are already linked |
|
|||
157 | # and URL's prefixed with ! !> !< != (textile images) |
|
|||
158 | all |
|
|||
159 | else |
|
|||
160 | # Idea below : an URL with unbalanced parethesis and |
|
|||
161 | # ending by ')' is put into external parenthesis |
|
|||
162 | if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) ) |
|
|||
163 | url=url[0..-2] # discard closing parenth from url |
|
|||
164 | post = ")"+post # add closing parenth to post |
|
|||
165 | end |
|
|||
166 | %(#{leading}<a class="external" href="#{proto=="www."?"http://www.":proto}#{url}">#{proto + url}</a>#{post}) |
|
|||
167 | end |
|
|||
168 | end |
|
63 | end | |
169 | end |
|
64 | end | |
170 |
|
65 | |||
171 | # Turns all email addresses into clickable links (code from Rails). |
|
66 | module Helper | |
172 | def inline_auto_mailto(text) |
|
67 | def wikitoolbar_for(field_id) | |
173 | text.gsub!(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do |
|
|||
174 | mail = $1 |
|
|||
175 | if text.match(/<a\b[^>]*>(.*)(#{Regexp.escape(mail)})(.*)<\/a>/) |
|
|||
176 |
|
||||
177 | else |
|
|||
178 | %{<a href="mailto:#{mail}" class="email">#{mail}</a>} |
|
|||
179 |
|
|
68 | end | |
|
69 | ||||
|
70 | def heads_for_wiki_formatter | |||
180 | end |
|
71 | end | |
|
72 | ||||
|
73 | def initial_page_content(page) | |||
|
74 | page.pretty_title.to_s | |||
181 | end |
|
75 | end | |
182 | end |
|
76 | end | |
183 |
|
||||
184 | public |
|
|||
185 |
|
||||
186 | def self.to_html(text, options = {}, &block) |
|
|||
187 | TextileFormatter.new(text).to_html(&block) |
|
|||
188 | end |
|
77 | end | |
189 | end |
|
78 | end | |
190 | end |
|
79 | end |
@@ -378,182 +378,3 jsToolBar.prototype.resizeDragStop = function(event) { | |||||
378 | document.removeEventListener('mousemove', this.dragMoveHdlr, false); |
|
378 | document.removeEventListener('mousemove', this.dragMoveHdlr, false); | |
379 | document.removeEventListener('mouseup', this.dragStopHdlr, false); |
|
379 | document.removeEventListener('mouseup', this.dragStopHdlr, false); | |
380 | }; |
|
380 | }; | |
381 |
|
||||
382 | // Elements definition ------------------------------------ |
|
|||
383 |
|
||||
384 | // strong |
|
|||
385 | jsToolBar.prototype.elements.strong = { |
|
|||
386 | type: 'button', |
|
|||
387 | title: 'Strong', |
|
|||
388 | fn: { |
|
|||
389 | wiki: function() { this.singleTag('*') } |
|
|||
390 | } |
|
|||
391 | } |
|
|||
392 |
|
||||
393 | // em |
|
|||
394 | jsToolBar.prototype.elements.em = { |
|
|||
395 | type: 'button', |
|
|||
396 | title: 'Italic', |
|
|||
397 | fn: { |
|
|||
398 | wiki: function() { this.singleTag("_") } |
|
|||
399 | } |
|
|||
400 | } |
|
|||
401 |
|
||||
402 | // ins |
|
|||
403 | jsToolBar.prototype.elements.ins = { |
|
|||
404 | type: 'button', |
|
|||
405 | title: 'Underline', |
|
|||
406 | fn: { |
|
|||
407 | wiki: function() { this.singleTag('+') } |
|
|||
408 | } |
|
|||
409 | } |
|
|||
410 |
|
||||
411 | // del |
|
|||
412 | jsToolBar.prototype.elements.del = { |
|
|||
413 | type: 'button', |
|
|||
414 | title: 'Deleted', |
|
|||
415 | fn: { |
|
|||
416 | wiki: function() { this.singleTag('-') } |
|
|||
417 | } |
|
|||
418 | } |
|
|||
419 |
|
||||
420 | // code |
|
|||
421 | jsToolBar.prototype.elements.code = { |
|
|||
422 | type: 'button', |
|
|||
423 | title: 'Code', |
|
|||
424 | fn: { |
|
|||
425 | wiki: function() { this.singleTag('@') } |
|
|||
426 | } |
|
|||
427 | } |
|
|||
428 |
|
||||
429 | // spacer |
|
|||
430 | jsToolBar.prototype.elements.space1 = {type: 'space'} |
|
|||
431 |
|
||||
432 | // headings |
|
|||
433 | jsToolBar.prototype.elements.h1 = { |
|
|||
434 | type: 'button', |
|
|||
435 | title: 'Heading 1', |
|
|||
436 | fn: { |
|
|||
437 | wiki: function() { |
|
|||
438 | this.encloseLineSelection('h1. ', '',function(str) { |
|
|||
439 | str = str.replace(/^h\d+\.\s+/, '') |
|
|||
440 | return str; |
|
|||
441 | }); |
|
|||
442 | } |
|
|||
443 | } |
|
|||
444 | } |
|
|||
445 | jsToolBar.prototype.elements.h2 = { |
|
|||
446 | type: 'button', |
|
|||
447 | title: 'Heading 2', |
|
|||
448 | fn: { |
|
|||
449 | wiki: function() { |
|
|||
450 | this.encloseLineSelection('h2. ', '',function(str) { |
|
|||
451 | str = str.replace(/^h\d+\.\s+/, '') |
|
|||
452 | return str; |
|
|||
453 | }); |
|
|||
454 | } |
|
|||
455 | } |
|
|||
456 | } |
|
|||
457 | jsToolBar.prototype.elements.h3 = { |
|
|||
458 | type: 'button', |
|
|||
459 | title: 'Heading 3', |
|
|||
460 | fn: { |
|
|||
461 | wiki: function() { |
|
|||
462 | this.encloseLineSelection('h3. ', '',function(str) { |
|
|||
463 | str = str.replace(/^h\d+\.\s+/, '') |
|
|||
464 | return str; |
|
|||
465 | }); |
|
|||
466 | } |
|
|||
467 | } |
|
|||
468 | } |
|
|||
469 |
|
||||
470 | // spacer |
|
|||
471 | jsToolBar.prototype.elements.space2 = {type: 'space'} |
|
|||
472 |
|
||||
473 | // ul |
|
|||
474 | jsToolBar.prototype.elements.ul = { |
|
|||
475 | type: 'button', |
|
|||
476 | title: 'Unordered list', |
|
|||
477 | fn: { |
|
|||
478 | wiki: function() { |
|
|||
479 | this.encloseLineSelection('','',function(str) { |
|
|||
480 | str = str.replace(/\r/g,''); |
|
|||
481 | return str.replace(/(\n|^)[#-]?\s*/g,"$1* "); |
|
|||
482 | }); |
|
|||
483 | } |
|
|||
484 | } |
|
|||
485 | } |
|
|||
486 |
|
||||
487 | // ol |
|
|||
488 | jsToolBar.prototype.elements.ol = { |
|
|||
489 | type: 'button', |
|
|||
490 | title: 'Ordered list', |
|
|||
491 | fn: { |
|
|||
492 | wiki: function() { |
|
|||
493 | this.encloseLineSelection('','',function(str) { |
|
|||
494 | str = str.replace(/\r/g,''); |
|
|||
495 | return str.replace(/(\n|^)[*-]?\s*/g,"$1# "); |
|
|||
496 | }); |
|
|||
497 | } |
|
|||
498 | } |
|
|||
499 | } |
|
|||
500 |
|
||||
501 | // spacer |
|
|||
502 | jsToolBar.prototype.elements.space3 = {type: 'space'} |
|
|||
503 |
|
||||
504 | // bq |
|
|||
505 | jsToolBar.prototype.elements.bq = { |
|
|||
506 | type: 'button', |
|
|||
507 | title: 'Quote', |
|
|||
508 | fn: { |
|
|||
509 | wiki: function() { |
|
|||
510 | this.encloseLineSelection('','',function(str) { |
|
|||
511 | str = str.replace(/\r/g,''); |
|
|||
512 | return str.replace(/(\n|^) *([^\n]*)/g,"$1> $2"); |
|
|||
513 | }); |
|
|||
514 | } |
|
|||
515 | } |
|
|||
516 | } |
|
|||
517 |
|
||||
518 | // unbq |
|
|||
519 | jsToolBar.prototype.elements.unbq = { |
|
|||
520 | type: 'button', |
|
|||
521 | title: 'Unquote', |
|
|||
522 | fn: { |
|
|||
523 | wiki: function() { |
|
|||
524 | this.encloseLineSelection('','',function(str) { |
|
|||
525 | str = str.replace(/\r/g,''); |
|
|||
526 | return str.replace(/(\n|^) *[>]? *([^\n]*)/g,"$1$2"); |
|
|||
527 | }); |
|
|||
528 | } |
|
|||
529 | } |
|
|||
530 | } |
|
|||
531 |
|
||||
532 | // pre |
|
|||
533 | jsToolBar.prototype.elements.pre = { |
|
|||
534 | type: 'button', |
|
|||
535 | title: 'Preformatted text', |
|
|||
536 | fn: { |
|
|||
537 | wiki: function() { this.encloseLineSelection('<pre>\n', '\n</pre>') } |
|
|||
538 | } |
|
|||
539 | } |
|
|||
540 |
|
||||
541 | // spacer |
|
|||
542 | jsToolBar.prototype.elements.space4 = {type: 'space'} |
|
|||
543 |
|
||||
544 | // wiki page |
|
|||
545 | jsToolBar.prototype.elements.link = { |
|
|||
546 | type: 'button', |
|
|||
547 | title: 'Wiki link', |
|
|||
548 | fn: { |
|
|||
549 | wiki: function() { this.encloseSelection("[[", "]]") } |
|
|||
550 | } |
|
|||
551 | } |
|
|||
552 | // image |
|
|||
553 | jsToolBar.prototype.elements.img = { |
|
|||
554 | type: 'button', |
|
|||
555 | title: 'Image', |
|
|||
556 | fn: { |
|
|||
557 | wiki: function() { this.encloseSelection("!", "!") } |
|
|||
558 | } |
|
|||
559 | } |
|
@@ -350,6 +350,13 EXPECTED | |||||
350 | assert textilizable(text).match(/Unknow project/) |
|
350 | assert textilizable(text).match(/Unknow project/) | |
351 | end |
|
351 | end | |
352 |
|
352 | |||
|
353 | def test_default_formatter | |||
|
354 | Setting.text_formatting = 'unknown' | |||
|
355 | text = 'a *link*: http://www.example.net/' | |||
|
356 | assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text) | |||
|
357 | Setting.text_formatting = 'textile' | |||
|
358 | end | |||
|
359 | ||||
353 | def test_date_format_default |
|
360 | def test_date_format_default | |
354 | today = Date.today |
|
361 | today = Date.today | |
355 | Setting.date_format = '' |
|
362 | Setting.date_format = '' |
General Comments 0
You need to be logged in to leave comments.
Login now