Remove project scope selector and integrate it into the autocompleter.

pull/6929/head
Wieland Lindenthal 6 years ago
parent 74ec63969e
commit 45b511a649
  1. 2
      app/controllers/search_controller.rb
  2. 7
      app/helpers/search_helper.rb
  3. 11
      app/views/search/_mini_form.html.erb
  4. 3
      frontend/src/app/components/global-search/global-search-input.component.html
  5. 155
      frontend/src/app/components/global-search/global-search-input.component.ts
  6. 48
      frontend/src/app/components/global-search/global-search.service.ts

@ -110,7 +110,7 @@ class SearchController < ApplicationController
@available_search_types = Redmine::Search.available_search_types.dup
gon.global_search = {
search_term: @question,
project_scope: @scope,
project_scope: search_params[:scope].to_s,
available_search_types: @available_search_types,
current_tab: @available_search_types.select { |search_type| search_params[search_type] }.first || 'all'
}

@ -98,13 +98,6 @@ module SearchHelper
l("label_#{t.singularize}_plural", default: t.to_s.humanize)
end
def project_select_tag
options = [[t(:label_project_all), 'all']]
options << [t(:label_in_this_project_subprojects)] unless @project.nil? || @project.descendants.active.empty?
options << [t(:label_in_this_project)] unless @project.nil?
select_tag('scope', options_for_select(options, current_scope), style: "width: 211px; display: inline-block;") if options.size > 1
end
def render_results_by_type(results_by_type)
links = []
# Sorts types by results count

@ -29,14 +29,7 @@ See docs/COPYRIGHT.rdoc for more details.
<div class="top-menu-search--wrapper hidden-for-mobile">
<%= label_tag("q", l(:label_search), class: "hidden-for-sighted") %>
<%= form_tag(search_path(@project), method: :get) do %>
<%= hidden_field_tag(controller.default_search_scope, 1, project_id: nil) if controller.default_search_scope %>
<global-search-input>
<%= styled_label_tag :scope, t(:description_project_scope), class: 'hidden-for-sighted' %>
<%= project_select_tag %>
<global-search-input>
</global-search-input>
<% end %>
</global-search-input>
</div>

@ -15,7 +15,8 @@
size="20"
class="top-menu-search--input"
placeholder= "{{I18n.t('js.select2.searching')}}"
[ngModel]="searchTerm">
[ngModel]="searchTerm"
(keyup.enter)="submitNonEmptySearch()">
<a #btn
id="top-menu-search-button"
class="top-menu-search--button search-form-normal"

@ -47,6 +47,7 @@ import {DynamicCssService} from "core-app/modules/common/dynamic-css/dynamic-css
import {GlobalSearchService} from "core-components/global-search/global-search.service";
import {distinctUntilChanged} from "rxjs/operators";
import {untilComponentDestroyed} from "ng2-rx-componentdestroyed";
import {CurrentProjectService} from "core-components/projects/current-project.service";
export const globalSearchSelector = 'global-search-input';
@ -76,23 +77,16 @@ export class GlobalSearchInputComponent implements OnDestroy {
readonly halResourceService:HalResourceService,
readonly dynamicCssService:DynamicCssService,
readonly globalSearchService:GlobalSearchService,
readonly cdRef:ChangeDetectorRef) {
readonly cdRef:ChangeDetectorRef,
readonly currentProjectService:CurrentProjectService) {
}
private projectScopeTypes = ['all_projects', 'this_project', 'this_project_and_all_descendants'];
ngOnInit() {
this.$element = jQuery(this.elementRef.nativeElement);
this.$input = jQuery(this.input.nativeElement);
this.globalSearchService.currentTab$
.pipe(
distinctUntilChanged(),
untilComponentDestroyed(this)
)
.subscribe((currentTab:string) => {
this.currentTab = currentTab;
this.cdRef.detectChanges();
});
this.globalSearchService.searchTerm$
.pipe(
distinctUntilChanged(),
@ -107,7 +101,7 @@ export class GlobalSearchInputComponent implements OnDestroy {
this.$input.autocomplete({
delay: 250,
autoFocus: false, // Accessibility!
autoFocus: true,
appendTo: '#top-menu',
classes: {
'ui-autocomplete': 'search-autocomplete--results'
@ -116,42 +110,58 @@ export class GlobalSearchInputComponent implements OnDestroy {
my: 'left top+10',
at: 'left bottom'
},
// focus: (event, ui) => {
// // this.searchTerm = ui.item.label;
// this.globalSearchService.searchTerm = this.searchTerm;
// return false;
// },
source: (request:{ term:string }, response:Function) => {
this.autocompleteWorkPackages(request.term).then((values) => {
selected = false;
response(values.map(wp => {
return { workPackage: wp };
return { item: wp };
}));
});
},
focus: (_evt:any, ui:any) => {
return false;
},
select: (_evt:any, ui:any) => {
selected = true;
this.redirectToWp(ui.item.workPackage.id);
this.globalSearchService.searchTerm = this.searchValue;
switch (ui.item.item) {
case 'all_projects': {
this.globalSearchService.projectScope = 'all';
this.globalSearchService.submitSearch();
break;
}
case 'this_project': {
this.globalSearchService.projectScope = 'current_project';
this.globalSearchService.submitSearch();
break;
}
case 'this_project_and_all_descendants': {
this.globalSearchService.projectScope = '';
this.globalSearchService.submitSearch();
break;
}
default: {
const workPackage = ui.item.item;
this.redirectToWp(workPackage.id);
}
}
return false;
},
minLength: 0
})
.data('ui-autocomplete')._renderItem = (ul:JQuery, item:{workPackage:WorkPackageResource}) => {
let workPackage = item.workPackage;
return jQuery("<li>")
.attr('data-value', workPackage.id)
.attr('tabindex', -1)
.append(
jQuery('<div>')
.addClass( 'ui-menu-item-wrapper')
.append(
jQuery('<span>')
.addClass('search-autocomplete--wp-id')
.addClass(`__hl_dot_status_${workPackage.status.idFromLink}`)
.attr('title', workPackage.status.name)
.append(`#${workPackage.id}`)
)
.append(
jQuery('<span>')
.addClass('search-autocomplete--subject')
.append(` ${workPackage.subject}`)
)
)
.appendTo(ul);
.data('ui-autocomplete')._renderItem = (ul:JQuery, item:{item:string|WorkPackageResource}) => {
if (_.includes(this.projectScopeTypes, item.item)) {
return this.renderProjectScopeItem(item.item as string).appendTo(ul);
} else {
return this.renderWorkPackageItem(item.item as WorkPackageResource).appendTo(ul);
}
};
}
@ -161,8 +171,6 @@ export class GlobalSearchInputComponent implements OnDestroy {
event.stopPropagation();
event.preventDefault();
this.dynamicCssService.requireHighlighting();
if (ContainHelpers.insideOrSelf(this.btn.nativeElement, event.target)) {
this.submitNonEmptySearch();
}
@ -179,11 +187,10 @@ export class GlobalSearchInputComponent implements OnDestroy {
}
}
private submitNonEmptySearch() {
public submitNonEmptySearch() {
if (this.searchValue !== '') {
jQuery(this.input.nativeElement)
.closest('form')
.submit();
this.globalSearchService.searchTerm = this.searchValue;
this.globalSearchService.submitSearch();
}
}
@ -191,16 +198,14 @@ export class GlobalSearchInputComponent implements OnDestroy {
return this.input.nativeElement.value;
}
private set searchValue(val:string) {
this.input.nativeElement.value = val;
}
ngOnDestroy():void {
this.$input.autocomplete('destroy');
this.unregister();
}
private autocompleteWorkPackages(query:string):Promise<WorkPackageResource[]> {
private autocompleteWorkPackages(query:string):Promise<(WorkPackageResource|string)[]> {
this.dynamicCssService.requireHighlighting();
this.$element.find('.ui-autocomplete--loading').show();
let idOnly:boolean = false;
@ -209,7 +214,16 @@ export class GlobalSearchInputComponent implements OnDestroy {
idOnly = true;
}
let href = this.PathHelperService.api.v3.wpBySubjectOrId(query, idOnly);
let href:string = this.PathHelperService.api.v3.wpBySubjectOrId(query, idOnly);
let suggestions:(string|WorkPackageResource)[] = [];
if (this.currentProjectService.path) {
suggestions.push('this_project_and_all_descendants');
suggestions.push('this_project');
}
suggestions.push('all_projects');
return this.halResourceService
.get<CollectionResource<WorkPackageResource>>(href)
@ -217,16 +231,57 @@ export class GlobalSearchInputComponent implements OnDestroy {
.then((collection) => {
this.noResults = collection.count === 0;
this.hideSpinner();
return collection.elements || [];
return suggestions.concat(collection.elements);
}).catch(() => {
this.hideSpinner();
return [];
return suggestions;
});
}
private hideSpinner():void {
this.$element.find('.ui-autocomplete--loading').hide();
}
private renderProjectScopeItem(scope:string):JQuery {
return jQuery("<li>")
.attr('data-value', scope)
.attr('tabindex', -1)
.append(
jQuery('<div>')
.addClass( 'ui-menu-item-wrapper')
.append(
jQuery('<span>')
.addClass('search-autocomplete--search-term')
.append(this.searchValue)
).append(
jQuery('<span>')
.addClass('search-autocomplete--project-scope')
.append(` [${scope}]`)
)
);
}
private renderWorkPackageItem(workPackage:WorkPackageResource) {
return jQuery("<li>")
.attr('data-value', workPackage.id)
.attr('tabindex', -1)
.append(
jQuery('<div>')
.addClass( 'ui-menu-item-wrapper')
.append(
jQuery('<span>')
.addClass('search-autocomplete--wp-id')
.addClass(`__hl_dot_status_${workPackage.status.idFromLink}`)
.attr('title', workPackage.status.name)
.append(`#${workPackage.id}`)
)
.append(
jQuery('<span>')
.addClass('search-autocomplete--subject')
.append(` ${workPackage.subject}`)
)
);
}
}
DynamicBootstrapper.register({

@ -46,9 +46,9 @@ export class GlobalSearchService {
private _tabs = new BehaviorSubject<any>([]);
public tabs$ = this._tabs.asObservable();
constructor(protected I18n:I18nService,
protected injector:Injector) {
protected injector:Injector,
readonly currentProjectService:CurrentProjectService) {
this.initialize();
}
@ -81,7 +81,51 @@ export class GlobalSearchService {
}
}
public submitSearch():void {
let searchPath:string = '';
if (this.currentProjectService.path) {
searchPath = this.currentProjectService.path;
}
searchPath = searchPath + `/search?${this.searchQueryParams()}`;
window.location.href = searchPath;
}
public set searchTerm(searchTerm:string) {
this._searchTerm.next(searchTerm);
}
public get searchTerm():string {
return this._searchTerm.value;
}
public get tabs():string {
return this._tabs.value;
}
public get currentTab():string {
return this._currentTab.value;
}
public get projectScope():string {
return this._projectScope.value;
}
public set projectScope(value:string) {
this._projectScope.next(value);
}
private searchQueryParams():string {
let params:string;
params = `q=${this.searchTerm}&scope=${this.projectScope}`;
if (this.currentTab.length > 0) {
params = `${params}&${this.currentTab}=1`;
}
if (this.projectScope.length > 0) {
params = `${params}&scope=${this.projectScope}`;
}
return params;
}
}

Loading…
Cancel
Save