]> source.dussan.org Git - redmine.git/commitdiff
- new controller "myController"
authorJean-Philippe Lang <jp_lang@yahoo.fr>
Sun, 3 Dec 2006 19:55:45 +0000 (19:55 +0000)
committerJean-Philippe Lang <jp_lang@yahoo.fr>
Sun, 3 Dec 2006 19:55:45 +0000 (19:55 +0000)
- account/my_page moved to my/page
- account/my_account mmoved to my/account
- "my page" is now customizable (table user_preferences added)

git-svn-id: http://redmine.rubyforge.org/svn/trunk@62 e93f8b46-1217-0410-a6f0-8f06a7374b81

32 files changed:
redmine/app/controllers/account_controller.rb
redmine/app/controllers/my_controller.rb [new file with mode: 0644]
redmine/app/helpers/my_helper.rb [new file with mode: 0644]
redmine/app/models/user.rb
redmine/app/models/user_preference.rb [new file with mode: 0644]
redmine/app/views/account/my_account.rhtml [deleted file]
redmine/app/views/account/my_page.rhtml [deleted file]
redmine/app/views/layouts/base.rhtml
redmine/app/views/my/_block.rhtml [new file with mode: 0644]
redmine/app/views/my/account.rhtml [new file with mode: 0644]
redmine/app/views/my/blocks/_calendar.rhtml [new file with mode: 0644]
redmine/app/views/my/blocks/_documents.rhtml [new file with mode: 0644]
redmine/app/views/my/blocks/_issues_assigned_to_me.rhtml [new file with mode: 0644]
redmine/app/views/my/blocks/_issues_reported_by_me.rhtml [new file with mode: 0644]
redmine/app/views/my/blocks/_latest_news.rhtml [new file with mode: 0644]
redmine/app/views/my/page.rhtml [new file with mode: 0644]
redmine/app/views/my/page_layout.rhtml [new file with mode: 0644]
redmine/db/migrate/008_create_user_preferences.rb [new file with mode: 0644]
redmine/doc/CHANGELOG
redmine/lang/de.yml
redmine/lang/en.yml
redmine/lang/es.yml
redmine/lang/fr.yml
redmine/public/images/close.png [new file with mode: 0644]
redmine/public/images/close_hl.png [new file with mode: 0644]
redmine/public/images/loading.gif [new file with mode: 0644]
redmine/public/stylesheets/application.css
redmine/test/fixtures/user_preferences.yml [new file with mode: 0644]
redmine/test/functional/my_controller_test.rb [new file with mode: 0644]
redmine/test/integration/account_test.rb
redmine/test/test_helper.rb
redmine/test/unit/user_preference_test.rb [new file with mode: 0644]

index 7031d71ec2fda5ea3bc69fafc40d1a99d61dfec5..ffd2419b3e315384f9f8d347e1f1c2f24fac82f4 100644 (file)
@@ -40,7 +40,7 @@ class AccountController < ApplicationController
       user = User.try_to_login(params[:login], params[:password])\r
       if user\r
         self.logged_in_user = user\r
-        redirect_back_or_default :controller => 'account', :action => 'my_page'\r
+        redirect_back_or_default :controller => 'my', :action => 'page'\r
       else\r
         flash.now[:notice] = l(:notice_account_invalid_creditentials)\r
       end\r
@@ -52,41 +52,6 @@ class AccountController < ApplicationController
     self.logged_in_user = nil\r
     redirect_to :controller => ''\r
   end\r
-\r
-  # Show logged in user's page\r
-  def my_page\r
-    @user = self.logged_in_user\r
-    @reported_issues = Issue.find(:all, :conditions => ["author_id=?", @user.id], :limit => 10, :include => [ :status, :project, :tracker ], :order => 'issues.updated_on DESC')\r
-    @assigned_issues = Issue.find(:all, :conditions => ["assigned_to_id=?", @user.id], :limit => 10, :include => [ :status, :project, :tracker ], :order => 'issues.updated_on DESC')\r
-  end\r
-\r
-  # Edit logged in user's account\r
-  def my_account\r
-    @user = self.logged_in_user\r
-    if request.post? and @user.update_attributes(@params[:user])\r
-      set_localization\r
-      flash.now[:notice] = l(:notice_account_updated)\r
-      self.logged_in_user.reload\r
-    end\r
-  end\r
-       \r
-  # Change logged in user's password\r
-  def change_password\r
-    @user = self.logged_in_user\r
-    flash[:notice] = l(:notice_can_t_change_password) and redirect_to :action => 'my_account' and return if @user.auth_source_id\r
-    if @user.check_password?(@params[:password])\r
-      @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]\r
-      if @user.save\r
-        flash[:notice] = l(:notice_account_password_updated)\r
-      else\r
-        render :action => 'my_account'\r
-        return\r
-      end\r
-    else\r
-      flash[:notice] = l(:notice_account_wrong_password)\r
-    end\r
-    redirect_to :action => 'my_account'\r
-  end\r
   \r
   # Enable user to choose a new password\r
   def lost_password\r
diff --git a/redmine/app/controllers/my_controller.rb b/redmine/app/controllers/my_controller.rb
new file mode 100644 (file)
index 0000000..25f362d
--- /dev/null
@@ -0,0 +1,128 @@
+# redMine - project management software
+# Copyright (C) 2006  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 MyController < ApplicationController
+  layout 'base'
+  before_filter :require_login
+
+  BLOCKS = { 'issues_assigned_to_me' => :label_assigned_to_me_issues,
+             'issues_reported_by_me' => :label_reported_issues,
+             'latest_news' => :label_news_latest,
+             'calendar' => :label_calendar,
+             'documents' => :label_document_plural
+           }.freeze
+
+  verify :xhr => true,
+         :session => :page_layout,
+         :only => [:add_block, :remove_block, :order_blocks]
+
+  def index
+    page
+    render :action => 'page'
+  end
+
+  # Show user's page
+  def page
+    @user = self.logged_in_user
+    @blocks = @user.pref[:my_page_layout] || { 'left' => ['issues_assigned_to_me'], 'right' => ['issues_reported_by_me'] }
+  end
+
+  # Edit user's account
+  def account
+    @user = self.logged_in_user
+    if request.post? and @user.update_attributes(@params[:user])
+      set_localization
+      flash.now[:notice] = l(:notice_account_updated)
+      self.logged_in_user.reload
+    end
+  end
+
+  # Change user's password
+  def change_password
+    @user = self.logged_in_user
+    flash[:notice] = l(:notice_can_t_change_password) and redirect_to :action => 'account' and return if @user.auth_source_id
+    if @user.check_password?(@params[:password])
+      @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
+      if @user.save
+        flash[:notice] = l(:notice_account_password_updated)
+      else
+        render :action => 'account'
+        return
+      end
+    else
+      flash[:notice] = l(:notice_account_wrong_password)
+    end
+    redirect_to :action => 'account'
+  end
+
+  # User's page layout configuration
+  def page_layout
+    @user = self.logged_in_user
+    @blocks = @user.pref[:my_page_layout] || { 'left' => ['issues_assigned_to_me'], 'right' => ['issues_reported_by_me'] }
+    session[:page_layout] = @blocks
+    %w(top left right).each {|f| session[:page_layout][f] ||= [] }
+    @block_options = []
+    BLOCKS.each {|k, v| @block_options << [l(v), k]}
+  end
+  
+  # Add a block to user's page
+  # The block is added on top of the page
+  # params[:block] : id of the block to add
+  def add_block
+    @user = self.logged_in_user
+    block = params[:block]
+    # remove if already present in a group
+    %w(top left right).each {|f| (session[:page_layout][f] ||= []).delete block }
+    # add it on top
+    session[:page_layout]['top'].unshift block
+    render :partial => "block", :locals => {:user => @user, :block_name => block}
+  end
+  
+  # Remove a block to user's page
+  # params[:block] : id of the block to remove
+  def remove_block
+    block = params[:block]
+    # remove block in all groups
+    %w(top left right).each {|f| (session[:page_layout][f] ||= []).delete block }
+    render :nothing => true
+  end
+
+  # Change blocks order on user's page
+  # params[:group] : group to order (top, left or right)
+  # params[:list-(top|left|right)] : array of block ids of the group
+  def order_blocks
+    group = params[:group]
+    group_items = params["list-#{group}"]
+    if group_items and group_items.is_a? Array
+      # remove group blocks if they are presents in other groups
+      %w(top left right).each {|f|
+        session[:page_layout][f] = (session[:page_layout][f] || []) - group_items
+      }
+      session[:page_layout][group] = group_items    
+    end
+    render :nothing => true
+  end
+  
+  # Save user's page layout  
+  def page_layout_save
+    @user = self.logged_in_user
+    @user.pref[:my_page_layout] = session[:page_layout] if session[:page_layout]
+    @user.pref.save
+    session[:page_layout] = nil
+    redirect_to :action => 'page'
+  end
+end
diff --git a/redmine/app/helpers/my_helper.rb b/redmine/app/helpers/my_helper.rb
new file mode 100644 (file)
index 0000000..9098f67
--- /dev/null
@@ -0,0 +1,19 @@
+# redMine - project management software
+# Copyright (C) 2006  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 MyHelper
+end
index 7f09ef550d14ac1f2ca6dc98465a5073159845f8..a82c98a88db68e71bf657c70a056206d8def737e 100644 (file)
@@ -19,7 +19,9 @@ require "digest/sha1"
 \r
 class User < ActiveRecord::Base\r
   has_many :memberships, :class_name => 'Member', :include => [ :project, :role ], :dependent => true\r
+  has_many :projects, :through => :memberships\r
   has_many :custom_values, :dependent => true, :as => :customized\r
+  has_one :preference, :dependent => true, :class_name => 'UserPreference'\r
   belongs_to :auth_source\r
   \r
   attr_accessor :password, :password_confirmation\r
@@ -114,6 +116,10 @@ class User < ActiveRecord::Base
       end\r
     @role_for_projects[project_id]\r
   end\r
+  \r
+  def pref\r
+    self.preference ||= UserPreference.new(:user => self)\r
+  end\r
        \r
 private\r
   # Return password digest\r
diff --git a/redmine/app/models/user_preference.rb b/redmine/app/models/user_preference.rb
new file mode 100644 (file)
index 0000000..5240c97
--- /dev/null
@@ -0,0 +1,44 @@
+# redMine - project management software
+# Copyright (C) 2006  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 UserPreference < ActiveRecord::Base
+  belongs_to :user
+  serialize :others, Hash
+  
+  attr_protected :others
+  
+  def initialize(attributes = nil)
+    super
+    self.others ||= {}
+  end
+  
+  def [](attr_name)
+    if attribute_present? attr_name
+      super
+    else
+      others[attr_name]
+    end
+  end
+  
+  def []=(attr_name, value)
+    if attribute_present? attr_name
+      super
+    else
+      others.store attr_name, value
+    end
+  end
+end
diff --git a/redmine/app/views/account/my_account.rhtml b/redmine/app/views/account/my_account.rhtml
deleted file mode 100644 (file)
index 9f561e8..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-<h2><%=l(:label_my_account)%></h2>
-\r
-<p><%=l(:field_login)%>: <strong><%= @user.login %></strong><br />\r
-<%=l(:field_created_on)%>: <%= format_time(@user.created_on) %>,\r
-<%=l(:field_updated_on)%>: <%= format_time(@user.updated_on) %></p>\r
-
-<%= error_messages_for 'user' %>
-\r
-  <div class="box">\r
-  <h3><%=l(:label_information_plural)%></h3>\r
-
-  <%= start_form_tag({:action => 'my_account'}, :class => "tabular") %>
-\r
-  <!--[form:user]-->
-  <p><label for="user_firstname"><%=l(:field_firstname)%> <span class="required">*</span></label>
-  <%= text_field 'user', 'firstname'  %></p>
-\r
-  <p><label for="user_lastname"><%=l(:field_lastname)%> <span class="required">*</span></label>
-  <%= text_field 'user', 'lastname'  %></p>
-
-  <p><label for="user_mail"><%=l(:field_mail)%> <span class="required">*</span></label>
-  <%= text_field 'user', 'mail', :size => 40 %></p>\r
-  \r
-  <p><label for="user_language"><%=l(:field_language)%></label>
-  <%= select("user", "language", lang_options_for_select) %></p>
-  <!--[eoform:user]-->\r
-\r
-  <p><label for="user_mail_notification"><%=l(:field_mail_notification)%></label>
-  <%= check_box 'user', 'mail_notification' %></p>\r
-
-  <center><%= submit_tag l(:button_save) %></center>
-  <%= end_form_tag %>\r
-  </div>\r
-\r
-
-<% unless @user.auth_source_id %>\r
-  <div class="box">\r
-  <h3><%=l(:field_password)%></h3>\r
-
-  <%= start_form_tag({:action => 'change_password'}, :class => "tabular") %>
-\r
-  <p><label for="password"><%=l(:field_password)%> <span class="required">*</span></label>
-  <%= password_field_tag 'password', nil, :size => 25 %></p>
-\r
-  <p><label for="new_password"><%=l(:field_new_password)%> <span class="required">*</span></label>
-  <%= password_field_tag 'new_password', nil, :size => 25 %></p>\r
-\r
-  <p><label for="new_password_confirmation"><%=l(:field_password_confirmation)%> <span class="required">*</span></label>
-  <%= password_field_tag 'new_password_confirmation', nil, :size => 25 %></p>\r
-
-  <center><%= submit_tag l(:button_save) %></center>
-  <%= end_form_tag %>\r
-  </div>
-<% end %>\r
diff --git a/redmine/app/views/account/my_page.rhtml b/redmine/app/views/account/my_page.rhtml
deleted file mode 100644 (file)
index 54b5685..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-<h2><%=l(:label_my_page)%></h2>\r
-\r
-<p>\r
-<% unless @user.last_before_login_on.nil? %>\r
-  <%=l(:label_last_login)%>: <%= format_time(@user.last_before_login_on) %>\r
-<% end %>\r
-</p>\r
-\r
-<div class="splitcontentleft">\r
-       <h3><%=l(:label_reported_issues)%></h3>\r
-       <%= render :partial => 'issues/list_simple', :locals => { :issues => @reported_issues } %>\r
-       <% if @reported_issues.length > 0 %>\r
-       <p><%=lwr(:label_last_updates, @reported_issues.length)%></p>\r
-       <% end %>\r
-</div>\r
-<div class="splitcontentright">\r
-<h3><%=l(:label_assigned_to_me_issues)%></h3>  \r
-       <%= render :partial => 'issues/list_simple', :locals => { :issues => @assigned_issues } %>\r
-       <% if @assigned_issues.length > 0 %>\r
-       <p><%=lwr(:label_last_updates, @assigned_issues.length)%></p>\r
-       <% end %>\r
-</div>
\ No newline at end of file
index 767f51f61a05286fb65a04b1284e96eda9e89061..951fa88f77b7191cf1b58eec919a73fe7991f6e9 100644 (file)
@@ -70,7 +70,7 @@ var menu_contenu=' \
        <div id="navigation">\r
                <ul>\r
        <li class="selected"><%= link_to l(:label_home), { :controller => '' }, :class => "picHome" %></li>\r
-       <li><%= link_to l(:label_my_page), { :controller => 'account', :action => 'my_page'}, :class => "picUserPage" %></li>\r
+       <li><%= link_to l(:label_my_page), { :controller => 'my', :action => 'page'}, :class => "picUserPage" %></li>\r
        <li><%= link_to l(:label_project_plural), { :controller => 'projects' }, :class => "picProject" %></li>\r
                        \r
     <% unless @project.nil? || @project.id.nil? %>\r
@@ -78,7 +78,7 @@ var menu_contenu=' \
     <% end %>\r
                        \r
     <% if loggedin? %>\r
-               <li><%= link_to l(:label_my_account), { :controller => 'account', :action => 'my_account' }, :class => "picUser" %></li>\r
+               <li><%= link_to l(:label_my_account), { :controller => 'my', :action => 'account' }, :class => "picUser" %></li>\r
     <% end %>\r
                        \r
        <% if admin_loggedin? %>\r
diff --git a/redmine/app/views/my/_block.rhtml b/redmine/app/views/my/_block.rhtml
new file mode 100644 (file)
index 0000000..3f72bda
--- /dev/null
@@ -0,0 +1,16 @@
+<div id="block_<%= block_name %>" class="mypage-box">\r
+\r
+    <div style="float:right;margin-right:16px;z-index:500;">\r
+    <%= link_to_remote "", {\r
+        :url => { :action => "remove_block", :block => block_name },\r
+        :complete => "removeBlock('block_#{block_name}')",\r
+        :loading => "Element.show('indicator')",\r
+        :loaded => "Element.hide('indicator')" },\r
+        :class => "close-icon"\r
+         %>        \r
+    </div>\r
+     \r
+    <div class="handle">\r
+       <%= render :partial => "my/blocks/#{block_name}", :locals => { :user => user } %>\r
+       </div>\r
+</div>
\ No newline at end of file
diff --git a/redmine/app/views/my/account.rhtml b/redmine/app/views/my/account.rhtml
new file mode 100644 (file)
index 0000000..3a2c5f3
--- /dev/null
@@ -0,0 +1,54 @@
+<h2><%=l(:label_my_account)%></h2>
+\r
+<p><%=l(:field_login)%>: <strong><%= @user.login %></strong><br />\r
+<%=l(:field_created_on)%>: <%= format_time(@user.created_on) %>,\r
+<%=l(:field_updated_on)%>: <%= format_time(@user.updated_on) %></p>\r
+
+<%= error_messages_for 'user' %>
+\r
+  <div class="box">\r
+  <h3><%=l(:label_information_plural)%></h3>\r
+
+  <%= start_form_tag({:action => 'account'}, :class => "tabular") %>
+\r
+  <!--[form:user]-->
+  <p><label for="user_firstname"><%=l(:field_firstname)%> <span class="required">*</span></label>
+  <%= text_field 'user', 'firstname'  %></p>
+\r
+  <p><label for="user_lastname"><%=l(:field_lastname)%> <span class="required">*</span></label>
+  <%= text_field 'user', 'lastname'  %></p>
+
+  <p><label for="user_mail"><%=l(:field_mail)%> <span class="required">*</span></label>
+  <%= text_field 'user', 'mail', :size => 40 %></p>\r
+  \r
+  <p><label for="user_language"><%=l(:field_language)%></label>
+  <%= select("user", "language", lang_options_for_select) %></p>
+  <!--[eoform:user]-->\r
+\r
+  <p><label for="user_mail_notification"><%=l(:field_mail_notification)%></label>
+  <%= check_box 'user', 'mail_notification' %></p>\r
+
+  <center><%= submit_tag l(:button_save) %></center>
+  <%= end_form_tag %>\r
+  </div>\r
+\r
+
+<% unless @user.auth_source_id %>\r
+  <div class="box">\r
+  <h3><%=l(:field_password)%></h3>\r
+
+  <%= start_form_tag({:action => 'change_password'}, :class => "tabular") %>
+\r
+  <p><label for="password"><%=l(:field_password)%> <span class="required">*</span></label>
+  <%= password_field_tag 'password', nil, :size => 25 %></p>
+\r
+  <p><label for="new_password"><%=l(:field_new_password)%> <span class="required">*</span></label>
+  <%= password_field_tag 'new_password', nil, :size => 25 %></p>\r
+\r
+  <p><label for="new_password_confirmation"><%=l(:field_password_confirmation)%> <span class="required">*</span></label>
+  <%= password_field_tag 'new_password_confirmation', nil, :size => 25 %></p>\r
+
+  <center><%= submit_tag l(:button_save) %></center>
+  <%= end_form_tag %>\r
+  </div>
+<% end %>\r
diff --git a/redmine/app/views/my/blocks/_calendar.rhtml b/redmine/app/views/my/blocks/_calendar.rhtml
new file mode 100644 (file)
index 0000000..2d7930f
--- /dev/null
@@ -0,0 +1,45 @@
+<h3><%= l(:label_calendar) %></h3>\r
+\r
+<%\r
+@date_from = Date.today - (Date.today.cwday-1)\r
+@date_to = Date.today + (7-Date.today.cwday)\r
+@issues = Issue.find :all,\r
+                     :conditions => ["issues.project_id in (#{@user.projects.collect{|m| m.id}.join(',')}) AND ((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?))", @date_from, @date_to, @date_from, @date_to],\r
+                     :include => [:project, :tracker]  unless @user.projects.empty?\r
+@issues ||= []\r
+%>\r
+\r
+<table class="calenderTable">\r
+<tr class="ListHead">\r
+<td></td>\r
+<% 1.upto(7) do |d| %>\r
+    <td align="center" width="14%"><%= day_name(d) %></td>\r
+<% end %>\r
+</tr>\r
+<tr height="100">\r
+<% day = @date_from\r
+while day <= @date_to\r
+       if day.cwday == 1 %>\r
+       <td valign="middle"><%= day.cweek %></td>\r
+       <% end %>       \r
+       <td valign="top" width="14%" class="<%= day.month==@month ? "even" : "odd" %>">\r
+       <p align="right"><%= day==Date.today ? "<b>#{day.day}</b>" : day.day %></p>     \r
+       <% day_issues = []\r
+       @issues.each { |i| day_issues << i if i.start_date == day or i.due_date == day } \r
+       day_issues.each do |i| %>       \r
+               <%= if day == i.start_date and day == i.due_date\r
+                   image_tag('arrow_bw')\r
+               elsif day == i.start_date\r
+                   image_tag('arrow_from') \r
+               elsif day == i.due_date\r
+                   image_tag('arrow_to') \r
+               end %>\r
+               <small><%= link_to "#{i.tracker.name} ##{i.id}", :controller => 'issues', :action => 'show', :id => i %>: <%= i.subject.sub(/^(.{30}[^\s]*\s).*$/, '\1 (...)') %></small><br />\r
+       <% end %>\r
+    </td>\r
+       <%= '</tr><tr height="100">' if day.cwday >= 7 and day!=@date_to %>\r
+       <%\r
+       day = day + 1\r
+end %>\r
+</tr>\r
+</table>
\ No newline at end of file
diff --git a/redmine/app/views/my/blocks/_documents.rhtml b/redmine/app/views/my/blocks/_documents.rhtml
new file mode 100644 (file)
index 0000000..5fa8c79
--- /dev/null
@@ -0,0 +1,15 @@
+<h3><%=l(:label_document_plural)%></h3>\r
+\r
+<ul>\r
+<% for document in Document.find :all,\r
+                         :limit => 10,\r
+                         :conditions => "documents.project_id in (#{@user.projects.collect{|m| m.id}.join(',')})",\r
+                         :include => [:project] %>\r
+       <li>\r
+       <b><%= link_to document.title, :controller => 'documents', :action => 'show', :id => document %></b>\r
+       <br />\r
+       <%= truncate document.description, 150 %><br />\r
+       <em><%= format_time(document.created_on) %></em><br />&nbsp;\r
+       </li>\r
+<% end unless @user.projects.empty? %>\r
+</ul>
\ No newline at end of file
diff --git a/redmine/app/views/my/blocks/_issues_assigned_to_me.rhtml b/redmine/app/views/my/blocks/_issues_assigned_to_me.rhtml
new file mode 100644 (file)
index 0000000..2a4e2a0
--- /dev/null
@@ -0,0 +1,10 @@
+<h3><%=l(:label_assigned_to_me_issues)%></h3>\r
+<% assigned_issues = Issue.find(:all, \r
+                                :conditions => ["assigned_to_id=?", user.id],\r
+                                :limit => 10, \r
+                                :include => [ :status, :project, :tracker ], \r
+                                :order => 'issues.updated_on DESC') %>\r
+<%= render :partial => 'issues/list_simple', :locals => { :issues => assigned_issues } %>\r
+<% if assigned_issues.length > 0 %>\r
+<p><%=lwr(:label_last_updates, assigned_issues.length)%></p>\r
+<% end %>\r
diff --git a/redmine/app/views/my/blocks/_issues_reported_by_me.rhtml b/redmine/app/views/my/blocks/_issues_reported_by_me.rhtml
new file mode 100644 (file)
index 0000000..9b40b36
--- /dev/null
@@ -0,0 +1,10 @@
+<h3><%=l(:label_reported_issues)%></h3>\r
+<% reported_issues = Issue.find(:all, \r
+                                :conditions => ["author_id=?", user.id],\r
+                                :limit => 10, \r
+                                :include => [ :status, :project, :tracker ], \r
+                                :order => 'issues.updated_on DESC') %>\r
+<%= render :partial => 'issues/list_simple', :locals => { :issues => reported_issues } %>\r
+<% if reported_issues.length > 0 %>\r
+<p><%=lwr(:label_last_updates, reported_issues.length)%></p>\r
+<% end %>
\ No newline at end of file
diff --git a/redmine/app/views/my/blocks/_latest_news.rhtml b/redmine/app/views/my/blocks/_latest_news.rhtml
new file mode 100644 (file)
index 0000000..85430ef
--- /dev/null
@@ -0,0 +1,13 @@
+<h3><%=l(:label_news_latest)%></h3>\r
+\r
+<ul>\r
+<% for news in News.find :all,\r
+                         :limit => 10,\r
+                         :conditions => "news.project_id in (#{@user.projects.collect{|m| m.id}.join(',')})",\r
+                         :include => [:project, :author] %>\r
+       <li><%= link_to news.title, :controller => 'news', :action => 'show', :id => news %><br />\r
+       <% unless news.summary.empty? %><%= news.summary %><br /><% end %>\r
+       <em><%= news.author.name %>, <%= format_time(news.created_on) %></em><br />&nbsp;\r
+       </li>\r
+<% end unless @user.projects.empty? %>\r
+</ul>
\ No newline at end of file
diff --git a/redmine/app/views/my/page.rhtml b/redmine/app/views/my/page.rhtml
new file mode 100644 (file)
index 0000000..121d48c
--- /dev/null
@@ -0,0 +1,30 @@
+<h2><%=l(:label_my_page)%></h2>\r
+\r
+<div class="topright">\r
+    <small><%= link_to l(:label_personalize_page), :action => 'page_layout' %></small>\r
+</div>\r
+\r
+<div id="list-top">\r
+       <% @blocks['top'].each do |b| %>\r
+       <div class="mypage-box">        \r
+               <%= render :partial => "my/blocks/#{b}", :locals => { :user => @user } %>\r
+       </div>\r
+       <% end if @blocks['top'] %>\r
+</div>\r
+\r
+<div id="list-left" class="splitcontentleft">\r
+       <% @blocks['left'].each do |b| %>\r
+       <div class="mypage-box">        \r
+               <%= render :partial => "my/blocks/#{b}", :locals => { :user => @user } %>\r
+       </div>\r
+       <% end if @blocks['left'] %>\r
+</div>\r
+\r
+<div id="list-right" class="splitcontentright">\r
+       <% @blocks['right'].each do |b| %>\r
+       <div class="mypage-box">        \r
+               <%= render :partial => "my/blocks/#{b}", :locals => { :user => @user } %>\r
+       </div>\r
+       <% end if @blocks['right'] %>\r
+</div>\r
+\r
diff --git a/redmine/app/views/my/page_layout.rhtml b/redmine/app/views/my/page_layout.rhtml
new file mode 100644 (file)
index 0000000..59a3856
--- /dev/null
@@ -0,0 +1,121 @@
+<script language="JavaScript">\r
+\r
+function recreateSortables() {\r
+    Sortable.destroy('list-top');\r
+    Sortable.destroy('list-left');\r
+    Sortable.destroy('list-right');\r
+    \r
+       Sortable.create("list-top", {constraint:false, containment:['list-top','list-left','list-right'], dropOnEmpty:true, handle:'handle', onUpdate:function(){new Ajax.Request('/my/order_blocks?group=top', {asynchronous:true, evalScripts:true, onComplete:function(request){new Effect.Highlight("list-top",{});}, onLoaded:function(request){Element.hide('indicator')}, onLoading:function(request){Element.show('indicator')}, parameters:Sortable.serialize("list-top")})}, only:'mypage-box', tag:'div'})\r
+       Sortable.create("list-left", {constraint:false, containment:['list-top','list-left','list-right'], dropOnEmpty:true, handle:'handle', onUpdate:function(){new Ajax.Request('/my/order_blocks?group=left', {asynchronous:true, evalScripts:true, onComplete:function(request){new Effect.Highlight("list-left",{});}, onLoaded:function(request){Element.hide('indicator')}, onLoading:function(request){Element.show('indicator')}, parameters:Sortable.serialize("list-left")})}, only:'mypage-box', tag:'div'})\r
+       Sortable.create("list-right", {constraint:false, containment:['list-top','list-left','list-right'], dropOnEmpty:true, handle:'handle', onUpdate:function(){new Ajax.Request('/my/order_blocks?group=right', {asynchronous:true, evalScripts:true, onComplete:function(request){new Effect.Highlight("list-right",{});}, onLoaded:function(request){Element.hide('indicator')}, onLoading:function(request){Element.show('indicator')}, parameters:Sortable.serialize("list-right")})}, only:'mypage-box', tag:'div'})\r
+}\r
+\r
+function updateSelect() {\r
+    s = $('block-select')\r
+    for (var i = 0; i < s.options.length; i++) {\r
+        if ($('block_' + s.options[i].value)) {\r
+            s.options[i].disabled = true;\r
+        } else {\r
+            s.options[i].disabled = false;\r
+        }\r
+    }\r
+    s.options[0].selected = true;\r
+}\r
+\r
+function afterAddBlock() {\r
+    recreateSortables();\r
+    updateSelect();\r
+}\r
+\r
+function removeBlock(block) {\r
+    $(block).parentNode.removeChild($(block));\r
+    updateSelect();\r
+}\r
+\r
+</script>\r
+\r
+<div style="float:right;">\r
+<%= start_form_tag({:action => "add_block"}, :id => "block-form") %>\r
+\r
+<%= select_tag 'block', "<option></option>" + options_for_select(@block_options), :id => "block-select", :class => "select-small" %>\r
+<small>\r
+<%= link_to_remote l(:button_add),\r
+            :url => { :action => "add_block" },\r
+            :with => "Form.serialize('block-form')",\r
+            :update => "list-top",\r
+            :position => :top,\r
+            :complete => "afterAddBlock();",\r
+            :loading => "Element.show('indicator')",\r
+            :loaded => "Element.hide('indicator')"\r
+             %>\r
+</small>\r
+<%= end_form_tag %> \r
+<small>|\r
+<%= link_to l(:button_save), :action => 'page_layout_save' %> |\r
+<%= link_to l(:button_cancel), :action => 'page' %>\r
+</small>\r
+</div>\r
+\r
+<div style="float:right;margin-right:20px;">\r
+<span id="indicator" style="display:none"><%= image_tag "loading.gif" %></span>\r
+</div>\r
+\r
+<h2><%=l(:label_my_page)%></h2>\r
+\r
+<div id="list-top" class="block-receiver">\r
+       <% @blocks['top'].each do |b| %>\r
+       <%= render :partial => 'block', :locals => {:user => @user, :block_name => b} %>\r
+       <% end if @blocks['top'] %>\r
+</div>\r
+\r
+<div id="list-left" class="splitcontentleft block-receiver">\r
+       <% @blocks['left'].each do |b| %>\r
+       <%= render :partial => 'block', :locals => {:user => @user, :block_name => b} %>\r
+       <% end if @blocks['left'] %>\r
+</div>\r
+\r
+<div id="list-right" class="splitcontentright block-receiver">\r
+       <% @blocks['right'].each do |b| %>\r
+       <%= render :partial => 'block', :locals => {:user => @user, :block_name => b} %>\r
+       <% end if @blocks['right'] %>\r
+</div>\r
+\r
+<%= sortable_element 'list-top',\r
+      :tag => 'div',\r
+      :only => 'mypage-box',\r
+      :handle => "handle",\r
+      :dropOnEmpty => true,\r
+      :containment => ['list-top', 'list-left', 'list-right'],\r
+      :constraint => false,\r
+      :complete => visual_effect(:highlight, 'list-top'), \r
+      :url => { :action => "order_blocks", :group => "top" },\r
+      :loading => "Element.show('indicator')",\r
+      :loaded => "Element.hide('indicator')"\r
+       %>\r
+      \r
+      \r
+<%= sortable_element 'list-left', \r
+      :tag => 'div',\r
+      :only => 'mypage-box',\r
+      :handle => "handle",\r
+      :dropOnEmpty => true,\r
+      :containment => ['list-top', 'list-left', 'list-right'],\r
+      :constraint => false,\r
+      :complete => visual_effect(:highlight, 'list-left'), \r
+      :url => { :action => "order_blocks", :group => "left" },\r
+      :loading => "Element.show('indicator')",\r
+      :loaded => "Element.hide('indicator')" %>\r
+      \r
+<%= sortable_element 'list-right', \r
+      :tag => 'div',\r
+      :only => 'mypage-box',\r
+      :handle => "handle",\r
+      :dropOnEmpty => true,\r
+      :containment => ['list-top', 'list-left', 'list-right'],\r
+      :constraint => false,\r
+      :complete => visual_effect(:highlight, 'list-right'), \r
+      :url => { :action => "order_blocks", :group => "right" },\r
+      :loading => "Element.show('indicator')",\r
+      :loaded => "Element.hide('indicator')" %>\r
+      \r
+<%= javascript_tag "updateSelect()" %>
\ No newline at end of file
diff --git a/redmine/db/migrate/008_create_user_preferences.rb b/redmine/db/migrate/008_create_user_preferences.rb
new file mode 100644 (file)
index 0000000..80ae1cd
--- /dev/null
@@ -0,0 +1,12 @@
+class CreateUserPreferences < ActiveRecord::Migration
+  def self.up
+    create_table :user_preferences do |t|
+      t.column "user_id", :integer, :default => 0, :null => false
+      t.column "others", :text
+    end
+  end
+
+  def self.down
+    drop_table :user_preferences
+  end
+end
index bbf8a4898867fa7fb719aa23f7819425ab119376..145d848c0df13f8ea07e77abccf974fe0c64b5a6 100644 (file)
@@ -7,6 +7,7 @@ http://redmine.org/
 \r
 == xx/xx/2006 v0.x.x\r
 \r
+* "my page" is now customizable \r
 * improved issues change history\r
 * new functionality: move an issue to another project or tracker\r
 * new functionality: add a note to an issue\r
index dfd29fde632e1e92dc9be64050616159dd15a364..98625237b1eeaee6e670131698e9de2c3f63417d 100644 (file)
@@ -257,6 +257,7 @@ label_gantt_chart: Gantt Diagramm
 label_internal: Intern\r
 label_last_changes: %d änderungen des Letzten\r
 label_change_view_all: Alle änderungen ansehen\r
+label_personalize_page: Diese Seite personifizieren\r
 \r
 button_login: Einloggen\r
 button_submit: Einreichen\r
@@ -278,6 +279,7 @@ button_list: Aufzulisten
 button_view: Siehe\r
 button_move: Bewegen\r
 button_back: Rückkehr\r
+button_cancel: Annullieren\r
 \r
 text_select_mail_notifications: Aktionen für die Mailbenachrichtigung aktiviert werden soll.\r
 text_regexp_info: eg. ^[A-Z0-9]+$\r
index d9dea7883295cf851eee718ea2ff950084d6ca10..acdd8c66f1ef3225cc92c84bec8d1694c3b4f389 100644 (file)
@@ -257,6 +257,7 @@ label_gantt_chart: Gantt chart
 label_internal: Internal\r
 label_last_changes: last %d changes\r
 label_change_view_all: View all changes\r
+label_personalize_page: Personalize this page\r
 \r
 button_login: Login\r
 button_submit: Submit\r
@@ -278,6 +279,7 @@ button_list: List
 button_view: View\r
 button_move: Move\r
 button_back: Back\r
+button_cancel: Cancel\r
 \r
 text_select_mail_notifications: Select actions for which mail notifications should be sent.\r
 text_regexp_info: eg. ^[A-Z0-9]+$\r
index f4e6c7682c12780972945c12ea056a37effbecaf..6cfb5c78f037201f8321e666a9703fb8f9fda50e 100644 (file)
@@ -257,6 +257,7 @@ label_gantt_chart: Diagrama de Gantt
 label_internal: Interno\r
 label_last_changes: %d cambios del último\r
 label_change_view_all: Ver todos los cambios\r
+label_personalize_page: Personalizar esta página\r
 \r
 button_login: Conexión\r
 button_submit: Someter\r
@@ -278,6 +279,7 @@ button_list: Listar
 button_view: Ver\r
 button_move: Mover\r
 button_back: Atrás\r
+button_cancel: Cancelar\r
 \r
 text_select_mail_notifications: Seleccionar las actividades que necesitan la activación de la notificación por mail.\r
 text_regexp_info: eg. ^[A-Z0-9]+$\r
index 9c5e484c6a0a7759dc363c85f7fe2ea386b1a411..5530b51524fc38e5f7fb0a14bf29f03a07a47a53 100644 (file)
@@ -258,10 +258,11 @@ label_gantt_chart: Diagramme de Gantt
 label_internal: Interne\r
 label_last_changes: %d derniers changements\r
 label_change_view_all: Voir tous les changements\r
+label_personalize_page: Personnaliser cette page\r
 \r
 button_login: Connexion\r
 button_submit: Soumettre\r
-button_save: Valider\r
+button_save: Sauvegarder\r
 button_check_all: Tout cocher\r
 button_uncheck_all: Tout décocher\r
 button_delete: Supprimer\r
@@ -279,6 +280,7 @@ button_list: Lister
 button_view: Voir\r
 button_move: Déplacer\r
 button_back: Retour\r
+button_cancel: Annuler\r
 \r
 text_select_mail_notifications: Sélectionner les actions pour lesquelles la notification par mail doit être activée.\r
 text_regexp_info: ex. ^[A-Z0-9]+$\r
diff --git a/redmine/public/images/close.png b/redmine/public/images/close.png
new file mode 100644 (file)
index 0000000..3501ed4
Binary files /dev/null and b/redmine/public/images/close.png differ
diff --git a/redmine/public/images/close_hl.png b/redmine/public/images/close_hl.png
new file mode 100644 (file)
index 0000000..a433f75
Binary files /dev/null and b/redmine/public/images/close_hl.png differ
diff --git a/redmine/public/images/loading.gif b/redmine/public/images/loading.gif
new file mode 100644 (file)
index 0000000..085ccae
Binary files /dev/null and b/redmine/public/images/loading.gif differ
index bd128a3a743c0c46c90bd3b3ba69b221472f0090..13c1f6e3435d8badb692603d5c9a64d3c68c166b 100644 (file)
@@ -379,6 +379,22 @@ color:#505050;
 line-height:1.5em;\r
 }\r
 \r
+a.close-icon {\r
+display:block;\r
+margin-top:3px;\r
+overflow:hidden;\r
+width:12px;\r
+height:12px;\r
+background-repeat: no-repeat;\r
+cursor:hand;\r
+cursor:pointer;\r
+background-image:url('../images/close.png');\r
+}\r
+\r
+a.close-icon:hover {\r
+background-image:url('../images/close_hl.png');\r
+}\r
+\r
 .rightbox{\r
 background: #fafbfc;\r
 border: 1px solid #c0c0c0;\r
@@ -388,6 +404,26 @@ position: relative;
 margin: 0 5px 5px;\r
 }\r
 \r
+.layout-active {\r
+background: #ECF3E1;\r
+}\r
+\r
+.block-receiver {\r
+border:1px dashed #c0c0c0;\r
+margin-bottom: 20px;\r
+padding: 15px 0 15px 0;\r
+}\r
+\r
+.mypage-box {\r
+margin:0 0 20px 0;\r
+color:#505050;\r
+line-height:1.5em;\r
+}\r
+\r
+.blocks {\r
+cursor: move;\r
+}\r
+\r
 .topright{\r
 position: absolute;\r
 right: 25px;\r
diff --git a/redmine/test/fixtures/user_preferences.yml b/redmine/test/fixtures/user_preferences.yml
new file mode 100644 (file)
index 0000000..8794d28
--- /dev/null
@@ -0,0 +1,5 @@
+# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+first:
+  id: 1
+another:
+  id: 2
diff --git a/redmine/test/functional/my_controller_test.rb b/redmine/test/functional/my_controller_test.rb
new file mode 100644 (file)
index 0000000..525c71b
--- /dev/null
@@ -0,0 +1,18 @@
+require File.dirname(__FILE__) + '/../test_helper'
+require 'my_controller'
+
+# Re-raise errors caught by the controller.
+class MyController; def rescue_action(e) raise e end; end
+
+class MyControllerTest < Test::Unit::TestCase
+  def setup
+    @controller = MyController.new
+    @request    = ActionController::TestRequest.new
+    @response   = ActionController::TestResponse.new
+  end
+
+  # Replace this with your real tests.
+  def test_truth
+    assert true
+  end
+end
index 88c75361d4832e45e1d71e3adada70eaa9174595..0d6f75d70250c475f204f01129ca98b88d6be931 100644 (file)
@@ -22,44 +22,44 @@ class AccountTest < ActionController::IntegrationTest
 
   # Replace this with your real tests.
   def test_login
-    get "account/my_page"
+    get "my/page"
     assert_redirected_to "account/login"
     log_user('jsmith', 'jsmith')
     
-    get "account/my_account"
+    get "my/account"
     assert_response :success
-    assert_template "account/my_account"    
+    assert_template "my/account"    
   end
   
   def test_change_password
     log_user('jsmith', 'jsmith')
-    get "account/my_account"
+    get "my/account"
     assert_response :success
-    assert_template "account/my_account" 
+    assert_template "my/account" 
     
-    post "account/change_password", :password => 'jsmith', :new_password => "hello", :new_password_confirmation => "hello2"
+    post "my/change_password", :password => 'jsmith', :new_password => "hello", :new_password_confirmation => "hello2"
     assert_response :success
-    assert_template "account/my_account" 
+    assert_template "my/account" 
     assert_tag :tag => "div", :attributes => { :class => "errorExplanation" }
 
-    post "account/change_password", :password => 'jsmithZZ', :new_password => "hello", :new_password_confirmation => "hello"
-    assert_redirected_to "account/my_account"
+    post "my/change_password", :password => 'jsmithZZ', :new_password => "hello", :new_password_confirmation => "hello"
+    assert_redirected_to "my/account"
     assert_equal 'Wrong password', flash[:notice]
         
-    post "account/change_password", :password => 'jsmith', :new_password => "hello", :new_password_confirmation => "hello"
-    assert_redirected_to "account/my_account"
+    post "my/change_password", :password => 'jsmith', :new_password => "hello", :new_password_confirmation => "hello"
+    assert_redirected_to "my/account"
     log_user('jsmith', 'hello')
   end
   
   def test_my_account
     log_user('jsmith', 'jsmith')
-    get "account/my_account"
+    get "my/account"
     assert_response :success
-    assert_template "account/my_account" 
+    assert_template "my/account" 
     
-    post "account/my_account", :user => {:firstname => "Joe", :login => "root", :admin => 1}
+    post "my/account", :user => {:firstname => "Joe", :login => "root", :admin => 1}
     assert_response :success
-    assert_template "account/my_account" 
+    assert_template "my/account" 
     user = User.find(2)
     assert_equal "Joe", user.firstname
     assert_equal "jsmith", user.login
@@ -68,9 +68,9 @@ class AccountTest < ActionController::IntegrationTest
   
   def test_my_page
     log_user('jsmith', 'jsmith')
-    get "account/my_page"
+    get "my/page"
     assert_response :success
-    assert_template "account/my_page"
+    assert_template "my/page"
   end
   
   def test_lost_password
index 3d9de8554e2026a0a6adaafa91da9bbaa7a92f0c..2e4f7dcd04def43802cdb0d4910c96e5c445c049 100644 (file)
@@ -49,7 +49,7 @@ class Test::Unit::TestCase
     assert_response :success
     assert_template "account/login"
     post "/account/login", :login => login, :password => password
-    assert_redirected_to "account/my_page"
+    assert_redirected_to "my/page"
     assert_equal login, User.find(session[:user_id]).login
   end
 end
diff --git a/redmine/test/unit/user_preference_test.rb b/redmine/test/unit/user_preference_test.rb
new file mode 100644 (file)
index 0000000..4675a26
--- /dev/null
@@ -0,0 +1,10 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class UserPreferenceTest < Test::Unit::TestCase
+  fixtures :user_preferences
+
+  # Replace this with your real tests.
+  def test_truth
+    assert true
+  end
+end