##// END OF EJS Templates
Added watchers for message boards (watchers controller modified to support any watchable model)....
Jean-Philippe Lang -
r527:bca5bd9c6276
parent child
Show More
@@ -1,87 +1,89
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class BoardsController < ApplicationController
18 class BoardsController < ApplicationController
19 layout 'base'
19 layout 'base'
20 before_filter :find_project
20 before_filter :find_project
21 before_filter :authorize, :except => [:index, :show]
21 before_filter :authorize, :except => [:index, :show]
22 before_filter :check_project_privacy, :only => [:index, :show]
22 before_filter :check_project_privacy, :only => [:index, :show]
23
23
24 helper :messages
24 helper :messages
25 include MessagesHelper
25 include MessagesHelper
26 helper :sort
26 helper :sort
27 include SortHelper
27 include SortHelper
28 helper :watchers
29 include WatchersHelper
28
30
29 def index
31 def index
30 @boards = @project.boards
32 @boards = @project.boards
31 # show the board if there is only one
33 # show the board if there is only one
32 if @boards.size == 1
34 if @boards.size == 1
33 @board = @boards.first
35 @board = @boards.first
34 show
36 show
35 render :action => 'show'
37 render :action => 'show'
36 end
38 end
37 end
39 end
38
40
39 def show
41 def show
40 sort_init "#{Message.table_name}.updated_on", "desc"
42 sort_init "#{Message.table_name}.updated_on", "desc"
41 sort_update
43 sort_update
42
44
43 @topic_count = @board.topics.count
45 @topic_count = @board.topics.count
44 @topic_pages = Paginator.new self, @topic_count, 25, params['page']
46 @topic_pages = Paginator.new self, @topic_count, 25, params['page']
45 @topics = @board.topics.find :all, :order => sort_clause,
47 @topics = @board.topics.find :all, :order => sort_clause,
46 :include => [:author, {:last_reply => :author}],
48 :include => [:author, {:last_reply => :author}],
47 :limit => @topic_pages.items_per_page,
49 :limit => @topic_pages.items_per_page,
48 :offset => @topic_pages.current.offset
50 :offset => @topic_pages.current.offset
49 render :action => 'show', :layout => false if request.xhr?
51 render :action => 'show', :layout => false if request.xhr?
50 end
52 end
51
53
52 verify :method => :post, :only => [ :destroy ], :redirect_to => { :action => :index }
54 verify :method => :post, :only => [ :destroy ], :redirect_to => { :action => :index }
53
55
54 def new
56 def new
55 @board = Board.new(params[:board])
57 @board = Board.new(params[:board])
56 @board.project = @project
58 @board.project = @project
57 if request.post? && @board.save
59 if request.post? && @board.save
58 flash[:notice] = l(:notice_successful_create)
60 flash[:notice] = l(:notice_successful_create)
59 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
61 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
60 end
62 end
61 end
63 end
62
64
63 def edit
65 def edit
64 if request.post? && @board.update_attributes(params[:board])
66 if request.post? && @board.update_attributes(params[:board])
65 case params[:position]
67 case params[:position]
66 when 'highest'; @board.move_to_top
68 when 'highest'; @board.move_to_top
67 when 'higher'; @board.move_higher
69 when 'higher'; @board.move_higher
68 when 'lower'; @board.move_lower
70 when 'lower'; @board.move_lower
69 when 'lowest'; @board.move_to_bottom
71 when 'lowest'; @board.move_to_bottom
70 end if params[:position]
72 end if params[:position]
71 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
73 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
72 end
74 end
73 end
75 end
74
76
75 def destroy
77 def destroy
76 @board.destroy
78 @board.destroy
77 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
79 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
78 end
80 end
79
81
80 private
82 private
81 def find_project
83 def find_project
82 @project = Project.find(params[:project_id])
84 @project = Project.find(params[:project_id])
83 @board = @project.boards.find(params[:id]) if params[:id]
85 @board = @project.boards.find(params[:id]) if params[:id]
84 rescue ActiveRecord::RecordNotFound
86 rescue ActiveRecord::RecordNotFound
85 render_404
87 render_404
86 end
88 end
87 end
89 end
@@ -1,163 +1,165
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class IssuesController < ApplicationController
18 class IssuesController < ApplicationController
19 layout 'base', :except => :export_pdf
19 layout 'base', :except => :export_pdf
20 before_filter :find_project, :authorize
20 before_filter :find_project, :authorize
21
21
22 cache_sweeper :issue_sweeper, :only => [ :edit, :change_status, :destroy ]
22 cache_sweeper :issue_sweeper, :only => [ :edit, :change_status, :destroy ]
23
23
24 helper :custom_fields
24 helper :custom_fields
25 include CustomFieldsHelper
25 include CustomFieldsHelper
26 helper :ifpdf
26 helper :ifpdf
27 include IfpdfHelper
27 include IfpdfHelper
28 helper :issue_relations
28 helper :issue_relations
29 include IssueRelationsHelper
29 include IssueRelationsHelper
30 helper :watchers
31 include WatchersHelper
30
32
31 def show
33 def show
32 @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
34 @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
33 @custom_values = @issue.custom_values.find(:all, :include => :custom_field)
35 @custom_values = @issue.custom_values.find(:all, :include => :custom_field)
34 @journals_count = @issue.journals.count
36 @journals_count = @issue.journals.count
35 @journals = @issue.journals.find(:all, :include => [:user, :details], :limit => 15, :order => "#{Journal.table_name}.created_on desc")
37 @journals = @issue.journals.find(:all, :include => [:user, :details], :limit => 15, :order => "#{Journal.table_name}.created_on desc")
36 end
38 end
37
39
38 def history
40 def history
39 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on desc")
41 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on desc")
40 @journals_count = @journals.length
42 @journals_count = @journals.length
41 end
43 end
42
44
43 def export_pdf
45 def export_pdf
44 @custom_values = @issue.custom_values.find(:all, :include => :custom_field)
46 @custom_values = @issue.custom_values.find(:all, :include => :custom_field)
45 @options_for_rfpdf ||= {}
47 @options_for_rfpdf ||= {}
46 @options_for_rfpdf[:file_name] = "#{@project.name}_#{@issue.id}.pdf"
48 @options_for_rfpdf[:file_name] = "#{@project.name}_#{@issue.id}.pdf"
47 end
49 end
48
50
49 def edit
51 def edit
50 @priorities = Enumeration::get_values('IPRI')
52 @priorities = Enumeration::get_values('IPRI')
51 if request.get?
53 if request.get?
52 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) }
54 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) }
53 else
55 else
54 begin
56 begin
55 @issue.init_journal(self.logged_in_user)
57 @issue.init_journal(self.logged_in_user)
56 # Retrieve custom fields and values
58 # Retrieve custom fields and values
57 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
59 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
58 @issue.custom_values = @custom_values
60 @issue.custom_values = @custom_values
59 @issue.attributes = params[:issue]
61 @issue.attributes = params[:issue]
60 if @issue.save
62 if @issue.save
61 flash[:notice] = l(:notice_successful_update)
63 flash[:notice] = l(:notice_successful_update)
62 redirect_to :action => 'show', :id => @issue
64 redirect_to :action => 'show', :id => @issue
63 end
65 end
64 rescue ActiveRecord::StaleObjectError
66 rescue ActiveRecord::StaleObjectError
65 # Optimistic locking exception
67 # Optimistic locking exception
66 flash[:notice] = l(:notice_locking_conflict)
68 flash[:notice] = l(:notice_locking_conflict)
67 end
69 end
68 end
70 end
69 end
71 end
70
72
71 def add_note
73 def add_note
72 unless params[:notes].empty?
74 unless params[:notes].empty?
73 journal = @issue.init_journal(self.logged_in_user, params[:notes])
75 journal = @issue.init_journal(self.logged_in_user, params[:notes])
74 if @issue.save
76 if @issue.save
75 flash[:notice] = l(:notice_successful_update)
77 flash[:notice] = l(:notice_successful_update)
76 Mailer.deliver_issue_edit(journal) if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
78 Mailer.deliver_issue_edit(journal) if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
77 redirect_to :action => 'show', :id => @issue
79 redirect_to :action => 'show', :id => @issue
78 return
80 return
79 end
81 end
80 end
82 end
81 show
83 show
82 render :action => 'show'
84 render :action => 'show'
83 end
85 end
84
86
85 def change_status
87 def change_status
86 @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
88 @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
87 @new_status = IssueStatus.find(params[:new_status_id])
89 @new_status = IssueStatus.find(params[:new_status_id])
88 if params[:confirm]
90 if params[:confirm]
89 begin
91 begin
90 journal = @issue.init_journal(self.logged_in_user, params[:notes])
92 journal = @issue.init_journal(self.logged_in_user, params[:notes])
91 @issue.status = @new_status
93 @issue.status = @new_status
92 if @issue.update_attributes(params[:issue])
94 if @issue.update_attributes(params[:issue])
93 # Save attachments
95 # Save attachments
94 params[:attachments].each { |file|
96 params[:attachments].each { |file|
95 next unless file.size > 0
97 next unless file.size > 0
96 a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
98 a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
97 journal.details << JournalDetail.new(:property => 'attachment',
99 journal.details << JournalDetail.new(:property => 'attachment',
98 :prop_key => a.id,
100 :prop_key => a.id,
99 :value => a.filename) unless a.new_record?
101 :value => a.filename) unless a.new_record?
100 } if params[:attachments] and params[:attachments].is_a? Array
102 } if params[:attachments] and params[:attachments].is_a? Array
101
103
102 flash[:notice] = l(:notice_successful_update)
104 flash[:notice] = l(:notice_successful_update)
103 Mailer.deliver_issue_edit(journal) if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
105 Mailer.deliver_issue_edit(journal) if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
104 redirect_to :action => 'show', :id => @issue
106 redirect_to :action => 'show', :id => @issue
105 end
107 end
106 rescue ActiveRecord::StaleObjectError
108 rescue ActiveRecord::StaleObjectError
107 # Optimistic locking exception
109 # Optimistic locking exception
108 flash[:notice] = l(:notice_locking_conflict)
110 flash[:notice] = l(:notice_locking_conflict)
109 end
111 end
110 end
112 end
111 @assignable_to = @project.members.find(:all, :include => :user).collect{ |m| m.user }
113 @assignable_to = @project.members.find(:all, :include => :user).collect{ |m| m.user }
112 end
114 end
113
115
114 def destroy
116 def destroy
115 @issue.destroy
117 @issue.destroy
116 redirect_to :controller => 'projects', :action => 'list_issues', :id => @project
118 redirect_to :controller => 'projects', :action => 'list_issues', :id => @project
117 end
119 end
118
120
119 def add_attachment
121 def add_attachment
120 # Save the attachments
122 # Save the attachments
121 @attachments = []
123 @attachments = []
122 journal = @issue.init_journal(self.logged_in_user)
124 journal = @issue.init_journal(self.logged_in_user)
123 params[:attachments].each { |file|
125 params[:attachments].each { |file|
124 next unless file.size > 0
126 next unless file.size > 0
125 a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
127 a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
126 @attachments << a unless a.new_record?
128 @attachments << a unless a.new_record?
127 journal.details << JournalDetail.new(:property => 'attachment',
129 journal.details << JournalDetail.new(:property => 'attachment',
128 :prop_key => a.id,
130 :prop_key => a.id,
129 :value => a.filename) unless a.new_record?
131 :value => a.filename) unless a.new_record?
130 } if params[:attachments] and params[:attachments].is_a? Array
132 } if params[:attachments] and params[:attachments].is_a? Array
131 journal.save if journal.details.any?
133 journal.save if journal.details.any?
132 Mailer.deliver_attachments_add(@attachments) if !@attachments.empty? and Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
134 Mailer.deliver_attachments_add(@attachments) if !@attachments.empty? and Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
133 redirect_to :action => 'show', :id => @issue
135 redirect_to :action => 'show', :id => @issue
134 end
136 end
135
137
136 def destroy_attachment
138 def destroy_attachment
137 a = @issue.attachments.find(params[:attachment_id])
139 a = @issue.attachments.find(params[:attachment_id])
138 a.destroy
140 a.destroy
139 journal = @issue.init_journal(self.logged_in_user)
141 journal = @issue.init_journal(self.logged_in_user)
140 journal.details << JournalDetail.new(:property => 'attachment',
142 journal.details << JournalDetail.new(:property => 'attachment',
141 :prop_key => a.id,
143 :prop_key => a.id,
142 :old_value => a.filename)
144 :old_value => a.filename)
143 journal.save
145 journal.save
144 redirect_to :action => 'show', :id => @issue
146 redirect_to :action => 'show', :id => @issue
145 end
147 end
146
148
147 # Send the file in stream mode
149 # Send the file in stream mode
148 def download
150 def download
149 @attachment = @issue.attachments.find(params[:attachment_id])
151 @attachment = @issue.attachments.find(params[:attachment_id])
150 send_file @attachment.diskfile, :filename => @attachment.filename
152 send_file @attachment.diskfile, :filename => @attachment.filename
151 rescue
153 rescue
152 render_404
154 render_404
153 end
155 end
154
156
155 private
157 private
156 def find_project
158 def find_project
157 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
159 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
158 @project = @issue.project
160 @project = @issue.project
159 @html_title = "#{@project.name} - #{@issue.tracker.name} ##{@issue.id}"
161 @html_title = "#{@project.name} - #{@issue.tracker.name} ##{@issue.id}"
160 rescue ActiveRecord::RecordNotFound
162 rescue ActiveRecord::RecordNotFound
161 render_404
163 render_404
162 end
164 end
163 end
165 end
@@ -1,38 +1,49
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class WatchersController < ApplicationController
18 class WatchersController < ApplicationController
19 layout 'base'
19 layout 'base'
20 before_filter :require_login, :find_project, :check_project_privacy
20 before_filter :require_login, :find_project, :check_project_privacy
21
21
22 def add
22 def add
23 @issue.add_watcher(logged_in_user)
23 user = logged_in_user
24 redirect_to :controller => 'issues', :action => 'show', :id => @issue
24 @watched.add_watcher(user)
25 respond_to do |format|
26 format.html { render :text => 'Watcher added.', :layout => true }
27 format.js { render(:update) {|page| page.replace_html 'watcher', watcher_link(@watched, user)} }
28 end
25 end
29 end
26
30
27 def remove
31 def remove
28 @issue.remove_watcher(logged_in_user)
32 user = logged_in_user
29 redirect_to :controller => 'issues', :action => 'show', :id => @issue
33 @watched.remove_watcher(user)
34 respond_to do |format|
35 format.html { render :text => 'Watcher removed.', :layout => true }
36 format.js { render(:update) {|page| page.replace_html 'watcher', watcher_link(@watched, user)} }
37 end
30 end
38 end
31
39
32 private
40 private
33
34 def find_project
41 def find_project
35 @issue = Issue.find(params[:issue_id])
42 klass = Object.const_get(params[:object_type].camelcase)
36 @project = @issue.project
43 return false unless klass.respond_to?('watched_by')
44 @watched = klass.find(params[:object_id])
45 @project = @watched.project
46 rescue
47 render_404
37 end
48 end
38 end
49 end
@@ -1,19 +1,36
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 module WatchersHelper
18 module WatchersHelper
19 def watcher_tag(object, user)
20 content_tag("span", watcher_link(object, user), :id => 'watcher')
21 end
22
23 def watcher_link(object, user)
24 return '' unless user && object.respond_to?('watched_by?')
25 watched = object.watched_by?(user)
26 url = {:controller => 'watchers',
27 :action => (watched ? 'remove' : 'add'),
28 :object_type => object.class.to_s.underscore,
29 :object_id => object.id}
30 link_to_remote((watched ? l(:button_unwatch) : l(:button_watch)),
31 {:url => url},
32 :href => url_for(url),
33 :class => (watched ? 'icon icon-fav' : 'icon icon-fav-off'))
34
35 end
19 end
36 end
@@ -1,28 +1,29
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Board < ActiveRecord::Base
18 class Board < ActiveRecord::Base
19 belongs_to :project
19 belongs_to :project
20 has_many :topics, :class_name => 'Message', :conditions => "#{Message.table_name}.parent_id IS NULL", :order => "#{Message.table_name}.created_on DESC"
20 has_many :topics, :class_name => 'Message', :conditions => "#{Message.table_name}.parent_id IS NULL", :order => "#{Message.table_name}.created_on DESC"
21 has_many :messages, :dependent => :delete_all, :order => "#{Message.table_name}.created_on DESC"
21 has_many :messages, :dependent => :delete_all, :order => "#{Message.table_name}.created_on DESC"
22 belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id
22 belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id
23 acts_as_list :scope => :project_id
23 acts_as_list :scope => :project_id
24 acts_as_watchable
24
25
25 validates_presence_of :name, :description
26 validates_presence_of :name, :description
26 validates_length_of :name, :maximum => 30
27 validates_length_of :name, :maximum => 30
27 validates_length_of :description, :maximum => 255
28 validates_length_of :description, :maximum => 255
28 end
29 end
@@ -1,36 +1,37
1 <div class="contextual">
1 <div class="contextual">
2 <%= link_to l(:label_message_new), {:controller => 'messages', :action => 'new', :board_id => @board}, :class => "icon icon-add" %>
2 <%= link_to l(:label_message_new), {:controller => 'messages', :action => 'new', :board_id => @board}, :class => "icon icon-add" %>
3 <%= watcher_tag(@board, @logged_in_user) %>
3 </div>
4 </div>
4
5
5 <h2><%=h @board.name %></h2>
6 <h2><%=h @board.name %></h2>
6
7
7 <table class="list">
8 <table class="list">
8 <thead><tr>
9 <thead><tr>
9 <th><%= l(:field_subject) %></th>
10 <th><%= l(:field_subject) %></th>
10 <th><%= l(:field_author) %></th>
11 <th><%= l(:field_author) %></th>
11 <%= sort_header_tag("#{Message.table_name}.created_on", :caption => l(:field_created_on)) %>
12 <%= sort_header_tag("#{Message.table_name}.created_on", :caption => l(:field_created_on)) %>
12 <th><%= l(:label_reply_plural) %></th>
13 <th><%= l(:label_reply_plural) %></th>
13 <%= sort_header_tag("#{Message.table_name}.updated_on", :caption => l(:label_message_last)) %>
14 <%= sort_header_tag("#{Message.table_name}.updated_on", :caption => l(:label_message_last)) %>
14 </tr></thead>
15 </tr></thead>
15 <tbody>
16 <tbody>
16 <% @topics.each do |topic| %>
17 <% @topics.each do |topic| %>
17 <tr class="<%= cycle 'odd', 'even' %>">
18 <tr class="<%= cycle 'odd', 'even' %>">
18 <td><%= link_to h(topic.subject), :controller => 'messages', :action => 'show', :board_id => @board, :id => topic %></td>
19 <td><%= link_to h(topic.subject), :controller => 'messages', :action => 'show', :board_id => @board, :id => topic %></td>
19 <td align="center"><%= link_to_user topic.author %></td>
20 <td align="center"><%= link_to_user topic.author %></td>
20 <td align="center"><%= format_time(topic.created_on) %></td>
21 <td align="center"><%= format_time(topic.created_on) %></td>
21 <td align="center"><%= topic.replies_count %></td>
22 <td align="center"><%= topic.replies_count %></td>
22 <td>
23 <td>
23 <small>
24 <small>
24 <% if topic.last_reply %>
25 <% if topic.last_reply %>
25 <%= topic.last_reply.author.name %>, <%= format_time(topic.last_reply.created_on) %><br />
26 <%= topic.last_reply.author.name %>, <%= format_time(topic.last_reply.created_on) %><br />
26 <%= link_to_message topic.last_reply %>
27 <%= link_to_message topic.last_reply %>
27 <% end %>
28 <% end %>
28 </small>
29 </small>
29 </td>
30 </td>
30 </tr>
31 </tr>
31 <% end %>
32 <% end %>
32 </tbody>
33 </tbody>
33 </table>
34 </table>
34
35
35 <p><%= pagination_links_full @topic_pages %>
36 <p><%= pagination_links_full @topic_pages %>
36 [ <%= @topic_pages.current.first_item %> - <%= @topic_pages.current.last_item %> / <%= @topic_count %> ]</p>
37 [ <%= @topic_pages.current.first_item %> - <%= @topic_pages.current.last_item %> / <%= @topic_count %> ]</p>
@@ -1,131 +1,125
1 <div class="contextual">
1 <div class="contextual">
2 <%= l(:label_export_to) %><%= link_to 'PDF', {:action => 'export_pdf', :id => @issue}, :class => 'icon icon-pdf' %>
2 <%= l(:label_export_to) %><%= link_to 'PDF', {:action => 'export_pdf', :id => @issue}, :class => 'icon icon-pdf' %>
3 </div>
3 </div>
4
4
5 <h2><%= @issue.tracker.name %> #<%= @issue.id %> - <%=h @issue.subject %></h2>
5 <h2><%= @issue.tracker.name %> #<%= @issue.id %> - <%=h @issue.subject %></h2>
6
6
7 <div class="box">
7 <div class="box">
8 <table width="100%">
8 <table width="100%">
9 <tr>
9 <tr>
10 <td style="width:15%"><b><%=l(:field_status)%> :</b></td><td style="width:35%"><%= @issue.status.name %></td>
10 <td style="width:15%"><b><%=l(:field_status)%> :</b></td><td style="width:35%"><%= @issue.status.name %></td>
11 <td style="width:15%"><b><%=l(:field_priority)%> :</b></td><td style="width:35%"><%= @issue.priority.name %></td>
11 <td style="width:15%"><b><%=l(:field_priority)%> :</b></td><td style="width:35%"><%= @issue.priority.name %></td>
12 </tr>
12 </tr>
13 <tr>
13 <tr>
14 <td><b><%=l(:field_assigned_to)%> :</b></td><td><%= @issue.assigned_to ? link_to_user(@issue.assigned_to) : "-" %></td>
14 <td><b><%=l(:field_assigned_to)%> :</b></td><td><%= @issue.assigned_to ? link_to_user(@issue.assigned_to) : "-" %></td>
15 <td><b><%=l(:field_category)%> :</b></td><td><%=h @issue.category ? @issue.category.name : "-" %></td>
15 <td><b><%=l(:field_category)%> :</b></td><td><%=h @issue.category ? @issue.category.name : "-" %></td>
16 </tr>
16 </tr>
17 <tr>
17 <tr>
18 <td><b><%=l(:field_author)%> :</b></td><td><%= link_to_user @issue.author %></td>
18 <td><b><%=l(:field_author)%> :</b></td><td><%= link_to_user @issue.author %></td>
19 <td><b><%=l(:field_start_date)%> :</b></td><td><%= format_date(@issue.start_date) %></td>
19 <td><b><%=l(:field_start_date)%> :</b></td><td><%= format_date(@issue.start_date) %></td>
20 </tr>
20 </tr>
21 <tr>
21 <tr>
22 <td><b><%=l(:field_created_on)%> :</b></td><td><%= format_date(@issue.created_on) %></td>
22 <td><b><%=l(:field_created_on)%> :</b></td><td><%= format_date(@issue.created_on) %></td>
23 <td><b><%=l(:field_due_date)%> :</b></td><td><%= format_date(@issue.due_date) %></td>
23 <td><b><%=l(:field_due_date)%> :</b></td><td><%= format_date(@issue.due_date) %></td>
24 </tr>
24 </tr>
25 <tr>
25 <tr>
26 <td><b><%=l(:field_updated_on)%> :</b></td><td><%= format_date(@issue.updated_on) %></td>
26 <td><b><%=l(:field_updated_on)%> :</b></td><td><%= format_date(@issue.updated_on) %></td>
27 <td><b><%=l(:field_done_ratio)%> :</b></td><td><%= @issue.done_ratio %> %</td>
27 <td><b><%=l(:field_done_ratio)%> :</b></td><td><%= @issue.done_ratio %> %</td>
28 </tr>
28 </tr>
29 <tr>
29 <tr>
30 <td><b><%=l(:field_fixed_version)%> :</b></td><td><%= @issue.fixed_version ? @issue.fixed_version.name : "-" %></td>
30 <td><b><%=l(:field_fixed_version)%> :</b></td><td><%= @issue.fixed_version ? @issue.fixed_version.name : "-" %></td>
31 <td><b><%=l(:label_spent_time)%> :</b></td>
31 <td><b><%=l(:label_spent_time)%> :</b></td>
32 <td><%= @issue.spent_hours > 0 ? (link_to lwr(:label_f_hour, @issue.spent_hours), {:controller => 'timelog', :action => 'details', :issue_id => @issue}, :class => 'icon icon-time') : "-" %></td>
32 <td><%= @issue.spent_hours > 0 ? (link_to lwr(:label_f_hour, @issue.spent_hours), {:controller => 'timelog', :action => 'details', :issue_id => @issue}, :class => 'icon icon-time') : "-" %></td>
33 </tr>
33 </tr>
34 <tr>
34 <tr>
35 <% n = 0
35 <% n = 0
36 for custom_value in @custom_values %>
36 for custom_value in @custom_values %>
37 <td><b><%= custom_value.custom_field.name %> :</b></td><td><%= h(show_value(custom_value)) %></td>
37 <td><b><%= custom_value.custom_field.name %> :</b></td><td><%= h(show_value(custom_value)) %></td>
38 <% n = n + 1
38 <% n = n + 1
39 if (n > 1)
39 if (n > 1)
40 n = 0 %>
40 n = 0 %>
41 </tr><tr>
41 </tr><tr>
42 <%end
42 <%end
43 end %>
43 end %>
44 </tr>
44 </tr>
45 </table>
45 </table>
46 <hr />
46 <hr />
47
47
48 <% if @issue.changesets.any? %>
48 <% if @issue.changesets.any? %>
49 <div style="float:right;">
49 <div style="float:right;">
50 <em><%= l(:label_revision_plural) %>: <%= @issue.changesets.collect{|changeset| link_to(changeset.revision, :controller => 'repositories', :action => 'revision', :id => @project, :rev => changeset.revision)}.join(", ") %></em>
50 <em><%= l(:label_revision_plural) %>: <%= @issue.changesets.collect{|changeset| link_to(changeset.revision, :controller => 'repositories', :action => 'revision', :id => @project, :rev => changeset.revision)}.join(", ") %></em>
51 </div>
51 </div>
52 <% end %>
52 <% end %>
53
53
54 <b><%=l(:field_description)%> :</b><br /><br />
54 <b><%=l(:field_description)%> :</b><br /><br />
55 <%= textilizable @issue.description %>
55 <%= textilizable @issue.description %>
56 <br />
56 <br />
57
57
58 <div class="contextual">
58 <div class="contextual">
59 <%= link_to_if_authorized l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue}, :class => 'icon icon-edit' %>
59 <%= link_to_if_authorized l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue}, :class => 'icon icon-edit' %>
60 <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon icon-time' %>
60 <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon icon-time' %>
61 <% if @logged_in_user %>
61 <%= watcher_tag(@issue, @logged_in_user) %>
62 <% if @issue.watched_by?(@logged_in_user) %>
63 <%= link_to l(:button_unwatch), {:controller => 'watchers', :action => 'remove', :issue_id => @issue}, :class => 'icon icon-fav' %>
64 <% else %>
65 <%= link_to l(:button_watch), {:controller => 'watchers', :action => 'add', :issue_id => @issue}, :class => 'icon icon-fav-off' %>
66 <% end %>
67 <% end %>
68 <%= link_to_if_authorized l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id }, :class => 'icon icon-move' %>
62 <%= link_to_if_authorized l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id }, :class => 'icon icon-move' %>
69 <%= link_to_if_authorized l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
63 <%= link_to_if_authorized l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
70 </div>
64 </div>
71
65
72 <% if authorize_for('issues', 'change_status') and @status_options and !@status_options.empty? %>
66 <% if authorize_for('issues', 'change_status') and @status_options and !@status_options.empty? %>
73 <% form_tag({:controller => 'issues', :action => 'change_status', :id => @issue}) do %>
67 <% form_tag({:controller => 'issues', :action => 'change_status', :id => @issue}) do %>
74 <%=l(:label_change_status)%> :
68 <%=l(:label_change_status)%> :
75 <select name="new_status_id">
69 <select name="new_status_id">
76 <%= options_from_collection_for_select @status_options, "id", "name" %>
70 <%= options_from_collection_for_select @status_options, "id", "name" %>
77 </select>
71 </select>
78 <%= submit_tag l(:button_change) %>
72 <%= submit_tag l(:button_change) %>
79 <% end %>
73 <% end %>
80 <% end %>
74 <% end %>
81 &nbsp;
75 &nbsp;
82 </div>
76 </div>
83
77
84 <% if authorize_for('issue_relations', 'new') || @issue.relations.any? %>
78 <% if authorize_for('issue_relations', 'new') || @issue.relations.any? %>
85 <div id="relations" class="box">
79 <div id="relations" class="box">
86 <%= render :partial => 'relations' %>
80 <%= render :partial => 'relations' %>
87 </div>
81 </div>
88 <% end %>
82 <% end %>
89
83
90 <div id="history" class="box">
84 <div id="history" class="box">
91 <h3><%=l(:label_history)%>
85 <h3><%=l(:label_history)%>
92 <% if @journals_count > @journals.length %>(<%= l(:label_last_changes, @journals.length) %>)<% end %></h3>
86 <% if @journals_count > @journals.length %>(<%= l(:label_last_changes, @journals.length) %>)<% end %></h3>
93 <%= render :partial => 'history', :locals => { :journals => @journals } %>
87 <%= render :partial => 'history', :locals => { :journals => @journals } %>
94 <% if @journals_count > @journals.length %>
88 <% if @journals_count > @journals.length %>
95 <p><center><small><%= link_to l(:label_change_view_all), :action => 'history', :id => @issue %></small></center></p>
89 <p><center><small><%= link_to l(:label_change_view_all), :action => 'history', :id => @issue %></small></center></p>
96 <% end %>
90 <% end %>
97 </div>
91 </div>
98
92
99 <div class="box">
93 <div class="box">
100 <h3><%=l(:label_attachment_plural)%></h3>
94 <h3><%=l(:label_attachment_plural)%></h3>
101 <table width="100%">
95 <table width="100%">
102 <% for attachment in @issue.attachments %>
96 <% for attachment in @issue.attachments %>
103 <tr>
97 <tr>
104 <td><%= link_to attachment.filename, { :action => 'download', :id => @issue, :attachment_id => attachment }, :class => 'icon icon-attachment' %> (<%= number_to_human_size(attachment.filesize) %>)</td>
98 <td><%= link_to attachment.filename, { :action => 'download', :id => @issue, :attachment_id => attachment }, :class => 'icon icon-attachment' %> (<%= number_to_human_size(attachment.filesize) %>)</td>
105 <td><%= format_date(attachment.created_on) %></td>
99 <td><%= format_date(attachment.created_on) %></td>
106 <td><%= attachment.author.display_name %></td>
100 <td><%= attachment.author.display_name %></td>
107 <td><div class="contextual"><%= link_to_if_authorized l(:button_delete), {:controller => 'issues', :action => 'destroy_attachment', :id => @issue, :attachment_id => attachment }, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %></div></td>
101 <td><div class="contextual"><%= link_to_if_authorized l(:button_delete), {:controller => 'issues', :action => 'destroy_attachment', :id => @issue, :attachment_id => attachment }, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %></div></td>
108 </tr>
102 </tr>
109 <% end %>
103 <% end %>
110 </table>
104 </table>
111 <br />
105 <br />
112 <% if authorize_for('issues', 'add_attachment') %>
106 <% if authorize_for('issues', 'add_attachment') %>
113 <% form_tag({ :controller => 'issues', :action => 'add_attachment', :id => @issue }, :multipart => true, :class => "tabular") do %>
107 <% form_tag({ :controller => 'issues', :action => 'add_attachment', :id => @issue }, :multipart => true, :class => "tabular") do %>
114 <p id="attachments_p"><label><%=l(:label_attachment_new)%>
108 <p id="attachments_p"><label><%=l(:label_attachment_new)%>
115 <%= image_to_function "add.png", "addFileField();return false" %></label>
109 <%= image_to_function "add.png", "addFileField();return false" %></label>
116 <%= file_field_tag 'attachments[]', :size => 30 %> <em>(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)</em></p>
110 <%= file_field_tag 'attachments[]', :size => 30 %> <em>(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)</em></p>
117 <%= submit_tag l(:button_add) %>
111 <%= submit_tag l(:button_add) %>
118 <% end %>
112 <% end %>
119 <% end %>
113 <% end %>
120 </div>
114 </div>
121
115
122 <% if authorize_for('issues', 'add_note') %>
116 <% if authorize_for('issues', 'add_note') %>
123 <div class="box">
117 <div class="box">
124 <h3><%= l(:label_add_note) %></h3>
118 <h3><%= l(:label_add_note) %></h3>
125 <% form_tag({:controller => 'issues', :action => 'add_note', :id => @issue}, :class => "tabular" ) do %>
119 <% form_tag({:controller => 'issues', :action => 'add_note', :id => @issue}, :class => "tabular" ) do %>
126 <p><label for="notes"><%=l(:field_notes)%></label>
120 <p><label for="notes"><%=l(:field_notes)%></label>
127 <%= text_area_tag 'notes', '', :cols => 60, :rows => 10, :class => 'wiki-edit' %></p>
121 <%= text_area_tag 'notes', '', :cols => 60, :rows => 10, :class => 'wiki-edit' %></p>
128 <%= submit_tag l(:button_add) %>
122 <%= submit_tag l(:button_add) %>
129 <% end %>
123 <% end %>
130 </div>
124 </div>
131 <% end %>
125 <% end %>
General Comments 0
You need to be logged in to leave comments. Login now