]> source.dussan.org Git - redmine.git/commitdiff
Added wiki annotate view. It's accessible for each version from the page history...
authorJean-Philippe Lang <jp_lang@yahoo.fr>
Thu, 20 Dec 2007 19:10:24 +0000 (19:10 +0000)
committerJean-Philippe Lang <jp_lang@yahoo.fr>
Thu, 20 Dec 2007 19:10:24 +0000 (19:10 +0000)
Slight style change: pre-wrap added on file/diff contents.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@1020 e93f8b46-1217-0410-a6f0-8f06a7374b81

app/controllers/wiki_controller.rb
app/models/wiki_content.rb
app/models/wiki_page.rb
app/views/wiki/annotate.rhtml [new file with mode: 0644]
app/views/wiki/history.rhtml
lib/redmine.rb
public/stylesheets/scm.css
test/fixtures/wiki_content_versions.yml
test/functional/wiki_controller_test.rb

index 2ee22167d36be53563b218bd5b70cc8915c32ecb..bc4b7b4512469728d45a103fcf1a99666c20f72f 100644 (file)
@@ -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])
index d0a48467b99ead90fe2cf770899570b53202abc0..c307beb1ddd695bc1390bacbc7254b54a4393bc9 100644 (file)
@@ -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
index cbca4fd68f8a09c5f5d01033a2e5a71c1c77beac..8ce71cb80751fd8cb6af41130f41f9ae3389c684 100644 (file)
@@ -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 (file)
index 0000000..1c68340
--- /dev/null
@@ -0,0 +1,32 @@
+<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 %>
index b2a46775284533e6af04f8ad32e7cb77da5b8267..fc77e7fb58fab05629c6c7db4363a1b7bf0053cf 100644 (file)
@@ -11,6 +11,7 @@
     <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 %>
@@ -23,6 +24,7 @@
     <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 %>
index 921e16b30039762c853561a85d98426667d2e10b..9b29257bd88a8863cc09791bfa176a487dd72042 100644 (file)
@@ -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
     
index c3dc307d66510e9f72cde6409473883b47d026cd..66847af8cf356c28326ac741bc19a2707ac535ff 100644 (file)
@@ -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; }
index 547030ccf0f8290dadaad35bbc0bd30bd4a29b05..2601490603fec1cba3a7809316faa6185def0578 100644 (file)
@@ -4,7 +4,7 @@ wiki_content_versions_001:
   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
index 043b0e805408154cfe9c56a1e4f575d48a1f5aaa..0418a02b60bc3454af3edbf1d1bf15b679bf5263 100644 (file)
@@ -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',