mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge branch 'DSpace:main' into main
This commit is contained in:
@@ -294,6 +294,8 @@ homePage:
|
|||||||
# No. of communities to list per page on the home page
|
# No. of communities to list per page on the home page
|
||||||
# This will always round to the nearest number from the list of page sizes. e.g. if you set it to 7 it'll use 10
|
# This will always round to the nearest number from the list of page sizes. e.g. if you set it to 7 it'll use 10
|
||||||
pageSize: 5
|
pageSize: 5
|
||||||
|
# Enable or disable the Discover filters on the homepage
|
||||||
|
showDiscoverFilters: false
|
||||||
|
|
||||||
# Item Config
|
# Item Config
|
||||||
item:
|
item:
|
||||||
@@ -422,3 +424,12 @@ comcolSelectionSort:
|
|||||||
# suggestion:
|
# suggestion:
|
||||||
# - collectionId: 8f7df5ca-f9c2-47a4-81ec-8a6393d6e5af
|
# - collectionId: 8f7df5ca-f9c2-47a4-81ec-8a6393d6e5af
|
||||||
# source: "openaire"
|
# source: "openaire"
|
||||||
|
|
||||||
|
|
||||||
|
# Search settings
|
||||||
|
search:
|
||||||
|
# Settings to enable/disable or configure advanced search filters.
|
||||||
|
advancedFilters:
|
||||||
|
enabled: false
|
||||||
|
# List of filters to enable in "Advanced Search" dropdown
|
||||||
|
filter: [ 'title', 'author', 'subject', 'entityType' ]
|
||||||
|
@@ -58,4 +58,4 @@
|
|||||||
<ds-themed-loading *ngIf="collectionRD?.isLoading"
|
<ds-themed-loading *ngIf="collectionRD?.isLoading"
|
||||||
message="{{'loading.collection' | translate}}"></ds-themed-loading>
|
message="{{'loading.collection' | translate}}"></ds-themed-loading>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@@ -7,7 +7,7 @@ import { Bitstream } from '../core/shared/bitstream.model';
|
|||||||
import { Community } from '../core/shared/community.model';
|
import { Community } from '../core/shared/community.model';
|
||||||
import { fadeInOut } from '../shared/animations/fade';
|
import { fadeInOut } from '../shared/animations/fade';
|
||||||
import { hasValue } from '../shared/empty.util';
|
import { hasValue } from '../shared/empty.util';
|
||||||
import { getAllSucceededRemoteDataPayload} from '../core/shared/operators';
|
import { getAllSucceededRemoteDataPayload } from '../core/shared/operators';
|
||||||
import { AuthService } from '../core/auth/auth.service';
|
import { AuthService } from '../core/auth/auth.service';
|
||||||
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
|
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
|
||||||
import { FeatureID } from '../core/data/feature-authorization/feature-id';
|
import { FeatureID } from '../core/data/feature-authorization/feature-id';
|
||||||
|
@@ -1,10 +1,19 @@
|
|||||||
<ds-themed-home-news></ds-themed-home-news>
|
<ds-themed-home-news></ds-themed-home-news>
|
||||||
<div class="container">
|
<div [ngClass]="appConfig.homePage.showDiscoverFilters ? 'container-fluid' : 'container'">
|
||||||
<ng-container *ngIf="(site$ | async) as site">
|
<div class="row m-5">
|
||||||
<ds-view-tracker [object]="site"></ds-view-tracker>
|
<div class="col-sm-3" *ngIf="appConfig.homePage.showDiscoverFilters">
|
||||||
</ng-container>
|
<ds-configuration-search-page [sideBarWidth]="12" [showViewModes]="false" [searchEnabled]="false"
|
||||||
<ds-themed-search-form [inPlaceSearch]="false" [searchPlaceholder]="'home.search-form.placeholder' | translate"></ds-themed-search-form>
|
[inPlaceSearch]="false" [showScopeSelector]="false"></ds-configuration-search-page>
|
||||||
<ds-themed-top-level-community-list></ds-themed-top-level-community-list>
|
</div>
|
||||||
<ds-recent-item-list *ngIf="recentSubmissionspageSize>0"></ds-recent-item-list>
|
<div [ngClass]="appConfig.homePage.showDiscoverFilters ? 'col-sm-9' : 'col-sm-12'">
|
||||||
|
<ng-container *ngIf="(site$ | async) as site">
|
||||||
|
<ds-view-tracker [object]="site"></ds-view-tracker>
|
||||||
|
</ng-container>
|
||||||
|
<ds-themed-search-form [inPlaceSearch]="false"
|
||||||
|
[searchPlaceholder]="'home.search-form.placeholder' | translate"></ds-themed-search-form>
|
||||||
|
<ds-themed-top-level-community-list></ds-themed-top-level-community-list>
|
||||||
|
<ds-recent-item-list *ngIf="recentSubmissionspageSize>0"></ds-recent-item-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ds-suggestions-popup></ds-suggestions-popup>
|
<ds-suggestions-popup></ds-suggestions-popup>
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, Inject, OnInit } from '@angular/core';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { Site } from '../core/shared/site.model';
|
import { Site } from '../core/shared/site.model';
|
||||||
import { environment } from '../../environments/environment';
|
import { environment } from '../../environments/environment';
|
||||||
|
import { APP_CONFIG, AppConfig } from 'src/config/app-config.interface';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-home-page',
|
selector: 'ds-home-page',
|
||||||
styleUrls: ['./home-page.component.scss'],
|
styleUrls: ['./home-page.component.scss'],
|
||||||
@@ -14,6 +15,7 @@ export class HomePageComponent implements OnInit {
|
|||||||
site$: Observable<Site>;
|
site$: Observable<Site>;
|
||||||
recentSubmissionspageSize: number;
|
recentSubmissionspageSize: number;
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
) {
|
) {
|
||||||
this.recentSubmissionspageSize = environment.homePage.recentSubmissions.pageSize;
|
this.recentSubmissionspageSize = environment.homePage.recentSubmissions.pageSize;
|
||||||
|
@@ -3,7 +3,6 @@ import { NgModule } from '@angular/core';
|
|||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from '../shared/shared.module';
|
||||||
import { HomeNewsComponent } from './home-news/home-news.component';
|
import { HomeNewsComponent } from './home-news/home-news.component';
|
||||||
import { HomePageRoutingModule } from './home-page-routing.module';
|
import { HomePageRoutingModule } from './home-page-routing.module';
|
||||||
|
|
||||||
import { HomePageComponent } from './home-page.component';
|
import { HomePageComponent } from './home-page.component';
|
||||||
import { TopLevelCommunityListComponent } from './top-level-community-list/top-level-community-list.component';
|
import { TopLevelCommunityListComponent } from './top-level-community-list/top-level-community-list.component';
|
||||||
import { StatisticsModule } from '../statistics/statistics.module';
|
import { StatisticsModule } from '../statistics/statistics.module';
|
||||||
@@ -13,6 +12,7 @@ import { RecentItemListComponent } from './recent-item-list/recent-item-list.com
|
|||||||
import { JournalEntitiesModule } from '../entity-groups/journal-entities/journal-entities.module';
|
import { JournalEntitiesModule } from '../entity-groups/journal-entities/journal-entities.module';
|
||||||
import { ResearchEntitiesModule } from '../entity-groups/research-entities/research-entities.module';
|
import { ResearchEntitiesModule } from '../entity-groups/research-entities/research-entities.module';
|
||||||
import { ThemedTopLevelCommunityListComponent } from './top-level-community-list/themed-top-level-community-list.component';
|
import { ThemedTopLevelCommunityListComponent } from './top-level-community-list/themed-top-level-community-list.component';
|
||||||
|
import { SearchModule } from '../shared/search/search.module';
|
||||||
import { NotificationsModule } from '../notifications/notifications.module';
|
import { NotificationsModule } from '../notifications/notifications.module';
|
||||||
|
|
||||||
const DECLARATIONS = [
|
const DECLARATIONS = [
|
||||||
@@ -29,6 +29,7 @@ const DECLARATIONS = [
|
|||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule.withEntryComponents(),
|
SharedModule.withEntryComponents(),
|
||||||
|
SearchModule,
|
||||||
JournalEntitiesModule.withEntryComponents(),
|
JournalEntitiesModule.withEntryComponents(),
|
||||||
ResearchEntitiesModule.withEntryComponents(),
|
ResearchEntitiesModule.withEntryComponents(),
|
||||||
HomePageRoutingModule,
|
HomePageRoutingModule,
|
||||||
|
@@ -0,0 +1,51 @@
|
|||||||
|
<div class="facet-filter d-block mb-3 p-3" [ngClass]="{ 'focus': focusBox }" role="region">
|
||||||
|
<button (click)="toggle()" (focusin)="focusBox = true" (focusout)="focusBox = false" class="filter-name d-flex"
|
||||||
|
[attr.aria-expanded]="false"
|
||||||
|
[attr.aria-label]="((collapsedSearch ? 'search.filters.filter.expand' : 'search.filters.filter.collapse') | translate) + ' ' + (('search.advanced.filters.head') | translate | lowercase)"
|
||||||
|
[attr.data-test]="'filter-toggle' | dsBrowserOnly">
|
||||||
|
<span class="h4 d-inline-block text-left mt-auto mb-auto">
|
||||||
|
{{'search.advanced.filters.head' | translate}}
|
||||||
|
</span>
|
||||||
|
<i class="filter-toggle flex-grow-1 fas p-auto" aria-hidden="true" [ngClass]="collapsedSearch ? 'fa-plus' : 'fa-minus'"
|
||||||
|
[title]="(collapsedSearch ? 'search.filters.filter.expand' : 'search.filters.filter.collapse') | translate">
|
||||||
|
</i>
|
||||||
|
</button>
|
||||||
|
<div [@slide]="collapsedSearch ? 'collapsed' : 'expanded'" (@slide.start)="startSlide($event)"
|
||||||
|
(@slide.done)="finishSlide($event)" class="search-filter-wrapper"
|
||||||
|
[ngClass]="{ 'closed' : closed, 'notab': notab }">
|
||||||
|
<form [class]="'ng-invalid'" [formGroup]="advSearchForm" (ngSubmit)="onSubmit(advSearchForm.value)">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<select
|
||||||
|
[className]="(filter.invalid) && (filter.dirty || filter.touched) ? 'form-control is-invalid' :'form-control'"
|
||||||
|
aria-label="filter" name="filter" id="filter" placeholder="select operator"
|
||||||
|
formControlName="filter" required>
|
||||||
|
<ng-container *ngFor="let filter of appConfig.search.advancedFilters.filter;">
|
||||||
|
<option [value]="filter">
|
||||||
|
{{'search.filters.filter.' + filter + '.text'| translate}}
|
||||||
|
</option>
|
||||||
|
</ng-container>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-12 mt-1">
|
||||||
|
<select
|
||||||
|
[className]="(operator.invalid) && (operator.dirty || operator.touched) ? 'form-control is-invalid' :'form-control'"
|
||||||
|
aria-label="operator" name="operator" id="operator" formControlName="operator" required>
|
||||||
|
<option value="equals">{{'search.filters.operator.equals.text'| translate}}</option>
|
||||||
|
<option value="notequals">{{'search.filters.operator.notequals.text'| translate}}</option>
|
||||||
|
<option value="contains">{{'search.filters.operator.contains.text'| translate}}</option>
|
||||||
|
<option value="notcontains">{{'search.filters.operator.notcontains.text'| translate}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-12 mt-1">
|
||||||
|
<input type="text" aria-label="textsearch" class="form-control" id="textsearch" name="textsearch"
|
||||||
|
formControlName="textsearch" #text [placeholder]="('filter.search.text.placeholder' | translate)" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-12 mt-1">
|
||||||
|
<button class="form-control btn w-50 float-right btn-primary" type="submit"
|
||||||
|
[disabled]="advSearchForm.invalid">{{'advancesearch.form.submit'| translate}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1 @@
|
|||||||
|
@import '../search-filters/search-filter/search-filter.component.scss';
|
@@ -0,0 +1,74 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { FormBuilderService } from '../../../shared/form/builder/form-builder.service';
|
||||||
|
import { AdvancedSearchComponent } from './advanced-search.component';
|
||||||
|
import { getMockFormBuilderService } from '../../../shared/mocks/form-builder-service.mock';
|
||||||
|
import { SearchService } from '../../../core/shared/search/search.service';
|
||||||
|
import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-page.component';
|
||||||
|
import { SearchConfigurationServiceStub } from '../../testing/search-configuration-service.stub';
|
||||||
|
import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service';
|
||||||
|
import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { APP_CONFIG } from '../../../../config/app-config.interface';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { RouterStub } from '../../testing/router.stub';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { BrowserOnlyMockPipe } from '../../testing/browser-only-mock.pipe';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
describe('AdvancedSearchComponent', () => {
|
||||||
|
let component: AdvancedSearchComponent;
|
||||||
|
let fixture: ComponentFixture<AdvancedSearchComponent>;
|
||||||
|
let builderService: FormBuilderService = getMockFormBuilderService();
|
||||||
|
let searchService: SearchService;
|
||||||
|
let router;
|
||||||
|
const searchServiceStub = {
|
||||||
|
/* eslint-disable no-empty,@typescript-eslint/no-empty-function */
|
||||||
|
getClearFiltersQueryParams: () => {
|
||||||
|
},
|
||||||
|
getSearchLink: () => {
|
||||||
|
},
|
||||||
|
getConfigurationSearchConfig: () => { },
|
||||||
|
/* eslint-enable no-empty, @typescript-eslint/no-empty-function */
|
||||||
|
};
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [AdvancedSearchComponent, BrowserOnlyMockPipe],
|
||||||
|
imports: [FormsModule, RouterTestingModule, TranslateModule.forRoot(), BrowserAnimationsModule, ReactiveFormsModule],
|
||||||
|
providers: [
|
||||||
|
FormBuilder,
|
||||||
|
{ provide: APP_CONFIG, useValue: environment },
|
||||||
|
{ provide: FormBuilderService, useValue: builderService },
|
||||||
|
{ provide: Router, useValue: new RouterStub() },
|
||||||
|
{ provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() },
|
||||||
|
{ provide: RemoteDataBuildService, useValue: {} },
|
||||||
|
{ provide: SearchService, useValue: searchServiceStub },
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(AdvancedSearchComponent, {
|
||||||
|
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(AdvancedSearchComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
router = TestBed.inject(Router);
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
describe('when the getSearchLink method is called', () => {
|
||||||
|
const data = { filter: 'title', textsearch: 'demo', operator: 'equals' };
|
||||||
|
it('should call navigate on the router with the right searchlink and parameters when the filter is provided with a valid operator', () => {
|
||||||
|
component.advSearchForm.get('textsearch').patchValue('1');
|
||||||
|
component.advSearchForm.get('filter').patchValue('1');
|
||||||
|
component.advSearchForm.get('operator').patchValue('1');
|
||||||
|
|
||||||
|
component.onSubmit(data);
|
||||||
|
expect(router.navigate).toHaveBeenCalledWith([undefined], {
|
||||||
|
queryParams: { ['f.' + data.filter]: data.textsearch + ',' + data.operator },
|
||||||
|
queryParamsHandling: 'merge'
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,115 @@
|
|||||||
|
import { Component, Inject, Input, OnInit } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { slide } from '../../animations/slide';
|
||||||
|
import { FormBuilder } from '@angular/forms';
|
||||||
|
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { SearchService } from '../../../core/shared/search/search.service';
|
||||||
|
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
|
||||||
|
import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-page.component';
|
||||||
|
import { AppConfig, APP_CONFIG } from 'src/config/app-config.interface';
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-advanced-search',
|
||||||
|
templateUrl: './advanced-search.component.html',
|
||||||
|
styleUrls: ['./advanced-search.component.scss'],
|
||||||
|
animations: [slide],
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* This component represents the part of the search sidebar that contains advanced filters.
|
||||||
|
*/
|
||||||
|
export class AdvancedSearchComponent implements OnInit {
|
||||||
|
/**
|
||||||
|
* True when the search component should show results on the current page
|
||||||
|
*/
|
||||||
|
@Input() inPlaceSearch;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Link to the search page
|
||||||
|
*/
|
||||||
|
notab: boolean;
|
||||||
|
|
||||||
|
closed: boolean;
|
||||||
|
collapsedSearch = false;
|
||||||
|
focusBox = false;
|
||||||
|
|
||||||
|
advSearchForm: FormGroup;
|
||||||
|
constructor(
|
||||||
|
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
protected searchService: SearchService,
|
||||||
|
protected router: Router,
|
||||||
|
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
|
||||||
|
this.advSearchForm = this.formBuilder.group({
|
||||||
|
textsearch: new FormControl('', {
|
||||||
|
validators: [Validators.required],
|
||||||
|
}),
|
||||||
|
filter: new FormControl('title', {
|
||||||
|
validators: [Validators.required],
|
||||||
|
}),
|
||||||
|
operator: new FormControl('equals',
|
||||||
|
{ validators: [Validators.required], }),
|
||||||
|
|
||||||
|
});
|
||||||
|
this.collapsedSearch = this.isCollapsed();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
get textsearch() {
|
||||||
|
return this.advSearchForm.get('textsearch');
|
||||||
|
}
|
||||||
|
|
||||||
|
get filter() {
|
||||||
|
return this.advSearchForm.get('filter');
|
||||||
|
}
|
||||||
|
|
||||||
|
get operator() {
|
||||||
|
return this.advSearchForm.get('operator');
|
||||||
|
}
|
||||||
|
paramName(filter) {
|
||||||
|
return 'f.' + filter;
|
||||||
|
}
|
||||||
|
onSubmit(data) {
|
||||||
|
if (this.advSearchForm.valid) {
|
||||||
|
let queryParams = { [this.paramName(data.filter)]: data.textsearch + ',' + data.operator };
|
||||||
|
if (!this.inPlaceSearch) {
|
||||||
|
this.router.navigate([this.searchService.getSearchLink()], { queryParams: queryParams, queryParamsHandling: 'merge' });
|
||||||
|
} else {
|
||||||
|
if (!this.router.url.includes('?')) {
|
||||||
|
this.router.navigateByUrl(this.router.url + '?f.' + data.filter + '=' + data.textsearch + ',' + data.operator);
|
||||||
|
} else {
|
||||||
|
this.router.navigateByUrl(this.router.url + '&f.' + data.filter + '=' + data.textsearch + ',' + data.operator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.advSearchForm.reset({ operator: data.operator, filter: data.filter, textsearch: '' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
startSlide(event: any): void {
|
||||||
|
if (event.toState === 'collapsed') {
|
||||||
|
this.closed = true;
|
||||||
|
}
|
||||||
|
if (event.fromState === 'collapsed') {
|
||||||
|
this.notab = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finishSlide(event: any): void {
|
||||||
|
if (event.fromState === 'collapsed') {
|
||||||
|
this.closed = false;
|
||||||
|
}
|
||||||
|
if (event.toState === 'collapsed') {
|
||||||
|
this.notab = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toggle() {
|
||||||
|
this.collapsedSearch = !this.collapsedSearch;
|
||||||
|
}
|
||||||
|
private isCollapsed(): boolean {
|
||||||
|
return !this.collapsedSearch;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@@ -157,11 +157,20 @@ export class SearchFilterComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get regionId(): string {
|
get regionId(): string {
|
||||||
return `search-filter-region-${this.sequenceId}`;
|
if (this.inPlaceSearch) {
|
||||||
|
return `search-filter-region-${this.sequenceId}`;
|
||||||
|
} else {
|
||||||
|
return `search-filter-region-home-${this.sequenceId}`;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get toggleId(): string {
|
get toggleId(): string {
|
||||||
return `search-filter-toggle-${this.sequenceId}`;
|
if (this.inPlaceSearch) {
|
||||||
|
return `search-filter-toggle-${this.sequenceId}`;
|
||||||
|
} else {
|
||||||
|
return `search-filter-toggle-home-${this.sequenceId}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
<h3>{{"search.filters.head" | translate}}</h3>
|
<h3 *ngIf="inPlaceSearch">{{filterLabel+'.filters.head' | translate}}</h3>
|
||||||
|
<h2 *ngIf="!inPlaceSearch">{{filterLabel+'.filters.head' | translate}}</h2>
|
||||||
<div *ngIf="(filters | async)?.hasSucceeded">
|
<div *ngIf="(filters | async)?.hasSucceeded">
|
||||||
<div *ngFor="let filter of (filters | async)?.payload; trackBy: trackUpdate">
|
<div *ngFor="let filter of (filters | async)?.payload; trackBy: trackUpdate">
|
||||||
<ds-search-filter [scope]="currentScope" [filter]="filter" [inPlaceSearch]="inPlaceSearch" [refreshFilters]="refreshFilters"></ds-search-filter>
|
<ds-search-filter [scope]="currentScope" [filter]="filter" [inPlaceSearch]="inPlaceSearch" [refreshFilters]="refreshFilters"></ds-search-filter>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<ds-advanced-search *ngIf="appConfig.search.advancedFilters.enabled"
|
||||||
|
[inPlaceSearch]="inPlaceSearch"></ds-advanced-search>
|
||||||
<a class="btn btn-primary" [routerLink]="[searchLink]" [queryParams]="clearParams | async" queryParamsHandling="merge" role="button"><i class="fas fa-undo"></i> {{"search.filters.reset" | translate}}</a>
|
<a class="btn btn-primary" [routerLink]="[searchLink]" [queryParams]="clearParams | async" queryParamsHandling="merge" role="button"><i class="fas fa-undo"></i> {{"search.filters.reset" | translate}}</a>
|
||||||
|
@@ -9,6 +9,8 @@ import { SearchFiltersComponent } from './search-filters.component';
|
|||||||
import { SearchService } from '../../../core/shared/search/search.service';
|
import { SearchService } from '../../../core/shared/search/search.service';
|
||||||
import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-page.component';
|
import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-page.component';
|
||||||
import { SearchConfigurationServiceStub } from '../../testing/search-configuration-service.stub';
|
import { SearchConfigurationServiceStub } from '../../testing/search-configuration-service.stub';
|
||||||
|
import { APP_CONFIG } from 'src/config/app-config.interface';
|
||||||
|
import { environment } from 'src/environments/environment';
|
||||||
|
|
||||||
describe('SearchFiltersComponent', () => {
|
describe('SearchFiltersComponent', () => {
|
||||||
let comp: SearchFiltersComponent;
|
let comp: SearchFiltersComponent;
|
||||||
@@ -20,7 +22,8 @@ describe('SearchFiltersComponent', () => {
|
|||||||
getClearFiltersQueryParams: () => {
|
getClearFiltersQueryParams: () => {
|
||||||
},
|
},
|
||||||
getSearchLink: () => {
|
getSearchLink: () => {
|
||||||
}
|
},
|
||||||
|
getConfigurationSearchConfig: () => { },
|
||||||
/* eslint-enable no-empty, @typescript-eslint/no-empty-function */
|
/* eslint-enable no-empty, @typescript-eslint/no-empty-function */
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -37,6 +40,7 @@ describe('SearchFiltersComponent', () => {
|
|||||||
{ provide: SearchService, useValue: searchServiceStub },
|
{ provide: SearchService, useValue: searchServiceStub },
|
||||||
{ provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() },
|
{ provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() },
|
||||||
{ provide: SearchFilterService, useValue: searchFiltersStub },
|
{ provide: SearchFilterService, useValue: searchFiltersStub },
|
||||||
|
{ provide: APP_CONFIG, useValue: environment },
|
||||||
|
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
@@ -3,7 +3,7 @@ import { Router } from '@angular/router';
|
|||||||
|
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
import { AppConfig, APP_CONFIG } from 'src/config/app-config.interface';
|
||||||
import { SearchService } from '../../../core/shared/search/search.service';
|
import { SearchService } from '../../../core/shared/search/search.service';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { SearchFilterConfig } from '../models/search-filter-config.model';
|
import { SearchFilterConfig } from '../models/search-filter-config.model';
|
||||||
@@ -12,6 +12,7 @@ import { SearchFilterService } from '../../../core/shared/search/search-filter.s
|
|||||||
import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-page.component';
|
import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-page.component';
|
||||||
import { currentPath } from '../../utils/route.utils';
|
import { currentPath } from '../../utils/route.utils';
|
||||||
import { hasValue } from '../../empty.util';
|
import { hasValue } from '../../empty.util';
|
||||||
|
import { PaginatedSearchOptions } from '../models/paginated-search-options.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search-filters',
|
selector: 'ds-search-filters',
|
||||||
@@ -28,7 +29,7 @@ export class SearchFiltersComponent implements OnInit, OnDestroy {
|
|||||||
* An observable containing configuration about which filters are shown and how they are shown
|
* An observable containing configuration about which filters are shown and how they are shown
|
||||||
*/
|
*/
|
||||||
@Input() filters: Observable<RemoteData<SearchFilterConfig[]>>;
|
@Input() filters: Observable<RemoteData<SearchFilterConfig[]>>;
|
||||||
|
@Input() searchOptions: PaginatedSearchOptions;
|
||||||
/**
|
/**
|
||||||
* List of all filters that are currently active with their value set to null.
|
* List of all filters that are currently active with their value set to null.
|
||||||
* Used to reset all filters at once
|
* Used to reset all filters at once
|
||||||
@@ -61,6 +62,7 @@ export class SearchFiltersComponent implements OnInit, OnDestroy {
|
|||||||
searchLink: string;
|
searchLink: string;
|
||||||
|
|
||||||
subs = [];
|
subs = [];
|
||||||
|
filterLabel = 'search';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize instance variables
|
* Initialize instance variables
|
||||||
@@ -70,6 +72,7 @@ export class SearchFiltersComponent implements OnInit, OnDestroy {
|
|||||||
* @param {SearchConfigurationService} searchConfigService
|
* @param {SearchConfigurationService} searchConfigService
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
private filterService: SearchFilterService,
|
private filterService: SearchFilterService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
@@ -77,6 +80,9 @@ export class SearchFiltersComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
if (!this.inPlaceSearch) {
|
||||||
|
this.filterLabel = 'discover';
|
||||||
|
}
|
||||||
this.clearParams = this.searchConfigService.getCurrentFrontendFilters().pipe(map((filters) => {
|
this.clearParams = this.searchConfigService.getCurrentFrontendFilters().pipe(map((filters) => {
|
||||||
Object.keys(filters).forEach((f) => filters[f] = null);
|
Object.keys(filters).forEach((f) => filters[f] = null);
|
||||||
return filters;
|
return filters;
|
||||||
|
@@ -23,7 +23,7 @@
|
|||||||
[filters]="filters"
|
[filters]="filters"
|
||||||
[refreshFilters]="refreshFilters"
|
[refreshFilters]="refreshFilters"
|
||||||
[inPlaceSearch]="inPlaceSearch"></ds-themed-search-filters>
|
[inPlaceSearch]="inPlaceSearch"></ds-themed-search-filters>
|
||||||
<ds-themed-search-settings [currentSortOption]="currentSortOption"
|
<ds-themed-search-settings *ngIf="inPlaceSearch" [currentSortOption]="currentSortOption"
|
||||||
[sortOptionsList]="sortOptionsList"></ds-themed-search-settings>
|
[sortOptionsList]="sortOptionsList"></ds-themed-search-settings>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -10,8 +10,9 @@
|
|||||||
<ng-template *ngTemplateOutlet="searchContent"></ng-template>
|
<ng-template *ngTemplateOutlet="searchContent"></ng-template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ds-page-with-sidebar *ngIf="showSidebar && (initialized$ | async)" [id]="'search-page'" [sidebarContent]="sidebarContent">
|
<ds-page-with-sidebar *ngIf="showSidebar && (initialized$ | async)" [id]="'search-page'" [sideBarWidth]="sideBarWidth"
|
||||||
<ng-template *ngTemplateOutlet="searchContent"></ng-template>
|
[sidebarContent]="sidebarContent">
|
||||||
|
<ng-template *ngTemplateOutlet="searchContent"></ng-template>
|
||||||
</ds-page-with-sidebar>
|
</ds-page-with-sidebar>
|
||||||
|
|
||||||
<ng-template #searchContent>
|
<ng-template #searchContent>
|
||||||
@@ -22,15 +23,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="search-content" class="col-12">
|
<div id="search-content" class="col-12">
|
||||||
<div class="d-block d-md-none search-controls clearfix">
|
<div class="d-block d-md-none search-controls clearfix">
|
||||||
<ds-view-mode-switch [viewModeList]="viewModeList" [inPlaceSearch]="inPlaceSearch"></ds-view-mode-switch>
|
<ds-view-mode-switch *ngIf="inPlaceSearch" [viewModeList]="viewModeList"
|
||||||
<button [attr.aria-label]="'search.sidebar.open' | translate" (click)="openSidebar()"
|
[inPlaceSearch]="inPlaceSearch"></ds-view-mode-switch>
|
||||||
aria-controls="search-sidebar-content"
|
<button (click)="openSidebar()" aria-controls="#search-body"
|
||||||
class="btn btn-outline-primary float-right open-sidebar"><i
|
class="btn btn-outline-primary float-right open-sidebar"><i
|
||||||
class="fas fa-sliders"></i> {{"search.sidebar.open"
|
class="fas fa-sliders"></i> {{"search.sidebar.open"
|
||||||
| translate}}
|
| translate}}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<ds-themed-search-results [searchResults]="resultsRD$ | async"
|
<ds-themed-search-results *ngIf="inPlaceSearch" [searchResults]="resultsRD$ | async"
|
||||||
[searchConfig]="searchOptions$ | async"
|
[searchConfig]="searchOptions$ | async"
|
||||||
[configuration]="(currentConfiguration$ | async)"
|
[configuration]="(currentConfiguration$ | async)"
|
||||||
[disableHeader]="!searchEnabled"
|
[disableHeader]="!searchEnabled"
|
||||||
|
@@ -32,9 +32,9 @@ import { ThemedSearchComponent } from './themed-search.component';
|
|||||||
import { ThemedSearchResultsComponent } from './search-results/themed-search-results.component';
|
import { ThemedSearchResultsComponent } from './search-results/themed-search-results.component';
|
||||||
import { ThemedSearchSettingsComponent } from './search-settings/themed-search-settings.component';
|
import { ThemedSearchSettingsComponent } from './search-settings/themed-search-settings.component';
|
||||||
import { NouisliderModule } from 'ng2-nouislider';
|
import { NouisliderModule } from 'ng2-nouislider';
|
||||||
|
import { AdvancedSearchComponent } from './advanced-search/advanced-search.component';
|
||||||
import { ThemedSearchFiltersComponent } from './search-filters/themed-search-filters.component';
|
import { ThemedSearchFiltersComponent } from './search-filters/themed-search-filters.component';
|
||||||
import { ThemedSearchSidebarComponent } from './search-sidebar/themed-search-sidebar.component';
|
import { ThemedSearchSidebarComponent } from './search-sidebar/themed-search-sidebar.component';
|
||||||
|
|
||||||
const COMPONENTS = [
|
const COMPONENTS = [
|
||||||
SearchComponent,
|
SearchComponent,
|
||||||
ThemedSearchComponent,
|
ThemedSearchComponent,
|
||||||
@@ -60,6 +60,7 @@ const COMPONENTS = [
|
|||||||
ThemedConfigurationSearchPageComponent,
|
ThemedConfigurationSearchPageComponent,
|
||||||
ThemedSearchResultsComponent,
|
ThemedSearchResultsComponent,
|
||||||
ThemedSearchSettingsComponent,
|
ThemedSearchSettingsComponent,
|
||||||
|
AdvancedSearchComponent,
|
||||||
ThemedSearchFiltersComponent,
|
ThemedSearchFiltersComponent,
|
||||||
ThemedSearchSidebarComponent,
|
ThemedSearchSidebarComponent,
|
||||||
];
|
];
|
||||||
|
@@ -49,7 +49,7 @@ export function stripOperatorFromFilterValue(value: string) {
|
|||||||
* @param operator
|
* @param operator
|
||||||
*/
|
*/
|
||||||
export function addOperatorToFilterValue(value: string, operator: string) {
|
export function addOperatorToFilterValue(value: string, operator: string) {
|
||||||
if (!value.match(new RegExp(`^.+,(equals|query|authority)$`))) {
|
if (!value.match(new RegExp(`^.+,(equals|query|authority|contains|notcontains|notequals)$`))) {
|
||||||
return `${value},${operator}`;
|
return `${value},${operator}`;
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
|
@@ -3,4 +3,4 @@
|
|||||||
<select id="{{id}}" class="form-control" (change)="change.emit($event)">
|
<select id="{{id}}" class="form-control" (change)="change.emit($event)">
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
@@ -5843,6 +5843,8 @@
|
|||||||
|
|
||||||
"admin.system-wide-alert.title": "System-wide Alerts",
|
"admin.system-wide-alert.title": "System-wide Alerts",
|
||||||
|
|
||||||
|
"discover.filters.head": "Discover",
|
||||||
|
|
||||||
"item-access-control-title": "This form allows you to perform changes to the access conditions of the item's metadata or its bitstreams.",
|
"item-access-control-title": "This form allows you to perform changes to the access conditions of the item's metadata or its bitstreams.",
|
||||||
|
|
||||||
"collection-access-control-title": "This form allows you to perform changes to the access conditions of all the items owned by this collection. Changes may be performed to either all Item metadata or all content (bitstreams).",
|
"collection-access-control-title": "This form allows you to perform changes to the access conditions of all the items owned by this collection. Changes may be performed to either all Item metadata or all content (bitstreams).",
|
||||||
@@ -5910,4 +5912,32 @@
|
|||||||
"admin.notifications.publicationclaim.breadcrumbs": "Publication Claim",
|
"admin.notifications.publicationclaim.breadcrumbs": "Publication Claim",
|
||||||
|
|
||||||
"admin.notifications.publicationclaim.page.title": "Publication Claim",
|
"admin.notifications.publicationclaim.page.title": "Publication Claim",
|
||||||
}
|
|
||||||
|
"filter.search.operator.placeholder": "Operator",
|
||||||
|
|
||||||
|
"search.filters.filter.entityType.text": "Item Type",
|
||||||
|
|
||||||
|
"search.filters.operator.equals.text": "Equals",
|
||||||
|
|
||||||
|
"search.filters.operator.notequals.text": "Not Equals",
|
||||||
|
|
||||||
|
"search.filters.operator.notcontains.text": "Not Contains",
|
||||||
|
|
||||||
|
"search.filters.operator.contains.text": "Contains",
|
||||||
|
|
||||||
|
"search.filters.filter.title.text": "Title",
|
||||||
|
|
||||||
|
"search.filters.applied.f.title": "Title",
|
||||||
|
|
||||||
|
"search.filters.filter.author.text": "Author",
|
||||||
|
|
||||||
|
"search.filters.filter.subject.text": "Subject",
|
||||||
|
|
||||||
|
"search.advanced.filters.head": "Advanced Search",
|
||||||
|
|
||||||
|
"filter.search.operator.placeholder": "Operator",
|
||||||
|
|
||||||
|
"filter.search.text.placeholder": "Search text",
|
||||||
|
|
||||||
|
"advancesearch.form.submit": "Add",
|
||||||
|
}
|
4
src/config/advance-search-config.interface.ts
Normal file
4
src/config/advance-search-config.interface.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export interface AdvancedSearchConfig {
|
||||||
|
enabled: boolean;
|
||||||
|
filter: string[];
|
||||||
|
}
|
@@ -25,7 +25,7 @@ import { MarkdownConfig } from './markdown-config.interface';
|
|||||||
import { FilterVocabularyConfig } from './filter-vocabulary-config';
|
import { FilterVocabularyConfig } from './filter-vocabulary-config';
|
||||||
import { DiscoverySortConfig } from './discovery-sort.config';
|
import { DiscoverySortConfig } from './discovery-sort.config';
|
||||||
import { QualityAssuranceConfig } from './quality-assurance.config';
|
import { QualityAssuranceConfig } from './quality-assurance.config';
|
||||||
|
import { SearchConfig } from './search-page-config.interface';
|
||||||
interface AppConfig extends Config {
|
interface AppConfig extends Config {
|
||||||
ui: UIServerConfig;
|
ui: UIServerConfig;
|
||||||
rest: ServerConfig;
|
rest: ServerConfig;
|
||||||
@@ -54,6 +54,7 @@ interface AppConfig extends Config {
|
|||||||
vocabularies: FilterVocabularyConfig[];
|
vocabularies: FilterVocabularyConfig[];
|
||||||
comcolSelectionSort: DiscoverySortConfig;
|
comcolSelectionSort: DiscoverySortConfig;
|
||||||
qualityAssuranceConfig: QualityAssuranceConfig;
|
qualityAssuranceConfig: QualityAssuranceConfig;
|
||||||
|
search: SearchConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -24,8 +24,8 @@ import { MarkdownConfig } from './markdown-config.interface';
|
|||||||
import { FilterVocabularyConfig } from './filter-vocabulary-config';
|
import { FilterVocabularyConfig } from './filter-vocabulary-config';
|
||||||
import { DiscoverySortConfig } from './discovery-sort.config';
|
import { DiscoverySortConfig } from './discovery-sort.config';
|
||||||
import { CommunityPageConfig } from './community-page-config.interface';
|
import { CommunityPageConfig } from './community-page-config.interface';
|
||||||
import {QualityAssuranceConfig} from './quality-assurance.config';
|
import { QualityAssuranceConfig } from './quality-assurance.config';
|
||||||
|
import { SearchConfig } from './search-page-config.interface';
|
||||||
export class DefaultAppConfig implements AppConfig {
|
export class DefaultAppConfig implements AppConfig {
|
||||||
production = false;
|
production = false;
|
||||||
|
|
||||||
@@ -306,7 +306,8 @@ export class DefaultAppConfig implements AppConfig {
|
|||||||
},
|
},
|
||||||
topLevelCommunityList: {
|
topLevelCommunityList: {
|
||||||
pageSize: 5
|
pageSize: 5
|
||||||
}
|
},
|
||||||
|
showDiscoverFilters: false
|
||||||
};
|
};
|
||||||
|
|
||||||
// Item Config
|
// Item Config
|
||||||
@@ -497,4 +498,12 @@ export class DefaultAppConfig implements AppConfig {
|
|||||||
},
|
},
|
||||||
pageSize: 5,
|
pageSize: 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
search: SearchConfig = {
|
||||||
|
advancedFilters: {
|
||||||
|
enabled: false,
|
||||||
|
filter: ['title', 'author', 'subject', 'entityType']
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { Config } from './config.interface';
|
import { Config } from './config.interface';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Config that determines how the dropdown list of years are created for browse-by-date components
|
* Config that determines how the recentSubmissions list showing at home page
|
||||||
*/
|
*/
|
||||||
export interface HomeConfig extends Config {
|
export interface HomeConfig extends Config {
|
||||||
recentSubmissions: {
|
recentSubmissions: {
|
||||||
@@ -19,4 +19,8 @@ export interface HomeConfig extends Config {
|
|||||||
topLevelCommunityList: {
|
topLevelCommunityList: {
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
};
|
};
|
||||||
|
/*
|
||||||
|
* Enable or disable the Discover filters on the homepage
|
||||||
|
*/
|
||||||
|
showDiscoverFilters: boolean;
|
||||||
}
|
}
|
||||||
|
11
src/config/search-page-config.interface.ts
Normal file
11
src/config/search-page-config.interface.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Config } from './config.interface';
|
||||||
|
import { AdvancedSearchConfig } from './advance-search-config.interface';
|
||||||
|
export interface SearchConfig extends Config {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of standard filter to select in adding advanced Search
|
||||||
|
* Used by {@link UploadBitstreamComponent}.
|
||||||
|
*/
|
||||||
|
advancedFilters: AdvancedSearchConfig;
|
||||||
|
|
||||||
|
}
|
@@ -246,7 +246,8 @@ export const environment: BuildConfig = {
|
|||||||
},
|
},
|
||||||
topLevelCommunityList: {
|
topLevelCommunityList: {
|
||||||
pageSize: 5
|
pageSize: 5
|
||||||
}
|
},
|
||||||
|
showDiscoverFilters: false
|
||||||
},
|
},
|
||||||
item: {
|
item: {
|
||||||
edit: {
|
edit: {
|
||||||
@@ -333,5 +334,13 @@ export const environment: BuildConfig = {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
suggestion: []
|
suggestion: [],
|
||||||
|
|
||||||
|
search: {
|
||||||
|
advancedFilters: {
|
||||||
|
enabled: false,
|
||||||
|
filter: ['title', 'author', 'subject', 'entityType']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user