mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge remote-tracking branch 'origin/main' into coar-notify-7
This commit is contained in:
@@ -285,8 +285,17 @@ item:
|
|||||||
# settings menu. See pageSizeOptions in 'pagination-component-options.model.ts'.
|
# settings menu. See pageSizeOptions in 'pagination-component-options.model.ts'.
|
||||||
pageSize: 5
|
pageSize: 5
|
||||||
|
|
||||||
|
# Community Page Config
|
||||||
|
community:
|
||||||
|
# Search tab config
|
||||||
|
searchSection:
|
||||||
|
showSidebar: true
|
||||||
|
|
||||||
# Collection Page Config
|
# Collection Page Config
|
||||||
collection:
|
collection:
|
||||||
|
# Search tab config
|
||||||
|
searchSection:
|
||||||
|
showSidebar: true
|
||||||
edit:
|
edit:
|
||||||
undoTimeout: 10000 # 10 seconds
|
undoTimeout: 10000 # 10 seconds
|
||||||
|
|
||||||
@@ -391,4 +400,3 @@ comcolSelectionSort:
|
|||||||
# suggestion:
|
# suggestion:
|
||||||
# - collectionId: 8f7df5ca-f9c2-47a4-81ec-8a6393d6e5af
|
# - collectionId: 8f7df5ca-f9c2-47a4-81ec-8a6393d6e5af
|
||||||
# source: "openaire"
|
# source: "openaire"
|
||||||
|
|
||||||
|
@@ -25,7 +25,7 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
|||||||
import { ComcolBrowseByComponent } from '../shared/comcol/sections/comcol-browse-by/comcol-browse-by.component';
|
import { ComcolBrowseByComponent } from '../shared/comcol/sections/comcol-browse-by/comcol-browse-by.component';
|
||||||
import { BrowseByGuard } from '../browse-by/browse-by-guard';
|
import { BrowseByGuard } from '../browse-by/browse-by-guard';
|
||||||
import { BrowseByI18nBreadcrumbResolver } from '../browse-by/browse-by-i18n-breadcrumb.resolver';
|
import { BrowseByI18nBreadcrumbResolver } from '../browse-by/browse-by-i18n-breadcrumb.resolver';
|
||||||
import { CollectionRecentlyAddedComponent } from './sections/recently-added/collection-recently-added.component';
|
import { ComcolSearchSectionComponent } from '../shared/comcol/sections/comcol-search-section/comcol-search-section.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -73,7 +73,7 @@ import { CollectionRecentlyAddedComponent } from './sections/recently-added/coll
|
|||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
component: CollectionRecentlyAddedComponent,
|
component: ComcolSearchSectionComponent,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'browse/:id',
|
path: 'browse/:id',
|
||||||
|
@@ -19,7 +19,6 @@ import { ComcolModule } from '../shared/comcol/comcol.module';
|
|||||||
import { DsoSharedModule } from '../dso-shared/dso-shared.module';
|
import { DsoSharedModule } from '../dso-shared/dso-shared.module';
|
||||||
import { DsoPageModule } from '../shared/dso-page/dso-page.module';
|
import { DsoPageModule } from '../shared/dso-page/dso-page.module';
|
||||||
import { BrowseByPageModule } from '../browse-by/browse-by-page.module';
|
import { BrowseByPageModule } from '../browse-by/browse-by-page.module';
|
||||||
import { CollectionRecentlyAddedComponent } from './sections/recently-added/collection-recently-added.component';
|
|
||||||
|
|
||||||
const DECLARATIONS = [
|
const DECLARATIONS = [
|
||||||
CollectionPageComponent,
|
CollectionPageComponent,
|
||||||
@@ -29,7 +28,6 @@ const DECLARATIONS = [
|
|||||||
EditItemTemplatePageComponent,
|
EditItemTemplatePageComponent,
|
||||||
ThemedEditItemTemplatePageComponent,
|
ThemedEditItemTemplatePageComponent,
|
||||||
CollectionItemMapperComponent,
|
CollectionItemMapperComponent,
|
||||||
CollectionRecentlyAddedComponent,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@@ -1,18 +0,0 @@
|
|||||||
<ng-container *ngVar="(itemRD$ | async) as itemRD">
|
|
||||||
<div class="mt-4" *ngIf="itemRD?.hasSucceeded" @fadeIn>
|
|
||||||
<h3 class="sr-only">{{'collection.page.browse.recent.head' | translate}}</h3>
|
|
||||||
<ds-viewable-collection
|
|
||||||
[config]="paginationConfig"
|
|
||||||
[sortConfig]="sortConfig"
|
|
||||||
[objects]="itemRD"
|
|
||||||
[hideGear]="true">
|
|
||||||
</ds-viewable-collection>
|
|
||||||
</div>
|
|
||||||
<ds-error *ngIf="itemRD?.hasFailed"
|
|
||||||
message="{{'error.recent-submissions' | translate}}"></ds-error>
|
|
||||||
<ds-themed-loading *ngIf="!itemRD || itemRD.isLoading"
|
|
||||||
message="{{'loading.recent-submissions' | translate}}"></ds-themed-loading>
|
|
||||||
<div *ngIf="!itemRD?.isLoading && itemRD?.payload?.page.length === 0" class="alert alert-info w-100" role="alert">
|
|
||||||
{{'collection.page.browse.recent.empty' | translate}}
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
@@ -1,53 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
import { CollectionRecentlyAddedComponent } from './collection-recently-added.component';
|
|
||||||
import { APP_CONFIG } from '../../../../config/app-config.interface';
|
|
||||||
import { environment } from '../../../../environments/environment.test';
|
|
||||||
import { ActivatedRoute } from '@angular/router';
|
|
||||||
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
|
||||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
|
||||||
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
|
||||||
import { SearchServiceStub } from '../../../shared/testing/search-service.stub';
|
|
||||||
import { SearchService } from '../../../core/shared/search/search.service';
|
|
||||||
import { VarDirective } from '../../../shared/utils/var.directive';
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|
||||||
|
|
||||||
describe('CollectionRecentlyAddedComponent', () => {
|
|
||||||
let component: CollectionRecentlyAddedComponent;
|
|
||||||
let fixture: ComponentFixture<CollectionRecentlyAddedComponent>;
|
|
||||||
|
|
||||||
let activatedRoute: ActivatedRouteStub;
|
|
||||||
let paginationService: PaginationServiceStub;
|
|
||||||
let searchService: SearchServiceStub;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
activatedRoute = new ActivatedRouteStub();
|
|
||||||
paginationService = new PaginationServiceStub();
|
|
||||||
searchService = new SearchServiceStub();
|
|
||||||
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
declarations: [
|
|
||||||
CollectionRecentlyAddedComponent,
|
|
||||||
VarDirective,
|
|
||||||
],
|
|
||||||
imports: [
|
|
||||||
TranslateModule.forRoot(),
|
|
||||||
],
|
|
||||||
providers: [
|
|
||||||
{ provide: ActivatedRoute, useValue: activatedRoute },
|
|
||||||
{ provide: APP_CONFIG, useValue: environment },
|
|
||||||
{ provide: PaginationService, useValue: paginationService },
|
|
||||||
{ provide: SearchService, useValue: SearchServiceStub },
|
|
||||||
],
|
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
||||||
}).compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(CollectionRecentlyAddedComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,82 +0,0 @@
|
|||||||
import { Component, OnInit, Inject, OnDestroy } from '@angular/core';
|
|
||||||
import { Observable, combineLatest as observableCombineLatest } from 'rxjs';
|
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
|
||||||
import { Item } from '../../../core/shared/item.model';
|
|
||||||
import { switchMap, map, startWith, take } from 'rxjs/operators';
|
|
||||||
import { getFirstSucceededRemoteData, toDSpaceObjectListRD } from '../../../core/shared/operators';
|
|
||||||
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
|
|
||||||
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
|
|
||||||
import { BROWSE_LINKS_TO_FOLLOW } from '../../../core/browse/browse.service';
|
|
||||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
|
||||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
|
||||||
import { SortOptions, SortDirection } from '../../../core/cache/models/sort-options.model';
|
|
||||||
import { APP_CONFIG, AppConfig } from '../../../../config/app-config.interface';
|
|
||||||
import { SearchService } from '../../../core/shared/search/search.service';
|
|
||||||
import { Collection } from '../../../core/shared/collection.model';
|
|
||||||
import { ActivatedRoute, Data } from '@angular/router';
|
|
||||||
import { fadeIn } from '../../../shared/animations/fade';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'ds-collection-recently-added',
|
|
||||||
templateUrl: './collection-recently-added.component.html',
|
|
||||||
styleUrls: ['./collection-recently-added.component.scss'],
|
|
||||||
animations: [fadeIn],
|
|
||||||
})
|
|
||||||
export class CollectionRecentlyAddedComponent implements OnInit, OnDestroy {
|
|
||||||
|
|
||||||
paginationConfig: PaginationComponentOptions;
|
|
||||||
|
|
||||||
sortConfig: SortOptions;
|
|
||||||
|
|
||||||
collectionRD$: Observable<RemoteData<Collection>>;
|
|
||||||
|
|
||||||
itemRD$: Observable<RemoteData<PaginatedList<Item>>>;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
|
||||||
protected paginationService: PaginationService,
|
|
||||||
protected route: ActivatedRoute,
|
|
||||||
protected searchService: SearchService,
|
|
||||||
) {
|
|
||||||
this.paginationConfig = Object.assign(new PaginationComponentOptions(), {
|
|
||||||
id: 'cp',
|
|
||||||
currentPage: 1,
|
|
||||||
pageSize: this.appConfig.browseBy.pageSize,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.sortConfig = new SortOptions('dc.date.accessioned', SortDirection.DESC);
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.collectionRD$ = this.route.data.pipe(
|
|
||||||
map((data: Data) => data.dso as RemoteData<Collection>),
|
|
||||||
take(1),
|
|
||||||
);
|
|
||||||
|
|
||||||
this.itemRD$ = observableCombineLatest([
|
|
||||||
this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig),
|
|
||||||
this.paginationService.getCurrentSort(this.paginationConfig.id, this.sortConfig),
|
|
||||||
]).pipe(
|
|
||||||
switchMap(([currentPagination, currentSort]: [PaginationComponentOptions, SortOptions]) => this.collectionRD$.pipe(
|
|
||||||
getFirstSucceededRemoteData(),
|
|
||||||
map((rd: RemoteData<Collection>) => rd.payload.id),
|
|
||||||
switchMap((id: string) => this.searchService.search<Item>(
|
|
||||||
new PaginatedSearchOptions({
|
|
||||||
scope: id,
|
|
||||||
pagination: currentPagination,
|
|
||||||
sort: currentSort,
|
|
||||||
dsoTypes: [DSpaceObjectType.ITEM]
|
|
||||||
}), null, true, true, ...BROWSE_LINKS_TO_FOLLOW).pipe(
|
|
||||||
toDSpaceObjectListRD()
|
|
||||||
) as Observable<RemoteData<PaginatedList<Item>>>),
|
|
||||||
startWith(undefined), // Make sure switching pages shows loading component
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
this.paginationService.clearPagination(this.paginationConfig.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -19,6 +19,8 @@ import { SubComColSectionComponent } from './sections/sub-com-col-section/sub-co
|
|||||||
import { BrowseByI18nBreadcrumbResolver } from '../browse-by/browse-by-i18n-breadcrumb.resolver';
|
import { BrowseByI18nBreadcrumbResolver } from '../browse-by/browse-by-i18n-breadcrumb.resolver';
|
||||||
import { BrowseByGuard } from '../browse-by/browse-by-guard';
|
import { BrowseByGuard } from '../browse-by/browse-by-guard';
|
||||||
import { ComcolBrowseByComponent } from '../shared/comcol/sections/comcol-browse-by/comcol-browse-by.component';
|
import { ComcolBrowseByComponent } from '../shared/comcol/sections/comcol-browse-by/comcol-browse-by.component';
|
||||||
|
import { ComcolSearchSectionComponent } from '../shared/comcol/sections/comcol-search-section/comcol-search-section.component';
|
||||||
|
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -56,7 +58,16 @@ import { ComcolBrowseByComponent } from '../shared/comcol/sections/comcol-browse
|
|||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
|
component: ComcolSearchSectionComponent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'subcoms-cols',
|
||||||
|
pathMatch: 'full',
|
||||||
component: SubComColSectionComponent,
|
component: SubComColSectionComponent,
|
||||||
|
resolve: {
|
||||||
|
breadcrumb: I18nBreadcrumbResolver,
|
||||||
|
},
|
||||||
|
data: { breadcrumbKey: 'community.subcoms-cols' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'browse/:id',
|
path: 'browse/:id',
|
||||||
|
@@ -11,6 +11,7 @@ describe('SubComColSectionComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
activatedRoute = new ActivatedRouteStub();
|
activatedRoute = new ActivatedRouteStub();
|
||||||
|
activatedRoute.parent = new ActivatedRouteStub();
|
||||||
|
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@@ -20,7 +20,7 @@ export class SubComColSectionComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.community$ = this.route.data.pipe(
|
this.community$ = this.route.parent.data.pipe(
|
||||||
map((data: Data) => (data.dso as RemoteData<Community>).payload),
|
map((data: Data) => (data.dso as RemoteData<Community>).payload),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,27 +1,36 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
|
||||||
|
|
||||||
import { of, forkJoin, Observable } from 'rxjs';
|
import { of, forkJoin, Observable } from 'rxjs';
|
||||||
import { catchError, map, mergeMap, take } from 'rxjs/operators';
|
import { catchError, map, mergeMap, take } from 'rxjs/operators';
|
||||||
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
|
||||||
import { FindListOptions } from '../core/data/find-list-options.model';
|
|
||||||
import { PaginatedList } from '../core/data/paginated-list.model';
|
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
|
||||||
import { SuggestionTarget } from '../core/notifications/models/suggestion-target.model';
|
|
||||||
import { SuggestionsDataService } from '../core/notifications/suggestions-data.service';
|
|
||||||
import { SuggestionTargetDataService } from '../core/notifications/target/suggestion-target-data.service';
|
|
||||||
import { ResearcherProfileDataService } from '../core/profile/researcher-profile-data.service';
|
|
||||||
import { NoContent } from '../core/shared/NoContent.model';
|
|
||||||
import { getFirstSucceededRemoteListPayload, getFirstSucceededRemoteDataPayload, getAllSucceededRemoteDataPayload, getFinishedRemoteData } from '../core/shared/operators';
|
|
||||||
import { getSuggestionPageRoute } from '../suggestions-page/suggestions-page-routing-paths';
|
|
||||||
import { environment } from '../../environments/environment';
|
|
||||||
import { SuggestionConfig } from '../../config/suggestion-config.interfaces';
|
|
||||||
import { Suggestion } from '../core/notifications/models/suggestion.model';
|
|
||||||
import { ResearcherProfile } from '../core/profile/model/researcher-profile.model';
|
|
||||||
import { hasValue, isNotEmpty } from '../shared/empty.util';
|
|
||||||
import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service';
|
|
||||||
import { WorkspaceItem } from '../core/submission/models/workspaceitem.model';
|
|
||||||
|
|
||||||
|
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
||||||
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
|
import { PaginatedList } from '../core/data/paginated-list.model';
|
||||||
|
import { SuggestionTarget } from '../core/suggestion-notifications/models/suggestion-target.model';
|
||||||
|
import { hasValue, isNotEmpty } from '../shared/empty.util';
|
||||||
|
import { ResearcherProfile } from '../core/profile/model/researcher-profile.model';
|
||||||
|
import {
|
||||||
|
getAllSucceededRemoteDataPayload,
|
||||||
|
getFinishedRemoteData, getFirstCompletedRemoteData,
|
||||||
|
getFirstSucceededRemoteDataPayload,
|
||||||
|
getFirstSucceededRemoteListPayload
|
||||||
|
} from '../core/shared/operators';
|
||||||
|
import { Suggestion } from '../core/suggestion-notifications/models/suggestion.model';
|
||||||
|
import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { NoContent } from '../core/shared/NoContent.model';
|
||||||
|
import { environment } from '../../environments/environment';
|
||||||
|
import { WorkspaceItem } from '../core/submission/models/workspaceitem.model';
|
||||||
|
import {FindListOptions} from '../core/data/find-list-options.model';
|
||||||
|
import {SuggestionConfig} from '../../config/suggestion-config.interfaces';
|
||||||
|
import { ResearcherProfileDataService } from '../core/profile/researcher-profile-data.service';
|
||||||
|
import {
|
||||||
|
SuggestionTargetDataService
|
||||||
|
} from '../core/suggestion-notifications/target/suggestion-target-data.service';
|
||||||
|
import {
|
||||||
|
SuggestionsDataService
|
||||||
|
} from '../core/suggestion-notifications/suggestions-data.service';
|
||||||
|
import { getSuggestionPageRoute } from '../suggestions-page/suggestions-page-routing-paths';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* useful for multiple approvals and ignores operation
|
* useful for multiple approvals and ignores operation
|
||||||
@@ -146,10 +155,10 @@ export class SuggestionsService {
|
|||||||
*/
|
*/
|
||||||
public retrieveCurrentUserSuggestions(userUuid: string): Observable<SuggestionTarget[]> {
|
public retrieveCurrentUserSuggestions(userUuid: string): Observable<SuggestionTarget[]> {
|
||||||
return this.researcherProfileService.findById(userUuid, true).pipe(
|
return this.researcherProfileService.findById(userUuid, true).pipe(
|
||||||
getFirstSucceededRemoteDataPayload(),
|
getFirstCompletedRemoteData(),
|
||||||
mergeMap((profile: ResearcherProfile) => {
|
mergeMap((profile: RemoteData<ResearcherProfile> ) => {
|
||||||
if (isNotEmpty(profile)) {
|
if (isNotEmpty(profile) && profile.hasSucceeded && isNotEmpty(profile.payload)) {
|
||||||
return this.researcherProfileService.findRelatedItemId(profile).pipe(
|
return this.researcherProfileService.findRelatedItemId(profile.payload).pipe(
|
||||||
mergeMap((itemId: string) => {
|
mergeMap((itemId: string) => {
|
||||||
return this.suggestionsDataService.getTargetsByUser(itemId).pipe(
|
return this.suggestionsDataService.getTargetsByUser(itemId).pipe(
|
||||||
getFirstSucceededRemoteListPayload()
|
getFirstSucceededRemoteListPayload()
|
||||||
@@ -160,7 +169,7 @@ export class SuggestionsService {
|
|||||||
return of([]);
|
return of([]);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
take(1)
|
catchError(() => of([]))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -88,7 +88,7 @@ describe('SearchNavbarComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
}));
|
}));
|
||||||
it('to search page with empty query', () => {
|
it('to search page with empty query', () => {
|
||||||
const extras: NavigationExtras = { queryParams: { query: '' }, queryParamsHandling: 'merge' };
|
const extras: NavigationExtras = { queryParams: { query: '' } };
|
||||||
expect(component.onSubmit).toHaveBeenCalledWith({ query: '' });
|
expect(component.onSubmit).toHaveBeenCalledWith({ query: '' });
|
||||||
expect(router.navigate).toHaveBeenCalledWith(['search'], extras);
|
expect(router.navigate).toHaveBeenCalledWith(['search'], extras);
|
||||||
});
|
});
|
||||||
@@ -113,7 +113,7 @@ describe('SearchNavbarComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
}));
|
}));
|
||||||
it('to search page with query', async () => {
|
it('to search page with query', async () => {
|
||||||
const extras: NavigationExtras = { queryParams: { query: 'test' }, queryParamsHandling: 'merge' };
|
const extras: NavigationExtras = { queryParams: { query: 'test' } };
|
||||||
expect(component.onSubmit).toHaveBeenCalledWith({ query: 'test' });
|
expect(component.onSubmit).toHaveBeenCalledWith({ query: 'test' });
|
||||||
|
|
||||||
expect(router.navigate).toHaveBeenCalledWith(['search'], extras);
|
expect(router.navigate).toHaveBeenCalledWith(['search'], extras);
|
||||||
|
@@ -66,8 +66,7 @@ export class SearchNavbarComponent {
|
|||||||
this.searchForm.reset();
|
this.searchForm.reset();
|
||||||
|
|
||||||
this.router.navigate(linkToNavigateTo, {
|
this.router.navigate(linkToNavigateTo, {
|
||||||
queryParams: queryParams,
|
queryParams: queryParams
|
||||||
queryParamsHandling: 'merge'
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,6 +8,7 @@ import { SearchConfigurationService } from '../core/shared/search/search-configu
|
|||||||
import { RouteService } from '../core/services/route.service';
|
import { RouteService } from '../core/services/route.service';
|
||||||
import { SearchService } from '../core/shared/search/search.service';
|
import { SearchService } from '../core/shared/search/search.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
import { APP_CONFIG, AppConfig } from '../../config/app-config.interface';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders a search page using a configuration as input.
|
* This component renders a search page using a configuration as input.
|
||||||
@@ -32,7 +33,9 @@ export class ConfigurationSearchPageComponent extends SearchComponent {
|
|||||||
protected windowService: HostWindowService,
|
protected windowService: HostWindowService,
|
||||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
||||||
protected routeService: RouteService,
|
protected routeService: RouteService,
|
||||||
protected router: Router) {
|
protected router: Router,
|
||||||
super(service, sidebarService, windowService, searchConfigService, routeService, router);
|
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||||
|
) {
|
||||||
|
super(service, sidebarService, windowService, searchConfigService, routeService, router, appConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -55,16 +55,21 @@ export class ComcolPageBrowseByComponent implements OnDestroy, OnInit {
|
|||||||
if (this.contentType === 'collection') {
|
if (this.contentType === 'collection') {
|
||||||
comColRoute = getCollectionPageRoute(this.id);
|
comColRoute = getCollectionPageRoute(this.id);
|
||||||
allOptions.push({
|
allOptions.push({
|
||||||
id: 'recent_submissions',
|
id: 'search',
|
||||||
label: 'collection.page.browse.recent.head',
|
label: 'collection.page.browse.search.head',
|
||||||
routerLink: comColRoute,
|
routerLink: comColRoute,
|
||||||
});
|
});
|
||||||
} else if (this.contentType === 'community') {
|
} else if (this.contentType === 'community') {
|
||||||
comColRoute = getCommunityPageRoute(this.id);
|
comColRoute = getCommunityPageRoute(this.id);
|
||||||
|
allOptions.push({
|
||||||
|
id: 'search',
|
||||||
|
label: 'collection.page.browse.search.head',
|
||||||
|
routerLink: comColRoute,
|
||||||
|
});
|
||||||
allOptions.push({
|
allOptions.push({
|
||||||
id: 'comcols',
|
id: 'comcols',
|
||||||
label: 'community.all-lists.head',
|
label: 'community.all-lists.head',
|
||||||
routerLink: comColRoute,
|
routerLink: `${comColRoute}/subcoms-cols`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -18,6 +18,8 @@ import { FormModule } from '../form/form.module';
|
|||||||
import { UploadModule } from '../upload/upload.module';
|
import { UploadModule } from '../upload/upload.module';
|
||||||
import { ComcolBrowseByComponent } from './sections/comcol-browse-by/comcol-browse-by.component';
|
import { ComcolBrowseByComponent } from './sections/comcol-browse-by/comcol-browse-by.component';
|
||||||
import { BrowseByModule } from '../../browse-by/browse-by.module';
|
import { BrowseByModule } from '../../browse-by/browse-by.module';
|
||||||
|
import { SearchModule } from '../search/search.module';
|
||||||
|
import { ComcolSearchSectionComponent } from './sections/comcol-search-section/comcol-search-section.component';
|
||||||
|
|
||||||
const COMPONENTS = [
|
const COMPONENTS = [
|
||||||
ComcolPageContentComponent,
|
ComcolPageContentComponent,
|
||||||
@@ -33,6 +35,7 @@ const COMPONENTS = [
|
|||||||
ComcolRoleComponent,
|
ComcolRoleComponent,
|
||||||
ThemedComcolPageHandleComponent,
|
ThemedComcolPageHandleComponent,
|
||||||
ComcolBrowseByComponent,
|
ComcolBrowseByComponent,
|
||||||
|
ComcolSearchSectionComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -45,6 +48,7 @@ const COMPONENTS = [
|
|||||||
SharedModule,
|
SharedModule,
|
||||||
UploadModule,
|
UploadModule,
|
||||||
BrowseByModule,
|
BrowseByModule,
|
||||||
|
SearchModule,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
...COMPONENTS,
|
...COMPONENTS,
|
||||||
|
@@ -0,0 +1,7 @@
|
|||||||
|
<ds-themed-search
|
||||||
|
[configuration]="(comcol$ | async)?.type"
|
||||||
|
[showSidebar]="showSidebar$ | async"
|
||||||
|
[showScopeSelector]="false"
|
||||||
|
[hideScopeInUrl]="true"
|
||||||
|
[scope]="(comcol$ | async)?.id">
|
||||||
|
</ds-themed-search>
|
@@ -0,0 +1,35 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { ComcolSearchSectionComponent } from './comcol-search-section.component';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { ActivatedRouteStub } from '../../../testing/active-router.stub';
|
||||||
|
import { APP_CONFIG } from '../../../../../config/app-config.interface';
|
||||||
|
import { environment } from '../../../../../environments/environment.test';
|
||||||
|
|
||||||
|
describe('ComcolSearchSectionComponent', () => {
|
||||||
|
let component: ComcolSearchSectionComponent;
|
||||||
|
let fixture: ComponentFixture<ComcolSearchSectionComponent>;
|
||||||
|
|
||||||
|
let route: ActivatedRouteStub;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
route = new ActivatedRouteStub();
|
||||||
|
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [
|
||||||
|
ComcolSearchSectionComponent,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: APP_CONFIG, useValue: environment },
|
||||||
|
{ provide: ActivatedRoute, useValue: route },
|
||||||
|
],
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ComcolSearchSectionComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,48 @@
|
|||||||
|
import { Component, OnInit, Inject } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { ActivatedRoute, Data } from '@angular/router';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { SEARCH_CONFIG_SERVICE } from '../../../../my-dspace-page/my-dspace-page.component';
|
||||||
|
import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service';
|
||||||
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
|
import { Collection } from '../../../../core/shared/collection.model';
|
||||||
|
import { APP_CONFIG, AppConfig } from '../../../../../config/app-config.interface';
|
||||||
|
import { hasValue } from '../../../empty.util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The search tab on community & collection pages
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-comcol-search-section',
|
||||||
|
templateUrl: './comcol-search-section.component.html',
|
||||||
|
styleUrls: ['./comcol-search-section.component.scss'],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: SEARCH_CONFIG_SERVICE,
|
||||||
|
useClass: SearchConfigurationService,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class ComcolSearchSectionComponent implements OnInit {
|
||||||
|
|
||||||
|
comcol$: Observable<Community | Collection>;
|
||||||
|
|
||||||
|
showSidebar$: Observable<boolean>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(APP_CONFIG) public appConfig: AppConfig,
|
||||||
|
protected route: ActivatedRoute,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.comcol$ = this.route.data.pipe(
|
||||||
|
map((data: Data) => (data.dso as RemoteData<Community | Collection>).payload),
|
||||||
|
);
|
||||||
|
this.showSidebar$ = this.comcol$.pipe(
|
||||||
|
map((comcol: Community | Collection) => hasValue(comcol) && this.appConfig[comcol.type as any].searchSection.showSidebar),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,7 +1,7 @@
|
|||||||
import { Component, EventEmitter, Input, Output, OnChanges } from '@angular/core';
|
import { Component, EventEmitter, Input, Output, OnChanges } from '@angular/core';
|
||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { isNotEmpty } from '../empty.util';
|
import { isNotEmpty, hasValue } from '../empty.util';
|
||||||
import { SearchService } from '../../core/shared/search/search.service';
|
import { SearchService } from '../../core/shared/search/search.service';
|
||||||
import { currentPath } from '../utils/route.utils';
|
import { currentPath } from '../utils/route.utils';
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||||
@@ -39,6 +39,11 @@ export class SearchFormComponent implements OnChanges {
|
|||||||
@Input()
|
@Input()
|
||||||
scope = '';
|
scope = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides the scope in the url, this can be useful when you hardcode the scope in another way
|
||||||
|
*/
|
||||||
|
@Input() hideScopeInUrl = false;
|
||||||
|
|
||||||
selectedScope: BehaviorSubject<DSpaceObject> = new BehaviorSubject<DSpaceObject>(undefined);
|
selectedScope: BehaviorSubject<DSpaceObject> = new BehaviorSubject<DSpaceObject>(undefined);
|
||||||
|
|
||||||
@Input() currentUrl: string;
|
@Input() currentUrl: string;
|
||||||
@@ -122,6 +127,9 @@ export class SearchFormComponent implements OnChanges {
|
|||||||
},
|
},
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
|
if (hasValue(data.scope) && this.hideScopeInUrl) {
|
||||||
|
delete queryParams.scope;
|
||||||
|
}
|
||||||
|
|
||||||
void this.router.navigate(this.getSearchLinkParts(), {
|
void this.router.navigate(this.getSearchLinkParts(), {
|
||||||
queryParams: queryParams,
|
queryParams: queryParams,
|
||||||
|
@@ -18,6 +18,8 @@ export class ThemedSearchFormComponent extends ThemedComponent<SearchFormCompone
|
|||||||
|
|
||||||
@Input() scope: string;
|
@Input() scope: string;
|
||||||
|
|
||||||
|
@Input() hideScopeInUrl: boolean;
|
||||||
|
|
||||||
@Input() currentUrl: string;
|
@Input() currentUrl: string;
|
||||||
|
|
||||||
@Input() large: boolean;
|
@Input() large: boolean;
|
||||||
@@ -31,7 +33,15 @@ export class ThemedSearchFormComponent extends ThemedComponent<SearchFormCompone
|
|||||||
@Output() submitSearch: EventEmitter<any> = new EventEmitter();
|
@Output() submitSearch: EventEmitter<any> = new EventEmitter();
|
||||||
|
|
||||||
protected inAndOutputNames: (keyof SearchFormComponent & keyof this)[] = [
|
protected inAndOutputNames: (keyof SearchFormComponent & keyof this)[] = [
|
||||||
'query', 'inPlaceSearch', 'scope', 'currentUrl', 'large', 'brandColor', 'searchPlaceholder', 'showScopeSelector',
|
'query',
|
||||||
|
'inPlaceSearch',
|
||||||
|
'scope',
|
||||||
|
'hideScopeInUrl',
|
||||||
|
'currentUrl',
|
||||||
|
'large',
|
||||||
|
'brandColor',
|
||||||
|
'searchPlaceholder',
|
||||||
|
'showScopeSelector',
|
||||||
'submitSearch',
|
'submitSearch',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -27,6 +27,8 @@ import { SearchConfigurationServiceStub } from '../../../../testing/search-confi
|
|||||||
import { VocabularyEntryDetail } from '../../../../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
|
import { VocabularyEntryDetail } from '../../../../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
|
||||||
import { FacetValue} from '../../../models/facet-value.model';
|
import { FacetValue} from '../../../models/facet-value.model';
|
||||||
import { SearchFilterConfig } from '../../../models/search-filter-config.model';
|
import { SearchFilterConfig } from '../../../models/search-filter-config.model';
|
||||||
|
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
|
||||||
|
import { environment } from '../../../../../../environments/environment.test';
|
||||||
|
|
||||||
describe('SearchHierarchyFilterComponent', () => {
|
describe('SearchHierarchyFilterComponent', () => {
|
||||||
|
|
||||||
@@ -34,7 +36,7 @@ describe('SearchHierarchyFilterComponent', () => {
|
|||||||
let showVocabularyTreeLink: DebugElement;
|
let showVocabularyTreeLink: DebugElement;
|
||||||
|
|
||||||
const testSearchLink = 'test-search';
|
const testSearchLink = 'test-search';
|
||||||
const testSearchFilter = 'test-search-filter';
|
const testSearchFilter = 'subject';
|
||||||
const VocabularyTreeViewComponent = {
|
const VocabularyTreeViewComponent = {
|
||||||
select: new EventEmitter<VocabularyEntryDetail>(),
|
select: new EventEmitter<VocabularyEntryDetail>(),
|
||||||
};
|
};
|
||||||
@@ -73,6 +75,7 @@ describe('SearchHierarchyFilterComponent', () => {
|
|||||||
{ provide: Router, useValue: router },
|
{ provide: Router, useValue: router },
|
||||||
{ provide: NgbModal, useValue: ngbModal },
|
{ provide: NgbModal, useValue: ngbModal },
|
||||||
{ provide: VocabularyService, useValue: vocabularyService },
|
{ provide: VocabularyService, useValue: vocabularyService },
|
||||||
|
{ provide: APP_CONFIG, useValue: environment },
|
||||||
{ provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() },
|
{ provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() },
|
||||||
{ provide: IN_PLACE_SEARCH, useValue: false },
|
{ provide: IN_PLACE_SEARCH, useValue: false },
|
||||||
{ provide: FILTER_CONFIG, useValue: Object.assign(new SearchFilterConfig(), { name: testSearchFilter }) },
|
{ provide: FILTER_CONFIG, useValue: Object.assign(new SearchFilterConfig(), { name: testSearchFilter }) },
|
||||||
@@ -86,7 +89,7 @@ describe('SearchHierarchyFilterComponent', () => {
|
|||||||
function init() {
|
function init() {
|
||||||
fixture = TestBed.createComponent(SearchHierarchyFilterComponent);
|
fixture = TestBed.createComponent(SearchHierarchyFilterComponent);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
showVocabularyTreeLink = fixture.debugElement.query(By.css('a#show-test-search-filter-tree'));
|
showVocabularyTreeLink = fixture.debugElement.query(By.css(`a#show-${testSearchFilter}-tree`));
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('if the vocabulary doesn\'t exist', () => {
|
describe('if the vocabulary doesn\'t exist', () => {
|
||||||
|
@@ -24,9 +24,11 @@ import { filter, map, take } from 'rxjs/operators';
|
|||||||
import { VocabularyService } from '../../../../../core/submission/vocabularies/vocabulary.service';
|
import { VocabularyService } from '../../../../../core/submission/vocabularies/vocabulary.service';
|
||||||
import { Observable, BehaviorSubject } from 'rxjs';
|
import { Observable, BehaviorSubject } from 'rxjs';
|
||||||
import { PageInfo } from '../../../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../../../core/shared/page-info.model';
|
||||||
import { environment } from '../../../../../../environments/environment';
|
|
||||||
import { addOperatorToFilterValue } from '../../../search.utils';
|
import { addOperatorToFilterValue } from '../../../search.utils';
|
||||||
import { VocabularyTreeviewModalComponent } from '../../../../form/vocabulary-treeview-modal/vocabulary-treeview-modal.component';
|
import { VocabularyTreeviewModalComponent } from '../../../../form/vocabulary-treeview-modal/vocabulary-treeview-modal.component';
|
||||||
|
import { hasValue } from '../../../../empty.util';
|
||||||
|
import { APP_CONFIG, AppConfig } from '../../../../../../config/app-config.interface';
|
||||||
|
import { FilterVocabularyConfig } from '../../../../../../config/filter-vocabulary-config';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search-hierarchy-filter',
|
selector: 'ds-search-hierarchy-filter',
|
||||||
@@ -47,6 +49,7 @@ export class SearchHierarchyFilterComponent extends SearchFacetFilterComponent i
|
|||||||
protected router: Router,
|
protected router: Router,
|
||||||
protected modalService: NgbModal,
|
protected modalService: NgbModal,
|
||||||
protected vocabularyService: VocabularyService,
|
protected vocabularyService: VocabularyService,
|
||||||
|
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
||||||
@Inject(IN_PLACE_SEARCH) public inPlaceSearch: boolean,
|
@Inject(IN_PLACE_SEARCH) public inPlaceSearch: boolean,
|
||||||
@Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig,
|
@Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig,
|
||||||
@@ -67,17 +70,20 @@ export class SearchHierarchyFilterComponent extends SearchFacetFilterComponent i
|
|||||||
super.onSubmit(addOperatorToFilterValue(data, 'query'));
|
super.onSubmit(addOperatorToFilterValue(data, 'query'));
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit(): void {
|
||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
this.vocabularyExists$ = this.vocabularyService.searchTopEntries(
|
const vocabularyName: string = this.getVocabularyEntry();
|
||||||
this.getVocabularyEntry(), new PageInfo(), true, false,
|
if (hasValue(vocabularyName)) {
|
||||||
).pipe(
|
this.vocabularyExists$ = this.vocabularyService.searchTopEntries(
|
||||||
filter(rd => rd.hasCompleted),
|
vocabularyName, new PageInfo(), true, false,
|
||||||
take(1),
|
).pipe(
|
||||||
map(rd => {
|
filter(rd => rd.hasCompleted),
|
||||||
return rd.hasSucceeded;
|
take(1),
|
||||||
}),
|
map(rd => {
|
||||||
);
|
return rd.hasSucceeded;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -93,11 +99,11 @@ export class SearchHierarchyFilterComponent extends SearchFacetFilterComponent i
|
|||||||
name: this.getVocabularyEntry(),
|
name: this.getVocabularyEntry(),
|
||||||
closed: true
|
closed: true
|
||||||
};
|
};
|
||||||
modalRef.result.then((detail: VocabularyEntryDetail) => {
|
void modalRef.result.then((detail: VocabularyEntryDetail) => {
|
||||||
this.selectedValues$
|
this.subs.push(this.selectedValues$
|
||||||
.pipe(take(1))
|
.pipe(take(1))
|
||||||
.subscribe((selectedValues) => {
|
.subscribe((selectedValues) => {
|
||||||
this.router.navigate(
|
void this.router.navigate(
|
||||||
[this.searchService.getSearchLink()],
|
[this.searchService.getSearchLink()],
|
||||||
{
|
{
|
||||||
queryParams: {
|
queryParams: {
|
||||||
@@ -107,16 +113,16 @@ export class SearchHierarchyFilterComponent extends SearchFacetFilterComponent i
|
|||||||
queryParamsHandling: 'merge',
|
queryParamsHandling: 'merge',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
}));
|
||||||
}).catch();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the matching vocabulary entry for the given search filter.
|
* Returns the matching vocabulary entry for the given search filter.
|
||||||
* These are configurable in the config file.
|
* These are configurable in the config file.
|
||||||
*/
|
*/
|
||||||
getVocabularyEntry() {
|
getVocabularyEntry(): string {
|
||||||
const foundVocabularyConfig = environment.vocabularies.filter((v) => v.filter === this.filterConfig.name);
|
const foundVocabularyConfig: FilterVocabularyConfig[] = this.appConfig.vocabularies.filter((v: FilterVocabularyConfig) => v.filter === this.filterConfig.name);
|
||||||
if (foundVocabularyConfig.length > 0 && foundVocabularyConfig[0].enabled === true) {
|
if (foundVocabularyConfig.length > 0 && foundVocabularyConfig[0].enabled === true) {
|
||||||
return foundVocabularyConfig[0].vocabulary;
|
return foundVocabularyConfig[0].vocabulary;
|
||||||
}
|
}
|
||||||
|
@@ -85,6 +85,7 @@
|
|||||||
<ds-themed-search-form *ngIf="searchEnabled" id="search-form"
|
<ds-themed-search-form *ngIf="searchEnabled" id="search-form"
|
||||||
[query]="(searchOptions$ | async)?.query"
|
[query]="(searchOptions$ | async)?.query"
|
||||||
[scope]="(searchOptions$ | async)?.scope"
|
[scope]="(searchOptions$ | async)?.scope"
|
||||||
|
[hideScopeInUrl]="hideScopeInUrl"
|
||||||
[currentUrl]="searchLink"
|
[currentUrl]="searchLink"
|
||||||
[showScopeSelector]="showScopeSelector"
|
[showScopeSelector]="showScopeSelector"
|
||||||
[inPlaceSearch]="inPlaceSearch"
|
[inPlaceSearch]="inPlaceSearch"
|
||||||
|
@@ -33,6 +33,8 @@ import { SearchFilterConfig } from './models/search-filter-config.model';
|
|||||||
import { FilterType } from './models/filter-type.model';
|
import { FilterType } from './models/filter-type.model';
|
||||||
import { getCommunityPageRoute } from '../../community-page/community-page-routing-paths';
|
import { getCommunityPageRoute } from '../../community-page/community-page-routing-paths';
|
||||||
import { getCollectionPageRoute } from '../../collection-page/collection-page-routing-paths';
|
import { getCollectionPageRoute } from '../../collection-page/collection-page-routing-paths';
|
||||||
|
import { environment } from '../../../environments/environment.test';
|
||||||
|
import { APP_CONFIG } from '../../../config/app-config.interface';
|
||||||
|
|
||||||
let comp: SearchComponent;
|
let comp: SearchComponent;
|
||||||
let fixture: ComponentFixture<SearchComponent>;
|
let fixture: ComponentFixture<SearchComponent>;
|
||||||
@@ -209,7 +211,8 @@ export function configureSearchComponentTestingModule(compType, additionalDeclar
|
|||||||
{
|
{
|
||||||
provide: SEARCH_CONFIG_SERVICE,
|
provide: SEARCH_CONFIG_SERVICE,
|
||||||
useValue: searchConfigurationServiceStub
|
useValue: searchConfigurationServiceStub
|
||||||
}
|
},
|
||||||
|
{ provide: APP_CONFIG, useValue: environment },
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).overrideComponent(compType, {
|
}).overrideComponent(compType, {
|
||||||
|
@@ -31,13 +31,13 @@ import { ViewMode } from '../../core/shared/view-mode.model';
|
|||||||
import { SelectionConfig } from './search-results/search-results.component';
|
import { SelectionConfig } from './search-results/search-results.component';
|
||||||
import { ListableObject } from '../object-collection/shared/listable-object.model';
|
import { ListableObject } from '../object-collection/shared/listable-object.model';
|
||||||
import { CollectionElementLinkType } from '../object-collection/collection-element-link.type';
|
import { CollectionElementLinkType } from '../object-collection/collection-element-link.type';
|
||||||
import { environment } from 'src/environments/environment';
|
|
||||||
import { SubmissionObject } from '../../core/submission/models/submission-object.model';
|
import { SubmissionObject } from '../../core/submission/models/submission-object.model';
|
||||||
import { SearchFilterConfig } from './models/search-filter-config.model';
|
import { SearchFilterConfig } from './models/search-filter-config.model';
|
||||||
import { WorkspaceItem } from '../../core/submission/models/workspaceitem.model';
|
import { WorkspaceItem } from '../../core/submission/models/workspaceitem.model';
|
||||||
import { ITEM_MODULE_PATH } from '../../item-page/item-page-routing-paths';
|
import { ITEM_MODULE_PATH } from '../../item-page/item-page-routing-paths';
|
||||||
import { COLLECTION_MODULE_PATH } from '../../collection-page/collection-page-routing-paths';
|
import { COLLECTION_MODULE_PATH } from '../../collection-page/collection-page-routing-paths';
|
||||||
import { COMMUNITY_MODULE_PATH } from '../../community-page/community-page-routing-paths';
|
import { COMMUNITY_MODULE_PATH } from '../../community-page/community-page-routing-paths';
|
||||||
|
import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search',
|
selector: 'ds-search',
|
||||||
@@ -171,6 +171,11 @@ export class SearchComponent implements OnDestroy, OnInit {
|
|||||||
*/
|
*/
|
||||||
@Input() scope: string;
|
@Input() scope: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides the scope in the url, this can be useful when you hardcode the scope in another way
|
||||||
|
*/
|
||||||
|
@Input() hideScopeInUrl: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current configuration used during the search
|
* The current configuration used during the search
|
||||||
*/
|
*/
|
||||||
@@ -278,7 +283,9 @@ export class SearchComponent implements OnDestroy, OnInit {
|
|||||||
protected windowService: HostWindowService,
|
protected windowService: HostWindowService,
|
||||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
||||||
protected routeService: RouteService,
|
protected routeService: RouteService,
|
||||||
protected router: Router) {
|
protected router: Router,
|
||||||
|
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||||
|
) {
|
||||||
this.isXsOrSm$ = this.windowService.isXsOrSm();
|
this.isXsOrSm$ = this.windowService.isXsOrSm();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,8 +452,10 @@ export class SearchComponent implements OnDestroy, OnInit {
|
|||||||
let followLinks = [
|
let followLinks = [
|
||||||
followLink<Item>('thumbnail', { isOptional: true }),
|
followLink<Item>('thumbnail', { isOptional: true }),
|
||||||
followLink<SubmissionObject>('item', { isOptional: true }, followLink<Item>('thumbnail', { isOptional: true })) as any,
|
followLink<SubmissionObject>('item', { isOptional: true }, followLink<Item>('thumbnail', { isOptional: true })) as any,
|
||||||
followLink<Item>('accessStatus', { isOptional: true, shouldEmbed: environment.item.showAccessStatuses }),
|
|
||||||
];
|
];
|
||||||
|
if (this.appConfig.item.showAccessStatuses) {
|
||||||
|
followLinks.push(followLink<Item>('accessStatus', { isOptional: true }));
|
||||||
|
}
|
||||||
if (this.configuration === 'supervision') {
|
if (this.configuration === 'supervision') {
|
||||||
followLinks.push(followLink<WorkspaceItem>('supervisionOrders', { isOptional: true }) as any);
|
followLinks.push(followLink<WorkspaceItem>('supervisionOrders', { isOptional: true }) as any);
|
||||||
}
|
}
|
||||||
@@ -476,18 +485,20 @@ export class SearchComponent implements OnDestroy, OnInit {
|
|||||||
* This method should only be called once and is essentially what SearchTrackingComponent used to do (now removed)
|
* This method should only be called once and is essentially what SearchTrackingComponent used to do (now removed)
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private subscribeToRoutingEvents() {
|
private subscribeToRoutingEvents(): void {
|
||||||
this.subs.push(
|
if (this.trackStatistics) {
|
||||||
this.router.events.pipe(
|
this.subs.push(
|
||||||
filter((event) => event instanceof NavigationStart),
|
this.router.events.pipe(
|
||||||
map((event: NavigationStart) => this.getDsoUUIDFromUrl(event.url)),
|
filter((event) => event instanceof NavigationStart),
|
||||||
hasValueOperator(),
|
map((event: NavigationStart) => this.getDsoUUIDFromUrl(event.url)),
|
||||||
).subscribe((uuid) => {
|
hasValueOperator(),
|
||||||
if (this.resultsRD$.value.hasSucceeded) {
|
).subscribe((uuid) => {
|
||||||
this.service.trackSearch(this.searchOptions$.value, this.resultsRD$.value.payload as SearchObjects<DSpaceObject>, uuid);
|
if (this.resultsRD$.value.hasSucceeded) {
|
||||||
}
|
this.service.trackSearch(this.searchOptions$.value, this.resultsRD$.value.payload as SearchObjects<DSpaceObject>, uuid);
|
||||||
}),
|
}
|
||||||
);
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -43,6 +43,7 @@ export class ThemedSearchComponent extends ThemedComponent<SearchComponent> {
|
|||||||
'trackStatistics',
|
'trackStatistics',
|
||||||
'query',
|
'query',
|
||||||
'scope',
|
'scope',
|
||||||
|
'hideScopeInUrl',
|
||||||
'resultFound',
|
'resultFound',
|
||||||
'deselectObject',
|
'deselectObject',
|
||||||
'selectObject',
|
'selectObject',
|
||||||
@@ -94,6 +95,8 @@ export class ThemedSearchComponent extends ThemedComponent<SearchComponent> {
|
|||||||
|
|
||||||
@Input() scope: string;
|
@Input() scope: string;
|
||||||
|
|
||||||
|
@Input() hideScopeInUrl: boolean;
|
||||||
|
|
||||||
@Output() resultFound: EventEmitter<SearchObjects<DSpaceObject>> = new EventEmitter();
|
@Output() resultFound: EventEmitter<SearchObjects<DSpaceObject>> = new EventEmitter();
|
||||||
|
|
||||||
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter();
|
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter();
|
||||||
|
@@ -3,10 +3,12 @@
|
|||||||
<div class="row-with-sidebar row-offcanvas row-offcanvas-left"
|
<div class="row-with-sidebar row-offcanvas row-offcanvas-left"
|
||||||
[@pushInOut]="(isSidebarCollapsed$ | async) ? 'collapsed' : 'expanded'">
|
[@pushInOut]="(isSidebarCollapsed$ | async) ? 'collapsed' : 'expanded'">
|
||||||
<div id="{{id}}-sidebar-content"
|
<div id="{{id}}-sidebar-content"
|
||||||
|
[class.invisible]="(isSidebarCollapsed$ | async) === true && (isXsOrSm$ | async) === true"
|
||||||
class="col-12 col-md-{{sideBarWidth}} sidebar-content {{sidebarClasses | async}}">
|
class="col-12 col-md-{{sideBarWidth}} sidebar-content {{sidebarClasses | async}}">
|
||||||
<ng-container *ngTemplateOutlet="sidebarContent"></ng-container>
|
<ng-container *ngTemplateOutlet="sidebarContent"></ng-container>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-{{12 - sideBarWidth}}">
|
<div class="col-12 col-md-{{12 - sideBarWidth}}"
|
||||||
|
[class.invisible]="(isSidebarCollapsed$ | async) === false && (isXsOrSm$ | async) === true">
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,19 +1,19 @@
|
|||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { convertToParamMap, Params } from '@angular/router';
|
import { ActivatedRoute, convertToParamMap, Data, Params } from '@angular/router';
|
||||||
|
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
export class ActivatedRouteStub {
|
export class ActivatedRouteStub {
|
||||||
|
|
||||||
private _testParams?: any;
|
private _testParams?: Params;
|
||||||
private _testData?: any;
|
private _testData?: Data;
|
||||||
// ActivatedRoute.params is Observable
|
// ActivatedRoute.params is Observable
|
||||||
private subject?: BehaviorSubject<any> = new BehaviorSubject(this.testParams);
|
private subject?: BehaviorSubject<Params> = new BehaviorSubject(this.testParams);
|
||||||
private dataSubject?: BehaviorSubject<any> = new BehaviorSubject(this.testData);
|
private dataSubject?: BehaviorSubject<Data> = new BehaviorSubject(this.testData);
|
||||||
|
|
||||||
params = this.subject.asObservable();
|
params = this.subject.asObservable();
|
||||||
queryParams = this.subject.asObservable();
|
queryParams = this.subject.asObservable();
|
||||||
paramMap = this.subject.asObservable().pipe(map((params: Params) => convertToParamMap(params)));
|
paramMap = this.subject.asObservable().pipe(map((params: Params) => convertToParamMap(params)));
|
||||||
|
parent: ActivatedRoute | ActivatedRouteStub;
|
||||||
queryParamMap = this.subject.asObservable().pipe(map((params: Params) => convertToParamMap(params)));
|
queryParamMap = this.subject.asObservable().pipe(map((params: Params) => convertToParamMap(params)));
|
||||||
data = this.dataSubject.asObservable();
|
data = this.dataSubject.asObservable();
|
||||||
|
|
||||||
@@ -35,17 +35,17 @@ export class ActivatedRouteStub {
|
|||||||
return this._testParams;
|
return this._testParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
set testParams(params: {}) {
|
set testParams(params: Params) {
|
||||||
this._testParams = params;
|
this._testParams = params;
|
||||||
this.subject.next(params);
|
this.subject.next(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test data
|
// Test data
|
||||||
get testData() {
|
get testData() {
|
||||||
return this._testParams;
|
return this._testData;
|
||||||
}
|
}
|
||||||
|
|
||||||
set testData(data: {}) {
|
set testData(data: Data) {
|
||||||
this._testData = data;
|
this._testData = data;
|
||||||
this.dataSubject.next(data);
|
this.dataSubject.next(data);
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Angulartics2 } from 'angulartics2';
|
import { Angulartics2, EventTrack } from 'angulartics2';
|
||||||
import { StatisticsService } from '../statistics.service';
|
import { StatisticsService } from '../statistics.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,7 +23,7 @@ export class Angulartics2DSpace {
|
|||||||
.subscribe((event) => this.eventTrack(event));
|
.subscribe((event) => this.eventTrack(event));
|
||||||
}
|
}
|
||||||
|
|
||||||
private eventTrack(event) {
|
private eventTrack(event: Partial<EventTrack>): void {
|
||||||
if (event.action === 'page_view') {
|
if (event.action === 'page_view') {
|
||||||
this.statisticsService.trackViewEvent(event.properties.object, event.properties.referrer);
|
this.statisticsService.trackViewEvent(event.properties.object, event.properties.referrer);
|
||||||
} else if (event.action === 'search') {
|
} else if (event.action === 'search') {
|
||||||
@@ -32,7 +32,7 @@ export class Angulartics2DSpace {
|
|||||||
event.properties.page,
|
event.properties.page,
|
||||||
event.properties.sort,
|
event.properties.sort,
|
||||||
event.properties.filters,
|
event.properties.filters,
|
||||||
event.properties.clickedObject,
|
event.properties.clickedObject?.split('?')[0],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1108,9 +1108,7 @@
|
|||||||
|
|
||||||
"collection.logo": "Collection logo",
|
"collection.logo": "Collection logo",
|
||||||
|
|
||||||
"collection.page.browse.recent.head": "Recent Submissions",
|
"collection.page.browse.search.head": "Search",
|
||||||
|
|
||||||
"collection.page.browse.recent.empty": "No items to show",
|
|
||||||
|
|
||||||
"collection.page.edit": "Edit this collection",
|
"collection.page.edit": "Edit this collection",
|
||||||
|
|
||||||
@@ -1120,6 +1118,8 @@
|
|||||||
|
|
||||||
"collection.page.news": "News",
|
"collection.page.news": "News",
|
||||||
|
|
||||||
|
"collection.search.results.head": "Search Results",
|
||||||
|
|
||||||
"collection.select.confirm": "Confirm selected",
|
"collection.select.confirm": "Confirm selected",
|
||||||
|
|
||||||
"collection.select.empty": "No collections to show",
|
"collection.select.empty": "No collections to show",
|
||||||
@@ -1196,6 +1196,8 @@
|
|||||||
|
|
||||||
"community.browse.logo": "Browse for a community logo",
|
"community.browse.logo": "Browse for a community logo",
|
||||||
|
|
||||||
|
"community.subcoms-cols.breadcrumbs": "Subcommunities and Collections",
|
||||||
|
|
||||||
"community.create.head": "Create a Community",
|
"community.create.head": "Create a Community",
|
||||||
|
|
||||||
"community.create.notifications.success": "Successfully created the Community",
|
"community.create.notifications.success": "Successfully created the Community",
|
||||||
@@ -1348,6 +1350,8 @@
|
|||||||
|
|
||||||
"community.all-lists.head": "Subcommunities and Collections",
|
"community.all-lists.head": "Subcommunities and Collections",
|
||||||
|
|
||||||
|
"community.search.results.head": "Search Results",
|
||||||
|
|
||||||
"community.sub-collection-list.head": "Collections in this Community",
|
"community.sub-collection-list.head": "Collections in this Community",
|
||||||
|
|
||||||
"community.sub-community-list.head": "Communities in this Community",
|
"community.sub-community-list.head": "Communities in this Community",
|
||||||
|
@@ -8,6 +8,7 @@ import { SubmissionConfig } from './submission-config.interface';
|
|||||||
import { FormConfig } from './form-config.interfaces';
|
import { FormConfig } from './form-config.interfaces';
|
||||||
import { LangConfig } from './lang-config.interface';
|
import { LangConfig } from './lang-config.interface';
|
||||||
import { ItemConfig } from './item-config.interface';
|
import { ItemConfig } from './item-config.interface';
|
||||||
|
import { CommunityPageConfig } from './community-page-config.interface';
|
||||||
import { CollectionPageConfig } from './collection-page-config.interface';
|
import { CollectionPageConfig } from './collection-page-config.interface';
|
||||||
import { ThemeConfig } from './theme.config';
|
import { ThemeConfig } from './theme.config';
|
||||||
import { AuthConfig } from './auth-config.interfaces';
|
import { AuthConfig } from './auth-config.interfaces';
|
||||||
@@ -41,6 +42,7 @@ interface AppConfig extends Config {
|
|||||||
communityList: CommunityListConfig;
|
communityList: CommunityListConfig;
|
||||||
homePage: HomeConfig;
|
homePage: HomeConfig;
|
||||||
item: ItemConfig;
|
item: ItemConfig;
|
||||||
|
community: CommunityPageConfig;
|
||||||
collection: CollectionPageConfig;
|
collection: CollectionPageConfig;
|
||||||
themes: ThemeConfig[];
|
themes: ThemeConfig[];
|
||||||
mediaViewer: MediaViewerConfig;
|
mediaViewer: MediaViewerConfig;
|
||||||
|
@@ -1,7 +1,18 @@
|
|||||||
import { Config } from './config.interface';
|
import { Config } from './config.interface';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection Page Config
|
||||||
|
*/
|
||||||
export interface CollectionPageConfig extends Config {
|
export interface CollectionPageConfig extends Config {
|
||||||
|
searchSection: CollectionSearchSectionConfig;
|
||||||
edit: {
|
edit: {
|
||||||
undoTimeout: number;
|
undoTimeout: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config related to the collection's search tab
|
||||||
|
*/
|
||||||
|
export interface CollectionSearchSectionConfig {
|
||||||
|
showSidebar: boolean;
|
||||||
|
}
|
||||||
|
15
src/config/community-page-config.interface.ts
Normal file
15
src/config/community-page-config.interface.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { Config } from './config.interface';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Community Page Config
|
||||||
|
*/
|
||||||
|
export interface CommunityPageConfig extends Config {
|
||||||
|
searchSection: CommunitySearchSectionConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config related to the community's search tab
|
||||||
|
*/
|
||||||
|
export interface CommunitySearchSectionConfig {
|
||||||
|
showSidebar: boolean;
|
||||||
|
}
|
@@ -23,6 +23,7 @@ import { HomeConfig } from './homepage-config.interface';
|
|||||||
import { MarkdownConfig } from './markdown-config.interface';
|
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 {QualityAssuranceConfig} from './quality-assurance.config';
|
import {QualityAssuranceConfig} from './quality-assurance.config';
|
||||||
|
|
||||||
export class DefaultAppConfig implements AppConfig {
|
export class DefaultAppConfig implements AppConfig {
|
||||||
@@ -291,8 +292,18 @@ export class DefaultAppConfig implements AppConfig {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Community Page Config
|
||||||
|
community: CommunityPageConfig = {
|
||||||
|
searchSection: {
|
||||||
|
showSidebar: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// Collection Page Config
|
// Collection Page Config
|
||||||
collection: CollectionPageConfig = {
|
collection: CollectionPageConfig = {
|
||||||
|
searchSection: {
|
||||||
|
showSidebar: true,
|
||||||
|
},
|
||||||
edit: {
|
edit: {
|
||||||
undoTimeout: 10000 // 10 seconds
|
undoTimeout: 10000 // 10 seconds
|
||||||
}
|
}
|
||||||
|
@@ -257,7 +257,15 @@ export const environment: BuildConfig = {
|
|||||||
pageSize: 5
|
pageSize: 5
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
community: {
|
||||||
|
searchSection: {
|
||||||
|
showSidebar: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
collection: {
|
collection: {
|
||||||
|
searchSection: {
|
||||||
|
showSidebar: true,
|
||||||
|
},
|
||||||
edit: {
|
edit: {
|
||||||
undoTimeout: 10000 // 10 seconds
|
undoTimeout: 10000 // 10 seconds
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user