@@ -0,0 +1,14 | |||||
|
1 | class SetTopicAuthorsAsWatchers < ActiveRecord::Migration | |||
|
2 | def self.up | |||
|
3 | # Sets active users who created/replied a topic as watchers of the topic | |||
|
4 | # so that the new watch functionality at topic level doesn't affect notifications behaviour | |||
|
5 | Message.connection.execute("INSERT INTO watchers (watchable_type, watchable_id, user_id)" + | |||
|
6 | " SELECT DISTINCT 'Message', COALESCE(messages.parent_id, messages.id), messages.author_id FROM messages, users" + | |||
|
7 | " WHERE messages.author_id = users.id AND users.status = 1") | |||
|
8 | end | |||
|
9 | ||||
|
10 | def self.down | |||
|
11 | # Removes all message watchers | |||
|
12 | Watcher.delete_all("watchable_type = 'Message'") | |||
|
13 | end | |||
|
14 | end |
@@ -1,123 +1,123 | |||||
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 MessagesController < ApplicationController |
|
18 | class MessagesController < ApplicationController | |
19 | menu_item :boards |
|
19 | menu_item :boards | |
20 | before_filter :find_board, :only => [:new, :preview] |
|
20 | before_filter :find_board, :only => [:new, :preview] | |
21 | before_filter :find_message, :except => [:new, :preview] |
|
21 | before_filter :find_message, :except => [:new, :preview] | |
22 | before_filter :authorize, :except => :preview |
|
22 | before_filter :authorize, :except => :preview | |
23 |
|
23 | |||
24 | verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show } |
|
24 | verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show } | |
25 | verify :xhr => true, :only => :quote |
|
25 | verify :xhr => true, :only => :quote | |
26 |
|
26 | |||
27 |
|
27 | helper :watchers | ||
28 | helper :attachments |
|
28 | helper :attachments | |
29 | include AttachmentsHelper |
|
29 | include AttachmentsHelper | |
30 |
|
30 | |||
31 | # Show a topic and its replies |
|
31 | # Show a topic and its replies | |
32 | def show |
|
32 | def show | |
33 | @replies = @topic.children |
|
33 | @replies = @topic.children | |
34 | @replies.reverse! if User.current.wants_comments_in_reverse_order? |
|
34 | @replies.reverse! if User.current.wants_comments_in_reverse_order? | |
35 | @reply = Message.new(:subject => "RE: #{@message.subject}") |
|
35 | @reply = Message.new(:subject => "RE: #{@message.subject}") | |
36 | render :action => "show", :layout => false if request.xhr? |
|
36 | render :action => "show", :layout => false if request.xhr? | |
37 | end |
|
37 | end | |
38 |
|
38 | |||
39 | # Create a new topic |
|
39 | # Create a new topic | |
40 | def new |
|
40 | def new | |
41 | @message = Message.new(params[:message]) |
|
41 | @message = Message.new(params[:message]) | |
42 | @message.author = User.current |
|
42 | @message.author = User.current | |
43 | @message.board = @board |
|
43 | @message.board = @board | |
44 | if params[:message] && User.current.allowed_to?(:edit_messages, @project) |
|
44 | if params[:message] && User.current.allowed_to?(:edit_messages, @project) | |
45 | @message.locked = params[:message]['locked'] |
|
45 | @message.locked = params[:message]['locked'] | |
46 | @message.sticky = params[:message]['sticky'] |
|
46 | @message.sticky = params[:message]['sticky'] | |
47 | end |
|
47 | end | |
48 | if request.post? && @message.save |
|
48 | if request.post? && @message.save | |
49 | attach_files(@message, params[:attachments]) |
|
49 | attach_files(@message, params[:attachments]) | |
50 | redirect_to :action => 'show', :id => @message |
|
50 | redirect_to :action => 'show', :id => @message | |
51 | end |
|
51 | end | |
52 | end |
|
52 | end | |
53 |
|
53 | |||
54 | # Reply to a topic |
|
54 | # Reply to a topic | |
55 | def reply |
|
55 | def reply | |
56 | @reply = Message.new(params[:reply]) |
|
56 | @reply = Message.new(params[:reply]) | |
57 | @reply.author = User.current |
|
57 | @reply.author = User.current | |
58 | @reply.board = @board |
|
58 | @reply.board = @board | |
59 | @topic.children << @reply |
|
59 | @topic.children << @reply | |
60 | if !@reply.new_record? |
|
60 | if !@reply.new_record? | |
61 | attach_files(@reply, params[:attachments]) |
|
61 | attach_files(@reply, params[:attachments]) | |
62 | end |
|
62 | end | |
63 | redirect_to :action => 'show', :id => @topic |
|
63 | redirect_to :action => 'show', :id => @topic | |
64 | end |
|
64 | end | |
65 |
|
65 | |||
66 | # Edit a message |
|
66 | # Edit a message | |
67 | def edit |
|
67 | def edit | |
68 | if params[:message] && User.current.allowed_to?(:edit_messages, @project) |
|
68 | if params[:message] && User.current.allowed_to?(:edit_messages, @project) | |
69 | @message.locked = params[:message]['locked'] |
|
69 | @message.locked = params[:message]['locked'] | |
70 | @message.sticky = params[:message]['sticky'] |
|
70 | @message.sticky = params[:message]['sticky'] | |
71 | end |
|
71 | end | |
72 | if request.post? && @message.update_attributes(params[:message]) |
|
72 | if request.post? && @message.update_attributes(params[:message]) | |
73 | attach_files(@message, params[:attachments]) |
|
73 | attach_files(@message, params[:attachments]) | |
74 | flash[:notice] = l(:notice_successful_update) |
|
74 | flash[:notice] = l(:notice_successful_update) | |
75 | redirect_to :action => 'show', :id => @topic |
|
75 | redirect_to :action => 'show', :id => @topic | |
76 | end |
|
76 | end | |
77 | end |
|
77 | end | |
78 |
|
78 | |||
79 | # Delete a messages |
|
79 | # Delete a messages | |
80 | def destroy |
|
80 | def destroy | |
81 | @message.destroy |
|
81 | @message.destroy | |
82 | redirect_to @message.parent.nil? ? |
|
82 | redirect_to @message.parent.nil? ? | |
83 | { :controller => 'boards', :action => 'show', :project_id => @project, :id => @board } : |
|
83 | { :controller => 'boards', :action => 'show', :project_id => @project, :id => @board } : | |
84 | { :action => 'show', :id => @message.parent } |
|
84 | { :action => 'show', :id => @message.parent } | |
85 | end |
|
85 | end | |
86 |
|
86 | |||
87 | def quote |
|
87 | def quote | |
88 | user = @message.author |
|
88 | user = @message.author | |
89 | text = @message.content |
|
89 | text = @message.content | |
90 | content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> " |
|
90 | content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> " | |
91 | content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n" |
|
91 | content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n" | |
92 | render(:update) { |page| |
|
92 | render(:update) { |page| | |
93 | page.<< "$('message_content').value = \"#{content}\";" |
|
93 | page.<< "$('message_content').value = \"#{content}\";" | |
94 | page.show 'reply' |
|
94 | page.show 'reply' | |
95 | page << "Form.Element.focus('message_content');" |
|
95 | page << "Form.Element.focus('message_content');" | |
96 | page << "Element.scrollTo('reply');" |
|
96 | page << "Element.scrollTo('reply');" | |
97 | page << "$('message_content').scrollTop = $('message_content').scrollHeight - $('message_content').clientHeight;" |
|
97 | page << "$('message_content').scrollTop = $('message_content').scrollHeight - $('message_content').clientHeight;" | |
98 | } |
|
98 | } | |
99 | end |
|
99 | end | |
100 |
|
100 | |||
101 | def preview |
|
101 | def preview | |
102 | message = @board.messages.find_by_id(params[:id]) |
|
102 | message = @board.messages.find_by_id(params[:id]) | |
103 | @attachements = message.attachments if message |
|
103 | @attachements = message.attachments if message | |
104 | @text = (params[:message] || params[:reply])[:content] |
|
104 | @text = (params[:message] || params[:reply])[:content] | |
105 | render :partial => 'common/preview' |
|
105 | render :partial => 'common/preview' | |
106 | end |
|
106 | end | |
107 |
|
107 | |||
108 | private |
|
108 | private | |
109 | def find_message |
|
109 | def find_message | |
110 | find_board |
|
110 | find_board | |
111 | @message = @board.messages.find(params[:id], :include => :parent) |
|
111 | @message = @board.messages.find(params[:id], :include => :parent) | |
112 | @topic = @message.root |
|
112 | @topic = @message.root | |
113 | rescue ActiveRecord::RecordNotFound |
|
113 | rescue ActiveRecord::RecordNotFound | |
114 | render_404 |
|
114 | render_404 | |
115 | end |
|
115 | end | |
116 |
|
116 | |||
117 | def find_board |
|
117 | def find_board | |
118 | @board = Board.find(params[:board_id], :include => :project) |
|
118 | @board = Board.find(params[:board_id], :include => :project) | |
119 | @project = @board.project |
|
119 | @project = @board.project | |
120 | rescue ActiveRecord::RecordNotFound |
|
120 | rescue ActiveRecord::RecordNotFound | |
121 | render_404 |
|
121 | render_404 | |
122 | end |
|
122 | end | |
123 | end |
|
123 | end |
@@ -1,71 +1,80 | |||||
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 Message < ActiveRecord::Base |
|
18 | class Message < ActiveRecord::Base | |
19 | belongs_to :board |
|
19 | belongs_to :board | |
20 | belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' |
|
20 | belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' | |
21 | acts_as_tree :counter_cache => :replies_count, :order => "#{Message.table_name}.created_on ASC" |
|
21 | acts_as_tree :counter_cache => :replies_count, :order => "#{Message.table_name}.created_on ASC" | |
22 | has_many :attachments, :as => :container, :dependent => :destroy |
|
22 | has_many :attachments, :as => :container, :dependent => :destroy | |
23 | belongs_to :last_reply, :class_name => 'Message', :foreign_key => 'last_reply_id' |
|
23 | belongs_to :last_reply, :class_name => 'Message', :foreign_key => 'last_reply_id' | |
24 |
|
24 | |||
25 | acts_as_searchable :columns => ['subject', 'content'], |
|
25 | acts_as_searchable :columns => ['subject', 'content'], | |
26 | :include => {:board, :project}, |
|
26 | :include => {:board, :project}, | |
27 | :project_key => 'project_id', |
|
27 | :project_key => 'project_id', | |
28 | :date_column => "#{table_name}.created_on" |
|
28 | :date_column => "#{table_name}.created_on" | |
29 | acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"}, |
|
29 | acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"}, | |
30 | :description => :content, |
|
30 | :description => :content, | |
31 | :type => Proc.new {|o| o.parent_id.nil? ? 'message' : 'reply'}, |
|
31 | :type => Proc.new {|o| o.parent_id.nil? ? 'message' : 'reply'}, | |
32 | :url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id}.merge(o.parent_id.nil? ? {:id => o.id} : |
|
32 | :url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id}.merge(o.parent_id.nil? ? {:id => o.id} : | |
33 | {:id => o.parent_id, :anchor => "message-#{o.id}"})} |
|
33 | {:id => o.parent_id, :anchor => "message-#{o.id}"})} | |
34 |
|
34 | |||
35 | acts_as_activity_provider :find_options => {:include => [{:board => :project}, :author]} |
|
35 | acts_as_activity_provider :find_options => {:include => [{:board => :project}, :author]} | |
|
36 | acts_as_watchable | |||
36 |
|
37 | |||
37 | attr_protected :locked, :sticky |
|
38 | attr_protected :locked, :sticky | |
38 | validates_presence_of :subject, :content |
|
39 | validates_presence_of :subject, :content | |
39 | validates_length_of :subject, :maximum => 255 |
|
40 | validates_length_of :subject, :maximum => 255 | |
40 |
|
41 | |||
|
42 | after_create :add_author_as_watcher | |||
|
43 | ||||
41 | def validate_on_create |
|
44 | def validate_on_create | |
42 | # Can not reply to a locked topic |
|
45 | # Can not reply to a locked topic | |
43 | errors.add_to_base 'Topic is locked' if root.locked? && self != root |
|
46 | errors.add_to_base 'Topic is locked' if root.locked? && self != root | |
44 | end |
|
47 | end | |
45 |
|
48 | |||
46 | def after_create |
|
49 | def after_create | |
47 | board.update_attribute(:last_message_id, self.id) |
|
50 | board.update_attribute(:last_message_id, self.id) | |
48 | board.increment! :messages_count |
|
51 | board.increment! :messages_count | |
49 | if parent |
|
52 | if parent | |
50 | parent.reload.update_attribute(:last_reply_id, self.id) |
|
53 | parent.reload.update_attribute(:last_reply_id, self.id) | |
51 | else |
|
54 | else | |
52 | board.increment! :topics_count |
|
55 | board.increment! :topics_count | |
53 | end |
|
56 | end | |
54 | end |
|
57 | end | |
55 |
|
58 | |||
56 | def after_destroy |
|
59 | def after_destroy | |
57 | # The following line is required so that the previous counter |
|
60 | # The following line is required so that the previous counter | |
58 | # updates (due to children removal) are not overwritten |
|
61 | # updates (due to children removal) are not overwritten | |
59 | board.reload |
|
62 | board.reload | |
60 | board.decrement! :messages_count |
|
63 | board.decrement! :messages_count | |
61 | board.decrement! :topics_count unless parent |
|
64 | board.decrement! :topics_count unless parent | |
62 | end |
|
65 | end | |
63 |
|
66 | |||
64 | def sticky? |
|
67 | def sticky? | |
65 | sticky == 1 |
|
68 | sticky == 1 | |
66 | end |
|
69 | end | |
67 |
|
70 | |||
68 | def project |
|
71 | def project | |
69 | board.project |
|
72 | board.project | |
70 | end |
|
73 | end | |
|
74 | ||||
|
75 | private | |||
|
76 | ||||
|
77 | def add_author_as_watcher | |||
|
78 | Watcher.create(:watchable => self.root, :user => author) | |||
|
79 | end | |||
71 | end |
|
80 | end |
@@ -1,29 +1,30 | |||||
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 MessageObserver < ActiveRecord::Observer |
|
18 | class MessageObserver < ActiveRecord::Observer | |
19 | def after_create(message) |
|
19 | def after_create(message) | |
20 | # send notification to the authors of the thread |
|
20 | recipients = [] | |
21 | recipients = ([message.root] + message.root.children).collect {|m| m.author.mail if m.author && m.author.active?} |
|
21 | # send notification to the topic watchers | |
|
22 | recipients += message.root.watcher_recipients | |||
22 | # send notification to the board watchers |
|
23 | # send notification to the board watchers | |
23 | recipients += message.board.watcher_recipients |
|
24 | recipients += message.board.watcher_recipients | |
24 | # send notification to project members who want to be notified |
|
25 | # send notification to project members who want to be notified | |
25 | recipients += message.board.project.recipients |
|
26 | recipients += message.board.project.recipients | |
26 | recipients = recipients.compact.uniq |
|
27 | recipients = recipients.compact.uniq | |
27 | Mailer.deliver_message_posted(message, recipients) if !recipients.empty? && Setting.notified_events.include?('message_posted') |
|
28 | Mailer.deliver_message_posted(message, recipients) if !recipients.empty? && Setting.notified_events.include?('message_posted') | |
28 | end |
|
29 | end | |
29 | end |
|
30 | end |
@@ -1,60 +1,61 | |||||
1 | <%= breadcrumb link_to(l(:label_board_plural), {:controller => 'boards', :action => 'index', :project_id => @project}), |
|
1 | <%= breadcrumb link_to(l(:label_board_plural), {:controller => 'boards', :action => 'index', :project_id => @project}), | |
2 | link_to(h(@board.name), {:controller => 'boards', :action => 'show', :project_id => @project, :id => @board}) %> |
|
2 | link_to(h(@board.name), {:controller => 'boards', :action => 'show', :project_id => @project, :id => @board}) %> | |
3 |
|
3 | |||
4 | <div class="contextual"> |
|
4 | <div class="contextual"> | |
|
5 | <%= watcher_tag(@topic, User.current) %> | |||
5 | <%= link_to_remote_if_authorized l(:button_quote), { :url => {:action => 'quote', :id => @topic} }, :class => 'icon icon-comment' %> |
|
6 | <%= link_to_remote_if_authorized l(:button_quote), { :url => {:action => 'quote', :id => @topic} }, :class => 'icon icon-comment' %> | |
6 | <%= link_to_if_authorized l(:button_edit), {:action => 'edit', :id => @topic}, :class => 'icon icon-edit' %> |
|
7 | <%= link_to_if_authorized l(:button_edit), {:action => 'edit', :id => @topic}, :class => 'icon icon-edit' %> | |
7 | <%= link_to_if_authorized l(:button_delete), {:action => 'destroy', :id => @topic}, :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon icon-del' %> |
|
8 | <%= link_to_if_authorized l(:button_delete), {:action => 'destroy', :id => @topic}, :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon icon-del' %> | |
8 | </div> |
|
9 | </div> | |
9 |
|
10 | |||
10 | <h2><%=h @topic.subject %></h2> |
|
11 | <h2><%=h @topic.subject %></h2> | |
11 |
|
12 | |||
12 | <div class="message"> |
|
13 | <div class="message"> | |
13 | <p><span class="author"><%= authoring @topic.created_on, @topic.author %></span></p> |
|
14 | <p><span class="author"><%= authoring @topic.created_on, @topic.author %></span></p> | |
14 | <div class="wiki"> |
|
15 | <div class="wiki"> | |
15 | <%= textilizable(@topic.content, :attachments => @topic.attachments) %> |
|
16 | <%= textilizable(@topic.content, :attachments => @topic.attachments) %> | |
16 | </div> |
|
17 | </div> | |
17 | <%= link_to_attachments @topic.attachments, :no_author => true %> |
|
18 | <%= link_to_attachments @topic.attachments, :no_author => true %> | |
18 | </div> |
|
19 | </div> | |
19 | <br /> |
|
20 | <br /> | |
20 |
|
21 | |||
21 | <% unless @replies.empty? %> |
|
22 | <% unless @replies.empty? %> | |
22 | <h3 class="icon22 icon22-comment"><%= l(:label_reply_plural) %></h3> |
|
23 | <h3 class="icon22 icon22-comment"><%= l(:label_reply_plural) %></h3> | |
23 | <% @replies.each do |message| %> |
|
24 | <% @replies.each do |message| %> | |
24 | <a name="<%= "message-#{message.id}" %>"></a> |
|
25 | <a name="<%= "message-#{message.id}" %>"></a> | |
25 | <div class="contextual"> |
|
26 | <div class="contextual"> | |
26 | <%= link_to_remote_if_authorized image_tag('comment.png'), { :url => {:action => 'quote', :id => message} }, :title => l(:button_quote) %> |
|
27 | <%= link_to_remote_if_authorized image_tag('comment.png'), { :url => {:action => 'quote', :id => message} }, :title => l(:button_quote) %> | |
27 | <%= link_to_if_authorized image_tag('edit.png'), {:action => 'edit', :id => message}, :title => l(:button_edit) %> |
|
28 | <%= link_to_if_authorized image_tag('edit.png'), {:action => 'edit', :id => message}, :title => l(:button_edit) %> | |
28 | <%= link_to_if_authorized image_tag('delete.png'), {:action => 'destroy', :id => message}, :method => :post, :confirm => l(:text_are_you_sure), :title => l(:button_delete) %> |
|
29 | <%= link_to_if_authorized image_tag('delete.png'), {:action => 'destroy', :id => message}, :method => :post, :confirm => l(:text_are_you_sure), :title => l(:button_delete) %> | |
29 | </div> |
|
30 | </div> | |
30 | <div class="message reply"> |
|
31 | <div class="message reply"> | |
31 | <h4><%=h message.subject %> - <%= authoring message.created_on, message.author %></h4> |
|
32 | <h4><%=h message.subject %> - <%= authoring message.created_on, message.author %></h4> | |
32 | <div class="wiki"><%= textilizable message, :content, :attachments => message.attachments %></div> |
|
33 | <div class="wiki"><%= textilizable message, :content, :attachments => message.attachments %></div> | |
33 | <%= link_to_attachments message.attachments, :no_author => true %> |
|
34 | <%= link_to_attachments message.attachments, :no_author => true %> | |
34 | </div> |
|
35 | </div> | |
35 | <% end %> |
|
36 | <% end %> | |
36 | <% end %> |
|
37 | <% end %> | |
37 |
|
38 | |||
38 | <% if !@topic.locked? && authorize_for('messages', 'reply') %> |
|
39 | <% if !@topic.locked? && authorize_for('messages', 'reply') %> | |
39 | <p><%= toggle_link l(:button_reply), "reply", :focus => 'message_content' %></p> |
|
40 | <p><%= toggle_link l(:button_reply), "reply", :focus => 'message_content' %></p> | |
40 | <div id="reply" style="display:none;"> |
|
41 | <div id="reply" style="display:none;"> | |
41 | <% form_for :reply, @reply, :url => {:action => 'reply', :id => @topic}, :html => {:multipart => true, :id => 'message-form'} do |f| %> |
|
42 | <% form_for :reply, @reply, :url => {:action => 'reply', :id => @topic}, :html => {:multipart => true, :id => 'message-form'} do |f| %> | |
42 | <%= render :partial => 'form', :locals => {:f => f, :replying => true} %> |
|
43 | <%= render :partial => 'form', :locals => {:f => f, :replying => true} %> | |
43 | <%= submit_tag l(:button_submit) %> |
|
44 | <%= submit_tag l(:button_submit) %> | |
44 | <%= link_to_remote l(:label_preview), |
|
45 | <%= link_to_remote l(:label_preview), | |
45 | { :url => { :controller => 'messages', :action => 'preview', :board_id => @board }, |
|
46 | { :url => { :controller => 'messages', :action => 'preview', :board_id => @board }, | |
46 | :method => 'post', |
|
47 | :method => 'post', | |
47 | :update => 'preview', |
|
48 | :update => 'preview', | |
48 | :with => "Form.serialize('message-form')", |
|
49 | :with => "Form.serialize('message-form')", | |
49 | :complete => "Element.scrollTo('preview')" |
|
50 | :complete => "Element.scrollTo('preview')" | |
50 | }, :accesskey => accesskey(:preview) %> |
|
51 | }, :accesskey => accesskey(:preview) %> | |
51 | <% end %> |
|
52 | <% end %> | |
52 | <div id="preview" class="wiki"></div> |
|
53 | <div id="preview" class="wiki"></div> | |
53 | </div> |
|
54 | </div> | |
54 | <% end %> |
|
55 | <% end %> | |
55 |
|
56 | |||
56 | <% content_for :header_tags do %> |
|
57 | <% content_for :header_tags do %> | |
57 | <%= stylesheet_link_tag 'scm' %> |
|
58 | <%= stylesheet_link_tag 'scm' %> | |
58 | <% end %> |
|
59 | <% end %> | |
59 |
|
60 | |||
60 | <% html_title h(@topic.subject) %> |
|
61 | <% html_title h(@topic.subject) %> |
@@ -1,6 +1,10 | |||||
1 | --- |
|
1 | --- | |
2 | watchers_001: |
|
2 | watchers_001: | |
3 | watchable_type: Issue |
|
3 | watchable_type: Issue | |
4 | watchable_id: 2 |
|
4 | watchable_id: 2 | |
5 | user_id: 3 |
|
5 | user_id: 3 | |
|
6 | watchers_002: | |||
|
7 | watchable_type: Message | |||
|
8 | watchable_id: 1 | |||
|
9 | user_id: 1 | |||
6 | No newline at end of file |
|
10 |
@@ -1,70 +1,79 | |||||
1 | require File.dirname(__FILE__) + '/../test_helper' |
|
1 | require File.dirname(__FILE__) + '/../test_helper' | |
2 |
|
2 | |||
3 | class MessageTest < Test::Unit::TestCase |
|
3 | class MessageTest < Test::Unit::TestCase | |
4 | fixtures :projects, :boards, :messages |
|
4 | fixtures :projects, :boards, :messages, :users, :watchers | |
5 |
|
5 | |||
6 | def setup |
|
6 | def setup | |
7 | @board = Board.find(1) |
|
7 | @board = Board.find(1) | |
8 | @user = User.find(1) |
|
8 | @user = User.find(1) | |
9 | end |
|
9 | end | |
10 |
|
10 | |||
11 | def test_create |
|
11 | def test_create | |
12 | topics_count = @board.topics_count |
|
12 | topics_count = @board.topics_count | |
13 | messages_count = @board.messages_count |
|
13 | messages_count = @board.messages_count | |
14 |
|
14 | |||
15 | message = Message.new(:board => @board, :subject => 'Test message', :content => 'Test message content', :author => @user) |
|
15 | message = Message.new(:board => @board, :subject => 'Test message', :content => 'Test message content', :author => @user) | |
16 | assert message.save |
|
16 | assert message.save | |
17 | @board.reload |
|
17 | @board.reload | |
18 | # topics count incremented |
|
18 | # topics count incremented | |
19 | assert_equal topics_count+1, @board[:topics_count] |
|
19 | assert_equal topics_count+1, @board[:topics_count] | |
20 | # messages count incremented |
|
20 | # messages count incremented | |
21 | assert_equal messages_count+1, @board[:messages_count] |
|
21 | assert_equal messages_count+1, @board[:messages_count] | |
22 | assert_equal message, @board.last_message |
|
22 | assert_equal message, @board.last_message | |
|
23 | # author should be watching the message | |||
|
24 | assert message.watched_by?(@user) | |||
23 | end |
|
25 | end | |
24 |
|
26 | |||
25 | def test_reply |
|
27 | def test_reply | |
26 | topics_count = @board.topics_count |
|
28 | topics_count = @board.topics_count | |
27 | messages_count = @board.messages_count |
|
29 | messages_count = @board.messages_count | |
28 | @message = Message.find(1) |
|
30 | @message = Message.find(1) | |
29 | replies_count = @message.replies_count |
|
31 | replies_count = @message.replies_count | |
30 |
|
32 | |||
31 | reply = Message.new(:board => @board, :subject => 'Test reply', :content => 'Test reply content', :parent => @message, :author => @user) |
|
33 | reply_author = User.find(2) | |
|
34 | reply = Message.new(:board => @board, :subject => 'Test reply', :content => 'Test reply content', :parent => @message, :author => reply_author) | |||
32 | assert reply.save |
|
35 | assert reply.save | |
33 | @board.reload |
|
36 | @board.reload | |
34 | # same topics count |
|
37 | # same topics count | |
35 | assert_equal topics_count, @board[:topics_count] |
|
38 | assert_equal topics_count, @board[:topics_count] | |
36 | # messages count incremented |
|
39 | # messages count incremented | |
37 | assert_equal messages_count+1, @board[:messages_count] |
|
40 | assert_equal messages_count+1, @board[:messages_count] | |
38 | assert_equal reply, @board.last_message |
|
41 | assert_equal reply, @board.last_message | |
39 | @message.reload |
|
42 | @message.reload | |
40 | # replies count incremented |
|
43 | # replies count incremented | |
41 | assert_equal replies_count+1, @message[:replies_count] |
|
44 | assert_equal replies_count+1, @message[:replies_count] | |
42 | assert_equal reply, @message.last_reply |
|
45 | assert_equal reply, @message.last_reply | |
|
46 | # author should be watching the message | |||
|
47 | assert @message.watched_by?(reply_author) | |||
43 | end |
|
48 | end | |
44 |
|
49 | |||
45 | def test_destroy_topic |
|
50 | def test_destroy_topic | |
46 | message = Message.find(1) |
|
51 | message = Message.find(1) | |
47 | board = message.board |
|
52 | board = message.board | |
48 | topics_count, messages_count = board.topics_count, board.messages_count |
|
53 | topics_count, messages_count = board.topics_count, board.messages_count | |
49 | assert message.destroy |
|
54 | ||
|
55 | assert_difference('Watcher.count', -1) do | |||
|
56 | assert message.destroy | |||
|
57 | end | |||
50 | board.reload |
|
58 | board.reload | |
51 |
|
59 | |||
52 | # Replies deleted |
|
60 | # Replies deleted | |
53 | assert Message.find_all_by_parent_id(1).empty? |
|
61 | assert Message.find_all_by_parent_id(1).empty? | |
54 | # Checks counters |
|
62 | # Checks counters | |
55 | assert_equal topics_count - 1, board.topics_count |
|
63 | assert_equal topics_count - 1, board.topics_count | |
56 | assert_equal messages_count - 3, board.messages_count |
|
64 | assert_equal messages_count - 3, board.messages_count | |
|
65 | # Watchers removed | |||
57 | end |
|
66 | end | |
58 |
|
67 | |||
59 | def test_destroy_reply |
|
68 | def test_destroy_reply | |
60 | message = Message.find(5) |
|
69 | message = Message.find(5) | |
61 | board = message.board |
|
70 | board = message.board | |
62 | topics_count, messages_count = board.topics_count, board.messages_count |
|
71 | topics_count, messages_count = board.topics_count, board.messages_count | |
63 | assert message.destroy |
|
72 | assert message.destroy | |
64 | board.reload |
|
73 | board.reload | |
65 |
|
74 | |||
66 | # Checks counters |
|
75 | # Checks counters | |
67 | assert_equal topics_count, board.topics_count |
|
76 | assert_equal topics_count, board.topics_count | |
68 | assert_equal messages_count - 1, board.messages_count |
|
77 | assert_equal messages_count - 1, board.messages_count | |
69 | end |
|
78 | end | |
70 | end |
|
79 | end |
General Comments 0
You need to be logged in to leave comments.
Login now