diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb
index 2ee22167..bc4b7b45 100644
--- a/app/controllers/wiki_controller.rb
+++ b/app/controllers/wiki_controller.rb
@@ -114,6 +114,11 @@ class WikiController < ApplicationController
render_404 unless @diff
end
+ def annotate
+ @page = @wiki.find_page(params[:page])
+ @annotate = @page.annotate(params[:version])
+ end
+
# remove a wiki page and its history
def destroy
@page = @wiki.find_page(params[:page])
diff --git a/app/models/wiki_content.rb b/app/models/wiki_content.rb
index d0a48467..c307beb1 100644
--- a/app/models/wiki_content.rb
+++ b/app/models/wiki_content.rb
@@ -60,6 +60,14 @@ class WikiContent < ActiveRecord::Base
data
end
end
+
+ # Returns the previous version or nil
+ def previous
+ @previous ||= WikiContent::Version.find(:first,
+ :order => 'version DESC',
+ :include => :author,
+ :conditions => ["wiki_content_id = ? AND version < ?", wiki_content_id, version])
+ end
end
end
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index cbca4fd6..8ce71cb8 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -16,6 +16,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'diff'
+require 'enumerator'
class WikiPage < ActiveRecord::Base
belongs_to :wiki
@@ -87,6 +88,12 @@ class WikiPage < ActiveRecord::Base
(content_to && content_from) ? WikiDiff.new(content_to, content_from) : nil
end
+ def annotate(version=nil)
+ version = version ? version.to_i : self.content.version
+ c = content.versions.find_by_version(version)
+ c ? WikiAnnotate.new(c) : nil
+ end
+
def self.pretty_title(str)
(str && str.is_a?(String)) ? str.tr('_', ' ') : str
end
@@ -113,3 +120,41 @@ class WikiDiff
@diff = words_from.diff @words
end
end
+
+class WikiAnnotate
+ attr_reader :lines, :content
+
+ def initialize(content)
+ @content = content
+ current = content
+ current_lines = current.text.split(/\r?\n/)
+ @lines = current_lines.collect {|t| [nil, nil, t]}
+ positions = []
+ current_lines.size.times {|i| positions << i}
+ while (current.previous)
+ d = current.previous.text.split(/\r?\n/).diff(current.text.split(/\r?\n/)).diffs.flatten
+ d.each_slice(3) do |s|
+ sign, line = s[0], s[1]
+ if sign == '+' && positions[line] && positions[line] != -1
+ if @lines[positions[line]][0].nil?
+ @lines[positions[line]][0] = current.version
+ @lines[positions[line]][1] = current.author
+ end
+ end
+ end
+ d.each_slice(3) do |s|
+ sign, line = s[0], s[1]
+ if sign == '-'
+ positions.insert(line, -1)
+ else
+ positions[line] = nil
+ end
+ end
+ positions.compact!
+ # Stop if every line is annotated
+ break unless @lines.detect { |line| line[0].nil? }
+ current = current.previous
+ end
+ @lines.each { |line| line[0] ||= current.version }
+ end
+end
diff --git a/app/views/wiki/annotate.rhtml b/app/views/wiki/annotate.rhtml
new file mode 100644
index 00000000..1c683404
--- /dev/null
+++ b/app/views/wiki/annotate.rhtml
@@ -0,0 +1,32 @@
+
+<%= link_to(l(:button_edit), {:action => 'edit', :page => @page.title}, :class => 'icon icon-edit') %>
+<%= link_to(l(:label_history), {:action => 'history', :page => @page.title}, :class => 'icon icon-history') %>
+
+
+<%= @page.pretty_title %>
+
+
+<%= l(:label_version) %> <%= link_to @annotate.content.version, :action => 'index', :page => @page.title, :version => @annotate.content.version %>
+(<%= @annotate.content.author ? @annotate.content.author.name : "anonyme" %>, <%= format_time(@annotate.content.updated_on) %>)
+
+
+<% colors = Hash.new {|k,v| k[v] = (k.size % 12) } %>
+
+
+
+<% line_num = 1 %>
+<% @annotate.lines.each do |line| -%>
+
+ <%= line_num %> |
+ <%= link_to line[0], :controller => 'wiki', :action => 'index', :id => @project, :page => @page.title, :version => line[0] %> |
+ <%= h(line[1]) %> |
+ <%= line[2] %> |
+
+<% line_num += 1 %>
+<% end -%>
+
+
+
+<% content_for :header_tags do %>
+<%= stylesheet_link_tag 'scm' %>
+<% end %>
diff --git a/app/views/wiki/history.rhtml b/app/views/wiki/history.rhtml
index b2a46775..fc77e7fb 100644
--- a/app/views/wiki/history.rhtml
+++ b/app/views/wiki/history.rhtml
@@ -11,6 +11,7 @@
<%= l(:field_updated_on) %> |
<%= l(:field_author) %> |
<%= l(:field_comments) %> |
+ |
<% show_diff = @versions.size > 1 %>
@@ -23,6 +24,7 @@
<%= format_time(ver.updated_on) %> |
<%= ver.author ? ver.author.name : "anonyme" %> |
<%=h ver.comments %> |
+ <%= link_to l(:button_annotate), :action => 'annotate', :page => @page.title, :version => ver.version %> |
<% line_num += 1 %>
<% end %>
diff --git a/lib/redmine.rb b/lib/redmine.rb
index 921e16b3..9b29257b 100644
--- a/lib/redmine.rb
+++ b/lib/redmine.rb
@@ -71,7 +71,7 @@ Redmine::AccessControl.map do |map|
map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
- map.permission :view_wiki_pages, :wiki => [:index, :history, :diff, :special]
+ map.permission :view_wiki_pages, :wiki => [:index, :history, :diff, :annotate, :special]
map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment, :destroy_attachment]
end
diff --git a/public/stylesheets/scm.css b/public/stylesheets/scm.css
index c3dc307d..66847af8 100644
--- a/public/stylesheets/scm.css
+++ b/public/stylesheets/scm.css
@@ -10,6 +10,11 @@ table.filecontent th.line-num {
width: 2%;
padding-right: 3px;
}
+table.filecontent td.line-code pre {
+ white-space: pre-wrap; /* CSS2.1 compliant */
+ white-space: -moz-pre-wrap; /* Mozilla-based browsers */
+ white-space: -o-pre-wrap; /* Opera 7+ */
+}
/* 12 different colors for the annonate view */
table.annotate tr.bloc-0 {background: #FFFFBF;}
@@ -40,6 +45,7 @@ table.annotate td.author {
padding-right: 1em;
width: 3%;
background: inherit;
+ font-size: 90%;
}
table.annotate td.line-code { background-color: #fafafa; }
diff --git a/test/fixtures/wiki_content_versions.yml b/test/fixtures/wiki_content_versions.yml
index 547030cc..26014906 100644
--- a/test/fixtures/wiki_content_versions.yml
+++ b/test/fixtures/wiki_content_versions.yml
@@ -4,7 +4,7 @@ wiki_content_versions_001:
page_id: 1
id: 1
version: 1
- author_id: 1
+ author_id: 2
comments: Page creation
wiki_content_id: 1
compression: ""
diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb
index 043b0e80..0418a02b 100644
--- a/test/functional/wiki_controller_test.rb
+++ b/test/functional/wiki_controller_test.rb
@@ -98,6 +98,20 @@ class WikiControllerTest < Test::Unit::TestCase
:content => /updated/
end
+ def test_annotate
+ get :annotate, :id => 1, :page => 'CookBook_documentation', :version => 2
+ assert_response :success
+ assert_template 'annotate'
+ # Line 1
+ assert_tag :tag => 'tr', :child => { :tag => 'th', :attributes => {:class => 'line-num'}, :content => '1' },
+ :child => { :tag => 'td', :attributes => {:class => 'author'}, :content => /John Smith/ },
+ :child => { :tag => 'td', :content => /h1\. CookBook documentation/ }
+ # Line 2
+ assert_tag :tag => 'tr', :child => { :tag => 'th', :attributes => {:class => 'line-num'}, :content => '2' },
+ :child => { :tag => 'td', :attributes => {:class => 'author'}, :content => /redMine Admin/ },
+ :child => { :tag => 'td', :content => /Some updated \[\[documentation\]\] here/ }
+ end
+
def test_rename_with_redirect
@request.session[:user_id] = 2
post :rename, :id => 1, :page => 'Another_page',