@@ -0,0 +1,128 | |||||
|
1 | # redMine - project management software | |||
|
2 | # Copyright (C) 2006 Jean-Philippe Lang | |||
|
3 | # | |||
|
4 | # This program is free software; you can redistribute it and/or | |||
|
5 | # modify it under the terms of the GNU General Public License | |||
|
6 | # as published by the Free Software Foundation; either version 2 | |||
|
7 | # of the License, or (at your option) any later version. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
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 | |||
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
|
17 | ||||
|
18 | class MyController < ApplicationController | |||
|
19 | layout 'base' | |||
|
20 | before_filter :require_login | |||
|
21 | ||||
|
22 | BLOCKS = { 'issues_assigned_to_me' => :label_assigned_to_me_issues, | |||
|
23 | 'issues_reported_by_me' => :label_reported_issues, | |||
|
24 | 'latest_news' => :label_news_latest, | |||
|
25 | 'calendar' => :label_calendar, | |||
|
26 | 'documents' => :label_document_plural | |||
|
27 | }.freeze | |||
|
28 | ||||
|
29 | verify :xhr => true, | |||
|
30 | :session => :page_layout, | |||
|
31 | :only => [:add_block, :remove_block, :order_blocks] | |||
|
32 | ||||
|
33 | def index | |||
|
34 | page | |||
|
35 | render :action => 'page' | |||
|
36 | end | |||
|
37 | ||||
|
38 | # Show user's page | |||
|
39 | def page | |||
|
40 | @user = self.logged_in_user | |||
|
41 | @blocks = @user.pref[:my_page_layout] || { 'left' => ['issues_assigned_to_me'], 'right' => ['issues_reported_by_me'] } | |||
|
42 | end | |||
|
43 | ||||
|
44 | # Edit user's account | |||
|
45 | def account | |||
|
46 | @user = self.logged_in_user | |||
|
47 | if request.post? and @user.update_attributes(@params[:user]) | |||
|
48 | set_localization | |||
|
49 | flash.now[:notice] = l(:notice_account_updated) | |||
|
50 | self.logged_in_user.reload | |||
|
51 | end | |||
|
52 | end | |||
|
53 | ||||
|
54 | # Change user's password | |||
|
55 | def change_password | |||
|
56 | @user = self.logged_in_user | |||
|
57 | flash[:notice] = l(:notice_can_t_change_password) and redirect_to :action => 'account' and return if @user.auth_source_id | |||
|
58 | if @user.check_password?(@params[:password]) | |||
|
59 | @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation] | |||
|
60 | if @user.save | |||
|
61 | flash[:notice] = l(:notice_account_password_updated) | |||
|
62 | else | |||
|
63 | render :action => 'account' | |||
|
64 | return | |||
|
65 | end | |||
|
66 | else | |||
|
67 | flash[:notice] = l(:notice_account_wrong_password) | |||
|
68 | end | |||
|
69 | redirect_to :action => 'account' | |||
|
70 | end | |||
|
71 | ||||
|
72 | # User's page layout configuration | |||
|
73 | def page_layout | |||
|
74 | @user = self.logged_in_user | |||
|
75 | @blocks = @user.pref[:my_page_layout] || { 'left' => ['issues_assigned_to_me'], 'right' => ['issues_reported_by_me'] } | |||
|
76 | session[:page_layout] = @blocks | |||
|
77 | %w(top left right).each {|f| session[:page_layout][f] ||= [] } | |||
|
78 | @block_options = [] | |||
|
79 | BLOCKS.each {|k, v| @block_options << [l(v), k]} | |||
|
80 | end | |||
|
81 | ||||
|
82 | # Add a block to user's page | |||
|
83 | # The block is added on top of the page | |||
|
84 | # params[:block] : id of the block to add | |||
|
85 | def add_block | |||
|
86 | @user = self.logged_in_user | |||
|
87 | block = params[:block] | |||
|
88 | # remove if already present in a group | |||
|
89 | %w(top left right).each {|f| (session[:page_layout][f] ||= []).delete block } | |||
|
90 | # add it on top | |||
|
91 | session[:page_layout]['top'].unshift block | |||
|
92 | render :partial => "block", :locals => {:user => @user, :block_name => block} | |||
|
93 | end | |||
|
94 | ||||
|
95 | # Remove a block to user's page | |||
|
96 | # params[:block] : id of the block to remove | |||
|
97 | def remove_block | |||
|
98 | block = params[:block] | |||
|
99 | # remove block in all groups | |||
|
100 | %w(top left right).each {|f| (session[:page_layout][f] ||= []).delete block } | |||
|
101 | render :nothing => true | |||
|
102 | end | |||
|
103 | ||||
|
104 | # Change blocks order on user's page | |||
|
105 | # params[:group] : group to order (top, left or right) | |||
|
106 | # params[:list-(top|left|right)] : array of block ids of the group | |||
|
107 | def order_blocks | |||
|
108 | group = params[:group] | |||
|
109 | group_items = params["list-#{group}"] | |||
|
110 | if group_items and group_items.is_a? Array | |||
|
111 | # remove group blocks if they are presents in other groups | |||
|
112 | %w(top left right).each {|f| | |||
|
113 | session[:page_layout][f] = (session[:page_layout][f] || []) - group_items | |||
|
114 | } | |||
|
115 | session[:page_layout][group] = group_items | |||
|
116 | end | |||
|
117 | render :nothing => true | |||
|
118 | end | |||
|
119 | ||||
|
120 | # Save user's page layout | |||
|
121 | def page_layout_save | |||
|
122 | @user = self.logged_in_user | |||
|
123 | @user.pref[:my_page_layout] = session[:page_layout] if session[:page_layout] | |||
|
124 | @user.pref.save | |||
|
125 | session[:page_layout] = nil | |||
|
126 | redirect_to :action => 'page' | |||
|
127 | end | |||
|
128 | end |
@@ -0,0 +1,19 | |||||
|
1 | # redMine - project management software | |||
|
2 | # Copyright (C) 2006 Jean-Philippe Lang | |||
|
3 | # | |||
|
4 | # This program is free software; you can redistribute it and/or | |||
|
5 | # modify it under the terms of the GNU General Public License | |||
|
6 | # as published by the Free Software Foundation; either version 2 | |||
|
7 | # of the License, or (at your option) any later version. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
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 | |||
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
|
17 | ||||
|
18 | module MyHelper | |||
|
19 | end |
@@ -0,0 +1,44 | |||||
|
1 | # redMine - project management software | |||
|
2 | # Copyright (C) 2006 Jean-Philippe Lang | |||
|
3 | # | |||
|
4 | # This program is free software; you can redistribute it and/or | |||
|
5 | # modify it under the terms of the GNU General Public License | |||
|
6 | # as published by the Free Software Foundation; either version 2 | |||
|
7 | # of the License, or (at your option) any later version. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
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 | |||
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
|
17 | ||||
|
18 | class UserPreference < ActiveRecord::Base | |||
|
19 | belongs_to :user | |||
|
20 | serialize :others, Hash | |||
|
21 | ||||
|
22 | attr_protected :others | |||
|
23 | ||||
|
24 | def initialize(attributes = nil) | |||
|
25 | super | |||
|
26 | self.others ||= {} | |||
|
27 | end | |||
|
28 | ||||
|
29 | def [](attr_name) | |||
|
30 | if attribute_present? attr_name | |||
|
31 | super | |||
|
32 | else | |||
|
33 | others[attr_name] | |||
|
34 | end | |||
|
35 | end | |||
|
36 | ||||
|
37 | def []=(attr_name, value) | |||
|
38 | if attribute_present? attr_name | |||
|
39 | super | |||
|
40 | else | |||
|
41 | others.store attr_name, value | |||
|
42 | end | |||
|
43 | end | |||
|
44 | end |
@@ -0,0 +1,16 | |||||
|
1 | <div id="block_<%= block_name %>" class="mypage-box"> | |||
|
2 | ||||
|
3 | <div style="float:right;margin-right:16px;z-index:500;"> | |||
|
4 | <%= link_to_remote "", { | |||
|
5 | :url => { :action => "remove_block", :block => block_name }, | |||
|
6 | :complete => "removeBlock('block_#{block_name}')", | |||
|
7 | :loading => "Element.show('indicator')", | |||
|
8 | :loaded => "Element.hide('indicator')" }, | |||
|
9 | :class => "close-icon" | |||
|
10 | %> | |||
|
11 | </div> | |||
|
12 | ||||
|
13 | <div class="handle"> | |||
|
14 | <%= render :partial => "my/blocks/#{block_name}", :locals => { :user => user } %> | |||
|
15 | </div> | |||
|
16 | </div> No newline at end of file |
@@ -0,0 +1,45 | |||||
|
1 | <h3><%= l(:label_calendar) %></h3> | |||
|
2 | ||||
|
3 | <% | |||
|
4 | @date_from = Date.today - (Date.today.cwday-1) | |||
|
5 | @date_to = Date.today + (7-Date.today.cwday) | |||
|
6 | @issues = Issue.find :all, | |||
|
7 | :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], | |||
|
8 | :include => [:project, :tracker] unless @user.projects.empty? | |||
|
9 | @issues ||= [] | |||
|
10 | %> | |||
|
11 | ||||
|
12 | <table class="calenderTable"> | |||
|
13 | <tr class="ListHead"> | |||
|
14 | <td></td> | |||
|
15 | <% 1.upto(7) do |d| %> | |||
|
16 | <td align="center" width="14%"><%= day_name(d) %></td> | |||
|
17 | <% end %> | |||
|
18 | </tr> | |||
|
19 | <tr height="100"> | |||
|
20 | <% day = @date_from | |||
|
21 | while day <= @date_to | |||
|
22 | if day.cwday == 1 %> | |||
|
23 | <td valign="middle"><%= day.cweek %></td> | |||
|
24 | <% end %> | |||
|
25 | <td valign="top" width="14%" class="<%= day.month==@month ? "even" : "odd" %>"> | |||
|
26 | <p align="right"><%= day==Date.today ? "<b>#{day.day}</b>" : day.day %></p> | |||
|
27 | <% day_issues = [] | |||
|
28 | @issues.each { |i| day_issues << i if i.start_date == day or i.due_date == day } | |||
|
29 | day_issues.each do |i| %> | |||
|
30 | <%= if day == i.start_date and day == i.due_date | |||
|
31 | image_tag('arrow_bw') | |||
|
32 | elsif day == i.start_date | |||
|
33 | image_tag('arrow_from') | |||
|
34 | elsif day == i.due_date | |||
|
35 | image_tag('arrow_to') | |||
|
36 | end %> | |||
|
37 | <small><%= link_to "#{i.tracker.name} ##{i.id}", :controller => 'issues', :action => 'show', :id => i %>: <%= i.subject.sub(/^(.{30}[^\s]*\s).*$/, '\1 (...)') %></small><br /> | |||
|
38 | <% end %> | |||
|
39 | </td> | |||
|
40 | <%= '</tr><tr height="100">' if day.cwday >= 7 and day!=@date_to %> | |||
|
41 | <% | |||
|
42 | day = day + 1 | |||
|
43 | end %> | |||
|
44 | </tr> | |||
|
45 | </table> No newline at end of file |
@@ -0,0 +1,15 | |||||
|
1 | <h3><%=l(:label_document_plural)%></h3> | |||
|
2 | ||||
|
3 | <ul> | |||
|
4 | <% for document in Document.find :all, | |||
|
5 | :limit => 10, | |||
|
6 | :conditions => "documents.project_id in (#{@user.projects.collect{|m| m.id}.join(',')})", | |||
|
7 | :include => [:project] %> | |||
|
8 | <li> | |||
|
9 | <b><%= link_to document.title, :controller => 'documents', :action => 'show', :id => document %></b> | |||
|
10 | <br /> | |||
|
11 | <%= truncate document.description, 150 %><br /> | |||
|
12 | <em><%= format_time(document.created_on) %></em><br /> | |||
|
13 | </li> | |||
|
14 | <% end unless @user.projects.empty? %> | |||
|
15 | </ul> No newline at end of file |
@@ -0,0 +1,10 | |||||
|
1 | <h3><%=l(:label_assigned_to_me_issues)%></h3> | |||
|
2 | <% assigned_issues = Issue.find(:all, | |||
|
3 | :conditions => ["assigned_to_id=?", user.id], | |||
|
4 | :limit => 10, | |||
|
5 | :include => [ :status, :project, :tracker ], | |||
|
6 | :order => 'issues.updated_on DESC') %> | |||
|
7 | <%= render :partial => 'issues/list_simple', :locals => { :issues => assigned_issues } %> | |||
|
8 | <% if assigned_issues.length > 0 %> | |||
|
9 | <p><%=lwr(:label_last_updates, assigned_issues.length)%></p> | |||
|
10 | <% end %> |
@@ -0,0 +1,10 | |||||
|
1 | <h3><%=l(:label_reported_issues)%></h3> | |||
|
2 | <% reported_issues = Issue.find(:all, | |||
|
3 | :conditions => ["author_id=?", user.id], | |||
|
4 | :limit => 10, | |||
|
5 | :include => [ :status, :project, :tracker ], | |||
|
6 | :order => 'issues.updated_on DESC') %> | |||
|
7 | <%= render :partial => 'issues/list_simple', :locals => { :issues => reported_issues } %> | |||
|
8 | <% if reported_issues.length > 0 %> | |||
|
9 | <p><%=lwr(:label_last_updates, reported_issues.length)%></p> | |||
|
10 | <% end %> No newline at end of file |
@@ -0,0 +1,13 | |||||
|
1 | <h3><%=l(:label_news_latest)%></h3> | |||
|
2 | ||||
|
3 | <ul> | |||
|
4 | <% for news in News.find :all, | |||
|
5 | :limit => 10, | |||
|
6 | :conditions => "news.project_id in (#{@user.projects.collect{|m| m.id}.join(',')})", | |||
|
7 | :include => [:project, :author] %> | |||
|
8 | <li><%= link_to news.title, :controller => 'news', :action => 'show', :id => news %><br /> | |||
|
9 | <% unless news.summary.empty? %><%= news.summary %><br /><% end %> | |||
|
10 | <em><%= news.author.name %>, <%= format_time(news.created_on) %></em><br /> | |||
|
11 | </li> | |||
|
12 | <% end unless @user.projects.empty? %> | |||
|
13 | </ul> No newline at end of file |
@@ -0,0 +1,30 | |||||
|
1 | <h2><%=l(:label_my_page)%></h2> | |||
|
2 | ||||
|
3 | <div class="topright"> | |||
|
4 | <small><%= link_to l(:label_personalize_page), :action => 'page_layout' %></small> | |||
|
5 | </div> | |||
|
6 | ||||
|
7 | <div id="list-top"> | |||
|
8 | <% @blocks['top'].each do |b| %> | |||
|
9 | <div class="mypage-box"> | |||
|
10 | <%= render :partial => "my/blocks/#{b}", :locals => { :user => @user } %> | |||
|
11 | </div> | |||
|
12 | <% end if @blocks['top'] %> | |||
|
13 | </div> | |||
|
14 | ||||
|
15 | <div id="list-left" class="splitcontentleft"> | |||
|
16 | <% @blocks['left'].each do |b| %> | |||
|
17 | <div class="mypage-box"> | |||
|
18 | <%= render :partial => "my/blocks/#{b}", :locals => { :user => @user } %> | |||
|
19 | </div> | |||
|
20 | <% end if @blocks['left'] %> | |||
|
21 | </div> | |||
|
22 | ||||
|
23 | <div id="list-right" class="splitcontentright"> | |||
|
24 | <% @blocks['right'].each do |b| %> | |||
|
25 | <div class="mypage-box"> | |||
|
26 | <%= render :partial => "my/blocks/#{b}", :locals => { :user => @user } %> | |||
|
27 | </div> | |||
|
28 | <% end if @blocks['right'] %> | |||
|
29 | </div> | |||
|
30 |
@@ -0,0 +1,121 | |||||
|
1 | <script language="JavaScript"> | |||
|
2 | ||||
|
3 | function recreateSortables() { | |||
|
4 | Sortable.destroy('list-top'); | |||
|
5 | Sortable.destroy('list-left'); | |||
|
6 | Sortable.destroy('list-right'); | |||
|
7 | ||||
|
8 | 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'}) | |||
|
9 | 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'}) | |||
|
10 | 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'}) | |||
|
11 | } | |||
|
12 | ||||
|
13 | function updateSelect() { | |||
|
14 | s = $('block-select') | |||
|
15 | for (var i = 0; i < s.options.length; i++) { | |||
|
16 | if ($('block_' + s.options[i].value)) { | |||
|
17 | s.options[i].disabled = true; | |||
|
18 | } else { | |||
|
19 | s.options[i].disabled = false; | |||
|
20 | } | |||
|
21 | } | |||
|
22 | s.options[0].selected = true; | |||
|
23 | } | |||
|
24 | ||||
|
25 | function afterAddBlock() { | |||
|
26 | recreateSortables(); | |||
|
27 | updateSelect(); | |||
|
28 | } | |||
|
29 | ||||
|
30 | function removeBlock(block) { | |||
|
31 | $(block).parentNode.removeChild($(block)); | |||
|
32 | updateSelect(); | |||
|
33 | } | |||
|
34 | ||||
|
35 | </script> | |||
|
36 | ||||
|
37 | <div style="float:right;"> | |||
|
38 | <%= start_form_tag({:action => "add_block"}, :id => "block-form") %> | |||
|
39 | ||||
|
40 | <%= select_tag 'block', "<option></option>" + options_for_select(@block_options), :id => "block-select", :class => "select-small" %> | |||
|
41 | <small> | |||
|
42 | <%= link_to_remote l(:button_add), | |||
|
43 | :url => { :action => "add_block" }, | |||
|
44 | :with => "Form.serialize('block-form')", | |||
|
45 | :update => "list-top", | |||
|
46 | :position => :top, | |||
|
47 | :complete => "afterAddBlock();", | |||
|
48 | :loading => "Element.show('indicator')", | |||
|
49 | :loaded => "Element.hide('indicator')" | |||
|
50 | %> | |||
|
51 | </small> | |||
|
52 | <%= end_form_tag %> | |||
|
53 | <small>| | |||
|
54 | <%= link_to l(:button_save), :action => 'page_layout_save' %> | | |||
|
55 | <%= link_to l(:button_cancel), :action => 'page' %> | |||
|
56 | </small> | |||
|
57 | </div> | |||
|
58 | ||||
|
59 | <div style="float:right;margin-right:20px;"> | |||
|
60 | <span id="indicator" style="display:none"><%= image_tag "loading.gif" %></span> | |||
|
61 | </div> | |||
|
62 | ||||
|
63 | <h2><%=l(:label_my_page)%></h2> | |||
|
64 | ||||
|
65 | <div id="list-top" class="block-receiver"> | |||
|
66 | <% @blocks['top'].each do |b| %> | |||
|
67 | <%= render :partial => 'block', :locals => {:user => @user, :block_name => b} %> | |||
|
68 | <% end if @blocks['top'] %> | |||
|
69 | </div> | |||
|
70 | ||||
|
71 | <div id="list-left" class="splitcontentleft block-receiver"> | |||
|
72 | <% @blocks['left'].each do |b| %> | |||
|
73 | <%= render :partial => 'block', :locals => {:user => @user, :block_name => b} %> | |||
|
74 | <% end if @blocks['left'] %> | |||
|
75 | </div> | |||
|
76 | ||||
|
77 | <div id="list-right" class="splitcontentright block-receiver"> | |||
|
78 | <% @blocks['right'].each do |b| %> | |||
|
79 | <%= render :partial => 'block', :locals => {:user => @user, :block_name => b} %> | |||
|
80 | <% end if @blocks['right'] %> | |||
|
81 | </div> | |||
|
82 | ||||
|
83 | <%= sortable_element 'list-top', | |||
|
84 | :tag => 'div', | |||
|
85 | :only => 'mypage-box', | |||
|
86 | :handle => "handle", | |||
|
87 | :dropOnEmpty => true, | |||
|
88 | :containment => ['list-top', 'list-left', 'list-right'], | |||
|
89 | :constraint => false, | |||
|
90 | :complete => visual_effect(:highlight, 'list-top'), | |||
|
91 | :url => { :action => "order_blocks", :group => "top" }, | |||
|
92 | :loading => "Element.show('indicator')", | |||
|
93 | :loaded => "Element.hide('indicator')" | |||
|
94 | %> | |||
|
95 | ||||
|
96 | ||||
|
97 | <%= sortable_element 'list-left', | |||
|
98 | :tag => 'div', | |||
|
99 | :only => 'mypage-box', | |||
|
100 | :handle => "handle", | |||
|
101 | :dropOnEmpty => true, | |||
|
102 | :containment => ['list-top', 'list-left', 'list-right'], | |||
|
103 | :constraint => false, | |||
|
104 | :complete => visual_effect(:highlight, 'list-left'), | |||
|
105 | :url => { :action => "order_blocks", :group => "left" }, | |||
|
106 | :loading => "Element.show('indicator')", | |||
|
107 | :loaded => "Element.hide('indicator')" %> | |||
|
108 | ||||
|
109 | <%= sortable_element 'list-right', | |||
|
110 | :tag => 'div', | |||
|
111 | :only => 'mypage-box', | |||
|
112 | :handle => "handle", | |||
|
113 | :dropOnEmpty => true, | |||
|
114 | :containment => ['list-top', 'list-left', 'list-right'], | |||
|
115 | :constraint => false, | |||
|
116 | :complete => visual_effect(:highlight, 'list-right'), | |||
|
117 | :url => { :action => "order_blocks", :group => "right" }, | |||
|
118 | :loading => "Element.show('indicator')", | |||
|
119 | :loaded => "Element.hide('indicator')" %> | |||
|
120 | ||||
|
121 | <%= javascript_tag "updateSelect()" %> No newline at end of file |
@@ -0,0 +1,12 | |||||
|
1 | class CreateUserPreferences < ActiveRecord::Migration | |||
|
2 | def self.up | |||
|
3 | create_table :user_preferences do |t| | |||
|
4 | t.column "user_id", :integer, :default => 0, :null => false | |||
|
5 | t.column "others", :text | |||
|
6 | end | |||
|
7 | end | |||
|
8 | ||||
|
9 | def self.down | |||
|
10 | drop_table :user_preferences | |||
|
11 | end | |||
|
12 | end |
1 | NO CONTENT: new file 100644, binary diff hidden |
|
NO CONTENT: new file 100644, binary diff hidden |
1 | NO CONTENT: new file 100644, binary diff hidden |
|
NO CONTENT: new file 100644, binary diff hidden |
1 | NO CONTENT: new file 100644, binary diff hidden |
|
NO CONTENT: new file 100644, binary diff hidden |
@@ -0,0 +1,5 | |||||
|
1 | # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html | |||
|
2 | first: | |||
|
3 | id: 1 | |||
|
4 | another: | |||
|
5 | id: 2 |
@@ -0,0 +1,18 | |||||
|
1 | require File.dirname(__FILE__) + '/../test_helper' | |||
|
2 | require 'my_controller' | |||
|
3 | ||||
|
4 | # Re-raise errors caught by the controller. | |||
|
5 | class MyController; def rescue_action(e) raise e end; end | |||
|
6 | ||||
|
7 | class MyControllerTest < Test::Unit::TestCase | |||
|
8 | def setup | |||
|
9 | @controller = MyController.new | |||
|
10 | @request = ActionController::TestRequest.new | |||
|
11 | @response = ActionController::TestResponse.new | |||
|
12 | end | |||
|
13 | ||||
|
14 | # Replace this with your real tests. | |||
|
15 | def test_truth | |||
|
16 | assert true | |||
|
17 | end | |||
|
18 | end |
@@ -0,0 +1,10 | |||||
|
1 | require File.dirname(__FILE__) + '/../test_helper' | |||
|
2 | ||||
|
3 | class UserPreferenceTest < Test::Unit::TestCase | |||
|
4 | fixtures :user_preferences | |||
|
5 | ||||
|
6 | # Replace this with your real tests. | |||
|
7 | def test_truth | |||
|
8 | assert true | |||
|
9 | end | |||
|
10 | end |
@@ -40,7 +40,7 class AccountController < ApplicationController | |||||
40 | user = User.try_to_login(params[:login], params[:password]) |
|
40 | user = User.try_to_login(params[:login], params[:password]) | |
41 | if user |
|
41 | if user | |
42 | self.logged_in_user = user |
|
42 | self.logged_in_user = user | |
43 |
redirect_back_or_default :controller => ' |
|
43 | redirect_back_or_default :controller => 'my', :action => 'page' | |
44 | else |
|
44 | else | |
45 | flash.now[:notice] = l(:notice_account_invalid_creditentials) |
|
45 | flash.now[:notice] = l(:notice_account_invalid_creditentials) | |
46 | end |
|
46 | end | |
@@ -52,41 +52,6 class AccountController < ApplicationController | |||||
52 | self.logged_in_user = nil |
|
52 | self.logged_in_user = nil | |
53 | redirect_to :controller => '' |
|
53 | redirect_to :controller => '' | |
54 | end |
|
54 | end | |
55 |
|
||||
56 | # Show logged in user's page |
|
|||
57 | def my_page |
|
|||
58 | @user = self.logged_in_user |
|
|||
59 | @reported_issues = Issue.find(:all, :conditions => ["author_id=?", @user.id], :limit => 10, :include => [ :status, :project, :tracker ], :order => 'issues.updated_on DESC') |
|
|||
60 | @assigned_issues = Issue.find(:all, :conditions => ["assigned_to_id=?", @user.id], :limit => 10, :include => [ :status, :project, :tracker ], :order => 'issues.updated_on DESC') |
|
|||
61 | end |
|
|||
62 |
|
||||
63 | # Edit logged in user's account |
|
|||
64 | def my_account |
|
|||
65 | @user = self.logged_in_user |
|
|||
66 | if request.post? and @user.update_attributes(@params[:user]) |
|
|||
67 | set_localization |
|
|||
68 | flash.now[:notice] = l(:notice_account_updated) |
|
|||
69 | self.logged_in_user.reload |
|
|||
70 | end |
|
|||
71 | end |
|
|||
72 |
|
||||
73 | # Change logged in user's password |
|
|||
74 | def change_password |
|
|||
75 | @user = self.logged_in_user |
|
|||
76 | flash[:notice] = l(:notice_can_t_change_password) and redirect_to :action => 'my_account' and return if @user.auth_source_id |
|
|||
77 | if @user.check_password?(@params[:password]) |
|
|||
78 | @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation] |
|
|||
79 | if @user.save |
|
|||
80 | flash[:notice] = l(:notice_account_password_updated) |
|
|||
81 | else |
|
|||
82 | render :action => 'my_account' |
|
|||
83 | return |
|
|||
84 | end |
|
|||
85 | else |
|
|||
86 | flash[:notice] = l(:notice_account_wrong_password) |
|
|||
87 | end |
|
|||
88 | redirect_to :action => 'my_account' |
|
|||
89 | end |
|
|||
90 |
|
55 | |||
91 | # Enable user to choose a new password |
|
56 | # Enable user to choose a new password | |
92 | def lost_password |
|
57 | def lost_password |
@@ -19,7 +19,9 require "digest/sha1" | |||||
19 |
|
19 | |||
20 | class User < ActiveRecord::Base |
|
20 | class User < ActiveRecord::Base | |
21 | has_many :memberships, :class_name => 'Member', :include => [ :project, :role ], :dependent => true |
|
21 | has_many :memberships, :class_name => 'Member', :include => [ :project, :role ], :dependent => true | |
|
22 | has_many :projects, :through => :memberships | |||
22 | has_many :custom_values, :dependent => true, :as => :customized |
|
23 | has_many :custom_values, :dependent => true, :as => :customized | |
|
24 | has_one :preference, :dependent => true, :class_name => 'UserPreference' | |||
23 | belongs_to :auth_source |
|
25 | belongs_to :auth_source | |
24 |
|
26 | |||
25 | attr_accessor :password, :password_confirmation |
|
27 | attr_accessor :password, :password_confirmation | |
@@ -114,6 +116,10 class User < ActiveRecord::Base | |||||
114 | end |
|
116 | end | |
115 | @role_for_projects[project_id] |
|
117 | @role_for_projects[project_id] | |
116 | end |
|
118 | end | |
|
119 | ||||
|
120 | def pref | |||
|
121 | self.preference ||= UserPreference.new(:user => self) | |||
|
122 | end | |||
117 |
|
123 | |||
118 | private |
|
124 | private | |
119 | # Return password digest |
|
125 | # Return password digest |
@@ -70,7 +70,7 var menu_contenu=' \ | |||||
70 | <div id="navigation"> |
|
70 | <div id="navigation"> | |
71 | <ul> |
|
71 | <ul> | |
72 | <li class="selected"><%= link_to l(:label_home), { :controller => '' }, :class => "picHome" %></li> |
|
72 | <li class="selected"><%= link_to l(:label_home), { :controller => '' }, :class => "picHome" %></li> | |
73 |
<li><%= link_to l(:label_my_page), { :controller => ' |
|
73 | <li><%= link_to l(:label_my_page), { :controller => 'my', :action => 'page'}, :class => "picUserPage" %></li> | |
74 | <li><%= link_to l(:label_project_plural), { :controller => 'projects' }, :class => "picProject" %></li> |
|
74 | <li><%= link_to l(:label_project_plural), { :controller => 'projects' }, :class => "picProject" %></li> | |
75 |
|
75 | |||
76 | <% unless @project.nil? || @project.id.nil? %> |
|
76 | <% unless @project.nil? || @project.id.nil? %> | |
@@ -78,7 +78,7 var menu_contenu=' \ | |||||
78 | <% end %> |
|
78 | <% end %> | |
79 |
|
79 | |||
80 | <% if loggedin? %> |
|
80 | <% if loggedin? %> | |
81 |
<li><%= link_to l(:label_my_account), { :controller => ' |
|
81 | <li><%= link_to l(:label_my_account), { :controller => 'my', :action => 'account' }, :class => "picUser" %></li> | |
82 | <% end %> |
|
82 | <% end %> | |
83 |
|
83 | |||
84 | <% if admin_loggedin? %> |
|
84 | <% if admin_loggedin? %> |
@@ -9,7 +9,7 | |||||
9 | <div class="box"> |
|
9 | <div class="box"> | |
10 | <h3><%=l(:label_information_plural)%></h3> |
|
10 | <h3><%=l(:label_information_plural)%></h3> | |
11 |
|
11 | |||
12 |
<%= start_form_tag({:action => ' |
|
12 | <%= start_form_tag({:action => 'account'}, :class => "tabular") %> | |
13 |
|
13 | |||
14 | <!--[form:user]--> |
|
14 | <!--[form:user]--> | |
15 | <p><label for="user_firstname"><%=l(:field_firstname)%> <span class="required">*</span></label> |
|
15 | <p><label for="user_firstname"><%=l(:field_firstname)%> <span class="required">*</span></label> |
@@ -7,6 +7,7 http://redmine.org/ | |||||
7 |
|
7 | |||
8 | == xx/xx/2006 v0.x.x |
|
8 | == xx/xx/2006 v0.x.x | |
9 |
|
9 | |||
|
10 | * "my page" is now customizable | |||
10 | * improved issues change history |
|
11 | * improved issues change history | |
11 | * new functionality: move an issue to another project or tracker |
|
12 | * new functionality: move an issue to another project or tracker | |
12 | * new functionality: add a note to an issue |
|
13 | * new functionality: add a note to an issue |
@@ -257,6 +257,7 label_gantt_chart: Gantt Diagramm | |||||
257 | label_internal: Intern |
|
257 | label_internal: Intern | |
258 | label_last_changes: %d änderungen des Letzten |
|
258 | label_last_changes: %d änderungen des Letzten | |
259 | label_change_view_all: Alle änderungen ansehen |
|
259 | label_change_view_all: Alle änderungen ansehen | |
|
260 | label_personalize_page: Diese Seite personifizieren | |||
260 |
|
261 | |||
261 | button_login: Einloggen |
|
262 | button_login: Einloggen | |
262 | button_submit: Einreichen |
|
263 | button_submit: Einreichen | |
@@ -278,6 +279,7 button_list: Aufzulisten | |||||
278 | button_view: Siehe |
|
279 | button_view: Siehe | |
279 | button_move: Bewegen |
|
280 | button_move: Bewegen | |
280 | button_back: Rückkehr |
|
281 | button_back: Rückkehr | |
|
282 | button_cancel: Annullieren | |||
281 |
|
283 | |||
282 | text_select_mail_notifications: Aktionen für die Mailbenachrichtigung aktiviert werden soll. |
|
284 | text_select_mail_notifications: Aktionen für die Mailbenachrichtigung aktiviert werden soll. | |
283 | text_regexp_info: eg. ^[A-Z0-9]+$ |
|
285 | text_regexp_info: eg. ^[A-Z0-9]+$ |
@@ -257,6 +257,7 label_gantt_chart: Gantt chart | |||||
257 | label_internal: Internal |
|
257 | label_internal: Internal | |
258 | label_last_changes: last %d changes |
|
258 | label_last_changes: last %d changes | |
259 | label_change_view_all: View all changes |
|
259 | label_change_view_all: View all changes | |
|
260 | label_personalize_page: Personalize this page | |||
260 |
|
261 | |||
261 | button_login: Login |
|
262 | button_login: Login | |
262 | button_submit: Submit |
|
263 | button_submit: Submit | |
@@ -278,6 +279,7 button_list: List | |||||
278 | button_view: View |
|
279 | button_view: View | |
279 | button_move: Move |
|
280 | button_move: Move | |
280 | button_back: Back |
|
281 | button_back: Back | |
|
282 | button_cancel: Cancel | |||
281 |
|
283 | |||
282 | text_select_mail_notifications: Select actions for which mail notifications should be sent. |
|
284 | text_select_mail_notifications: Select actions for which mail notifications should be sent. | |
283 | text_regexp_info: eg. ^[A-Z0-9]+$ |
|
285 | text_regexp_info: eg. ^[A-Z0-9]+$ |
@@ -257,6 +257,7 label_gantt_chart: Diagrama de Gantt | |||||
257 | label_internal: Interno |
|
257 | label_internal: Interno | |
258 | label_last_changes: %d cambios del último |
|
258 | label_last_changes: %d cambios del último | |
259 | label_change_view_all: Ver todos los cambios |
|
259 | label_change_view_all: Ver todos los cambios | |
|
260 | label_personalize_page: Personalizar esta página | |||
260 |
|
261 | |||
261 | button_login: Conexión |
|
262 | button_login: Conexión | |
262 | button_submit: Someter |
|
263 | button_submit: Someter | |
@@ -278,6 +279,7 button_list: Listar | |||||
278 | button_view: Ver |
|
279 | button_view: Ver | |
279 | button_move: Mover |
|
280 | button_move: Mover | |
280 | button_back: Atrás |
|
281 | button_back: Atrás | |
|
282 | button_cancel: Cancelar | |||
281 |
|
283 | |||
282 | text_select_mail_notifications: Seleccionar las actividades que necesitan la activación de la notificación por mail. |
|
284 | text_select_mail_notifications: Seleccionar las actividades que necesitan la activación de la notificación por mail. | |
283 | text_regexp_info: eg. ^[A-Z0-9]+$ |
|
285 | text_regexp_info: eg. ^[A-Z0-9]+$ |
@@ -258,10 +258,11 label_gantt_chart: Diagramme de Gantt | |||||
258 | label_internal: Interne |
|
258 | label_internal: Interne | |
259 | label_last_changes: %d derniers changements |
|
259 | label_last_changes: %d derniers changements | |
260 | label_change_view_all: Voir tous les changements |
|
260 | label_change_view_all: Voir tous les changements | |
|
261 | label_personalize_page: Personnaliser cette page | |||
261 |
|
262 | |||
262 | button_login: Connexion |
|
263 | button_login: Connexion | |
263 | button_submit: Soumettre |
|
264 | button_submit: Soumettre | |
264 |
button_save: |
|
265 | button_save: Sauvegarder | |
265 | button_check_all: Tout cocher |
|
266 | button_check_all: Tout cocher | |
266 | button_uncheck_all: Tout décocher |
|
267 | button_uncheck_all: Tout décocher | |
267 | button_delete: Supprimer |
|
268 | button_delete: Supprimer | |
@@ -279,6 +280,7 button_list: Lister | |||||
279 | button_view: Voir |
|
280 | button_view: Voir | |
280 | button_move: Déplacer |
|
281 | button_move: Déplacer | |
281 | button_back: Retour |
|
282 | button_back: Retour | |
|
283 | button_cancel: Annuler | |||
282 |
|
284 | |||
283 | text_select_mail_notifications: Sélectionner les actions pour lesquelles la notification par mail doit être activée. |
|
285 | text_select_mail_notifications: Sélectionner les actions pour lesquelles la notification par mail doit être activée. | |
284 | text_regexp_info: ex. ^[A-Z0-9]+$ |
|
286 | text_regexp_info: ex. ^[A-Z0-9]+$ |
@@ -379,6 +379,22 color:#505050; | |||||
379 | line-height:1.5em; |
|
379 | line-height:1.5em; | |
380 | } |
|
380 | } | |
381 |
|
381 | |||
|
382 | a.close-icon { | |||
|
383 | display:block; | |||
|
384 | margin-top:3px; | |||
|
385 | overflow:hidden; | |||
|
386 | width:12px; | |||
|
387 | height:12px; | |||
|
388 | background-repeat: no-repeat; | |||
|
389 | cursor:hand; | |||
|
390 | cursor:pointer; | |||
|
391 | background-image:url('../images/close.png'); | |||
|
392 | } | |||
|
393 | ||||
|
394 | a.close-icon:hover { | |||
|
395 | background-image:url('../images/close_hl.png'); | |||
|
396 | } | |||
|
397 | ||||
382 | .rightbox{ |
|
398 | .rightbox{ | |
383 | background: #fafbfc; |
|
399 | background: #fafbfc; | |
384 | border: 1px solid #c0c0c0; |
|
400 | border: 1px solid #c0c0c0; | |
@@ -388,6 +404,26 position: relative; | |||||
388 | margin: 0 5px 5px; |
|
404 | margin: 0 5px 5px; | |
389 | } |
|
405 | } | |
390 |
|
406 | |||
|
407 | .layout-active { | |||
|
408 | background: #ECF3E1; | |||
|
409 | } | |||
|
410 | ||||
|
411 | .block-receiver { | |||
|
412 | border:1px dashed #c0c0c0; | |||
|
413 | margin-bottom: 20px; | |||
|
414 | padding: 15px 0 15px 0; | |||
|
415 | } | |||
|
416 | ||||
|
417 | .mypage-box { | |||
|
418 | margin:0 0 20px 0; | |||
|
419 | color:#505050; | |||
|
420 | line-height:1.5em; | |||
|
421 | } | |||
|
422 | ||||
|
423 | .blocks { | |||
|
424 | cursor: move; | |||
|
425 | } | |||
|
426 | ||||
391 | .topright{ |
|
427 | .topright{ | |
392 | position: absolute; |
|
428 | position: absolute; | |
393 | right: 25px; |
|
429 | right: 25px; |
@@ -22,44 +22,44 class AccountTest < ActionController::IntegrationTest | |||||
22 |
|
22 | |||
23 | # Replace this with your real tests. |
|
23 | # Replace this with your real tests. | |
24 | def test_login |
|
24 | def test_login | |
25 |
get " |
|
25 | get "my/page" | |
26 | assert_redirected_to "account/login" |
|
26 | assert_redirected_to "account/login" | |
27 | log_user('jsmith', 'jsmith') |
|
27 | log_user('jsmith', 'jsmith') | |
28 |
|
28 | |||
29 |
get " |
|
29 | get "my/account" | |
30 | assert_response :success |
|
30 | assert_response :success | |
31 |
assert_template " |
|
31 | assert_template "my/account" | |
32 | end |
|
32 | end | |
33 |
|
33 | |||
34 | def test_change_password |
|
34 | def test_change_password | |
35 | log_user('jsmith', 'jsmith') |
|
35 | log_user('jsmith', 'jsmith') | |
36 |
get " |
|
36 | get "my/account" | |
37 | assert_response :success |
|
37 | assert_response :success | |
38 |
assert_template " |
|
38 | assert_template "my/account" | |
39 |
|
39 | |||
40 |
post " |
|
40 | post "my/change_password", :password => 'jsmith', :new_password => "hello", :new_password_confirmation => "hello2" | |
41 | assert_response :success |
|
41 | assert_response :success | |
42 |
assert_template " |
|
42 | assert_template "my/account" | |
43 | assert_tag :tag => "div", :attributes => { :class => "errorExplanation" } |
|
43 | assert_tag :tag => "div", :attributes => { :class => "errorExplanation" } | |
44 |
|
44 | |||
45 |
post " |
|
45 | post "my/change_password", :password => 'jsmithZZ', :new_password => "hello", :new_password_confirmation => "hello" | |
46 |
assert_redirected_to " |
|
46 | assert_redirected_to "my/account" | |
47 | assert_equal 'Wrong password', flash[:notice] |
|
47 | assert_equal 'Wrong password', flash[:notice] | |
48 |
|
48 | |||
49 |
post " |
|
49 | post "my/change_password", :password => 'jsmith', :new_password => "hello", :new_password_confirmation => "hello" | |
50 |
assert_redirected_to " |
|
50 | assert_redirected_to "my/account" | |
51 | log_user('jsmith', 'hello') |
|
51 | log_user('jsmith', 'hello') | |
52 | end |
|
52 | end | |
53 |
|
53 | |||
54 | def test_my_account |
|
54 | def test_my_account | |
55 | log_user('jsmith', 'jsmith') |
|
55 | log_user('jsmith', 'jsmith') | |
56 |
get " |
|
56 | get "my/account" | |
57 | assert_response :success |
|
57 | assert_response :success | |
58 |
assert_template " |
|
58 | assert_template "my/account" | |
59 |
|
59 | |||
60 |
post " |
|
60 | post "my/account", :user => {:firstname => "Joe", :login => "root", :admin => 1} | |
61 | assert_response :success |
|
61 | assert_response :success | |
62 |
assert_template " |
|
62 | assert_template "my/account" | |
63 | user = User.find(2) |
|
63 | user = User.find(2) | |
64 | assert_equal "Joe", user.firstname |
|
64 | assert_equal "Joe", user.firstname | |
65 | assert_equal "jsmith", user.login |
|
65 | assert_equal "jsmith", user.login | |
@@ -68,9 +68,9 class AccountTest < ActionController::IntegrationTest | |||||
68 |
|
68 | |||
69 | def test_my_page |
|
69 | def test_my_page | |
70 | log_user('jsmith', 'jsmith') |
|
70 | log_user('jsmith', 'jsmith') | |
71 |
get " |
|
71 | get "my/page" | |
72 | assert_response :success |
|
72 | assert_response :success | |
73 |
assert_template " |
|
73 | assert_template "my/page" | |
74 | end |
|
74 | end | |
75 |
|
75 | |||
76 | def test_lost_password |
|
76 | def test_lost_password |
@@ -49,7 +49,7 class Test::Unit::TestCase | |||||
49 | assert_response :success |
|
49 | assert_response :success | |
50 | assert_template "account/login" |
|
50 | assert_template "account/login" | |
51 | post "/account/login", :login => login, :password => password |
|
51 | post "/account/login", :login => login, :password => password | |
52 |
assert_redirected_to " |
|
52 | assert_redirected_to "my/page" | |
53 | assert_equal login, User.find(session[:user_id]).login |
|
53 | assert_equal login, User.find(session[:user_id]).login | |
54 | end |
|
54 | end | |
55 | end |
|
55 | end |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now