Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Per project forums added.

Permissions for forums management can be set in "Admin -> Roles & Permissions".
Forums can be created on the project settings screen ("Forums" tab).
Once a project has a forum, a "Forums" link appears in the project menu.
For now, posting messages in forums requires to be logged in. Files can be attached to messages.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@529 e93f8b46-1217-0410-a6f0-8f06a7374b81
  • Loading branch information...
commit b90e84b9fe252df464d084f0222c65367407a4ba 1 parent 75582f8
jplang jplang authored
Showing with 706 additions and 1 deletion.
  1. +87 −0 app/controllers/boards_controller.rb
  2. +66 −0 app/controllers/messages_controller.rb
  3. +5 −0 app/helpers/application_helper.rb
  4. +19 −0 app/helpers/boards_helper.rb
  5. +28 −0 app/helpers/messages_helper.rb
  6. +28 −0 app/models/board.rb
  7. +37 −0 app/models/message.rb
  8. +2 −1  app/models/permission.rb
  9. +1 −0  app/models/project.rb
  10. +8 −0 app/views/boards/_form.rhtml
  11. +6 −0 app/views/boards/edit.rhtml
  12. +30 −0 app/views/boards/index.rhtml
  13. +6 −0 app/views/boards/new.rhtml
  14. +36 −0 app/views/boards/show.rhtml
  15. +2 −0  app/views/layouts/base.rhtml
  16. +17 −0 app/views/messages/_form.rhtml
  17. +6 −0 app/views/messages/new.rhtml
  18. +29 −0 app/views/messages/show.rhtml
  19. +24 −0 app/views/projects/_boards.rhtml
  20. +5 −0 app/views/projects/settings.rhtml
  21. +2 −0  config/routes.rb
  22. +18 −0 db/migrate/045_create_boards.rb
  23. +21 −0 db/migrate/046_create_messages.rb
  24. +13 −0 db/migrate/047_add_boards_permissions.rb
  25. +9 −0 lang/bg.yml
  26. +9 −0 lang/de.yml
  27. +9 −0 lang/en.yml
  28. +9 −0 lang/es.yml
  29. +9 −0 lang/fr.yml
  30. +9 −0 lang/it.yml
  31. +9 −0 lang/ja.yml
  32. +9 −0 lang/pt-br.yml
  33. +9 −0 lang/pt.yml
  34. +9 −0 lang/zh.yml
  35. BIN  public/images/22x22/comment.png
  36. +2 −0  public/stylesheets/application.css
  37. +19 −0 test/fixtures/boards.yml
  38. +25 −0 test/fixtures/messages.yml
  39. +30 −0 test/unit/board_test.rb
  40. +44 −0 test/unit/message_test.rb
87 app/controllers/boards_controller.rb
View
@@ -0,0 +1,87 @@
+# redMine - project management software
+# Copyright (C) 2006-2007 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class BoardsController < ApplicationController
+ layout 'base'
+ before_filter :find_project
+ before_filter :authorize, :except => [:index, :show]
+ before_filter :check_project_privacy, :only => [:index, :show]
+
+ helper :messages
+ include MessagesHelper
+ helper :sort
+ include SortHelper
+
+ def index
+ @boards = @project.boards
+ # show the board if there is only one
+ if @boards.size == 1
+ @board = @boards.first
+ show
+ render :action => 'show'
+ end
+ end
+
+ def show
+ sort_init "#{Message.table_name}.updated_on", "desc"
+ sort_update
+
+ @topic_count = @board.topics.count
+ @topic_pages = Paginator.new self, @topic_count, 25, params['page']
+ @topics = @board.topics.find :all, :order => sort_clause,
+ :include => [:author, {:last_reply => :author}],
+ :limit => @topic_pages.items_per_page,
+ :offset => @topic_pages.current.offset
+ render :action => 'show', :layout => false if request.xhr?
+ end
+
+ verify :method => :post, :only => [ :destroy ], :redirect_to => { :action => :index }
+
+ def new
+ @board = Board.new(params[:board])
+ @board.project = @project
+ if request.post? && @board.save
+ flash[:notice] = l(:notice_successful_create)
+ redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
+ end
+ end
+
+ def edit
+ if request.post? && @board.update_attributes(params[:board])
+ case params[:position]
+ when 'highest'; @board.move_to_top
+ when 'higher'; @board.move_higher
+ when 'lower'; @board.move_lower
+ when 'lowest'; @board.move_to_bottom
+ end if params[:position]
+ redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
+ end
+ end
+
+ def destroy
+ @board.destroy
+ redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
+ end
+
+private
+ def find_project
+ @project = Project.find(params[:project_id])
+ @board = @project.boards.find(params[:id]) if params[:id]
+ rescue ActiveRecord::RecordNotFound
+ render_404
+ end
+end
66 app/controllers/messages_controller.rb
View
@@ -0,0 +1,66 @@
+# redMine - project management software
+# Copyright (C) 2006-2007 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class MessagesController < ApplicationController
+ layout 'base'
+ before_filter :find_project, :check_project_privacy
+ before_filter :require_login, :only => [:new, :reply]
+
+ verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show }
+
+ def show
+ @reply = Message.new(:subject => "RE: #{@message.subject}")
+ render :action => "show", :layout => false if request.xhr?
+ end
+
+ def new
+ @message = Message.new(params[:message])
+ @message.author = logged_in_user
+ @message.board = @board
+ if request.post? && @message.save
+ params[:attachments].each { |file|
+ next unless file.size > 0
+ Attachment.create(:container => @message, :file => file, :author => logged_in_user)
+ } if params[:attachments] and params[:attachments].is_a? Array
+ redirect_to :action => 'show', :id => @message
+ end
+ end
+
+ def reply
+ @reply = Message.new(params[:reply])
+ @reply.author = logged_in_user
+ @reply.board = @board
+ @message.children << @reply
+ redirect_to :action => 'show', :id => @message
+ end
+
+ def download
+ @attachment = @message.attachments.find(params[:attachment_id])
+ send_file @attachment.diskfile, :filename => @attachment.filename
+ rescue
+ render_404
+ end
+
+private
+ def find_project
+ @board = Board.find(params[:board_id], :include => :project)
+ @project = @board.project
+ @message = @board.topics.find(params[:id]) if params[:id]
+ rescue ActiveRecord::RecordNotFound
+ render_404
+ end
+end
5 app/helpers/application_helper.rb
View
@@ -215,6 +215,11 @@ def calendar_for(field_id)
image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
end
+
+ def wikitoolbar_for(field_id)
+ return '' unless Setting.text_formatting == 'textile'
+ javascript_include_tag('jstoolbar') + javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.draw();")
+ end
end
class TabularFormBuilder < ActionView::Helpers::FormBuilder
19 app/helpers/boards_helper.rb
View
@@ -0,0 +1,19 @@
+# redMine - project management software
+# Copyright (C) 2006-2007 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module BoardsHelper
+end
28 app/helpers/messages_helper.rb
View
@@ -0,0 +1,28 @@
+# redMine - project management software
+# Copyright (C) 2006-2007 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module MessagesHelper
+
+ def link_to_message(message)
+ return '' unless message
+ link_to h(truncate(message.subject, 60)), :controller => 'messages',
+ :action => 'show',
+ :board_id => message.board_id,
+ :id => message.root,
+ :anchor => (message.parent_id ? "message-#{message.id}" : nil)
+ end
+end
28 app/models/board.rb
View
@@ -0,0 +1,28 @@
+# redMine - project management software
+# Copyright (C) 2006-2007 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class Board < ActiveRecord::Base
+ belongs_to :project
+ has_many :topics, :class_name => 'Message', :conditions => "#{Message.table_name}.parent_id IS NULL", :order => "#{Message.table_name}.created_on DESC"
+ has_many :messages, :dependent => :delete_all, :order => "#{Message.table_name}.created_on DESC"
+ belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id
+ acts_as_list :scope => :project_id
+
+ validates_presence_of :name, :description
+ validates_length_of :name, :maximum => 30
+ validates_length_of :description, :maximum => 255
+end
37 app/models/message.rb
View
@@ -0,0 +1,37 @@
+# redMine - project management software
+# Copyright (C) 2006-2007 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class Message < ActiveRecord::Base
+ belongs_to :board
+ belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
+ acts_as_tree :counter_cache => :replies_count, :order => "#{Message.table_name}.created_on ASC"
+ has_many :attachments, :as => :container, :dependent => :destroy
+ belongs_to :last_reply, :class_name => 'Message', :foreign_key => 'last_reply_id'
+
+ validates_presence_of :subject, :content
+ validates_length_of :subject, :maximum => 255
+
+ def after_create
+ board.update_attribute(:last_message_id, self.id)
+ board.increment! :messages_count
+ if parent
+ parent.reload.update_attribute(:last_reply_id, self.id)
+ else
+ board.increment! :topics_count
+ end
+ end
+end
3  app/models/permission.rb
View
@@ -31,7 +31,8 @@ class Permission < ActiveRecord::Base
1200 => :label_document_plural,
1300 => :label_attachment_plural,
1400 => :label_repository,
- 1500 => :label_time_tracking
+ 1500 => :label_time_tracking,
+ 2000 => :label_board_plural
}.freeze
@@cached_perms_for_public = nil
1  app/models/project.rb
View
@@ -26,6 +26,7 @@ class Project < ActiveRecord::Base
has_many :documents, :dependent => :destroy
has_many :news, :dependent => :delete_all, :include => :author
has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
+ has_many :boards, :order => "position ASC"
has_one :repository, :dependent => :destroy
has_one :wiki, :dependent => :destroy
has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", :association_foreign_key => 'custom_field_id'
8 app/views/boards/_form.rhtml
View
@@ -0,0 +1,8 @@
+<%= error_messages_for 'board' %>
+
+<!--[form:board]-->
+<div class="box">
+<p><%= f.text_field :name, :required => true %></p>
+<p><%= f.text_field :description, :required => true, :size => 80 %></p>
+</div>
+<!--[eoform:board]-->
6 app/views/boards/edit.rhtml
View
@@ -0,0 +1,6 @@
+<h2><%= l(:label_board) %></h2>
+
+<% labelled_tabular_form_for :board, @board, :url => {:action => 'edit', :id => @board} do |f| %>
+ <%= render :partial => 'form', :locals => {:f => f} %>
+ <%= submit_tag l(:button_save) %>
+<% end %>
30 app/views/boards/index.rhtml
View
@@ -0,0 +1,30 @@
+<h2><%= l(:label_board_plural) %></h2>
+
+<table class="list">
+ <thead><tr>
+ <th><%= l(:label_board) %></th>
+ <th><%= l(:label_topic_plural) %></th>
+ <th><%= l(:label_message_plural) %></th>
+ <th><%= l(:label_message_last) %></th>
+ </tr></thead>
+ <tbody>
+<% for board in @boards %>
+ <tr class="<%= cycle 'odd', 'even' %>">
+ <td>
+ <%= link_to h(board.name), {:action => 'show', :id => board}, :class => "icon22 icon22-comment" %><br />
+ <%=h board.description %>
+ </td>
+ <td align="center"><%= board.topics_count %></td>
+ <td align="center"><%= board.messages_count %></td>
+ <td>
+ <small>
+ <% if board.last_message %>
+ <%= board.last_message.author.name %>, <%= format_time(board.last_message.created_on) %><br />
+ <%= link_to_message board.last_message %>
+ <% end %>
+ </small>
+ </td>
+ </tr>
+<% end %>
+ </tbody>
+</table>
6 app/views/boards/new.rhtml
View
@@ -0,0 +1,6 @@
+<h2><%= l(:label_board_new) %></h2>
+
+<% labelled_tabular_form_for :board, @board, :url => {:action => 'new'} do |f| %>
+ <%= render :partial => 'form', :locals => {:f => f} %>
+ <%= submit_tag l(:button_create) %>
+<% end %>
36 app/views/boards/show.rhtml
View
@@ -0,0 +1,36 @@
+<div class="contextual">
+<%= link_to l(:label_message_new), {:controller => 'messages', :action => 'new', :board_id => @board}, :class => "icon icon-add" %>
+</div>
+
+<h2><%=h @board.name %></h2>
+
+<table class="list">
+ <thead><tr>
+ <th><%= l(:field_subject) %></th>
+ <th><%= l(:field_author) %></th>
+ <%= sort_header_tag("#{Message.table_name}.created_on", :caption => l(:field_created_on)) %>
+ <th><%= l(:label_reply_plural) %></th>
+ <%= sort_header_tag("#{Message.table_name}.updated_on", :caption => l(:label_message_last)) %>
+ </tr></thead>
+ <tbody>
+ <% @topics.each do |topic| %>
+ <tr class="<%= cycle 'odd', 'even' %>">
+ <td><%= link_to h(topic.subject), :controller => 'messages', :action => 'show', :board_id => @board, :id => topic %></td>
+ <td align="center"><%= link_to_user topic.author %></td>
+ <td align="center"><%= format_time(topic.created_on) %></td>
+ <td align="center"><%= topic.replies_count %></td>
+ <td>
+ <small>
+ <% if topic.last_reply %>
+ <%= topic.last_reply.author.name %>, <%= format_time(topic.last_reply.created_on) %><br />
+ <%= link_to_message topic.last_reply %>
+ <% end %>
+ </small>
+ </td>
+ </tr>
+ <% end %>
+ </tbody>
+</table>
+
+<p><%= pagination_links_full @topic_pages %>
+[ <%= @topic_pages.current.first_item %> - <%= @topic_pages.current.last_item %> / <%= @topic_count %> ]</p>
2  app/views/layouts/base.rhtml
View
@@ -79,6 +79,7 @@
<%= link_to l(:label_roadmap), {:controller => 'projects', :action => 'roadmap', :id => @project }, :class => "menuItem" %>
<%= link_to l(:label_document_plural), {:controller => 'projects', :action => 'list_documents', :id => @project }, :class => "menuItem" %>
<%= link_to l(:label_wiki), {:controller => 'wiki', :id => @project, :page => nil }, :class => "menuItem" if @project.wiki and !@project.wiki.new_record? %>
+ <%= link_to l(:label_board_plural), {:controller => 'boards', :project_id => @project }, :class => "menuItem" unless @project.boards.empty? %>
<%= link_to l(:label_attachment_plural), {:controller => 'projects', :action => 'list_files', :id => @project }, :class => "menuItem" %>
<%= link_to l(:label_search), {:controller => 'search', :action => 'index', :id => @project }, :class => "menuItem" %>
<%= link_to l(:label_repository), {:controller => 'repositories', :action => 'show', :id => @project}, :class => "menuItem" if @project.repository and !@project.repository.new_record? %>
@@ -103,6 +104,7 @@
<li><%= link_to l(:label_roadmap), :controller => 'projects', :action => 'roadmap', :id => @project %></li>
<li><%= link_to l(:label_document_plural), :controller => 'projects', :action => 'list_documents', :id => @project %></li>
<%= content_tag("li", link_to(l(:label_wiki), :controller => 'wiki', :id => @project, :page => nil)) if @project.wiki and !@project.wiki.new_record? %>
+ <%= content_tag("li", link_to(l(:label_board_plural), :controller => 'boards', :project_id => @project)) unless @project.boards.empty? %>
<li><%= link_to l(:label_attachment_plural), :controller => 'projects', :action => 'list_files', :id => @project %></li>
<li><%= link_to l(:label_search), :controller => 'search', :action => 'index', :id => @project %></li>
<%= content_tag("li", link_to(l(:label_repository), :controller => 'repositories', :action => 'show', :id => @project)) if @project.repository and !@project.repository.new_record? %>
17 app/views/messages/_form.rhtml
View
@@ -0,0 +1,17 @@
+<%= error_messages_for 'message' %>
+
+<div class="box">
+<!--[form:message]-->
+<p><label><%= l(:field_subject) %></label><br />
+<%= f.text_field :subject, :required => true, :size => 80 %></p>
+
+<p><%= f.text_area :content, :required => true, :cols => 80, :rows => 15 %></p>
+<%= wikitoolbar_for 'message_content' %>
+<!--[eoform:message]-->
+
+<span class="tabular">
+<p id="attachments_p"><label><%=l(:label_attachment)%>
+<%= image_to_function "add.png", "addFileField();return false" %></label>
+<%= file_field_tag 'attachments[]', :size => 30 %> <em>(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)</em></p>
+</span>
+</div>
6 app/views/messages/new.rhtml
View
@@ -0,0 +1,6 @@
+<h2><%= link_to h(@board.name), :controller => 'boards', :action => 'show', :project_id => @project, :id => @board %> &#187; <%= l(:label_message_new) %></h2>
+
+<% form_for :message, @message, :url => {:action => 'new'}, :html => {:multipart => true} do |f| %>
+ <%= render :partial => 'form', :locals => {:f => f} %>
+ <%= submit_tag l(:button_create) %>
+<% end %>
29 app/views/messages/show.rhtml
View
@@ -0,0 +1,29 @@
+<h2><%= link_to h(@board.name), :controller => 'boards', :action => 'show', :project_id => @project, :id => @board %> &#187; <%=h @message.subject %></h2>
+
+<p><em><%= @message.author.name %>, <%= format_time(@message.created_on) %></em></p>
+<div class="wiki">
+<%= textilizable(@message.content) %>
+</div>
+<div class="attachments">
+<% @message.attachments.each do |attachment| %>
+<%= link_to attachment.filename, { :action => 'download', :id => @message, :attachment_id => attachment }, :class => 'icon icon-attachment' %>
+(<%= number_to_human_size(attachment.filesize) %>)<br />
+<% end %>
+</div>
+<br />
+<h3 class="icon22 icon22-comment"><%= l(:label_reply_plural) %></h3>
+<% @message.children.each do |message| %>
+ <a name="<%= "message-#{message.id}" %>"></a>
+ <h4><%=h message.subject %> - <%= message.author.name %>, <%= format_time(message.created_on) %></h4>
+ <div class="wiki"><p><%= textilizable message.content %></p></div>
+<% end %>
+
+<p><%= toggle_link l(:button_reply), "reply", :focus => "reply_content" %></p>
+<div id="reply" style="display:none;">
+<%= error_messages_for 'message' %>
+<% form_for :reply, @reply, :url => {:action => 'reply', :id => @message} do |f| %>
+ <p><%= f.text_field :subject, :required => true, :size => 60 %></p>
+ <p><%= f.text_area :content, :required => true, :cols => 80, :rows => 10 %></p>
+ <p><%= submit_tag l(:button_submit) %></p>
+<% end %>
+</div>
24 app/views/projects/_boards.rhtml
View
@@ -0,0 +1,24 @@
+<table class="list">
+ <thead><th><%= l(:label_board) %></th><th><%= l(:field_description) %></th><th style="width:15%"></th><th style="width:15%"></th><th style="width:15%"></th></thead>
+ <tbody>
+<% @project.boards.each do |board|
+ next if board.new_record? %>
+ <tr class="<%= cycle 'odd', 'even' %>">
+ <td><%=h board.name %></td>
+ <td><%=h board.description %></td>
+ <td align="center">
+ <% if authorize_for("boards", "edit") %>
+ <%= link_to image_tag('2uparrow.png', :alt => l(:label_sort_highest)), {:controller => 'boards', :action => 'edit', :project_id => @project, :id => board, :position => 'highest'}, :method => :post, :title => l(:label_sort_highest) %>
+ <%= link_to image_tag('1uparrow.png', :alt => l(:label_sort_higher)), {:controller => 'boards', :action => 'edit', :project_id => @project, :id => board, :position => 'higher'}, :method => :post, :title => l(:label_sort_higher) %> -
+ <%= link_to image_tag('1downarrow.png', :alt => l(:label_sort_lower)), {:controller => 'boards', :action => 'edit', :project_id => @project, :id => board, :position => 'lower'}, :method => :post, :title => l(:label_sort_lower) %>
+ <%= link_to image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), {:controller => 'boards', :action => 'edit', :project_id => @project, :id => board, :position => 'lowest'}, :method => :post, :title => l(:label_sort_lowest) %>
+ <% end %>
+ </td>
+ <td align="center"><small><%= link_to_if_authorized l(:button_edit), {:controller => 'boards', :action => 'edit', :project_id => @project, :id => board}, :class => 'icon icon-edit' %></small></td>
+ <td align="center"><small><%= link_to_if_authorized l(:button_delete), {:controller => 'boards', :action => 'destroy', :project_id => @project, :id => board}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %></small></td>
+ </tr>
+<% end %>
+ </tbody>
+</table>
+&nbsp;
+<p><%= link_to_if_authorized l(:label_board_new), {:controller => 'boards', :action => 'new', :project_id => @project} %></p>
5 app/views/projects/settings.rhtml
View
@@ -6,6 +6,7 @@
<li><%= link_to l(:label_member_plural), {}, :id=> "tab-members", :onclick => "showTab('members'); this.blur(); return false;" %></li>
<li><%= link_to l(:label_version_plural), {}, :id=> "tab-versions", :onclick => "showTab('versions'); this.blur(); return false;" %></li>
<li><%= link_to l(:label_issue_category_plural), {}, :id=> "tab-categories", :onclick => "showTab('categories'); this.blur(); return false;" %></li>
+<li><%= link_to l(:label_board_plural), {}, :id=> "tab-boards", :onclick => "showTab('boards'); this.blur(); return false;" %></li>
</ul>
</div>
@@ -76,5 +77,9 @@
<% end %>
</div>
+<div id="tab-content-boards" class="tab-content" style="display:none;">
+ <%= render :partial => 'boards' %>
+</div>
+
<%= tab = params[:tab] ? h(params[:tab]) : 'info'
javascript_tag "showTab('#{tab}');" %>
2  config/routes.rb
View
@@ -16,6 +16,8 @@
#map.connect ':controller/:action/:id/:sort_key/:sort_order'
map.connect 'issues/:issue_id/relations/:action/:id', :controller => 'issue_relations'
+ map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards'
+ map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages'
# Allow downloading Web Service WSDL as a file with an extension
# instead of a file named 'wsdl'
18 db/migrate/045_create_boards.rb
View
@@ -0,0 +1,18 @@
+class CreateBoards < ActiveRecord::Migration
+ def self.up
+ create_table :boards do |t|
+ t.column :project_id, :integer, :null => false
+ t.column :name, :string, :default => "", :null => false
+ t.column :description, :string
+ t.column :position, :integer, :default => 1, :null => false
+ t.column :topics_count, :integer, :default => 0, :null => false
+ t.column :messages_count, :integer, :default => 0, :null => false
+ t.column :last_message_id, :integer
+ end
+ add_index :boards, [:project_id], :name => :boards_project_id
+ end
+
+ def self.down
+ drop_table :boards
+ end
+end
21 db/migrate/046_create_messages.rb
View
@@ -0,0 +1,21 @@
+class CreateMessages < ActiveRecord::Migration
+ def self.up
+ create_table :messages do |t|
+ t.column :board_id, :integer, :null => false
+ t.column :parent_id, :integer
+ t.column :subject, :string, :default => "", :null => false
+ t.column :content, :text
+ t.column :author_id, :integer
+ t.column :replies_count, :integer, :default => 0, :null => false
+ t.column :last_reply_id, :integer
+ t.column :created_on, :datetime, :null => false
+ t.column :updated_on, :datetime, :null => false
+ end
+ add_index :messages, [:board_id], :name => :messages_board_id
+ add_index :messages, [:parent_id], :name => :messages_parent_id
+ end
+
+ def self.down
+ drop_table :messages
+ end
+end
13 db/migrate/047_add_boards_permissions.rb
View
@@ -0,0 +1,13 @@
+class AddBoardsPermissions < ActiveRecord::Migration
+ def self.up
+ Permission.create :controller => "boards", :action => "new", :description => "button_add", :sort => 2000, :is_public => false, :mail_option => 0, :mail_enabled => 0
+ Permission.create :controller => "boards", :action => "edit", :description => "button_edit", :sort => 2005, :is_public => false, :mail_option => 0, :mail_enabled => 0
+ Permission.create :controller => "boards", :action => "destroy", :description => "button_delete", :sort => 2010, :is_public => false, :mail_option => 0, :mail_enabled => 0
+ end
+
+ def self.down
+ Permission.find_by_controller_and_action("boards", "new").destroy
+ Permission.find_by_controller_and_action("boards", "edit").destroy
+ Permission.find_by_controller_and_action("boards", "destroy").destroy
+ end
+end
9 lang/bg.yml
View
@@ -385,6 +385,14 @@ label_stay_logged_in: Stay logged in
label_disabled: disabled
label_show_completed_versions: Show completed versions
label_me: me
+label_board: Forum
+label_board_new: New forum
+label_board_plural: Forums
+label_topic_plural: Topics
+label_message_plural: Messages
+label_message_last: Last message
+label_message_new: New message
+label_reply_plural: Replies
button_login: Вход
button_submit: Изпращане
@@ -413,6 +421,7 @@ button_log_time: Отделяне на време
button_rollback: Върни се към тази ревизия
button_watch: Наблюдавай
button_unwatch: Спри наблюдението
+button_reply: Reply
status_active: активен
status_registered: регистриран
9 lang/de.yml
View
@@ -385,6 +385,14 @@ label_stay_logged_in: Stay logged in
label_disabled: disabled
label_show_completed_versions: Show completed versions
label_me: me
+label_board: Forum
+label_board_new: New forum
+label_board_plural: Forums
+label_topic_plural: Topics
+label_message_plural: Messages
+label_message_last: Last message
+label_message_new: New message
+label_reply_plural: Replies
button_login: Einloggen
button_submit: OK
@@ -413,6 +421,7 @@ button_log_time: Log time
button_rollback: Rollback to this version
button_watch: Watch
button_unwatch: Unwatch
+button_reply: Reply
status_active: aktiv
status_registered: angemeldet
9 lang/en.yml
View
@@ -385,6 +385,14 @@ label_stay_logged_in: Stay logged in
label_disabled: disabled
label_show_completed_versions: Show completed versions
label_me: me
+label_board: Forum
+label_board_new: New forum
+label_board_plural: Forums
+label_topic_plural: Topics
+label_message_plural: Messages
+label_message_last: Last message
+label_message_new: New message
+label_reply_plural: Replies
button_login: Login
button_submit: Submit
@@ -413,6 +421,7 @@ button_log_time: Log time
button_rollback: Rollback to this version
button_watch: Watch
button_unwatch: Unwatch
+button_reply: Reply
status_active: active
status_registered: registered
9 lang/es.yml
View
@@ -385,6 +385,14 @@ label_stay_logged_in: Stay logged in
label_disabled: disabled
label_show_completed_versions: Show completed versions
label_me: me
+label_board: Forum
+label_board_new: New forum
+label_board_plural: Forums
+label_topic_plural: Topics
+label_message_plural: Messages
+label_message_last: Last message
+label_message_new: New message
+label_reply_plural: Replies
button_login: Conexión
button_submit: Someter
@@ -413,6 +421,7 @@ button_log_time: Log time
button_rollback: Rollback to this version
button_watch: Watch
button_unwatch: Unwatch
+button_reply: Reply
status_active: active
status_registered: registered
9 lang/fr.yml
View
@@ -385,6 +385,14 @@ label_stay_logged_in: Rester connecté
label_disabled: désactivé
label_show_completed_versions: Voire les versions passées
label_me: moi
+label_board: Forum
+label_board_new: Nouveau forum
+label_board_plural: Forums
+label_topic_plural: Discussions
+label_message_plural: Messages
+label_message_last: Dernier message
+label_message_new: Nouveau message
+label_reply_plural: Réponses
button_login: Connexion
button_submit: Soumettre
@@ -413,6 +421,7 @@ button_log_time: Saisir temps
button_rollback: Revenir à cette version
button_watch: Surveiller
button_unwatch: Ne plus surveiller
+button_reply: Répondre
status_active: actif
status_registered: enregistré
9 lang/it.yml
View
@@ -385,6 +385,14 @@ label_stay_logged_in: Stay logged in
label_disabled: disabled
label_show_completed_versions: Show completed versions
label_me: me
+label_board: Forum
+label_board_new: New forum
+label_board_plural: Forums
+label_topic_plural: Topics
+label_message_plural: Messages
+label_message_last: Last message
+label_message_new: New message
+label_reply_plural: Replies
button_login: Login
button_submit: Invia
@@ -413,6 +421,7 @@ button_log_time: Registra tempo
button_rollback: Ripristina questa versione
button_watch: Watch
button_unwatch: Unwatch
+button_reply: Reply
status_active: attivo
status_registered: registrato
9 lang/ja.yml
View
@@ -386,6 +386,14 @@ label_stay_logged_in: Stay logged in
label_disabled: disabled
label_show_completed_versions: Show completed versions
label_me: me
+label_board: Forum
+label_board_new: New forum
+label_board_plural: Forums
+label_topic_plural: Topics
+label_message_plural: Messages
+label_message_last: Last message
+label_message_new: New message
+label_reply_plural: Replies
button_login: ログイン
button_submit: 変更
@@ -414,6 +422,7 @@ button_log_time: 時間を記録
button_rollback: このバージョンにロールバック
button_watch: Watch
button_unwatch: Unwatch
+button_reply: Reply
status_active: 有効
status_registered: 登録
9 lang/pt-br.yml
View
@@ -385,6 +385,14 @@ label_stay_logged_in: Stay logged in
label_disabled: disabled
label_show_completed_versions: Show completed versions
label_me: me
+label_board: Forum
+label_board_new: New forum
+label_board_plural: Forums
+label_topic_plural: Topics
+label_message_plural: Messages
+label_message_last: Last message
+label_message_new: New message
+label_reply_plural: Replies
button_login: Login
button_submit: Enviar
@@ -413,6 +421,7 @@ button_log_time: Tempo de trabalho
button_rollback: Voltar para esta versao
button_watch: Watch
button_unwatch: Unwatch
+button_reply: Reply
status_active: ativo
status_registered: registrado
9 lang/pt.yml
View
@@ -385,6 +385,14 @@ label_stay_logged_in: Rester connecté
label_disabled: désactivé
label_show_completed_versions: Voire les versions passées
label_me: me
+label_board: Forum
+label_board_new: New forum
+label_board_plural: Forums
+label_topic_plural: Topics
+label_message_plural: Messages
+label_message_last: Last message
+label_message_new: New message
+label_reply_plural: Replies
button_login: Login
button_submit: Enviar
@@ -413,6 +421,7 @@ button_log_time: Tempo de trabalho
button_rollback: Voltar para esta versão
button_watch: Observar
button_unwatch: Não observar
+button_reply: Reply
status_active: ativo
status_registered: registrado
9 lang/zh.yml
View
@@ -388,6 +388,14 @@ label_stay_logged_in: Stay logged in
label_disabled: disabled
label_show_completed_versions: Show completed versions
label_me: me
+label_board: Forum
+label_board_new: New forum
+label_board_plural: Forums
+label_topic_plural: Topics
+label_message_plural: Messages
+label_message_last: Last message
+label_message_new: New message
+label_reply_plural: Replies
button_login: 登录
button_submit: 提交
@@ -416,6 +424,7 @@ button_log_time: 登记工时
button_rollback: Rollback to this version
button_watch: Watch
button_unwatch: Unwatch
+button_reply: Reply
status_active: 激活
status_registered: 已注册
BIN  public/images/22x22/comment.png
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2  public/stylesheets/application.css
View
@@ -475,6 +475,8 @@ position: relative;
margin: 0 5px 5px;
}
+div.attachments {padding-left: 6px; border-left: 2px solid #ccc;}
+
.overlay{
position: absolute;
margin-left:0;
19 test/fixtures/boards.yml
View
@@ -0,0 +1,19 @@
+---
+boards_001:
+ name: Help
+ project_id: 1
+ topics_count: 1
+ id: 1
+ description: Help board
+ position: 1
+ last_message_id: 2
+ messages_count: 2
+boards_002:
+ name: Discussion
+ project_id: 1
+ topics_count: 0
+ id: 2
+ description: Discussion board
+ position: 2
+ last_message_id:
+ messages_count: 0
25 test/fixtures/messages.yml
View
@@ -0,0 +1,25 @@
+---
+messages_001:
+ created_on: 2007-05-12 17:15:32 +02:00
+ updated_on: 2007-05-12 17:15:32 +02:00
+ subject: First post
+ id: 1
+ replies_count: 1
+ last_reply_id: 2
+ content: "This is the very first post\n\
+ in the forum"
+ author_id: 1
+ parent_id:
+ board_id: 1
+messages_002:
+ created_on: 2007-05-12 17:18:00 +02:00
+ updated_on: 2007-05-12 17:18:00 +02:00
+ subject: First reply
+ id: 2
+ replies_count: 0
+ last_reply_id:
+ content: "Reply to the first post"
+ author_id: 1
+ parent_id: 1
+ board_id: 1
+
30 test/unit/board_test.rb
View
@@ -0,0 +1,30 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class BoardTest < Test::Unit::TestCase
+ fixtures :projects, :boards, :messages
+
+ def setup
+ @project = Project.find(1)
+ end
+
+ def test_create
+ board = Board.new(:project => @project, :name => 'Test board', :description => 'Test board description')
+ assert board.save
+ board.reload
+ assert_equal 'Test board', board.name
+ assert_equal 'Test board description', board.description
+ assert_equal @project, board.project
+ assert_equal 0, board.topics_count
+ assert_equal 0, board.messages_count
+ assert_nil board.last_message
+ # last position
+ assert_equal @project.boards.size, board.position
+ end
+
+ def test_destroy
+ board = Board.find(1)
+ assert board.destroy
+ # make sure that the associated messages are removed
+ assert_equal 0, Message.count(:conditions => {:board_id => 1})
+ end
+end
44 test/unit/message_test.rb
View
@@ -0,0 +1,44 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class MessageTest < Test::Unit::TestCase
+ fixtures :projects, :boards, :messages
+
+ def setup
+ @board = Board.find(1)
+ @user = User.find(1)
+ end
+
+ def test_create
+ topics_count = @board.topics_count
+ messages_count = @board.messages_count
+
+ message = Message.new(:board => @board, :subject => 'Test message', :content => 'Test message content', :author => @user)
+ assert message.save
+ @board.reload
+ # topics count incremented
+ assert_equal topics_count+1, @board[:topics_count]
+ # messages count incremented
+ assert_equal messages_count+1, @board[:messages_count]
+ assert_equal message, @board.last_message
+ end
+
+ def test_reply
+ topics_count = @board.topics_count
+ messages_count = @board.messages_count
+ @message = Message.find(1)
+ replies_count = @message.replies_count
+
+ reply = Message.new(:board => @board, :subject => 'Test reply', :content => 'Test reply content', :parent => @message, :author => @user)
+ assert reply.save
+ @board.reload
+ # same topics count
+ assert_equal topics_count, @board[:topics_count]
+ # messages count incremented
+ assert_equal messages_count+1, @board[:messages_count]
+ assert_equal reply, @board.last_message
+ @message.reload
+ # replies count incremented
+ assert_equal replies_count+1, @message[:replies_count]
+ assert_equal reply, @message.last_reply
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.