Commit 349ae153 authored by jplang's avatar jplang

Replaces project jump select with custom dropdown (#23310).

git-svn-id: https://svn.redmine.org/redmine/trunk@15994 e93f8b46-1217-0410-a6f0-8f06a7374b81
parent 2d9e9a13
......@@ -45,6 +45,13 @@ class ProjectsController < ApplicationController
end
@projects = scope.to_a
}
format.js {
if params[:q].present?
@projects = Project.visible.like(params[:q]).to_a
else
@projects = User.current.projects.to_a
end
}
format.api {
@offset, @limit = api_offset_and_limit
@project_count = scope.count
......
......@@ -330,22 +330,40 @@ module ApplicationHelper
content_tag 'p', l(:label_no_data), :class => "nodata"
end
end
# Returns an array of projects that are displayed in the quick-jump box
def projects_for_jump_box(user=User.current)
if user.logged?
user.projects.active.select(:id, :name, :identifier, :lft, :rgt).to_a
else
[]
end
end
def render_projects_for_jump_box(projects, selected=nil)
s = ''.html_safe
project_tree(projects) do |project, level|
padding = level * 16
text = content_tag('span', project.name, :style => "padding-left:#{padding}px;")
s << link_to(text, project_path(project, :jump => current_menu_item), :title => project.name, :class => (project == selected ? 'selected' : nil))
end
s
end
# Renders the project quick-jump box
def render_project_jump_box
return unless User.current.logged?
projects = User.current.projects.active.select(:id, :name, :identifier, :lft, :rgt).to_a
projects = projects_for_jump_box(User.current)
if projects.any?
options =
("<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
'<option value="" disabled="disabled">---</option>').html_safe
options << project_tree_options_for_select(projects, :selected => @project) do |p|
{ :value => project_path(:id => p, :jump => current_menu_item) }
end
content_tag( :span, nil, :class => 'jump-box-arrow') +
select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }')
text = @project.try(:name) || l(:label_jump_to_a_project)
trigger = content_tag('span', text, :class => 'drdn-trigger')
q = text_field_tag('q', '', :id => 'projects-quick-search', :class => 'autocomplete', :data => {:automcomplete_url => projects_path(:format => 'js')})
content = content_tag('div',
content_tag('div', q, :class => 'quick-search') +
content_tag('div', render_projects_for_jump_box(projects, @project), :class => 'drdn-items selection'),
:class => 'drdn-content'
)
content_tag('span', trigger + content, :id => "project-jump", :class => "drdn")
end
end
......
<% s = @projects.any? ? render_projects_for_jump_box(@projects) : content_tag('span', l(:label_no_data)) %>
$('#project-jump .drdn-items').html('<%= escape_javascript s %>');
<div class="contextual">
<% if User.current.allowed_to?(:edit_wiki_pages, @project) %>
<%= link_to l(:label_wiki_page_new), new_project_wiki_page_path(@project), :remote => true, :class => 'icon icon-add' %>
<% end %>
<% if @editable %>
<% if @content.current_version? %>
<% if @editable && @content.current_version? %>
<%= link_to_if_authorized(l(:button_edit), {:action => 'edit', :id => @page.title}, :class => 'icon icon-edit', :accesskey => accesskey(:edit)) %>
<% end %>
<%= watcher_link(@page, User.current) %>
<span class="drdn">
<span class="drdn-trigger">Hello</span>
<div class="drdn-content drdn-items">
<% if @editable %>
<% if @content.current_version? %>
<%= link_to_if_authorized(l(:button_lock), {:action => 'protect', :id => @page.title, :protected => 1}, :method => :post, :class => 'icon icon-lock') if !@page.protected? %>
<%= link_to_if_authorized(l(:button_unlock), {:action => 'protect', :id => @page.title, :protected => 0}, :method => :post, :class => 'icon icon-unlock') if @page.protected? %>
<%= link_to_if_authorized(l(:button_rename), {:action => 'rename', :id => @page.title}, :class => 'icon icon-move') %>
......@@ -15,6 +17,11 @@
<% end %>
<% end %>
<%= link_to_if_authorized(l(:label_history), {:action => 'history', :id => @page.title}, :class => 'icon icon-history') %>
<% if User.current.allowed_to?(:edit_wiki_pages, @project) %>
<%= link_to l(:label_wiki_page_new), new_project_wiki_page_path(@project), :remote => true, :class => 'icon icon-add' %>
<% end %>
</div>
</span>
</div>
<%= wiki_page_breadcrumb(@page) %>
......
......@@ -572,6 +572,69 @@ function observeSearchfield(fieldId, targetId, url) {
});
}
$(document).ready(function(){
$(".drdn .autocomplete").val('');
$(".drdn-trigger").click(function(e){
var drdn = $(this).closest(".drdn");
if (drdn.hasClass("expanded")) {
drdn.removeClass("expanded");
} else {
$(".drdn").removeClass("expanded");
drdn.addClass("expanded");
if (!isMobile()) {
drdn.find(".autocomplete").focus();
}
e.stopPropagation();
}
});
$(document).click(function(e){
if ($(e.target).closest(".drdn").length < 1) {
$(".drdn.expanded").removeClass("expanded");
}
});
observeSearchfield('projects-quick-search', null, $('#projects-quick-search').data('automcomplete-url'));
$(".drdn-content").keydown(function(event){
var items = $(this).find(".drdn-items");
var focused = items.find("a:focus");
switch (event.which) {
case 40: //down
if (focused.length > 0) {
focused.nextAll("a").first().focus();;
} else {
items.find("a").first().focus();;
}
event.preventDefault();
break;
case 38: //up
if (focused.length > 0) {
var prev = focused.prevAll("a");
if (prev.length > 0) {
prev.first().focus();
} else {
$(this).find(".autocomplete").focus();
}
event.preventDefault();
}
break;
case 35: //end
if (focused.length > 0) {
focused.nextAll("a").last().focus();
event.preventDefault();
}
break;
case 36: //home
if (focused.length > 0) {
focused.prevAll("a").last().focus();
event.preventDefault();
}
break;
}
});
});
function beforeShowDatePicker(input, inst) {
var default_date = null;
switch ($(input).attr("id")) {
......
......@@ -30,7 +30,9 @@ pre, code {font-family: Consolas, Menlo, "Liberation Mono", Courier, monospace;}
#header a {color:#f8f8f8;}
#header h1 { overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
#header h1 .breadcrumbs { display:block; font-size: .5em; font-weight: normal; }
#quick-search {float:right;}
#quick-search #q {width:130px; height:24px; box-sizing:border-box; vertical-align:middle; border:1px solid #ccc; border-radius:3px;}
#main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px; width: 100%;}
#main-menu ul {margin: 0; padding: 0; width: 100%; white-space: nowrap;}
......@@ -140,6 +142,79 @@ a#toggle-completed-versions {color:#999;}
a.toggle-checkboxes { margin-left: 5px; padding-left: 12px; background: url(../images/toggle_check.png) no-repeat 0% 50%; }
/***** Dropdown *****/
.drdn {position:relative;}
.drdn-trigger {
width:100%;
height:24px;
box-sizing:border-box;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
padding:3px 18px 3px 6px;
background:#fff url(../images/sort_desc.png) no-repeat 97% 50%;
cursor:pointer;
user-select:none;
-moz-user-select:none;
-webkit-user-select:none;
}
.drdn-content {
display:none;
position:absolute;
right:0px;
top:25px;
min-width:100px;
background-color:#fff;
border:1px solid #ccc;
border-radius:4px;
color:#555;
z-index:99;
}
.drdn.expanded .drdn-trigger {background-image:url(../images/sort_asc.png);}
.drdn.expanded .drdn-content {display:block;}
.drdn-content .quick-search {margin:8px;}
.drdn-content .autocomplete {box-sizing: border-box; width:100% !important; height:28px;}
.drdn-content .autocomplete:focus {border-color:#5ad;}
.drdn-items {max-height:400px; overflow:auto;}
.quick-search + .drdn-items {border-top:1px solid #ccc;}
.drdn-items>* {
display:block;
border:1px solid #fff;
color:#555 !important;
overflow:hidden;
text-overflow: ellipsis;
white-space:nowrap;
padding:4px 8px;
}
.drdn-items>a:hover {text-decoration:none; background-color:#759FCF; color:#fff !important;}
.drdn-items>*:focus {border:1px dotted #bbb;}
.drdn-items.selection>*:before {
content:' ';
display:inline-block;
line-height:1em;
width:1em;
height:1em;
margin-right:4px;
font-weight:bold;
}
.drdn-items.selection>*.selected:before {
content:"\2713 ";
}
.drdn-items>span {color:#999;}
#project-jump.drdn {width:200px;display:inline-block;}
#project-jump .drdn-trigger {
display:inline-block;
border-radius:3px;
border:1px solid #ccc;
margin:0 !important;
vertical-align:middle;
color:#555;
}
#project-jump .drdn-content {width:280px;}
/***** Tables *****/
table.list, .table-list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
table.list th, .table-list-header { background-color:#EEEEEE; padding: 4px; white-space:nowrap; font-weight:bold; }
......
......@@ -125,18 +125,40 @@
background: inherit;
}
/* this represents the dropdown arrow to left of the mobile project menu */
#header .jump-box-arrow:before {
/* styles for combobox within quick-search (#project_quick_jump_box) */
#project-jump.drdn {
position: absolute;
top: 0px;
left: 0;
width: 100%;
max-width: 100%;
height: 2em;
height: 64px;
padding: 5px;
padding-right: 72px;
padding-left: 20px;
}
#project-jump .drdn-trigger {
font-size:1.5em;
font-weight:bold;
display:block;
width:100%;
color:#fff;
padding-left:24px;
background:transparent;
height:50px;
line-height:40px;
border:0;
}
#project-jump .drdn-trigger:before {
/* set a font-size in order to achive same result in different themes */
font-family: Verdana, sans-serif;
font-size: 2em;
line-height: 64px;
font-size: 1.5em;
position: absolute;
left: 0;
width: 2em;
padding: 0 .5em;
padding: 0 8px;
/* achieve dropdwon arrow by scaling a caret character */
content: '^';
-webkit-transform: scale(1,-.8);
......@@ -147,39 +169,27 @@
opacity: .6;
}
#project-jump.expanded .drdn-trigger:before {
-webkit-transform: scale(1,.8);
-ms-transform: scale(1,.8);
transform: scale(1,.8);
padding-top:8px;
}
/* styles for combobox within quick-search (#project_quick_jump_box) */
#header #quick-search select {
font-size: 1.5em;
font-weight: bold;
line-height: 1.2;
position: absolute;
top: 15px;
left: 0;
float: left;
width: 100%;
max-width: 100%;
height: 2em;
height: 35px;
padding: 5px;
padding-right: 72px;
padding-left: 50px;
text-indent: .01px;
color: inherit;
border: 0;
-webkit-border-radius: 0;
border-radius: 0;
background: none;
-webkit-box-shadow: none;
box-shadow: none;
/* hide default browser arrow */
-webkit-appearance: none;
-moz-appearance: none;
#project-jump .drdn-content {
position:absolute;
left:0px;
top:64px;
width:100%;
font-size:15px;
font-weight:normal;
}
#project-jump .drdn-content .autocomplete {
height:40px;
font-size:20px;
}
#project-jump .drdn-content a {
padding:8px;
}
#header #quick-search form {
......
......@@ -52,6 +52,12 @@ class ProjectsControllerTest < Redmine::ControllerTest
assert_select 'feed>entry', :count => Project.visible(User.current).count
end
def test_index_js
xhr :get, :index, :format => 'js', :q => 'coo'
assert_response :success
assert_equal 'text/javascript', response.content_type
end
test "#index by non-admin user with view_time_entries permission should show overall spent time link" do
@request.session[:user_id] = 3
get :index
......
......@@ -138,8 +138,8 @@ class WelcomeControllerTest < Redmine::ControllerTest
@request.session[:user_id] = 2
get :index
assert_select "#header select" do
assert_select "option", :text => 'Foo & Bar'
assert_select "#header #project-jump" do
assert_select "a", :text => 'Foo & Bar'
end
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment