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])
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
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'diff'
+require 'enumerator'
class WikiPage < ActiveRecord::Base
belongs_to :wiki
(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
@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
--- /dev/null
+<div class="contextual">
+<%= 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') %>
+</div>
+
+<h2><%= @page.pretty_title %></h2>
+
+<p>
+<%= l(:label_version) %> <%= link_to @annotate.content.version, :action => 'index', :page => @page.title, :version => @annotate.content.version %>
+<em>(<%= @annotate.content.author ? @annotate.content.author.name : "anonyme" %>, <%= format_time(@annotate.content.updated_on) %>)</em>
+</p>
+
+<% colors = Hash.new {|k,v| k[v] = (k.size % 12) } %>
+
+<table class="filecontent annotate CodeRay ">
+<tbody>
+<% line_num = 1 %>
+<% @annotate.lines.each do |line| -%>
+<tr class="bloc-<%= colors[line[0]] %>">
+ <th class="line-num"><%= line_num %></th>
+ <td class="revision"><%= link_to line[0], :controller => 'wiki', :action => 'index', :id => @project, :page => @page.title, :version => line[0] %></td>
+ <td class="author"><%= h(line[1]) %></td>
+ <td class="line-code"><pre><%= line[2] %></pre></td>
+</tr>
+<% line_num += 1 %>
+<% end -%>
+</tbody>
+</table>
+
+<% content_for :header_tags do %>
+<%= stylesheet_link_tag 'scm' %>
+<% end %>
<th><%= l(:field_updated_on) %></th>
<th><%= l(:field_author) %></th>
<th><%= l(:field_comments) %></th>
+ <th></th>
</tr></thead>
<tbody>
<% show_diff = @versions.size > 1 %>
<td align="center"><%= format_time(ver.updated_on) %></td>
<td><em><%= ver.author ? ver.author.name : "anonyme" %></em></td>
<td><%=h ver.comments %></td>
+ <td align="center"><%= link_to l(:button_annotate), :action => 'annotate', :page => @page.title, :version => ver.version %></td>
</tr>
<% line_num += 1 %>
<% end %>
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
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;}
padding-right: 1em;
width: 3%;
background: inherit;
+ font-size: 90%;
}
table.annotate td.line-code { background-color: #fafafa; }
page_id: 1\r
id: 1\r
version: 1\r
- author_id: 1\r
+ author_id: 2\r
comments: Page creation\r
wiki_content_id: 1\r
compression: ""\r
: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',