##// END OF EJS Templates
Extracts a diff helper from the WikiDiff class....
Jean-Philippe Lang -
r4832:d06f4d013d32
parent child
Show More
@@ -0,0 +1,72
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 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 Helpers
20 class Diff
21 include ERB::Util
22 include ActionView::Helpers::TagHelper
23 include ActionView::Helpers::TextHelper
24 attr_reader :diff, :words
25
26 def initialize(content_to, content_from)
27 @words = content_to.to_s.split(/(\s+)/)
28 @words = @words.select {|word| word != ' '}
29 words_from = content_from.to_s.split(/(\s+)/)
30 words_from = words_from.select {|word| word != ' '}
31 @diff = words_from.diff @words
32 end
33
34 def to_html
35 words = self.words.collect{|word| h(word)}
36 words_add = 0
37 words_del = 0
38 dels = 0
39 del_off = 0
40 diff.diffs.each do |diff|
41 add_at = nil
42 add_to = nil
43 del_at = nil
44 deleted = ""
45 diff.each do |change|
46 pos = change[1]
47 if change[0] == "+"
48 add_at = pos + dels unless add_at
49 add_to = pos + dels
50 words_add += 1
51 else
52 del_at = pos unless del_at
53 deleted << ' ' + h(change[2])
54 words_del += 1
55 end
56 end
57 if add_at
58 words[add_at] = '<span class="diff_in">' + words[add_at]
59 words[add_to] = words[add_to] + '</span>'
60 end
61 if del_at
62 words.insert del_at - del_off + dels + words_add, '<span class="diff_out">' + deleted + '</span>'
63 dels += 1
64 del_off += words_del
65 words_del = 0
66 end
67 end
68 simple_format(words.join(' '))
69 end
70 end
71 end
72 end
@@ -1,69 +1,32
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 module WikiHelper
19 19
20 20 def wiki_page_options_for_select(pages, selected = nil, parent = nil, level = 0)
21 21 s = ''
22 22 pages.select {|p| p.parent == parent}.each do |page|
23 23 attrs = "value='#{page.id}'"
24 24 attrs << " selected='selected'" if selected == page
25 25 indent = (level > 0) ? ('&nbsp;' * level * 2 + '&#187; ') : nil
26 26
27 27 s << "<option #{attrs}>#{indent}#{h page.pretty_title}</option>\n" +
28 28 wiki_page_options_for_select(pages, selected, page, level + 1)
29 29 end
30 30 s
31 31 end
32
33 def html_diff(wdiff)
34 words = wdiff.words.collect{|word| h(word)}
35 words_add = 0
36 words_del = 0
37 dels = 0
38 del_off = 0
39 wdiff.diff.diffs.each do |diff|
40 add_at = nil
41 add_to = nil
42 del_at = nil
43 deleted = ""
44 diff.each do |change|
45 pos = change[1]
46 if change[0] == "+"
47 add_at = pos + dels unless add_at
48 add_to = pos + dels
49 words_add += 1
50 else
51 del_at = pos unless del_at
52 deleted << ' ' + h(change[2])
53 words_del += 1
54 end
55 end
56 if add_at
57 words[add_at] = '<span class="diff_in">' + words[add_at]
58 words[add_to] = words[add_to] + '</span>'
59 end
60 if del_at
61 words.insert del_at - del_off + dels + words_add, '<span class="diff_out">' + deleted + '</span>'
62 dels += 1
63 del_off += words_del
64 words_del = 0
65 end
66 end
67 simple_format_without_paragraph(words.join(' '))
68 end
69 32 end
@@ -1,202 +1,198
1 1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'diff'
19 19 require 'enumerator'
20 20
21 21 class WikiPage < ActiveRecord::Base
22 22 belongs_to :wiki
23 23 has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy
24 24 acts_as_attachable :delete_permission => :delete_wiki_pages_attachments
25 25 acts_as_tree :dependent => :nullify, :order => 'title'
26 26
27 27 acts_as_watchable
28 28 acts_as_event :title => Proc.new {|o| "#{l(:label_wiki)}: #{o.title}"},
29 29 :description => :text,
30 30 :datetime => :created_on,
31 31 :url => Proc.new {|o| {:controller => 'wiki', :action => 'show', :project_id => o.wiki.project, :id => o.title}}
32 32
33 33 acts_as_searchable :columns => ['title', 'text'],
34 34 :include => [{:wiki => :project}, :content],
35 35 :project_key => "#{Wiki.table_name}.project_id"
36 36
37 37 attr_accessor :redirect_existing_links
38 38
39 39 validates_presence_of :title
40 40 validates_format_of :title, :with => /^[^,\.\/\?\;\|\s]*$/
41 41 validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false
42 42 validates_associated :content
43 43
44 44 # Wiki pages that are protected by default
45 45 DEFAULT_PROTECTED_PAGES = %w(sidebar)
46 46
47 47 def after_initialize
48 48 if new_record? && DEFAULT_PROTECTED_PAGES.include?(title.to_s.downcase)
49 49 self.protected = true
50 50 end
51 51 end
52 52
53 53 def visible?(user=User.current)
54 54 !user.nil? && user.allowed_to?(:view_wiki_pages, project)
55 55 end
56 56
57 57 def title=(value)
58 58 value = Wiki.titleize(value)
59 59 @previous_title = read_attribute(:title) if @previous_title.blank?
60 60 write_attribute(:title, value)
61 61 end
62 62
63 63 def before_save
64 64 self.title = Wiki.titleize(title)
65 65 # Manage redirects if the title has changed
66 66 if !@previous_title.blank? && (@previous_title != title) && !new_record?
67 67 # Update redirects that point to the old title
68 68 wiki.redirects.find_all_by_redirects_to(@previous_title).each do |r|
69 69 r.redirects_to = title
70 70 r.title == r.redirects_to ? r.destroy : r.save
71 71 end
72 72 # Remove redirects for the new title
73 73 wiki.redirects.find_all_by_title(title).each(&:destroy)
74 74 # Create a redirect to the new title
75 75 wiki.redirects << WikiRedirect.new(:title => @previous_title, :redirects_to => title) unless redirect_existing_links == "0"
76 76 @previous_title = nil
77 77 end
78 78 end
79 79
80 80 def before_destroy
81 81 # Remove redirects to this page
82 82 wiki.redirects.find_all_by_redirects_to(title).each(&:destroy)
83 83 end
84 84
85 85 def pretty_title
86 86 WikiPage.pretty_title(title)
87 87 end
88 88
89 89 def content_for_version(version=nil)
90 90 result = content.versions.find_by_version(version.to_i) if version
91 91 result ||= content
92 92 result
93 93 end
94 94
95 95 def diff(version_to=nil, version_from=nil)
96 96 version_to = version_to ? version_to.to_i : self.content.version
97 97 version_from = version_from ? version_from.to_i : version_to - 1
98 98 version_to, version_from = version_from, version_to unless version_from < version_to
99 99
100 100 content_to = content.versions.find_by_version(version_to)
101 101 content_from = content.versions.find_by_version(version_from)
102 102
103 103 (content_to && content_from) ? WikiDiff.new(content_to, content_from) : nil
104 104 end
105 105
106 106 def annotate(version=nil)
107 107 version = version ? version.to_i : self.content.version
108 108 c = content.versions.find_by_version(version)
109 109 c ? WikiAnnotate.new(c) : nil
110 110 end
111 111
112 112 def self.pretty_title(str)
113 113 (str && str.is_a?(String)) ? str.tr('_', ' ') : str
114 114 end
115 115
116 116 def project
117 117 wiki.project
118 118 end
119 119
120 120 def text
121 121 content.text if content
122 122 end
123 123
124 124 # Returns true if usr is allowed to edit the page, otherwise false
125 125 def editable_by?(usr)
126 126 !protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project)
127 127 end
128 128
129 129 def attachments_deletable?(usr=User.current)
130 130 editable_by?(usr) && super(usr)
131 131 end
132 132
133 133 def parent_title
134 134 @parent_title || (self.parent && self.parent.pretty_title)
135 135 end
136 136
137 137 def parent_title=(t)
138 138 @parent_title = t
139 139 parent_page = t.blank? ? nil : self.wiki.find_page(t)
140 140 self.parent = parent_page
141 141 end
142 142
143 143 protected
144 144
145 145 def validate
146 146 errors.add(:parent_title, :invalid) if !@parent_title.blank? && parent.nil?
147 147 errors.add(:parent_title, :circular_dependency) if parent && (parent == self || parent.ancestors.include?(self))
148 148 errors.add(:parent_title, :not_same_project) if parent && (parent.wiki_id != wiki_id)
149 149 end
150 150 end
151 151
152 class WikiDiff
153 attr_reader :diff, :words, :content_to, :content_from
152 class WikiDiff < Redmine::Helpers::Diff
153 attr_reader :content_to, :content_from
154 154
155 155 def initialize(content_to, content_from)
156 156 @content_to = content_to
157 157 @content_from = content_from
158 @words = content_to.text.split(/(\s+)/)
159 @words = @words.select {|word| word != ' '}
160 words_from = content_from.text.split(/(\s+)/)
161 words_from = words_from.select {|word| word != ' '}
162 @diff = words_from.diff @words
158 super(content_to.text, content_from.text)
163 159 end
164 160 end
165 161
166 162 class WikiAnnotate
167 163 attr_reader :lines, :content
168 164
169 165 def initialize(content)
170 166 @content = content
171 167 current = content
172 168 current_lines = current.text.split(/\r?\n/)
173 169 @lines = current_lines.collect {|t| [nil, nil, t]}
174 170 positions = []
175 171 current_lines.size.times {|i| positions << i}
176 172 while (current.previous)
177 173 d = current.previous.text.split(/\r?\n/).diff(current.text.split(/\r?\n/)).diffs.flatten
178 174 d.each_slice(3) do |s|
179 175 sign, line = s[0], s[1]
180 176 if sign == '+' && positions[line] && positions[line] != -1
181 177 if @lines[positions[line]][0].nil?
182 178 @lines[positions[line]][0] = current.version
183 179 @lines[positions[line]][1] = current.author
184 180 end
185 181 end
186 182 end
187 183 d.each_slice(3) do |s|
188 184 sign, line = s[0], s[1]
189 185 if sign == '-'
190 186 positions.insert(line, -1)
191 187 else
192 188 positions[line] = nil
193 189 end
194 190 end
195 191 positions.compact!
196 192 # Stop if every line is annotated
197 193 break unless @lines.detect { |line| line[0].nil? }
198 194 current = current.previous
199 195 end
200 196 @lines.each { |line| line[0] ||= current.version }
201 197 end
202 198 end
@@ -1,17 +1,17
1 1 <div class="contextual">
2 2 <%= link_to(l(:label_history), {:action => 'history', :id => @page.title}, :class => 'icon icon-history') %>
3 3 </div>
4 4
5 5 <h2><%= @page.pretty_title %></h2>
6 6
7 7 <p>
8 8 <%= l(:label_version) %> <%= link_to @diff.content_from.version, :action => 'show', :id => @page.title, :project_id => @page.project, :version => @diff.content_from.version %>
9 9 <em>(<%= @diff.content_from.author ? @diff.content_from.author.name : "anonyme" %>, <%= format_time(@diff.content_from.updated_on) %>)</em>
10 10 &#8594;
11 11 <%= l(:label_version) %> <%= link_to @diff.content_to.version, :action => 'show', :id => @page.title, :project_id => @page.project, :version => @diff.content_to.version %>/<%= @page.content.version %>
12 12 <em>(<%= @diff.content_to.author ? @diff.content_to.author.name : "anonyme" %>, <%= format_time(@diff.content_to.updated_on) %>)</em>
13 13 </p>
14 14
15 <hr />
16
17 <%= html_diff(@diff) %>
15 <div class="text-diff">
16 <%= @diff.to_html %>
17 </div>
General Comments 0
You need to be logged in to leave comments. Login now