##// END OF EJS Templates
Added pagination on wiki page history....
Jean-Philippe Lang -
r568:6d7a855ca2f4
parent child
Show More
@@ -1,148 +1,155
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 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 class WikiController < ApplicationController
19 19 layout 'base'
20 20 before_filter :find_wiki, :check_project_privacy
21 21 before_filter :authorize, :only => [:destroy, :add_attachment, :destroy_attachment]
22 22
23 23 verify :method => :post, :only => [:destroy, :destroy_attachment], :redirect_to => { :action => :index }
24 24
25 25 helper :attachments
26 26 include AttachmentsHelper
27 27
28 28 # display a page (in editing mode if it doesn't exist)
29 29 def index
30 30 page_title = params[:page]
31 31 @page = @wiki.find_or_new_page(page_title)
32 32 if @page.new_record?
33 33 edit
34 34 render :action => 'edit' and return
35 35 end
36 36 @content = @page.content_for_version(params[:version])
37 37 if params[:export] == 'html'
38 38 export = render_to_string :action => 'export', :layout => false
39 39 send_data(export, :type => 'text/html', :filename => "#{@page.title}.html")
40 40 return
41 41 elsif params[:export] == 'txt'
42 42 send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt")
43 43 return
44 44 end
45 45 render :action => 'show'
46 46 end
47 47
48 48 # edit an existing page or a new one
49 49 def edit
50 50 @page = @wiki.find_or_new_page(params[:page])
51 51 @page.content = WikiContent.new(:page => @page) if @page.new_record?
52 52
53 53 @content = @page.content_for_version(params[:version])
54 54 @content.text = "h1. #{@page.pretty_title}" if @content.text.blank?
55 55 # don't keep previous comment
56 56 @content.comments = nil
57 57 if request.post?
58 58 if @content.text == params[:content][:text]
59 59 # don't save if text wasn't changed
60 60 redirect_to :action => 'index', :id => @project, :page => @page.title
61 61 return
62 62 end
63 63 #@content.text = params[:content][:text]
64 64 #@content.comments = params[:content][:comments]
65 65 @content.attributes = params[:content]
66 66 @content.author = logged_in_user
67 67 # if page is new @page.save will also save content, but not if page isn't a new record
68 68 if (@page.new_record? ? @page.save : @content.save)
69 69 redirect_to :action => 'index', :id => @project, :page => @page.title
70 70 end
71 71 end
72 72 rescue ActiveRecord::StaleObjectError
73 73 # Optimistic locking exception
74 74 flash[:notice] = l(:notice_locking_conflict)
75 75 end
76 76
77 77 # show page history
78 78 def history
79 79 @page = @wiki.find_page(params[:page])
80 # don't load text
80
81 @version_count = @page.content.versions.count
82 @version_pages = Paginator.new self, @version_count, 25, params['p']
83 # don't load text
81 84 @versions = @page.content.versions.find :all,
82 85 :select => "id, author_id, comments, updated_on, version",
83 :order => 'version DESC'
86 :order => 'version DESC',
87 :limit => @version_pages.items_per_page,
88 :offset => @version_pages.current.offset
89
90 render :layout => false if request.xhr?
84 91 end
85 92
86 93 # remove a wiki page and its history
87 94 def destroy
88 95 @page = @wiki.find_page(params[:page])
89 96 @page.destroy if @page
90 97 redirect_to :action => 'special', :id => @project, :page => 'Page_index'
91 98 end
92 99
93 100 # display special pages
94 101 def special
95 102 page_title = params[:page].downcase
96 103 case page_title
97 104 # show pages index, sorted by title
98 105 when 'page_index'
99 106 # eager load information about last updates, without loading text
100 107 @pages = @wiki.pages.find :all, :select => "#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on",
101 108 :joins => "LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id",
102 109 :order => 'title'
103 110 # export wiki to a single html file
104 111 when 'export'
105 112 @pages = @wiki.pages.find :all, :order => 'title'
106 113 export = render_to_string :action => 'export_multiple', :layout => false
107 114 send_data(export, :type => 'text/html', :filename => "wiki.html")
108 115 return
109 116 else
110 117 # requested special page doesn't exist, redirect to default page
111 118 redirect_to :action => 'index', :id => @project, :page => nil and return
112 119 end
113 120 render :action => "special_#{page_title}"
114 121 end
115 122
116 123 def preview
117 124 page = @wiki.find_page(params[:page])
118 125 @attachements = page.attachments if page
119 126 @text = params[:content][:text]
120 127 render :partial => 'preview'
121 128 end
122 129
123 130 def add_attachment
124 131 @page = @wiki.find_page(params[:page])
125 132 # Save the attachments
126 133 params[:attachments].each { |file|
127 134 next unless file.size > 0
128 135 a = Attachment.create(:container => @page, :file => file, :author => logged_in_user)
129 136 } if params[:attachments] and params[:attachments].is_a? Array
130 137 redirect_to :action => 'index', :page => @page.title
131 138 end
132 139
133 140 def destroy_attachment
134 141 @page = @wiki.find_page(params[:page])
135 142 @page.attachments.find(params[:attachment_id]).destroy
136 143 redirect_to :action => 'index', :page => @page.title
137 144 end
138 145
139 146 private
140 147
141 148 def find_wiki
142 149 @project = Project.find(params[:id])
143 150 @wiki = @project.wiki
144 151 render_404 unless @wiki
145 152 rescue ActiveRecord::RecordNotFound
146 153 render_404
147 154 end
148 155 end
@@ -1,275 +1,277
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 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 class RedCloth
19 19 # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet.
20 20 # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a>
21 21 def hard_break( text )
22 22 text.gsub!( /(.)\n(?!\n|\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
23 23 end
24 24 end
25 25
26 26 module ApplicationHelper
27 27
28 28 # Return current logged in user or nil
29 29 def loggedin?
30 30 @logged_in_user
31 31 end
32 32
33 33 # Return true if user is logged in and is admin, otherwise false
34 34 def admin_loggedin?
35 35 @logged_in_user and @logged_in_user.admin?
36 36 end
37 37
38 38 # Return true if user is authorized for controller/action, otherwise false
39 39 def authorize_for(controller, action)
40 40 # check if action is allowed on public projects
41 41 if @project.is_public? and Permission.allowed_to_public "%s/%s" % [ controller, action ]
42 42 return true
43 43 end
44 44 # check if user is authorized
45 45 if @logged_in_user and (@logged_in_user.admin? or Permission.allowed_to_role( "%s/%s" % [ controller, action ], @logged_in_user.role_for_project(@project) ) )
46 46 return true
47 47 end
48 48 return false
49 49 end
50 50
51 51 # Display a link if user is authorized
52 52 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
53 53 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller], options[:action])
54 54 end
55 55
56 56 # Display a link to user's account page
57 57 def link_to_user(user)
58 58 link_to user.display_name, :controller => 'account', :action => 'show', :id => user
59 59 end
60 60
61 61 def link_to_issue(issue)
62 62 link_to "#{issue.tracker.name} ##{issue.id}", :controller => "issues", :action => "show", :id => issue
63 63 end
64 64
65 65 def toggle_link(name, id, options={})
66 66 onclick = "Element.toggle('#{id}'); "
67 67 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
68 68 onclick << "return false;"
69 69 link_to(name, "#", :onclick => onclick)
70 70 end
71 71
72 72 def image_to_function(name, function, html_options = {})
73 73 html_options.symbolize_keys!
74 74 tag(:input, html_options.merge({
75 75 :type => "image", :src => image_path(name),
76 76 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
77 77 }))
78 78 end
79 79
80 80 def format_date(date)
81 81 l_date(date) if date
82 82 end
83 83
84 84 def format_time(time)
85 85 l_datetime((time.is_a? String) ? time.to_time : time) if time
86 86 end
87 87
88 88 def day_name(day)
89 89 l(:general_day_names).split(',')[day-1]
90 90 end
91 91
92 92 def month_name(month)
93 93 l(:actionview_datehelper_select_month_names).split(',')[month-1]
94 94 end
95 95
96 96 def pagination_links_full(paginator, options={}, html_options={})
97 page_param = options.delete(:page_param) || :page
98
97 99 html = ''
98 100 html << link_to_remote(('&#171; ' + l(:label_previous)),
99 {:update => "content", :url => options.merge(:page => paginator.current.previous)},
100 {:href => url_for(:params => options.merge(:page => paginator.current.previous))}) + ' ' if paginator.current.previous
101 {:update => "content", :url => options.merge(page_param => paginator.current.previous)},
102 {:href => url_for(:params => options.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
101 103
102 104 html << (pagination_links_each(paginator, options) do |n|
103 105 link_to_remote(n.to_s,
104 {:url => {:params => options.merge(:page => n)}, :update => 'content'},
105 {:href => url_for(:params => options.merge(:page => n))})
106 {:url => {:params => options.merge(page_param => n)}, :update => 'content'},
107 {:href => url_for(:params => options.merge(page_param => n))})
106 108 end || '')
107 109
108 110 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
109 {:update => "content", :url => options.merge(:page => paginator.current.next)},
110 {:href => url_for(:params => options.merge(:page => paginator.current.next))}) if paginator.current.next
111 {:update => "content", :url => options.merge(page_param => paginator.current.next)},
112 {:href => url_for(:params => options.merge(page_param => paginator.current.next))}) if paginator.current.next
111 113 html
112 114 end
113 115
114 116 # textilize text according to system settings and RedCloth availability
115 117 def textilizable(text, options = {})
116 118 return "" if text.blank?
117 119
118 120 # different methods for formatting wiki links
119 121 case options[:wiki_links]
120 122 when :local
121 123 # used for local links to html files
122 124 format_wiki_link = Proc.new {|title| "#{title}.html" }
123 125 when :anchor
124 126 # used for single-file wiki export
125 127 format_wiki_link = Proc.new {|title| "##{title}" }
126 128 else
127 129 if @project
128 130 format_wiki_link = Proc.new {|title| url_for :controller => 'wiki', :action => 'index', :id => @project, :page => title }
129 131 else
130 132 format_wiki_link = Proc.new {|title| title }
131 133 end
132 134 end
133 135
134 136 # turn wiki links into textile links:
135 137 # example:
136 138 # [[link]] -> "link":link
137 139 # [[link|title]] -> "title":link
138 140 text = text.gsub(/\[\[([^\]\|]+)(\|([^\]\|]+))?\]\]/) {|m| "\"#{$3 || $1}\":" + format_wiki_link.call(Wiki.titleize($1)) }
139 141
140 142 # turn issue ids into links
141 143 # example:
142 144 # #52 -> <a href="/issues/show/52">#52</a>
143 145 text = text.gsub(/#(\d+)(?=\b)/) {|m| link_to "##{$1}", :controller => 'issues', :action => 'show', :id => $1}
144 146
145 147 # turn revision ids into links (@project needed)
146 148 # example:
147 149 # r52 -> <a href="/repositories/revision/6?rev=52">r52</a> (@project.id is 6)
148 150 text = text.gsub(/(?=\b)r(\d+)(?=\b)/) {|m| link_to "r#{$1}", :controller => 'repositories', :action => 'revision', :id => @project.id, :rev => $1} if @project
149 151
150 152 # when using an image link, try to use an attachment, if possible
151 153 attachments = options[:attachments]
152 154 if attachments
153 155 text = text.gsub(/!([<>=]*)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
154 156 align = $1
155 157 filename = $2
156 158 rf = Regexp.new(filename, Regexp::IGNORECASE)
157 159 # search for the picture in attachments
158 160 if found = attachments.detect { |att| att.filename =~ rf }
159 161 image_url = url_for :controller => 'attachments', :action => 'show', :id => found.id
160 162 "!#{align}#{image_url}!"
161 163 else
162 164 "!#{align}#{filename}!"
163 165 end
164 166 end
165 167 end
166 168
167 169 # finally textilize text
168 170 @do_textilize ||= (Setting.text_formatting == 'textile') && (ActionView::Helpers::TextHelper.method_defined? "textilize")
169 171 text = @do_textilize ? auto_link(RedCloth.new(text, [:hard_breaks]).to_html) : simple_format(auto_link(h(text)))
170 172 end
171 173
172 174 def error_messages_for(object_name, options = {})
173 175 options = options.symbolize_keys
174 176 object = instance_variable_get("@#{object_name}")
175 177 if object && !object.errors.empty?
176 178 # build full_messages here with controller current language
177 179 full_messages = []
178 180 object.errors.each do |attr, msg|
179 181 next if msg.nil?
180 182 msg = msg.first if msg.is_a? Array
181 183 if attr == "base"
182 184 full_messages << l(msg)
183 185 else
184 186 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
185 187 end
186 188 end
187 189 # retrieve custom values error messages
188 190 if object.errors[:custom_values]
189 191 object.custom_values.each do |v|
190 192 v.errors.each do |attr, msg|
191 193 next if msg.nil?
192 194 msg = msg.first if msg.is_a? Array
193 195 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
194 196 end
195 197 end
196 198 end
197 199 content_tag("div",
198 200 content_tag(
199 201 options[:header_tag] || "h2", lwr(:gui_validation_error, full_messages.length) + " :"
200 202 ) +
201 203 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
202 204 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
203 205 )
204 206 else
205 207 ""
206 208 end
207 209 end
208 210
209 211 def lang_options_for_select(blank=true)
210 212 (blank ? [["(auto)", ""]] : []) +
211 213 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.first <=> y.first }
212 214 end
213 215
214 216 def label_tag_for(name, option_tags = nil, options = {})
215 217 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
216 218 content_tag("label", label_text)
217 219 end
218 220
219 221 def labelled_tabular_form_for(name, object, options, &proc)
220 222 options[:html] ||= {}
221 223 options[:html].store :class, "tabular"
222 224 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
223 225 end
224 226
225 227 def check_all_links(form_name)
226 228 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
227 229 " | " +
228 230 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
229 231 end
230 232
231 233 def calendar_for(field_id)
232 234 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
233 235 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
234 236 end
235 237
236 238 def wikitoolbar_for(field_id)
237 239 return '' unless Setting.text_formatting == 'textile'
238 240 javascript_include_tag('jstoolbar') + javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.draw();")
239 241 end
240 242 end
241 243
242 244 class TabularFormBuilder < ActionView::Helpers::FormBuilder
243 245 include GLoc
244 246
245 247 def initialize(object_name, object, template, options, proc)
246 248 set_language_if_valid options.delete(:lang)
247 249 @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
248 250 end
249 251
250 252 (field_helpers - %w(radio_button hidden_field) + %w(date_select)).each do |selector|
251 253 src = <<-END_SRC
252 254 def #{selector}(field, options = {})
253 255 return super if options.delete :no_label
254 256 label_text = l(options[:label]) if options[:label]
255 257 label_text ||= l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym)
256 258 label_text << @template.content_tag("span", " *", :class => "required") if options.delete(:required)
257 259 label = @template.content_tag("label", label_text,
258 260 :class => (@object && @object.errors[field] ? "error" : nil),
259 261 :for => (@object_name.to_s + "_" + field.to_s))
260 262 label + super
261 263 end
262 264 END_SRC
263 265 class_eval src, __FILE__, __LINE__
264 266 end
265 267
266 268 def select(field, choices, options = {}, html_options = {})
267 269 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
268 270 label = @template.content_tag("label", label_text,
269 271 :class => (@object && @object.errors[field] ? "error" : nil),
270 272 :for => (@object_name.to_s + "_" + field.to_s))
271 273 label + super
272 274 end
273 275
274 276 end
275 277
@@ -1,28 +1,31
1 1 <div class="contextual">
2 2 <%= link_to(l(:label_page_index), {:action => 'special', :page => 'Page_index'}, :class => 'icon icon-index') %>
3 3 </div>
4 4
5 5 <h2><%= @page.pretty_title %></h2>
6 6
7 7 <h3><%= l(:label_history) %></h3>
8 8
9 9 <table class="list">
10 10 <thead><tr>
11 11 <th>#</th>
12 12 <th><%= l(:field_updated_on) %></th>
13 13 <th><%= l(:field_author) %></th>
14 14 <th><%= l(:field_comments) %></th>
15 15 </tr></thead>
16 16 <tbody>
17 17 <% @versions.each do |ver| %>
18 18 <tr class="<%= cycle("odd", "even") %>">
19 19 <th align="center"><%= link_to ver.version, :action => 'index', :page => @page.title, :version => ver.version %></th>
20 20 <td align="center"><%= format_time(ver.updated_on) %></td>
21 21 <td><em><%= ver.author ? ver.author.name : "anonyme" %></em></td>
22 22 <td><%=h ver.comments %></td>
23 23 </tr>
24 24 <% end %>
25 25 </tbody>
26 26 </table>
27 27
28 <p><%= link_to l(:button_back), :action => 'index', :page => @page.title %></p> No newline at end of file
28 <p><%= pagination_links_full @version_pages, :page_param => :p %>
29 [ <%= @version_pages.current.first_item %> - <%= @version_pages.current.last_item %> / <%= @version_count %> ]</p>
30
31 <p><%= link_to l(:button_back), :action => 'index', :page => @page.title %></p>
General Comments 0
You need to be logged in to leave comments. Login now