summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/controllers/wiki_controller.rb21
-rw-r--r--app/helpers/application_helper.rb51
-rw-r--r--app/views/wiki/_content.html.erb5
-rw-r--r--app/views/wiki/edit.html.erb6
-rw-r--r--config/locales/bg.yml1
-rw-r--r--config/locales/bs.yml1
-rw-r--r--config/locales/ca.yml1
-rw-r--r--config/locales/cs.yml1
-rw-r--r--config/locales/da.yml1
-rw-r--r--config/locales/de.yml1
-rw-r--r--config/locales/el.yml1
-rw-r--r--config/locales/en-GB.yml1
-rw-r--r--config/locales/en.yml1
-rw-r--r--config/locales/es.yml1
-rw-r--r--config/locales/eu.yml1
-rw-r--r--config/locales/fa.yml1
-rw-r--r--config/locales/fi.yml1
-rw-r--r--config/locales/fr.yml1
-rw-r--r--config/locales/gl.yml1
-rw-r--r--config/locales/he.yml1
-rw-r--r--config/locales/hr.yml1
-rw-r--r--config/locales/hu.yml1
-rw-r--r--config/locales/id.yml1
-rw-r--r--config/locales/it.yml1
-rw-r--r--config/locales/ja.yml1
-rw-r--r--config/locales/ko.yml1
-rw-r--r--config/locales/lt.yml1
-rw-r--r--config/locales/lv.yml1
-rw-r--r--config/locales/mk.yml1
-rw-r--r--config/locales/mn.yml1
-rw-r--r--config/locales/nl.yml1
-rw-r--r--config/locales/no.yml1
-rw-r--r--config/locales/pl.yml1
-rw-r--r--config/locales/pt-BR.yml1
-rw-r--r--config/locales/pt.yml1
-rw-r--r--config/locales/ro.yml1
-rw-r--r--config/locales/ru.yml1
-rw-r--r--config/locales/sk.yml1
-rw-r--r--config/locales/sl.yml1
-rw-r--r--config/locales/sr-YU.yml1
-rw-r--r--config/locales/sr.yml1
-rw-r--r--config/locales/sv.yml1
-rw-r--r--config/locales/th.yml1
-rw-r--r--config/locales/tr.yml1
-rw-r--r--config/locales/uk.yml1
-rw-r--r--config/locales/vi.yml1
-rw-r--r--config/locales/zh-TW.yml1
-rw-r--r--config/locales/zh.yml1
-rw-r--r--lib/redmine/wiki_formatting.rb38
-rw-r--r--lib/redmine/wiki_formatting/textile/formatter.rb63
-rw-r--r--public/stylesheets/application.css3
-rw-r--r--test/fixtures/wiki_contents.yml23
-rw-r--r--test/fixtures/wiki_pages.yml7
-rw-r--r--test/functional/wiki_controller_test.rb115
-rw-r--r--test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb105
55 files changed, 440 insertions, 41 deletions
diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb
index 8111a617b..721ea28de 100644
--- a/app/controllers/wiki_controller.rb
+++ b/app/controllers/wiki_controller.rb
@@ -100,6 +100,13 @@ class WikiController < ApplicationController
# To prevent StaleObjectError exception when reverting to a previous version
@content.version = @page.content.version
+
+ @text = @content.text
+ if params[:section].present?
+ @section = params[:section].to_i
+ @text, @section_hash = Redmine::WikiFormatting.formatter.new(@text).get_section(@section)
+ render_404 if @text.blank?
+ end
end
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
@@ -120,7 +127,17 @@ class WikiController < ApplicationController
redirect_to :action => 'show', :project_id => @project, :id => @page.title
return
end
- @content.attributes = params[:content]
+
+ @content.comments = params[:content][:comments]
+ @text = params[:content][:text]
+ if params[:section].present?
+ @section = params[:section].to_i
+ @section_hash = params[:section_hash]
+ @content.text = Redmine::WikiFormatting.formatter.new(@content.text).update_section(params[:section].to_i, @text, @section_hash)
+ else
+ @content.version = params[:content][:version]
+ @content.text = @text
+ end
@content.author = User.current
# if page is new @page.save will also save content, but not if page isn't a new record
if (@page.new_record? ? @page.save : @content.save)
@@ -132,7 +149,7 @@ class WikiController < ApplicationController
render :action => 'edit'
end
- rescue ActiveRecord::StaleObjectError
+ rescue ActiveRecord::StaleObjectError, Redmine::WikiFormatting::StaleSectionError
# Optimistic locking exception
flash.now[:error] = l(:notice_locking_conflict)
render :action => 'edit'
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index b037e72c8..bec54a83e 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -486,11 +486,11 @@ module ApplicationHelper
project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
only_path = options.delete(:only_path) == false ? false : true
- text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
+ text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
@parsed_headings = []
text = parse_non_pre_blocks(text) do |text|
- [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings].each do |method_name|
+ [:parse_sections, :parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros, :parse_headings].each do |method_name|
send method_name, text, project, obj, attr, only_path, options
end
end
@@ -728,7 +728,23 @@ module ApplicationHelper
end
end
- HEADING_RE = /<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>/i unless const_defined?(:HEADING_RE)
+ HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
+
+ def parse_sections(text, project, obj, attr, only_path, options)
+ return unless options[:edit_section_links]
+ section = 0
+ text.gsub!(HEADING_RE) do
+ section += 1
+ if section > 1
+ content_tag('div',
+ link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => section)),
+ :class => 'contextual',
+ :title => l(:button_edit_section)) + $1
+ else
+ $1
+ end
+ end
+ end
# Headings and TOC
# Adds ids and links to headings unless options[:headings] is set to false
@@ -736,7 +752,7 @@ module ApplicationHelper
return if options[:headings] == false
text.gsub!(HEADING_RE) do
- level, attrs, content = $1.to_i, $2, $3
+ level, attrs, content = $2.to_i, $3, $4
item = strip_tags(content).strip
anchor = sanitize_anchor_name(item)
# used for single-file wiki export
@@ -746,6 +762,33 @@ module ApplicationHelper
end
end
+ MACROS_RE = /
+ (!)? # escaping
+ (
+ \{\{ # opening tag
+ ([\w]+) # macro name
+ (\(([^\}]*)\))? # optional arguments
+ \}\} # closing tag
+ )
+ /x unless const_defined?(:MACROS_RE)
+
+ # Macros substitution
+ def parse_macros(text, project, obj, attr, only_path, options)
+ text.gsub!(MACROS_RE) do
+ esc, all, macro = $1, $2, $3.downcase
+ args = ($5 || '').split(',').each(&:strip)
+ if esc.nil?
+ begin
+ exec_macro(macro, obj, args)
+ rescue => e
+ "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
+ end || all
+ else
+ all
+ end
+ end
+ end
+
TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
# Renders the TOC with given headings
diff --git a/app/views/wiki/_content.html.erb b/app/views/wiki/_content.html.erb
index 421d26cbb..0e747799d 100644
--- a/app/views/wiki/_content.html.erb
+++ b/app/views/wiki/_content.html.erb
@@ -1,3 +1,4 @@
-<div class="wiki">
- <%= textilizable content, :text, :attachments => content.page.attachments %>
+<div class="wiki wiki-page">
+ <%= textilizable content, :text, :attachments => content.page.attachments,
+ :edit_section_links => (content.is_a?(WikiContent) && @editable && User.current.allowed_to?(:edit_wiki_pages, @page.project) && {:controller => 'wiki', :action => 'edit', :project_id => @page.project, :id => @page.title}) %>
</div>
diff --git a/app/views/wiki/edit.html.erb b/app/views/wiki/edit.html.erb
index ae04a1a51..c628c2f7a 100644
--- a/app/views/wiki/edit.html.erb
+++ b/app/views/wiki/edit.html.erb
@@ -4,9 +4,13 @@
<% form_for :content, @content, :url => {:action => 'update', :id => @page.title}, :html => {:method => :put, :multipart => true, :id => 'wiki_form'} do |f| %>
<%= f.hidden_field :version %>
+<% if @section %>
+<%= hidden_field_tag 'section', @section %>
+<%= hidden_field_tag 'section_hash', @section_hash %>
+<% end %>
<%= error_messages_for 'content' %>
-<p><%= f.text_area :text, :cols => 100, :rows => 25, :class => 'wiki-edit', :accesskey => accesskey(:edit) %></p>
+<p><%= text_area_tag 'content[text]', @text, :cols => 100, :rows => 25, :class => 'wiki-edit', :accesskey => accesskey(:edit) %></p>
<p><label><%= l(:field_comments) %></label><br /><%= f.text_field :comments, :size => 120 %></p>
<p><label><%=l(:label_attachment_plural)%></label><br /><%= render :partial => 'attachments/form' %></p>
diff --git a/config/locales/bg.yml b/config/locales/bg.yml
index a7dabbda1..17bbdbcc2 100644
--- a/config/locales/bg.yml
+++ b/config/locales/bg.yml
@@ -999,3 +999,4 @@ bg:
description_date_range_interval: Изберете диапазон чрез задаване на начална и крайна дати
description_date_from: Въведете начална дата
description_date_to: Въведете крайна дата
+ button_edit_section: Edit this section
diff --git a/config/locales/bs.yml b/config/locales/bs.yml
index 3b8bb76dc..653114b88 100644
--- a/config/locales/bs.yml
+++ b/config/locales/bs.yml
@@ -1015,3 +1015,4 @@ bs:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/ca.yml b/config/locales/ca.yml
index f0f17c55b..5b6480568 100644
--- a/config/locales/ca.yml
+++ b/config/locales/ca.yml
@@ -1004,3 +1004,4 @@ ca:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/cs.yml b/config/locales/cs.yml
index 99e27b0d9..3c6a7fa64 100644
--- a/config/locales/cs.yml
+++ b/config/locales/cs.yml
@@ -1005,3 +1005,4 @@ cs:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/da.yml b/config/locales/da.yml
index 56266600b..bec03cea9 100644
--- a/config/locales/da.yml
+++ b/config/locales/da.yml
@@ -1018,3 +1018,4 @@ da:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/de.yml b/config/locales/de.yml
index ce96a8644..803579c75 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -1022,3 +1022,4 @@ de:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/el.yml b/config/locales/el.yml
index 6a15477de..a3aea6555 100644
--- a/config/locales/el.yml
+++ b/config/locales/el.yml
@@ -1001,3 +1001,4 @@ el:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml
index 964ae1a52..ad5871be0 100644
--- a/config/locales/en-GB.yml
+++ b/config/locales/en-GB.yml
@@ -1004,3 +1004,4 @@ en-GB:
description_selected_columns: Selected Columns
label_parent_revision: Parent
label_child_revision: Child
+ button_edit_section: Edit this section
diff --git a/config/locales/en.yml b/config/locales/en.yml
index db13b545b..8c4325506 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -874,6 +874,7 @@ en:
button_quote: Quote
button_duplicate: Duplicate
button_show: Show
+ button_edit_section: Edit this section
status_active: active
status_registered: registered
diff --git a/config/locales/es.yml b/config/locales/es.yml
index 4c8b248bb..d9db6ca89 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -1038,3 +1038,4 @@ es:
label_parent_revision: Parent
label_child_revision: Child
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/eu.yml b/config/locales/eu.yml
index b9ea8ef40..6d8a30570 100644
--- a/config/locales/eu.yml
+++ b/config/locales/eu.yml
@@ -1005,3 +1005,4 @@ eu:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/fa.yml b/config/locales/fa.yml
index a342b3611..02ceb84c5 100644
--- a/config/locales/fa.yml
+++ b/config/locales/fa.yml
@@ -1004,3 +1004,4 @@ fa:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/fi.yml b/config/locales/fi.yml
index e02e97dd8..5b80d2e71 100644
--- a/config/locales/fi.yml
+++ b/config/locales/fi.yml
@@ -1022,3 +1022,4 @@ fi:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index b119c1afe..ea130ad10 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -850,6 +850,7 @@ fr:
button_quote: Citer
button_duplicate: Dupliquer
button_show: Afficher
+ button_edit_section: Modifier cette section
status_active: actif
status_registered: enregistré
diff --git a/config/locales/gl.yml b/config/locales/gl.yml
index ff0c31806..3a6f122c7 100644
--- a/config/locales/gl.yml
+++ b/config/locales/gl.yml
@@ -1013,3 +1013,4 @@ gl:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/he.yml b/config/locales/he.yml
index da1008fb4..c70c005d3 100644
--- a/config/locales/he.yml
+++ b/config/locales/he.yml
@@ -1006,3 +1006,4 @@ he:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/hr.yml b/config/locales/hr.yml
index 672dc71ff..f1f60e134 100644
--- a/config/locales/hr.yml
+++ b/config/locales/hr.yml
@@ -1008,3 +1008,4 @@ hr:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/hu.yml b/config/locales/hu.yml
index b93d84525..345c9f4d7 100644
--- a/config/locales/hu.yml
+++ b/config/locales/hu.yml
@@ -1020,3 +1020,4 @@
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/id.yml b/config/locales/id.yml
index 4b7d54c4a..dc790edd4 100644
--- a/config/locales/id.yml
+++ b/config/locales/id.yml
@@ -1009,3 +1009,4 @@ id:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/it.yml b/config/locales/it.yml
index c607caf05..d59b443ec 100644
--- a/config/locales/it.yml
+++ b/config/locales/it.yml
@@ -1002,3 +1002,4 @@ it:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index 80cf49bf9..e3bd477ea 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -1031,3 +1031,4 @@ ja:
description_selected_columns: Selected Columns
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/ko.yml b/config/locales/ko.yml
index 6e8cefa6e..8a042ad8b 100644
--- a/config/locales/ko.yml
+++ b/config/locales/ko.yml
@@ -1053,3 +1053,4 @@ ko:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/lt.yml b/config/locales/lt.yml
index 71c9ea02f..ad7699697 100644
--- a/config/locales/lt.yml
+++ b/config/locales/lt.yml
@@ -1061,3 +1061,4 @@ lt:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/lv.yml b/config/locales/lv.yml
index 7839e8a60..654e13009 100644
--- a/config/locales/lv.yml
+++ b/config/locales/lv.yml
@@ -996,3 +996,4 @@ lv:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/mk.yml b/config/locales/mk.yml
index 96aea4fe2..a215cc702 100644
--- a/config/locales/mk.yml
+++ b/config/locales/mk.yml
@@ -1001,3 +1001,4 @@ mk:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/mn.yml b/config/locales/mn.yml
index cc4947e27..b243feb2e 100644
--- a/config/locales/mn.yml
+++ b/config/locales/mn.yml
@@ -1002,3 +1002,4 @@ mn:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/nl.yml b/config/locales/nl.yml
index 8e33be66b..58caf9419 100644
--- a/config/locales/nl.yml
+++ b/config/locales/nl.yml
@@ -983,3 +983,4 @@ nl:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/no.yml b/config/locales/no.yml
index a76c925eb..1e189d547 100644
--- a/config/locales/no.yml
+++ b/config/locales/no.yml
@@ -991,3 +991,4 @@
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/pl.yml b/config/locales/pl.yml
index 5c70da426..f38fed23f 100644
--- a/config/locales/pl.yml
+++ b/config/locales/pl.yml
@@ -1018,3 +1018,4 @@ pl:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml
index 87f821839..31bdda551 100644
--- a/config/locales/pt-BR.yml
+++ b/config/locales/pt-BR.yml
@@ -1022,3 +1022,4 @@ pt-BR:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/pt.yml b/config/locales/pt.yml
index a6ec7e1e6..4ae10b799 100644
--- a/config/locales/pt.yml
+++ b/config/locales/pt.yml
@@ -1006,3 +1006,4 @@ pt:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/ro.yml b/config/locales/ro.yml
index 1bd5c0253..b040674e0 100644
--- a/config/locales/ro.yml
+++ b/config/locales/ro.yml
@@ -994,3 +994,4 @@ ro:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/ru.yml b/config/locales/ru.yml
index 5ff4076b4..7ea6c8bd2 100644
--- a/config/locales/ru.yml
+++ b/config/locales/ru.yml
@@ -1114,3 +1114,4 @@ ru:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/sk.yml b/config/locales/sk.yml
index 9d9c282fa..f114ee44d 100644
--- a/config/locales/sk.yml
+++ b/config/locales/sk.yml
@@ -996,3 +996,4 @@ sk:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/sl.yml b/config/locales/sl.yml
index 80b9ea7a1..823b29091 100644
--- a/config/locales/sl.yml
+++ b/config/locales/sl.yml
@@ -1001,3 +1001,4 @@ sl:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/sr-YU.yml b/config/locales/sr-YU.yml
index 723446bcb..12a38c3eb 100644
--- a/config/locales/sr-YU.yml
+++ b/config/locales/sr-YU.yml
@@ -1001,3 +1001,4 @@ sr-YU:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/sr.yml b/config/locales/sr.yml
index ffef453aa..7ecf860bb 100644
--- a/config/locales/sr.yml
+++ b/config/locales/sr.yml
@@ -1002,3 +1002,4 @@ sr:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/sv.yml b/config/locales/sv.yml
index 90727b36e..31aedcbad 100644
--- a/config/locales/sv.yml
+++ b/config/locales/sv.yml
@@ -1042,3 +1042,4 @@ sv:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/th.yml b/config/locales/th.yml
index 407e3e4d4..ba0fafb96 100644
--- a/config/locales/th.yml
+++ b/config/locales/th.yml
@@ -998,3 +998,4 @@ th:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/tr.yml b/config/locales/tr.yml
index 508e3f9a6..86bb8dd10 100644
--- a/config/locales/tr.yml
+++ b/config/locales/tr.yml
@@ -1020,3 +1020,4 @@ tr:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/uk.yml b/config/locales/uk.yml
index edc4586dd..be10b95c6 100644
--- a/config/locales/uk.yml
+++ b/config/locales/uk.yml
@@ -997,3 +997,4 @@ uk:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/vi.yml b/config/locales/vi.yml
index 7718e429b..fd1657c62 100644
--- a/config/locales/vi.yml
+++ b/config/locales/vi.yml
@@ -1052,3 +1052,4 @@ vi:
label_child_revision: Child
error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size.
setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
+ button_edit_section: Edit this section
diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml
index b99afe950..aa3ad0575 100644
--- a/config/locales/zh-TW.yml
+++ b/config/locales/zh-TW.yml
@@ -1081,3 +1081,4 @@
description_date_range_interval: 選擇起始與結束日期以設定範圍區間
description_date_from: 輸入起始日期
description_date_to: 輸入結束日期
+ button_edit_section: Edit this section
diff --git a/config/locales/zh.yml b/config/locales/zh.yml
index c79fe5980..19abd880e 100644
--- a/config/locales/zh.yml
+++ b/config/locales/zh.yml
@@ -1003,3 +1003,4 @@ zh:
label_child_revision: 子修订
error_scm_annotate_big_text_file: 输入文本内容超长,无法输入。
setting_default_issue_start_date_to_creation_date: 使用当前日期作为新问题的开始日期
+ button_edit_section: Edit this section
diff --git a/lib/redmine/wiki_formatting.rb b/lib/redmine/wiki_formatting.rb
index 0635aad8b..2f25fe0e6 100644
--- a/lib/redmine/wiki_formatting.rb
+++ b/lib/redmine/wiki_formatting.rb
@@ -17,6 +17,8 @@
module Redmine
module WikiFormatting
+ class StaleSectionError < Exception; end
+
@@formatters = {}
class << self
@@ -29,6 +31,10 @@ module Redmine
@@formatters[name.to_s] = {:formatter => formatter, :helper => helper}
end
+ def formatter
+ formatter_for(Setting.text_formatting)
+ end
+
def formatter_for(name)
entry = @@formatters[name.to_s]
(entry && entry[:formatter]) || Redmine::WikiFormatting::NullFormatter::Formatter
@@ -43,7 +49,7 @@ module Redmine
@@formatters.keys.map
end
- def to_html(format, text, options = {}, &block)
+ def to_html(format, text, options = {})
text = if Setting.cache_formatted_text? && text.size > 2.kilobyte && cache_store && cache_key = cache_key_for(format, options[:object], options[:attribute])
# Text retrieved from the cache store may be frozen
# We need to dup it so we can do in-place substitutions with gsub!
@@ -53,9 +59,6 @@ module Redmine
else
formatter_for(format).new(text).to_html
end
- if block_given?
- execute_macros(text, block)
- end
text
end
@@ -70,33 +73,6 @@ module Redmine
def cache_store
ActionController::Base.cache_store
end
-
- MACROS_RE = /
- (!)? # escaping
- (
- \{\{ # opening tag
- ([\w]+) # macro name
- (\(([^\}]*)\))? # optional arguments
- \}\} # closing tag
- )
- /x unless const_defined?(:MACROS_RE)
-
- # Macros substitution
- def execute_macros(text, macros_runner)
- 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
# Default formatter module
diff --git a/lib/redmine/wiki_formatting/textile/formatter.rb b/lib/redmine/wiki_formatting/textile/formatter.rb
index 520e3f9ad..1beb9563c 100644
--- a/lib/redmine/wiki_formatting/textile/formatter.rb
+++ b/lib/redmine/wiki_formatting/textile/formatter.rb
@@ -16,6 +16,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'redcloth3'
+require 'digest/md5'
module Redmine
module WikiFormatting
@@ -38,6 +39,68 @@ module Redmine
super(*RULES).to_s
end
+ def get_section(index)
+ section = extract_sections(index)[1]
+ hash = Digest::MD5.hexdigest(section)
+ return section, hash
+ end
+
+ def update_section(index, update, hash=nil)
+ t = extract_sections(index)
+ if hash.present? && hash != Digest::MD5.hexdigest(t[1])
+ raise Redmine::WikiFormatting::StaleSectionError
+ end
+ t[1] = update unless t[1].blank?
+ t.reject(&:blank?).join "\n\n"
+ end
+
+ def extract_sections(index)
+ @pre_list = []
+ text = self.dup
+ rip_offtags text
+ before = ''
+ s = ''
+ after = ''
+ i = 0
+ l = 1
+ started = false
+ ended = false
+ text.scan(/(((?:.*?)(\A|\r?\n\r?\n))(h(\d+)(#{A}#{C})\.(?::(\S+))? (.*?)$)|.*)/m).each do |all, content, lf, heading, level|
+ if heading.nil?
+ if ended
+ after << all
+ elsif started
+ s << all
+ else
+ before << all
+ end
+ break
+ end
+ i += 1
+ if ended
+ after << all
+ elsif i == index
+ l = level.to_i
+ before << content
+ s << heading
+ started = true
+ elsif i > index
+ s << content
+ if level.to_i > l
+ s << heading
+ else
+ after << heading
+ ended = true
+ end
+ else
+ before << all
+ end
+ end
+ sections = [before.strip, s.strip, after.strip]
+ sections.each {|section| smooth_offtags section}
+ sections
+ end
+
private
# Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet.
diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css
index 911646104..4f708c907 100644
--- a/public/stylesheets/application.css
+++ b/public/stylesheets/application.css
@@ -358,6 +358,9 @@ table#time-report tbody tr.last-level { font-style: normal; color: #555; }
table#time-report tbody tr.total { font-style: normal; font-weight: bold; color: #555; background-color:#EEEEEE; }
table#time-report .hours-dec { font-size: 0.9em; }
+div.wiki-page .contextual a {opacity: 0.4}
+div.wiki-page .contextual a:hover {opacity: 1}
+
form .attributes { margin-bottom: 8px; }
form .attributes p { padding-top: 1px; padding-bottom: 2px; }
form .attributes select { width: 60%; }
diff --git a/test/fixtures/wiki_contents.yml b/test/fixtures/wiki_contents.yml
index acf5086af..b5fd01080 100644
--- a/test/fixtures/wiki_contents.yml
+++ b/test/fixtures/wiki_contents.yml
@@ -103,4 +103,25 @@ wiki_contents_010:
version: 1
author_id: 1
comments:
- \ No newline at end of file
+wiki_contents_011:
+ text: |-
+ h1. Title
+
+ Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
+
+ h2. Heading 1
+
+ Maecenas sed elit sit amet mi accumsan vestibulum non nec velit. Proin porta tincidunt lorem, consequat rhoncus dolor fermentum in.
+
+ Cras ipsum felis, ultrices at porttitor vel, faucibus eu nunc.
+
+ h2. Heading 2
+
+ Morbi facilisis accumsan orci non pharetra.
+ updated_on: 2007-03-08 00:18:07 +01:00
+ page_id: 11
+ id: 11
+ version: 3
+ author_id: 1
+ comments:
+
diff --git a/test/fixtures/wiki_pages.yml b/test/fixtures/wiki_pages.yml
index 2d2e2eceb..8a00e4af4 100644
--- a/test/fixtures/wiki_pages.yml
+++ b/test/fixtures/wiki_pages.yml
@@ -69,3 +69,10 @@ wiki_pages_010:
wiki_id: 1
protected: false
parent_id:
+wiki_pages_011:
+ created_on: 2007-03-08 00:18:07 +01:00
+ title: Page_with_sections
+ id: 11
+ wiki_id: 1
+ protected: false
+ parent_id:
diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb
index 48db9e650..1274e676b 100644
--- a/test/functional/wiki_controller_test.rb
+++ b/test/functional/wiki_controller_test.rb
@@ -118,6 +118,44 @@ class WikiControllerTest < ActionController::TestCase
assert_equal 'testfile.txt', page.attachments.first.filename
end
+ def test_edit_page
+ @request.session[:user_id] = 2
+ get :edit, :project_id => 'ecookbook', :id => 'Another_page'
+
+ assert_response :success
+ assert_template 'edit'
+
+ assert_tag 'textarea',
+ :attributes => { :name => 'content[text]' },
+ :content => WikiPage.find_by_title('Another_page').content.text
+ end
+
+ def test_edit_section
+ @request.session[:user_id] = 2
+ get :edit, :project_id => 'ecookbook', :id => 'Page_with_sections', :section => 2
+
+ assert_response :success
+ assert_template 'edit'
+
+ page = WikiPage.find_by_title('Page_with_sections')
+ section, hash = Redmine::WikiFormatting::Textile::Formatter.new(page.content.text).get_section(2)
+
+ assert_tag 'textarea',
+ :attributes => { :name => 'content[text]' },
+ :content => section
+ assert_tag 'input',
+ :attributes => { :name => 'section', :type => 'hidden', :value => '2' }
+ assert_tag 'input',
+ :attributes => { :name => 'section_hash', :type => 'hidden', :value => hash }
+ end
+
+ def test_edit_invalid_section_should_respond_with_404
+ @request.session[:user_id] = 2
+ get :edit, :project_id => 'ecookbook', :id => 'Page_with_sections', :section => 10
+
+ assert_response 404
+ end
+
def test_update_page
@request.session[:user_id] = 2
assert_no_difference 'WikiPage.count' do
@@ -200,6 +238,83 @@ class WikiControllerTest < ActionController::TestCase
assert_equal 2, c.version
end
+ def test_update_section
+ @request.session[:user_id] = 2
+ page = WikiPage.find_by_title('Page_with_sections')
+ section, hash = Redmine::WikiFormatting::Textile::Formatter.new(page.content.text).get_section(2)
+ text = page.content.text
+
+ assert_no_difference 'WikiPage.count' do
+ assert_no_difference 'WikiContent.count' do
+ assert_difference 'WikiContent::Version.count' do
+ put :update, :project_id => 1, :id => 'Page_with_sections',
+ :content => {
+ :text => "New section content",
+ :version => 3
+ },
+ :section => 2,
+ :section_hash => hash
+ end
+ end
+ end
+ assert_redirected_to '/projects/ecookbook/wiki/Page_with_sections'
+ assert_equal Redmine::WikiFormatting::Textile::Formatter.new(text).update_section(2, "New section content"), page.reload.content.text
+ end
+
+ def test_update_section_should_allow_stale_page_update
+ @request.session[:user_id] = 2
+ page = WikiPage.find_by_title('Page_with_sections')
+ section, hash = Redmine::WikiFormatting::Textile::Formatter.new(page.content.text).get_section(2)
+ text = page.content.text
+
+ assert_no_difference 'WikiPage.count' do
+ assert_no_difference 'WikiContent.count' do
+ assert_difference 'WikiContent::Version.count' do
+ put :update, :project_id => 1, :id => 'Page_with_sections',
+ :content => {
+ :text => "New section content",
+ :version => 2 # Current version is 3
+ },
+ :section => 2,
+ :section_hash => hash
+ end
+ end
+ end
+ assert_redirected_to '/projects/ecookbook/wiki/Page_with_sections'
+ page.reload
+ assert_equal Redmine::WikiFormatting::Textile::Formatter.new(text).update_section(2, "New section content"), page.content.text
+ assert_equal 4, page.content.version
+ end
+
+ def test_update_section_should_not_allow_stale_section_update
+ @request.session[:user_id] = 2
+
+ assert_no_difference 'WikiPage.count' do
+ assert_no_difference 'WikiContent.count' do
+ assert_no_difference 'WikiContent::Version.count' do
+ put :update, :project_id => 1, :id => 'Page_with_sections',
+ :content => {
+ :comments => 'My comments',
+ :text => "Text should not be lost",
+ :version => 3
+ },
+ :section => 2,
+ :section_hash => Digest::MD5.hexdigest("wrong hash")
+ end
+ end
+ end
+ assert_response :success
+ assert_template 'edit'
+ assert_tag :div,
+ :attributes => { :class => /error/ },
+ :content => /Data has been updated by another user/
+ assert_tag 'textarea',
+ :attributes => { :name => 'content[text]' },
+ :content => /Text should not be lost/
+ assert_tag 'input',
+ :attributes => { :name => 'content[comments]', :value => 'My comments' }
+ end
+
def test_preview
@request.session[:user_id] = 2
xhr :post, :preview, :project_id => 1, :id => 'CookBook_documentation',
diff --git a/test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb b/test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb
index 4d9f90518..ec9b883b0 100644
--- a/test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb
+++ b/test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb
@@ -16,6 +16,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require File.expand_path('../../../../../test_helper', __FILE__)
+require 'digest/md5'
class Redmine::WikiFormatting::TextileFormatterTest < ActionView::TestCase
@@ -203,6 +204,101 @@ EXPECTED
expected = '<p><img src="/images/comment.png&quot;onclick=&amp;#x61;&amp;#x6c;&amp;#x65;&amp;#x72;&amp;#x74;&amp;#x28;&amp;#x27;&amp;#x58;&amp;#x53;&amp;#x53;&amp;#x27;&amp;#x29;;&amp;#x22;" alt="" /></p>'
assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
end
+
+
+ STR_WITHOUT_PRE = [
+ # 0
+"h1. Title
+
+Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.",
+ # 1
+"h2. Heading 2
+
+Maecenas sed elit sit amet mi accumsan vestibulum non nec velit. Proin porta tincidunt lorem, consequat rhoncus dolor fermentum in.
+
+Cras ipsum felis, ultrices at porttitor vel, faucibus eu nunc.",
+ # 2
+"h2. Heading 2
+
+Morbi facilisis accumsan orci non pharetra.
+
+h3. Heading 3
+
+Nulla nunc nisi, egestas in ornare vel, posuere ac libero.",
+ # 3
+"h3. Heading 3
+
+Praesent eget turpis nibh, a lacinia nulla.",
+ # 4
+"h2. Heading 2
+
+Ut rhoncus elementum adipiscing."]
+
+ TEXT_WITHOUT_PRE = STR_WITHOUT_PRE.join("\n\n").freeze
+
+ def test_get_section_should_return_the_requested_section_and_its_hash
+ assert_section_with_hash STR_WITHOUT_PRE[1], TEXT_WITHOUT_PRE, 2
+ assert_section_with_hash STR_WITHOUT_PRE[2..3].join("\n\n"), TEXT_WITHOUT_PRE, 3
+ assert_section_with_hash STR_WITHOUT_PRE[3], TEXT_WITHOUT_PRE, 5
+ assert_section_with_hash STR_WITHOUT_PRE[4], TEXT_WITHOUT_PRE, 6
+
+ assert_section_with_hash '', TEXT_WITHOUT_PRE, 0
+ assert_section_with_hash '', TEXT_WITHOUT_PRE, 10
+ end
+
+ def test_update_section_should_update_the_requested_section
+ replacement = "New text"
+
+ assert_equal [STR_WITHOUT_PRE[0], replacement, STR_WITHOUT_PRE[2..4]].flatten.join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(2, replacement)
+ assert_equal [STR_WITHOUT_PRE[0..1], replacement, STR_WITHOUT_PRE[4]].flatten.join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(3, replacement)
+ assert_equal [STR_WITHOUT_PRE[0..2], replacement, STR_WITHOUT_PRE[4]].flatten.join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(5, replacement)
+ assert_equal [STR_WITHOUT_PRE[0..3], replacement].flatten.join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(6, replacement)
+
+ assert_equal TEXT_WITHOUT_PRE, @formatter.new(TEXT_WITHOUT_PRE).update_section(0, replacement)
+ assert_equal TEXT_WITHOUT_PRE, @formatter.new(TEXT_WITHOUT_PRE).update_section(10, replacement)
+ end
+
+ def test_update_section_with_hash_should_update_the_requested_section
+ replacement = "New text"
+
+ assert_equal [STR_WITHOUT_PRE[0], replacement, STR_WITHOUT_PRE[2..4]].flatten.join("\n\n"),
+ @formatter.new(TEXT_WITHOUT_PRE).update_section(2, replacement, Digest::MD5.hexdigest(STR_WITHOUT_PRE[1]))
+ end
+
+ def test_update_section_with_wrong_hash_should_raise_an_error
+ assert_raise Redmine::WikiFormatting::StaleSectionError do
+ @formatter.new(TEXT_WITHOUT_PRE).update_section(2, "New text", Digest::MD5.hexdigest("Old text"))
+ end
+ end
+
+ STR_WITH_PRE = [
+ # 0
+"h1. Title
+
+Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.",
+ # 1
+"h2. Heading 2
+
+Morbi facilisis accumsan orci non pharetra.
+
+<pre>
+Pre Content:
+
+h2. Inside pre
+
+Morbi facilisis accumsan orci non pharetra.
+</pre>",
+ # 2
+"h3. Heading 3
+
+Nulla nunc nisi, egestas in ornare vel, posuere ac libero."]
+
+ def test_get_section_should_ignore_pre_content
+ text = STR_WITH_PRE.join("\n\n")
+
+ assert_section_with_hash STR_WITH_PRE[1..2].join("\n\n"), text, 2
+ assert_section_with_hash STR_WITH_PRE[2], text, 3
+ end
private
@@ -215,4 +311,13 @@ EXPECTED
def to_html(text)
@formatter.new(text).to_html
end
+
+ def assert_section_with_hash(expected, text, index)
+ result = @formatter.new(text).get_section(index)
+
+ assert_kind_of Array, result
+ assert_equal 2, result.size
+ assert_equal expected, result.first, "section content did not match"
+ assert_equal Digest::MD5.hexdigest(expected), result.last, "section hash did not match"
+ end
end