Merge pull request #615 from atmire/administrative_search

Add Administrative search
This commit is contained in:
Tim Donohue
2020-03-17 15:35:24 -05:00
committed by GitHub
75 changed files with 1622 additions and 388 deletions

View File

@@ -170,6 +170,34 @@
"admin.search.breadcrumbs": "Administrative Search",
"admin.search.collection.edit": "Edit",
"admin.search.community.edit": "Edit",
"admin.search.item.delete": "Delete",
"admin.search.item.edit": "Edit",
"admin.search.item.make-private": "Make Private",
"admin.search.item.make-public": "Make Public",
"admin.search.item.move": "Move",
"admin.search.item.private": "Private",
"admin.search.item.reinstate": "Reinstate",
"admin.search.item.withdraw": "Withdraw",
"admin.search.item.withdrawn": "Withdrawn",
"admin.search.title": "Administrative Search",
"auth.errors.invalid-user": "Invalid email address or password.",
"auth.messages.expired": "Your session has expired. Please log in again.",
@@ -1117,6 +1145,10 @@
"menu.section.admin_search": "Admin Search",
"menu.section.browse_community": "This Community",
"menu.section.browse_community_by_author": "By Author",
@@ -1167,18 +1199,10 @@
"menu.section.find": "Find",
"menu.section.find_items": "Items",
"menu.section.find_private_items": "Private Items",
"menu.section.find_withdrawn_items": "Withdrawn Items",
"menu.section.icon.access_control": "Access Control menu section",
"menu.section.icon.admin_search": "Admin search menu section",
"menu.section.icon.control_panel": "Control Panel menu section",
"menu.section.icon.curation_task": "Curation Task menu section",
@@ -1498,6 +1522,8 @@
"search.filters.applied.f.dateSubmitted": "Date submitted",
"search.filters.applied.f.discoverable": "Private",
"search.filters.applied.f.entityType": "Item Type",
"search.filters.applied.f.has_content_in_original_bundle": "Has files",
@@ -1509,10 +1535,15 @@
"search.filters.applied.f.subject": "Subject",
"search.filters.applied.f.submitter": "Submitter",
"search.filters.applied.f.jobTitle": "Job Title",
"search.filters.applied.f.birthDate.max": "End birth date",
"search.filters.applied.f.birthDate.min": "Start birth date",
"search.filters.applied.f.withdrawn": "Withdrawn",
"search.filters.filter.author.head": "Author",
@@ -1549,6 +1580,10 @@
"search.filters.filter.dateSubmitted.placeholder": "Date submitted",
"search.filters.filter.discoverable.head": "Private",
"search.filters.filter.withdrawn.head": "Withdrawn",
"search.filters.filter.entityType.head": "Item Type",
"search.filters.filter.entityType.placeholder": "Item Type",
@@ -1605,6 +1640,25 @@
"search.filters.entityType.JournalIssue": "Journal Issue",
"search.filters.entityType.JournalVolume": "Journal Volume",
"search.filters.entityType.OrgUnit": "Organizational Unit",
"search.filters.has_content_in_original_bundle.true": "Yes",
"search.filters.has_content_in_original_bundle.false": "No",
"search.filters.discoverable.true": "No",
"search.filters.discoverable.false": "Yes",
"search.filters.withdrawn.true": "Yes",
"search.filters.withdrawn.false": "No",
"search.filters.head": "Filters",
"search.filters.reset": "Reset filters",
@@ -1984,6 +2038,8 @@
"title": "DSpace",
"administrativeView.search.results.head": "Administrative Search",
"uploader.browse": "browse",

View File

@@ -2,6 +2,8 @@ import { RouterModule } from '@angular/router';
import { NgModule } from '@angular/core';
import { URLCombiner } from '../core/url-combiner/url-combiner';
import { getAdminModulePath } from '../app-routing.module';
import { AdminSearchPageComponent } from './admin-search-page/admin-search-page.component';
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
const REGISTRIES_MODULE_PATH = 'registries';
@@ -15,7 +17,13 @@ export function getRegistriesModulePath() {
{
path: REGISTRIES_MODULE_PATH,
loadChildren: './admin-registries/admin-registries.module#AdminRegistriesModule'
}
},
{
path: 'search',
resolve: { breadcrumb: I18nBreadcrumbResolver },
component: AdminSearchPageComponent,
data: { title: 'admin.search.title', breadcrumbKey: 'admin.search' }
},
])
]
})

View File

@@ -0,0 +1 @@
<ds-configuration-search-page configuration="administrativeView" [context]="context"></ds-configuration-search-page>

View File

@@ -0,0 +1,27 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AdminSearchPageComponent } from './admin-search-page.component';
import { NO_ERRORS_SCHEMA } from '@angular/core';
describe('AdminSearchPageComponent', () => {
let component: AdminSearchPageComponent;
let fixture: ComponentFixture<AdminSearchPageComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AdminSearchPageComponent ],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AdminSearchPageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,18 @@
import { Component, OnInit } from '@angular/core';
import { Context } from '../../core/shared/context.model';
@Component({
selector: 'ds-admin-search-page',
templateUrl: './admin-search-page.component.html',
styleUrls: ['./admin-search-page.component.scss']
})
/**
* Component that represents a search page for administrators
*/
export class AdminSearchPageComponent {
/**
* The context of this page
*/
context: Context = Context.AdminSearch;
}

View File

@@ -0,0 +1,13 @@
<ds-collection-search-result-grid-element [object]="object"
[index]="index"
[linkType]="linkType"
[listID]="listID">
<ul class="list-group list-group-flush">
<li class="list-group-item text-center">
<a class="btn btn-light btn-sm btn-auto my-1 edit-link" [routerLink]="[editPath]">
<i class="fa fa-edit"></i>
</a>
</li>
</ul>
</ds-collection-search-result-grid-element>

View File

@@ -0,0 +1,66 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
import { mockTruncatableService } from '../../../../../shared/mocks/mock-trucatable.service';
import { SharedModule } from '../../../../../shared/shared.module';
import { CollectionAdminSearchResultGridElementComponent } from './collection-admin-search-result-grid-element.component';
import { TranslateModule } from '@ngx-translate/core';
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
import { CollectionElementLinkType } from '../../../../../shared/object-collection/collection-element-link.type';
import { ViewMode } from '../../../../../core/shared/view-mode.model';
import { CollectionSearchResult } from '../../../../../shared/object-collection/shared/collection-search-result.model';
import { Collection } from '../../../../../core/shared/collection.model';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { getCollectionEditPath } from '../../../../../+collection-page/collection-page-routing.module';
describe('CollectionAdminSearchResultGridElementComponent', () => {
let component: CollectionAdminSearchResultGridElementComponent;
let fixture: ComponentFixture<CollectionAdminSearchResultGridElementComponent>;
let id;
let searchResult;
function init() {
id = '780b2588-bda5-4112-a1cd-0b15000a5339';
searchResult = new CollectionSearchResult();
searchResult.indexableObject = new Collection();
searchResult.indexableObject.uuid = id;
}
beforeEach(async(() => {
init();
TestBed.configureTestingModule({
imports: [
NoopAnimationsModule,
TranslateModule.forRoot(),
RouterTestingModule.withRoutes([]),
SharedModule
],
declarations: [CollectionAdminSearchResultGridElementComponent],
providers: [
{ provide: TruncatableService, useValue: mockTruncatableService },
{ provide: BitstreamDataService, useValue: {} },
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CollectionAdminSearchResultGridElementComponent);
component = fixture.componentInstance;
component.object = searchResult;
component.linkTypes = CollectionElementLinkType;
component.index = 0;
component.viewModes = ViewMode;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should render an edit button with the correct link', () => {
const a = fixture.debugElement.query(By.css('a.edit-link'));
const link = a.nativeElement.href;
expect(link).toContain(getCollectionEditPath(id));
})
});

View File

@@ -0,0 +1,26 @@
import { Component } from '@angular/core';
import { ViewMode } from '../../../../../core/shared/view-mode.model';
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
import { Context } from '../../../../../core/shared/context.model';
import { CollectionSearchResult } from '../../../../../shared/object-collection/shared/collection-search-result.model';
import { Collection } from '../../../../../core/shared/collection.model';
import { getCollectionEditPath } from '../../../../../+collection-page/collection-page-routing.module';
import { SearchResultGridElementComponent } from '../../../../../shared/object-grid/search-result-grid-element/search-result-grid-element.component';
@listableObjectComponent(CollectionSearchResult, ViewMode.GridElement, Context.AdminSearch)
@Component({
selector: 'ds-collection-admin-search-result-list-element',
styleUrls: ['./collection-admin-search-result-grid-element.component.scss'],
templateUrl: './collection-admin-search-result-grid-element.component.html'
})
/**
* The component for displaying a list element for a collection search result on the admin search page
*/
export class CollectionAdminSearchResultGridElementComponent extends SearchResultGridElementComponent<CollectionSearchResult, Collection> {
editPath: string;
ngOnInit() {
super.ngOnInit();
this.editPath = getCollectionEditPath(this.dso.uuid);
}
}

View File

@@ -0,0 +1,13 @@
<ds-community-search-result-grid-element [object]="object"
[index]="index"
[linkType]="linkType"
[listID]="listID">
<ul class="list-group list-group-flush">
<li class="list-group-item text-center">
<a class="btn btn-light btn-sm btn-auto my-1 edit-link" [routerLink]="[editPath]">
<i class="fa fa-edit"></i>
</a>
</li>
</ul>
</ds-community-search-result-grid-element>

View File

@@ -0,0 +1,70 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateModule } from '@ngx-translate/core';
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
import { mockTruncatableService } from '../../../../../shared/mocks/mock-trucatable.service';
import { SharedModule } from '../../../../../shared/shared.module';
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
import { CollectionElementLinkType } from '../../../../../shared/object-collection/collection-element-link.type';
import { ViewMode } from '../../../../../core/shared/view-mode.model';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { CommunityAdminSearchResultGridElementComponent } from './community-admin-search-result-grid-element.component';
import { CommunitySearchResult } from '../../../../../shared/object-collection/shared/community-search-result.model';
import { getCommunityEditPath } from '../../../../../+community-page/community-page-routing.module';
import { Community } from '../../../../../core/shared/community.model';
import { CommunityAdminSearchResultListElementComponent } from '../../admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component';
describe('CommunityAdminSearchResultGridElementComponent', () => {
let component: CommunityAdminSearchResultGridElementComponent;
let fixture: ComponentFixture<CommunityAdminSearchResultGridElementComponent>;
let id;
let searchResult;
function init() {
id = '780b2588-bda5-4112-a1cd-0b15000a5339';
searchResult = new CommunitySearchResult();
searchResult.indexableObject = new Community();
searchResult.indexableObject.uuid = id;
}
beforeEach(async(() => {
init();
TestBed.configureTestingModule({
imports: [
NoopAnimationsModule,
TranslateModule.forRoot(),
RouterTestingModule.withRoutes([]),
SharedModule
],
declarations: [CommunityAdminSearchResultGridElementComponent],
providers: [
{ provide: TruncatableService, useValue: mockTruncatableService },
{ provide: BitstreamDataService, useValue: {} },
],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CommunityAdminSearchResultGridElementComponent);
component = fixture.componentInstance;
component.object = searchResult;
component.linkTypes = CollectionElementLinkType;
component.index = 0;
component.viewModes = ViewMode;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should render an edit button with the correct link', () => {
const a = fixture.debugElement.query(By.css('a.edit-link'));
const link = a.nativeElement.href;
expect(link).toContain(getCommunityEditPath(id));
})
});

View File

@@ -0,0 +1,26 @@
import { Component } from '@angular/core';
import { ViewMode } from '../../../../../core/shared/view-mode.model';
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
import { Context } from '../../../../../core/shared/context.model';
import { CommunitySearchResult } from '../../../../../shared/object-collection/shared/community-search-result.model';
import { Community } from '../../../../../core/shared/community.model';
import { getCommunityEditPath } from '../../../../../+community-page/community-page-routing.module';
import { SearchResultGridElementComponent } from '../../../../../shared/object-grid/search-result-grid-element/search-result-grid-element.component';
@listableObjectComponent(CommunitySearchResult, ViewMode.GridElement, Context.AdminSearch)
@Component({
selector: 'ds-community-admin-search-result-grid-element',
styleUrls: ['./community-admin-search-result-grid-element.component.scss'],
templateUrl: './community-admin-search-result-grid-element.component.html'
})
/**
* The component for displaying a list element for a community search result on the admin search page
*/
export class CommunityAdminSearchResultGridElementComponent extends SearchResultGridElementComponent<CommunitySearchResult, Community> {
editPath: string;
ngOnInit() {
super.ngOnInit();
this.editPath = getCommunityEditPath(this.dso.uuid);
}
}

View File

@@ -0,0 +1,15 @@
<ng-template dsListableObject>
</ng-template>
<div #badges class="position-absolute ml-1">
<div *ngIf="dso && !dso.isDiscoverable" class="private-badge">
<span class="badge badge-danger">{{ "admin.search.item.private" | translate }}</span>
</div>
<div *ngIf="dso && dso.isWithdrawn" class="withdrawn-badge">
<span class="badge badge-warning">{{ "admin.search.item.withdrawn" | translate }}</span>
</div>
</div>
<ul #buttons class="list-group list-group-flush">
<li class="list-group-item">
<ds-item-admin-search-result-actions-element class="d-flex justify-content-between" [item]="dso" [small]="true"></ds-item-admin-search-result-actions-element>
</li>
</ul>

View File

@@ -0,0 +1,121 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateModule } from '@ngx-translate/core';
import { Observable } from 'rxjs/internal/Observable';
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
import { RemoteData } from '../../../../../core/data/remote-data';
import { Bitstream } from '../../../../../core/shared/bitstream.model';
import { Item } from '../../../../../core/shared/item.model';
import { mockTruncatableService } from '../../../../../shared/mocks/mock-trucatable.service';
import { SharedModule } from '../../../../../shared/shared.module';
import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/testing/utils';
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
import { CollectionElementLinkType } from '../../../../../shared/object-collection/collection-element-link.type';
import { ViewMode } from '../../../../../core/shared/view-mode.model';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
import { ItemAdminSearchResultGridElementComponent } from './item-admin-search-result-grid-element.component';
describe('ItemAdminSearchResultGridElementComponent', () => {
let component: ItemAdminSearchResultGridElementComponent;
let fixture: ComponentFixture<ItemAdminSearchResultGridElementComponent>;
let id;
let searchResult;
const mockBitstreamDataService = {
getThumbnailFor(item: Item): Observable<RemoteData<Bitstream>> {
return createSuccessfulRemoteDataObject$(new Bitstream());
}
};
function init() {
id = '780b2588-bda5-4112-a1cd-0b15000a5339';
searchResult = new ItemSearchResult();
searchResult.indexableObject = new Item();
searchResult.indexableObject.uuid = id;
}
beforeEach(async(() => {
init();
TestBed.configureTestingModule(
{
declarations: [ItemAdminSearchResultGridElementComponent],
imports: [
NoopAnimationsModule,
TranslateModule.forRoot(),
RouterTestingModule.withRoutes([]),
SharedModule
],
providers: [
{ provide: TruncatableService, useValue: mockTruncatableService },
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ItemAdminSearchResultGridElementComponent);
component = fixture.componentInstance;
component.object = searchResult;
component.linkTypes = CollectionElementLinkType;
component.index = 0;
component.viewModes = ViewMode;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
describe('when the item is not withdrawn', () => {
beforeEach(() => {
component.dso.isWithdrawn = false;
fixture.detectChanges();
});
it('should not show the withdrawn badge', () => {
const badge = fixture.debugElement.query(By.css('div.withdrawn-badge'));
expect(badge).toBeNull();
});
});
describe('when the item is withdrawn', () => {
beforeEach(() => {
component.dso.isWithdrawn = true;
fixture.detectChanges();
});
it('should show the withdrawn badge', () => {
const badge = fixture.debugElement.query(By.css('div.withdrawn-badge'));
expect(badge).not.toBeNull();
});
});
describe('when the item is not private', () => {
beforeEach(() => {
component.dso.isDiscoverable = true;
fixture.detectChanges();
});
it('should not show the private badge', () => {
const badge = fixture.debugElement.query(By.css('div.private-badge'));
expect(badge).toBeNull();
});
});
describe('when the item is private', () => {
beforeEach(() => {
component.dso.isDiscoverable = false;
fixture.detectChanges();
});
it('should show the private badge', () => {
const badge = fixture.debugElement.query(By.css('div.private-badge'));
expect(badge).not.toBeNull();
});
})
});

View File

@@ -0,0 +1,75 @@
import { Component, ComponentFactoryResolver, ElementRef, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
import { Item } from '../../../../../core/shared/item.model';
import { ViewMode } from '../../../../../core/shared/view-mode.model';
import { getListableObjectComponent, listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
import { Context } from '../../../../../core/shared/context.model';
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
import { getItemEditPath } from '../../../../../+item-page/item-page-routing.module';
import { URLCombiner } from '../../../../../core/url-combiner/url-combiner';
import {
ITEM_EDIT_DELETE_PATH,
ITEM_EDIT_MOVE_PATH,
ITEM_EDIT_PRIVATE_PATH,
ITEM_EDIT_PUBLIC_PATH,
ITEM_EDIT_REINSTATE_PATH,
ITEM_EDIT_WITHDRAW_PATH
} from '../../../../../+item-page/edit-item-page/edit-item-page.routing.module';
import { SearchResultGridElementComponent } from '../../../../../shared/object-grid/search-result-grid-element/search-result-grid-element.component';
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
import { GenericConstructor } from '../../../../../core/shared/generic-constructor';
import { ListableObjectDirective } from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive';
@listableObjectComponent(ItemSearchResult, ViewMode.GridElement, Context.AdminSearch)
@Component({
selector: 'ds-item-admin-search-result-grid-element',
styleUrls: ['./item-admin-search-result-grid-element.component.scss'],
templateUrl: './item-admin-search-result-grid-element.component.html'
})
/**
* The component for displaying a list element for an item search result on the admin search page
*/
export class ItemAdminSearchResultGridElementComponent extends SearchResultGridElementComponent<ItemSearchResult, Item> implements OnInit {
@ViewChild(ListableObjectDirective, { static: true }) listableObjectDirective: ListableObjectDirective;
@ViewChild('badges', { static: true }) badges: ElementRef;
@ViewChild('buttons', { static: true }) buttons: ElementRef;
constructor(protected truncatableService: TruncatableService,
protected bitstreamDataService: BitstreamDataService,
private componentFactoryResolver: ComponentFactoryResolver
) {
super(truncatableService, bitstreamDataService);
}
/**
* Setup the dynamic child component
*/
ngOnInit(): void {
super.ngOnInit();
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent());
const viewContainerRef = this.listableObjectDirective.viewContainerRef;
viewContainerRef.clear();
const componentRef = viewContainerRef.createComponent(
componentFactory,
0,
undefined,
[
[this.badges.nativeElement],
[this.buttons.nativeElement]
]);
(componentRef.instance as any).object = this.object;
(componentRef.instance as any).index = this.index;
(componentRef.instance as any).linkType = this.linkType;
(componentRef.instance as any).listID = this.listID;
}
/**
* Fetch the component depending on the item's relationship type, view mode and context
* @returns {GenericConstructor<Component>}
*/
private getComponent(): GenericConstructor<Component> {
return getListableObjectComponent(this.object.getRenderTypes(), ViewMode.GridElement, undefined)
}
}

View File

@@ -0,0 +1,9 @@
<ds-collection-search-result-list-element [object]="object"
[index]="index"
[linkType]="linkType"
[listID]="listID"></ds-collection-search-result-list-element>
<div>
<a class="btn btn-light mt-1" [routerLink]="[editPath]">
<i class="fa fa-edit"></i> {{"admin.search.collection.edit" | translate}}
</a>
</div>

View File

@@ -0,0 +1,60 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { CollectionAdminSearchResultListElementComponent } from './collection-admin-search-result-list-element.component';
import { TranslateModule } from '@ngx-translate/core';
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
import { CollectionElementLinkType } from '../../../../../shared/object-collection/collection-element-link.type';
import { ViewMode } from '../../../../../core/shared/view-mode.model';
import { CollectionSearchResult } from '../../../../../shared/object-collection/shared/collection-search-result.model';
import { Collection } from '../../../../../core/shared/collection.model';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { getCollectionEditPath } from '../../../../../+collection-page/collection-page-routing.module';
describe('CollectionAdminSearchResultListElementComponent', () => {
let component: CollectionAdminSearchResultListElementComponent;
let fixture: ComponentFixture<CollectionAdminSearchResultListElementComponent>;
let id;
let searchResult;
function init() {
id = '780b2588-bda5-4112-a1cd-0b15000a5339';
searchResult = new CollectionSearchResult();
searchResult.indexableObject = new Collection();
searchResult.indexableObject.uuid = id;
}
beforeEach(async(() => {
init();
TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot(),
RouterTestingModule.withRoutes([])
],
declarations: [CollectionAdminSearchResultListElementComponent],
providers: [{ provide: TruncatableService, useValue: {} }],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CollectionAdminSearchResultListElementComponent);
component = fixture.componentInstance;
component.object = searchResult;
component.linkTypes = CollectionElementLinkType;
component.index = 0;
component.viewModes = ViewMode;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should render an edit button with the correct link', () => {
const a = fixture.debugElement.query(By.css('a'));
const link = a.nativeElement.href;
expect(link).toContain(getCollectionEditPath(id));
})
});

View File

@@ -0,0 +1,26 @@
import { Component } from '@angular/core';
import { ViewMode } from '../../../../../core/shared/view-mode.model';
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
import { Context } from '../../../../../core/shared/context.model';
import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component';
import { CollectionSearchResult } from '../../../../../shared/object-collection/shared/collection-search-result.model';
import { Collection } from '../../../../../core/shared/collection.model';
import { getCollectionEditPath } from '../../../../../+collection-page/collection-page-routing.module';
@listableObjectComponent(CollectionSearchResult, ViewMode.ListElement, Context.AdminSearch)
@Component({
selector: 'ds-collection-admin-search-result-list-element',
styleUrls: ['./collection-admin-search-result-list-element.component.scss'],
templateUrl: './collection-admin-search-result-list-element.component.html'
})
/**
* The component for displaying a list element for a collection search result on the admin search page
*/
export class CollectionAdminSearchResultListElementComponent extends SearchResultListElementComponent<CollectionSearchResult, Collection> {
editPath: string;
ngOnInit() {
super.ngOnInit();
this.editPath = getCollectionEditPath(this.dso.uuid);
}
}

View File

@@ -0,0 +1,9 @@
<ds-community-search-result-list-element [object]="object"
[index]="index"
[linkType]="linkType"
[listID]="listID"></ds-community-search-result-list-element>
<div>
<a class="btn btn-light mt-1" [routerLink]="[editPath]">
<i class="fa fa-edit"></i> {{"admin.search.community.edit" | translate}}
</a>
</div>

View File

@@ -0,0 +1,60 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
import { CollectionElementLinkType } from '../../../../../shared/object-collection/collection-element-link.type';
import { ViewMode } from '../../../../../core/shared/view-mode.model';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { CommunityAdminSearchResultListElementComponent } from './community-admin-search-result-list-element.component';
import { CommunitySearchResult } from '../../../../../shared/object-collection/shared/community-search-result.model';
import { getCommunityEditPath } from '../../../../../+community-page/community-page-routing.module';
import { Community } from '../../../../../core/shared/community.model';
describe('CommunityAdminSearchResultListElementComponent', () => {
let component: CommunityAdminSearchResultListElementComponent;
let fixture: ComponentFixture<CommunityAdminSearchResultListElementComponent>;
let id;
let searchResult;
function init() {
id = '780b2588-bda5-4112-a1cd-0b15000a5339';
searchResult = new CommunitySearchResult();
searchResult.indexableObject = new Community();
searchResult.indexableObject.uuid = id;
}
beforeEach(async(() => {
init();
TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot(),
RouterTestingModule.withRoutes([])
],
declarations: [CommunityAdminSearchResultListElementComponent],
providers: [{ provide: TruncatableService, useValue: {} }],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CommunityAdminSearchResultListElementComponent);
component = fixture.componentInstance;
component.object = searchResult;
component.linkTypes = CollectionElementLinkType;
component.index = 0;
component.viewModes = ViewMode;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should render an edit button with the correct link', () => {
const a = fixture.debugElement.query(By.css('a'));
const link = a.nativeElement.href;
expect(link).toContain(getCommunityEditPath(id));
})
});

View File

@@ -0,0 +1,26 @@
import { Component } from '@angular/core';
import { ViewMode } from '../../../../../core/shared/view-mode.model';
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
import { Context } from '../../../../../core/shared/context.model';
import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component';
import { CommunitySearchResult } from '../../../../../shared/object-collection/shared/community-search-result.model';
import { Community } from '../../../../../core/shared/community.model';
import { getCommunityEditPath } from '../../../../../+community-page/community-page-routing.module';
@listableObjectComponent(CommunitySearchResult, ViewMode.ListElement, Context.AdminSearch)
@Component({
selector: 'ds-community-admin-search-result-list-element',
styleUrls: ['./community-admin-search-result-list-element.component.scss'],
templateUrl: './community-admin-search-result-list-element.component.html'
})
/**
* The component for displaying a list element for a community search result on the admin search page
*/
export class CommunityAdminSearchResultListElementComponent extends SearchResultListElementComponent<CommunitySearchResult, Community> {
editPath: string;
ngOnInit() {
super.ngOnInit();
this.editPath = getCommunityEditPath(this.dso.uuid);
}
}

View File

@@ -0,0 +1,12 @@
<div *ngIf="dso && !dso.isDiscoverable" class="private-badge">
<span class="badge badge-danger">{{ "admin.search.item.private" | translate }}</span>
</div>
<div *ngIf="dso && dso.isWithdrawn" class="withdrawn-badge">
<span class="badge badge-warning">{{ "admin.search.item.withdrawn" | translate }}</span>
</div>
<ds-listable-object-component-loader [object]="object"
[viewMode]="viewModes.ListElement"
[index]="index"
[linkType]="linkType"
[listID]="listID"></ds-listable-object-component-loader>
<ds-item-admin-search-result-actions-element [item]="dso" [small]="false"></ds-item-admin-search-result-actions-element>

View File

@@ -0,0 +1,101 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
import { CollectionElementLinkType } from '../../../../../shared/object-collection/collection-element-link.type';
import { ViewMode } from '../../../../../core/shared/view-mode.model';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
import { ItemAdminSearchResultListElementComponent } from './item-admin-search-result-list-element.component';
import { Item } from '../../../../../core/shared/item.model';
describe('ItemAdminSearchResultListElementComponent', () => {
let component: ItemAdminSearchResultListElementComponent;
let fixture: ComponentFixture<ItemAdminSearchResultListElementComponent>;
let id;
let searchResult;
function init() {
id = '780b2588-bda5-4112-a1cd-0b15000a5339';
searchResult = new ItemSearchResult();
searchResult.indexableObject = new Item();
searchResult.indexableObject.uuid = id;
}
beforeEach(async(() => {
init();
TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot(),
RouterTestingModule.withRoutes([])
],
declarations: [ItemAdminSearchResultListElementComponent],
providers: [{ provide: TruncatableService, useValue: {} }],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ItemAdminSearchResultListElementComponent);
component = fixture.componentInstance;
component.object = searchResult;
component.linkTypes = CollectionElementLinkType;
component.index = 0;
component.viewModes = ViewMode;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
describe('when the item is not withdrawn', () => {
beforeEach(() => {
component.dso.isWithdrawn = false;
fixture.detectChanges();
});
it('should not show the withdrawn badge', () => {
const badge = fixture.debugElement.query(By.css('div.withdrawn-badge'));
expect(badge).toBeNull();
});
});
describe('when the item is withdrawn', () => {
beforeEach(() => {
component.dso.isWithdrawn = true;
fixture.detectChanges();
});
it('should show the withdrawn badge', () => {
const badge = fixture.debugElement.query(By.css('div.withdrawn-badge'));
expect(badge).not.toBeNull();
});
});
describe('when the item is not private', () => {
beforeEach(() => {
component.dso.isDiscoverable = true;
fixture.detectChanges();
});
it('should not show the private badge', () => {
const badge = fixture.debugElement.query(By.css('div.private-badge'));
expect(badge).toBeNull();
});
});
describe('when the item is private', () => {
beforeEach(() => {
component.dso.isDiscoverable = false;
fixture.detectChanges();
});
it('should show the private badge', () => {
const badge = fixture.debugElement.query(By.css('div.private-badge'));
expect(badge).not.toBeNull();
});
})
});

View File

@@ -0,0 +1,20 @@
import { Component } from '@angular/core';
import { Item } from '../../../../../core/shared/item.model';
import { ViewMode } from '../../../../../core/shared/view-mode.model';
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
import { Context } from '../../../../../core/shared/context.model';
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component';
@listableObjectComponent(ItemSearchResult, ViewMode.ListElement, Context.AdminSearch)
@Component({
selector: 'ds-item-admin-search-result-list-element',
styleUrls: ['./item-admin-search-result-list-element.component.scss'],
templateUrl: './item-admin-search-result-list-element.component.html'
})
/**
* The component for displaying a list element for an item search result on the admin search page
*/
export class ItemAdminSearchResultListElementComponent extends SearchResultListElementComponent<ItemSearchResult, Item> {
}

View File

@@ -0,0 +1,27 @@
<a [ngClass]="{'btn-sm': small}" class="btn btn-light my-1 edit-link" [routerLink]="[getEditPath()]" [title]="'admin.search.item.edit' | translate">
<i class="fa fa-edit"></i><span *ngIf="!small" class="d-none d-sm-inline"> {{"admin.search.item.edit" | translate}}</span>
</a>
<a [ngClass]="{'btn-sm': small}" *ngIf="item && !item.isWithdrawn" class="btn btn-light my-1 withdraw-link" [routerLink]="[getWithdrawPath()]" [title]="'admin.search.item.withdraw' | translate">
<i class="fa fa-ban"></i><span *ngIf="!small" class="d-none d-sm-inline"> {{"admin.search.item.withdraw" | translate}}</span>
</a>
<a [ngClass]="{'btn-sm': small}" *ngIf="item && item.isWithdrawn" class="btn btn-light my-1 reinstate-link" [routerLink]="[getReinstatePath()]" [title]="'admin.search.item.reinstate' | translate">
<i class="fa fa-undo"></i><span *ngIf="!small" class="d-none d-sm-inline"> {{"admin.search.item.reinstate" | translate}}</span>
</a>
<a [ngClass]="{'btn-sm': small}" *ngIf="item && item.isDiscoverable" class="btn btn-light my-1 private-link" [routerLink]="[getPrivatePath()]" [title]="'admin.search.item.make-private' | translate">
<i class="fa fa-eye-slash"></i><span *ngIf="!small" class="d-none d-sm-inline"> {{"admin.search.item.make-private" | translate}}</span>
</a>
<a [ngClass]="{'btn-sm': small}" *ngIf="item && !item.isDiscoverable" class="btn btn-light my-1 public-link" [routerLink]="[getPublicPath()]" [title]="'admin.search.item.make-public' | translate">
<i class="fa fa-eye"></i><span *ngIf="!small" class="d-none d-sm-inline"> {{"admin.search.item.make-public" | translate}}</span>
</a>
<a [ngClass]="{'btn-sm': small}" class="btn btn-light my-1 delete-link" [routerLink]="[getDeletePath()]" [title]="'admin.search.item.delete' | translate">
<i class="fa fa-trash"></i><span *ngIf="!small" class="d-none d-sm-inline"> {{"admin.search.item.delete" | translate}}</span>
</a>
<a [ngClass]="{'btn-sm': small}" class="btn btn-light my-1 move-link" [routerLink]="[getMovePath()]" [title]="'admin.search.item.move' | translate">
<i class="fa fa-arrow-circle-right"></i><span *ngIf="!small" class="d-none d-sm-inline"> {{"admin.search.item.move" | translate}}</span>
</a>

View File

@@ -0,0 +1,144 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { ItemAdminSearchResultActionsComponent } from './item-admin-search-result-actions.component';
import { Item } from '../../../core/shared/item.model';
import {
ITEM_EDIT_DELETE_PATH,
ITEM_EDIT_MOVE_PATH,
ITEM_EDIT_PRIVATE_PATH,
ITEM_EDIT_PUBLIC_PATH,
ITEM_EDIT_REINSTATE_PATH,
ITEM_EDIT_WITHDRAW_PATH
} from '../../../+item-page/edit-item-page/edit-item-page.routing.module';
import { getItemEditPath } from '../../../+item-page/item-page-routing.module';
import { URLCombiner } from '../../../core/url-combiner/url-combiner';
describe('ItemAdminSearchResultActionsComponent', () => {
let component: ItemAdminSearchResultActionsComponent;
let fixture: ComponentFixture<ItemAdminSearchResultActionsComponent>;
let id;
let item;
function init() {
id = '780b2588-bda5-4112-a1cd-0b15000a5339';
item = new Item();
item.uuid = id;
}
beforeEach(async(() => {
init();
TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot(),
RouterTestingModule.withRoutes([])
],
declarations: [ItemAdminSearchResultActionsComponent],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ItemAdminSearchResultActionsComponent);
component = fixture.componentInstance;
component.item = item;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should render an edit button with the correct link', () => {
const button = fixture.debugElement.query(By.css('a.edit-link'));
const link = button.nativeElement.href;
expect(link).toContain(getItemEditPath(id));
});
it('should render a delete button with the correct link', () => {
const button = fixture.debugElement.query(By.css('a.delete-link'));
const link = button.nativeElement.href;
expect(link).toContain(new URLCombiner(getItemEditPath(id), ITEM_EDIT_DELETE_PATH).toString());
});
it('should render a move button with the correct link', () => {
const a = fixture.debugElement.query(By.css('a.move-link'));
const link = a.nativeElement.href;
expect(link).toContain(new URLCombiner(getItemEditPath(id), ITEM_EDIT_MOVE_PATH).toString());
});
describe('when the item is not withdrawn', () => {
beforeEach(() => {
component.item.isWithdrawn = false;
fixture.detectChanges();
});
it('should render a withdraw button with the correct link', () => {
const a = fixture.debugElement.query(By.css('a.withdraw-link'));
const link = a.nativeElement.href;
expect(link).toContain(new URLCombiner(getItemEditPath(id), ITEM_EDIT_WITHDRAW_PATH).toString());
});
it('should not render a reinstate button with the correct link', () => {
const a = fixture.debugElement.query(By.css('a.reinstate-link'));
expect(a).toBeNull();
});
});
describe('when the item is withdrawn', () => {
beforeEach(() => {
component.item.isWithdrawn = true;
fixture.detectChanges();
});
it('should not render a withdraw button with the correct link', () => {
const a = fixture.debugElement.query(By.css('a.withdraw-link'));
expect(a).toBeNull();
});
it('should render a reinstate button with the correct link', () => {
const a = fixture.debugElement.query(By.css('a.reinstate-link'));
const link = a.nativeElement.href;
expect(link).toContain(new URLCombiner(getItemEditPath(id), ITEM_EDIT_REINSTATE_PATH).toString());
});
});
describe('when the item is not private', () => {
beforeEach(() => {
component.item.isDiscoverable = true;
fixture.detectChanges();
});
it('should render a make private button with the correct link', () => {
const a = fixture.debugElement.query(By.css('a.private-link'));
const link = a.nativeElement.href;
expect(link).toContain(new URLCombiner(getItemEditPath(id), ITEM_EDIT_PRIVATE_PATH).toString());
});
it('should not render a make public button with the correct link', () => {
const a = fixture.debugElement.query(By.css('a.public-link'));
expect(a).toBeNull();
});
});
describe('when the item is private', () => {
beforeEach(() => {
component.item.isDiscoverable = false;
fixture.detectChanges();
});
it('should not render a make private button with the correct link', () => {
const a = fixture.debugElement.query(By.css('a.private-link'));
expect(a).toBeNull();
});
it('should render a make private button with the correct link', () => {
const a = fixture.debugElement.query(By.css('a.public-link'));
const link = a.nativeElement.href;
expect(link).toContain(new URLCombiner(getItemEditPath(id), ITEM_EDIT_PUBLIC_PATH).toString());
});
})
});

View File

@@ -0,0 +1,81 @@
import { Component, Input } from '@angular/core';
import { Item } from '../../../core/shared/item.model';
import { getItemEditPath } from '../../../+item-page/item-page-routing.module';
import { URLCombiner } from '../../../core/url-combiner/url-combiner';
import {
ITEM_EDIT_DELETE_PATH,
ITEM_EDIT_MOVE_PATH,
ITEM_EDIT_PRIVATE_PATH,
ITEM_EDIT_PUBLIC_PATH,
ITEM_EDIT_REINSTATE_PATH,
ITEM_EDIT_WITHDRAW_PATH
} from '../../../+item-page/edit-item-page/edit-item-page.routing.module';
@Component({
selector: 'ds-item-admin-search-result-actions-element',
styleUrls: ['./item-admin-search-result-actions.component.scss'],
templateUrl: './item-admin-search-result-actions.component.html'
})
/**
* The component for displaying the actions for a list element for an item search result on the admin search page
*/
export class ItemAdminSearchResultActionsComponent {
/**
* The item to perform the actions on
*/
@Input() public item: Item;
/**
* Whether or not to use small buttons
*/
@Input() public small: boolean;
/**
* Returns the path to the edit page of this item
*/
getEditPath(): string {
return getItemEditPath(this.item.uuid)
}
/**
* Returns the path to the move page of this item
*/
getMovePath(): string {
return new URLCombiner(this.getEditPath(), ITEM_EDIT_MOVE_PATH).toString();
}
/**
* Returns the path to the delete page of this item
*/
getDeletePath(): string {
return new URLCombiner(this.getEditPath(), ITEM_EDIT_DELETE_PATH).toString();
}
/**
* Returns the path to the withdraw page of this item
*/
getWithdrawPath(): string {
return new URLCombiner(this.getEditPath(), ITEM_EDIT_WITHDRAW_PATH).toString();
}
/**
* Returns the path to the reinstate page of this item
*/
getReinstatePath(): string {
return new URLCombiner(this.getEditPath(), ITEM_EDIT_REINSTATE_PATH).toString();
}
/**
* Returns the path to the page where the user can make this item private
*/
getPrivatePath(): string {
return new URLCombiner(this.getEditPath(), ITEM_EDIT_PRIVATE_PATH).toString();
}
/**
* Returns the path to the page where the user can make this item public
*/
getPublicPath(): string {
return new URLCombiner(this.getEditPath(), ITEM_EDIT_PUBLIC_PATH).toString();
}
}

View File

@@ -350,53 +350,19 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
link: ''
} as LinkMenuItemModel,
},
/* Search */
/* Admin Search */
{
id: 'find',
id: 'admin_search',
active: false,
visible: true,
model: {
type: MenuItemType.TEXT,
text: 'menu.section.find'
} as TextMenuItemModel,
type: MenuItemType.LINK,
text: 'menu.section.admin_search',
link: '/admin/search'
} as LinkMenuItemModel,
icon: 'search',
index: 5
},
{
id: 'find_items',
parentID: 'find',
active: false,
visible: true,
model: {
type: MenuItemType.LINK,
text: 'menu.section.find_items',
link: '/search'
} as LinkMenuItemModel,
},
{
id: 'find_withdrawn_items',
parentID: 'find',
active: false,
visible: true,
model: {
type: MenuItemType.LINK,
text: 'menu.section.find_withdrawn_items',
link: ''
} as LinkMenuItemModel,
},
{
id: 'find_private_items',
parentID: 'find',
active: false,
visible: true,
model: {
type: MenuItemType.LINK,
text: 'menu.section.find_private_items',
link: ''
} as LinkMenuItemModel,
},
/* Registries */
{
id: 'registries',

View File

@@ -2,13 +2,42 @@ import { NgModule } from '@angular/core';
import { AdminRegistriesModule } from './admin-registries/admin-registries.module';
import { AdminRoutingModule } from './admin-routing.module';
import { SharedModule } from '../shared/shared.module';
import { AdminSearchPageComponent } from './admin-search-page/admin-search-page.component';
import { SearchPageModule } from '../+search-page/search-page.module';
import { ItemAdminSearchResultListElementComponent } from './admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component';
import { CommunityAdminSearchResultListElementComponent } from './admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component';
import { CollectionAdminSearchResultListElementComponent } from './admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component';
import { ItemAdminSearchResultGridElementComponent } from './admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component';
import { CommunityAdminSearchResultGridElementComponent } from './admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component';
import { CollectionAdminSearchResultGridElementComponent } from './admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component';
import { ItemAdminSearchResultActionsComponent } from './admin-search-page/admin-search-results/item-admin-search-result-actions.component';
@NgModule({
imports: [
AdminRegistriesModule,
AdminRoutingModule,
SharedModule,
SearchPageModule
],
declarations: [
AdminSearchPageComponent,
ItemAdminSearchResultListElementComponent,
CommunityAdminSearchResultListElementComponent,
CollectionAdminSearchResultListElementComponent,
ItemAdminSearchResultGridElementComponent,
CommunityAdminSearchResultGridElementComponent,
CollectionAdminSearchResultGridElementComponent,
ItemAdminSearchResultActionsComponent
],
entryComponents: [
ItemAdminSearchResultListElementComponent,
CommunityAdminSearchResultListElementComponent,
CollectionAdminSearchResultListElementComponent,
ItemAdminSearchResultGridElementComponent,
CommunityAdminSearchResultGridElementComponent,
CollectionAdminSearchResultGridElementComponent,
ItemAdminSearchResultActionsComponent
]
})
export class AdminModule {

View File

@@ -40,6 +40,7 @@ const COLLECTION_EDIT_PATH = 'edit';
dso: CollectionPageResolver,
breadcrumb: CollectionBreadcrumbResolver
},
runGuardsAndResolvers: 'always',
children: [
{
path: COLLECTION_EDIT_PATH,

View File

@@ -39,6 +39,7 @@ const COMMUNITY_EDIT_PATH = 'edit';
dso: CommunityPageResolver,
breadcrumb: CommunityBreadcrumbResolver
},
runGuardsAndResolvers: 'always',
children: [
{
path: COMMUNITY_EDIT_PATH,

View File

@@ -15,12 +15,12 @@ import { ItemMoveComponent } from './item-move/item-move.component';
import { ItemRelationshipsComponent } from './item-relationships/item-relationships.component';
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
const ITEM_EDIT_WITHDRAW_PATH = 'withdraw';
const ITEM_EDIT_REINSTATE_PATH = 'reinstate';
const ITEM_EDIT_PRIVATE_PATH = 'private';
const ITEM_EDIT_PUBLIC_PATH = 'public';
const ITEM_EDIT_DELETE_PATH = 'delete';
const ITEM_EDIT_MOVE_PATH = 'move';
export const ITEM_EDIT_WITHDRAW_PATH = 'withdraw';
export const ITEM_EDIT_REINSTATE_PATH = 'reinstate';
export const ITEM_EDIT_PRIVATE_PATH = 'private';
export const ITEM_EDIT_PUBLIC_PATH = 'public';
export const ITEM_EDIT_DELETE_PATH = 'delete';
export const ITEM_EDIT_MOVE_PATH = 'move';
/**
* Routing module that handles the routing for the Edit Item page administrator functionality

View File

@@ -30,6 +30,7 @@ const ITEM_EDIT_PATH = 'edit';
item: ItemPageResolver,
breadcrumb: ItemBreadcrumbResolver
},
runGuardsAndResolvers: 'always',
children: [
{
path: '',

View File

@@ -1,3 +1,4 @@
import { switchMap } from 'rxjs/operators';
import { HostWindowService } from '../shared/host-window.service';
import { SidebarService } from '../shared/sidebar/sidebar.service';
import { SearchComponent } from './search.component';

View File

@@ -14,6 +14,7 @@ import { SearchPageComponent } from './search-page.component';
import { SidebarFilterService } from '../shared/sidebar/filter/sidebar-filter.service';
import { SearchFilterService } from '../core/shared/search/search-filter.service';
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
import { TranslateModule } from '@ngx-translate/core';
const components = [
SearchPageComponent,
@@ -28,7 +29,7 @@ const components = [
CommonModule,
SharedModule,
CoreModule.forRoot(),
StatisticsModule.forRoot(),
StatisticsModule.forRoot()
],
declarations: components,
providers: [

View File

@@ -22,7 +22,8 @@
<ds-search-results [searchResults]="resultsRD$ | async"
[searchConfig]="searchOptions$ | async"
[configuration]="configuration$ | async"
[disableHeader]="!searchEnabled"></ds-search-results>
[disableHeader]="!searchEnabled"
[context]="context"></ds-search-results>
</div>
</div>
</ds-page-with-sidebar>

View File

@@ -17,6 +17,7 @@ import { SearchConfigurationService } from '../core/shared/search/search-configu
import { SearchService } from '../core/shared/search/search.service';
import { currentPath } from '../shared/utils/route.utils';
import { Router } from '@angular/router';
import { Context } from '../core/shared/context.model';
@Component({
selector: 'ds-search',
@@ -84,6 +85,12 @@ export class SearchComponent implements OnInit {
@Input()
configuration$: Observable<string>;
/**
* The current context
*/
@Input()
context: Context;
/**
* Link to the search page
*/

View File

@@ -3,7 +3,6 @@ import { RouterModule } from '@angular/router';
import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
import { AuthenticatedGuard } from './core/auth/authenticated.guard';
import { Breadcrumb } from './breadcrumbs/breadcrumb/breadcrumb.model';
import { DSpaceObject } from './core/shared/dspace-object.model';
import { Community } from './core/shared/community.model';
import { getCommunityPageRoute } from './+community-page/community-page-routing.module';
@@ -11,7 +10,6 @@ import { Collection } from './core/shared/collection.model';
import { Item } from './core/shared/item.model';
import { getItemPageRoute } from './+item-page/item-page-routing.module';
import { getCollectionPageRoute } from './+collection-page/collection-page-routing.module';
import { BrowseByDSOBreadcrumbResolver } from './+browse-by/browse-by-dso-breadcrumb.resolver';
const ITEM_MODULE_PATH = 'items';
@@ -69,7 +67,10 @@ export function getDSOPath(dso: DSpaceObject): string {
{ path: 'workspaceitems', loadChildren: './+workspaceitems-edit-page/workspaceitems-edit-page.module#WorkspaceitemsEditPageModule' },
{ path: 'workflowitems', loadChildren: './+workflowitems-edit-page/workflowitems-edit-page.module#WorkflowItemsEditPageModule' },
{ path: '**', pathMatch: 'full', component: PageNotFoundComponent },
])
],
{
onSameUrlNavigation: 'reload',
})
],
exports: [RouterModule],
})

View File

@@ -20,7 +20,7 @@ import { ITEM } from '../shared/item.resource-type';
import {
configureRequest,
filterSuccessfulResponses,
getRequestFromRequestHref,
getRequestFromRequestHref, getRequestFromRequestUUID,
getResponseFromEntry
} from '../shared/operators';
import { URLCombiner } from '../url-combiner/url-combiner';
@@ -180,14 +180,17 @@ export class ItemDataService extends DataService<Item> {
const patchOperation = [{
op: 'replace', path: '/withdrawn', value: withdrawn
}];
this.requestService.removeByHrefSubstring('/discover');
return this.getItemWithdrawEndpoint(itemId).pipe(
distinctUntilChanged(),
map((endpointURL: string) =>
new PatchRequest(this.requestService.generateRequestId(), endpointURL, patchOperation)
),
configureRequest(this.requestService),
map((request: RestRequest) => request.href),
getRequestFromRequestHref(this.requestService),
map((request: RestRequest) => request.uuid),
getRequestFromRequestUUID(this.requestService),
filter((requestEntry: RequestEntry) => requestEntry.completed),
map((requestEntry: RequestEntry) => requestEntry.response)
);
}
@@ -201,14 +204,17 @@ export class ItemDataService extends DataService<Item> {
const patchOperation = [{
op: 'replace', path: '/discoverable', value: discoverable
}];
this.requestService.removeByHrefSubstring('/discover');
return this.getItemDiscoverableEndpoint(itemId).pipe(
distinctUntilChanged(),
map((endpointURL: string) =>
new PatchRequest(this.requestService.generateRequestId(), endpointURL, patchOperation)
),
configureRequest(this.requestService),
map((request: RestRequest) => request.href),
getRequestFromRequestHref(this.requestService),
map((request: RestRequest) => request.uuid),
getRequestFromRequestUUID(this.requestService),
filter((requestEntry: RequestEntry) => requestEntry.completed),
map((requestEntry: RequestEntry) => requestEntry.response)
);
}

View File

@@ -9,6 +9,7 @@ import {
SetQueryParameterAction,
SetQueryParametersAction
} from './route.actions';
import { isNotEmpty } from '../../shared/empty.util';
/**
* Interface to represent the parameter state of a current route in the store
@@ -81,7 +82,8 @@ function addParameter(state: RouteState, action: AddParameterAction | AddQueryPa
* @param paramType The type of parameter to set: route or query parameter
*/
function setParameters(state: RouteState, action: SetParametersAction | SetQueryParametersAction, paramType: string): RouteState {
return Object.assign({}, state, { [paramType]: { [action.payload.key]: action.payload.value } });
const param = isNotEmpty(action.payload) ? { [paramType]: { [action.payload.key]: action.payload.value } } : {};
return Object.assign({}, state, param);
}
/**

View File

@@ -6,7 +6,7 @@ import { combineLatest, Observable } from 'rxjs';
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
import { isEqual } from 'lodash';
import { AddParameterAction, SetParameterAction, SetParametersAction, SetQueryParametersAction } from './route.actions';
import { AddParameterAction, SetParameterAction, SetParametersAction, SetQueryParameterAction, SetQueryParametersAction } from './route.actions';
import { CoreState } from '../core.reducers';
import { coreSelector } from '../core.selectors';
import { hasValue } from '../../shared/empty.util';
@@ -194,6 +194,10 @@ export class RouteService {
this.store.dispatch(new SetParameterAction(key, value));
}
public setQueryParameter(key, value) {
this.store.dispatch(new SetQueryParameterAction(key, value));
}
/**
* Sets the current route parameters and query parameters in the store
*/

View File

@@ -10,4 +10,5 @@ export enum Context {
Workspace = 'workspace',
AdminMenu = 'adminMenu',
SubmissionModal = 'submissionModal',
AdminSearch = 'adminSearch',
}

View File

@@ -1,6 +1,10 @@
<ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
<ds-truncatable [id]="dso.id">
<ng-content></ng-content>
<a *ngIf="linkType != linkTypes.None"
[target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]"
class="card-img-top full-width">
<div>
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
</ds-grid-thumbnail>
@@ -17,7 +21,8 @@
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
<h4 class="card-title" [innerHTML]="firstMetadataValue('dc.title')"></h4>
</ds-truncatable-part>
<p *ngIf="dso.hasMetadata('creativework.datePublished')" class="item-date card-text text-muted">
<p *ngIf="dso.hasMetadata('creativework.datePublished')"
class="item-date card-text text-muted">
<ds-truncatable-part [id]="dso.id" [minLines]="1">
<span [innerHTML]="firstMetadataValue('creativework.datePublished')"></span>
</ds-truncatable-part>
@@ -28,9 +33,11 @@
</ds-truncatable-part>
</p>
<div *ngIf="linkType != linkTypes.None" class="text-center">
<a [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]"
<a [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]"
class="lead btn btn-primary viewButton">View</a>
</div>
</div>
</div>
</ds-truncatable>
<ng-content></ng-content>
</div>

View File

@@ -1,6 +1,10 @@
<ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
<ds-truncatable [id]="dso.id">
<ng-content></ng-content>
<a *ngIf="linkType != linkTypes.None"
[target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]"
class="card-img-top full-width">
<div>
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
</ds-grid-thumbnail>
@@ -17,7 +21,8 @@
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
<h4 class="card-title" [innerHTML]="dso.firstMetadataValue('dc.title')"></h4>
</ds-truncatable-part>
<p *ngIf="dso.hasMetadata('creativework.datePublished')" class="item-date card-text text-muted">
<p *ngIf="dso.hasMetadata('creativework.datePublished')"
class="item-date card-text text-muted">
<ds-truncatable-part [id]="dso.id" [minLines]="1">
<span [innerHTML]="firstMetadataValue('creativework.datePublished')"></span>
</ds-truncatable-part>
@@ -28,9 +33,11 @@
</ds-truncatable-part>
</p>
<div *ngIf="linkType != linkTypes.None" class="text-center">
<a [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]"
<a [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]"
class="lead btn btn-primary viewButton">View</a>
</div>
</div>
</div>
</ds-truncatable>
<ng-content></ng-content>
</div>

View File

@@ -1,6 +1,10 @@
<ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
<ds-truncatable [id]="dso.id">
<ng-content></ng-content>
<a *ngIf="linkType != linkTypes.None"
[target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]"
class="card-img-top full-width">
<div>
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
</ds-grid-thumbnail>
@@ -33,9 +37,11 @@
</ds-truncatable-part>
</p>
<div *ngIf="linkType != linkTypes.None" class="text-center">
<a [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]"
<a [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]"
class="lead btn btn-primary viewButton">View</a>
</div>
</div>
</div>
</ds-truncatable>
<ng-content></ng-content>
</div>

View File

@@ -1,6 +1,10 @@
<ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
<ds-truncatable [id]="dso.id">
<ng-content></ng-content>
<a *ngIf="linkType != linkTypes.None"
[target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]"
class="card-img-top full-width">
<div>
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
</ds-grid-thumbnail>
@@ -17,7 +21,8 @@
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
<h4 class="card-title" [innerHTML]="firstMetadataValue('organization.legalName')"></h4>
</ds-truncatable-part>
<p *ngIf="dso.hasMetadata('organization.foundingDate')" class="item-date card-text text-muted">
<p *ngIf="dso.hasMetadata('organization.foundingDate')"
class="item-date card-text text-muted">
<ds-truncatable-part [id]="dso.id" [minLines]="1">
<span [innerHTML]="firstMetadataValue('organization.foundingDate')"></span>
</ds-truncatable-part>
@@ -25,7 +30,8 @@
<p *ngIf="dso.hasMetadata('organization.address.addressCountry')"
class="item-location card-text">
<ds-truncatable-part [id]="dso.id" [minLines]="3">
<span class="item-country">{{firstMetadataValue('organization.address.addressCountry')}}</span>
<span
class="item-country">{{firstMetadataValue('organization.address.addressCountry')}}</span>
<span *ngIf="dso.hasMetadata('organization.address.addressLocality')" class="item-city">
<span>, </span>
{{firstMetadataValue('organization.address.addressLocality')}}
@@ -33,9 +39,11 @@
</ds-truncatable-part>
</p>
<div *ngIf="linkType != linkTypes.None" class="text-center">
<a [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]"
<a [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]"
class="lead btn btn-primary viewButton">View</a>
</div>
</div>
</div>
</ds-truncatable>
<ng-content></ng-content>
</div>

View File

@@ -1,6 +1,9 @@
<ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]"
<ds-truncatable [id]="dso.id">
<ng-content></ng-content>
<a *ngIf="linkType != linkTypes.None"
[target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]"
class="card-img-top full-width">
<div>
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
@@ -16,7 +19,8 @@
<div class="card-body">
<ds-item-type-badge [object]="dso"></ds-item-type-badge>
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
<h4 class="card-title" [innerHTML]="firstMetadataValue('person.familyName') + ', ' + firstMetadataValue('person.givenName')"></h4>
<h4 class="card-title"
[innerHTML]="firstMetadataValue('person.familyName') + ', ' + firstMetadataValue('person.givenName')"></h4>
</ds-truncatable-part>
<p *ngIf="dso.hasMetadata('person.email')" class="item-email card-text text-muted">
<ds-truncatable-part [id]="dso.id" [minLines]="1">
@@ -29,9 +33,11 @@
</ds-truncatable-part>
</p>
<div *ngIf="linkType != linkTypes.None" class="text-center">
<a [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]"
<a [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]"
class="lead btn btn-primary viewButton">View</a>
</div>
</div>
</div>
</ds-truncatable>
<ng-content></ng-content>
</div>

View File

@@ -1,6 +1,10 @@
<ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
<ds-truncatable [id]="dso.id">
<ng-content></ng-content>
<a *ngIf="linkType != linkTypes.None"
[target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]"
class="card-img-top full-width">
<div>
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
</ds-grid-thumbnail>
@@ -23,9 +27,11 @@
</ds-truncatable-part>
</p>
<div *ngIf="linkType != linkTypes.None" class="text-center">
<a [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]"
<a [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]"
class="lead btn btn-primary viewButton">View</a>
</div>
</div>
</div>
</ds-truncatable>
<ng-content></ng-content>
</div>

View File

@@ -0,0 +1,19 @@
import { of as observableOf } from 'rxjs/internal/observable/of';
export const mockTruncatableService: any = {
/* tslint:disable:no-empty */
isCollapsed: (id: string) => {
if (id === '1') {
return observableOf(true)
} else {
return observableOf(false);
}
},
expand: (id: string) => {
},
collapse: (id: string) => {
},
toggle: (id: string) => {
}
/* tslint:enable:no-empty */
};

View File

@@ -1,6 +1,8 @@
import { Component, Input } from '@angular/core';
import { ListableObject } from '../listable-object.model';
import { CollectionElementLinkType } from '../../collection-element-link.type';
import { Context } from '../../../../core/shared/context.model';
import { ViewMode } from '../../../../core/shared/view-mode.model';
@Component({
selector: 'ds-abstract-object-element',
@@ -22,8 +24,23 @@ export class AbstractListableElementComponent<T extends ListableObject> {
*/
@Input() listID: string;
/**
* The index of this element
*/
@Input() index: number;
/**
* The available link types
*/
linkTypes = CollectionElementLinkType;
/**
* The available view modes
*/
viewModes = ViewMode;
/**
* The available contexts
*/
contexts = Context;
}

View File

@@ -14,4 +14,5 @@
<a [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/collections/', dso.id]" class="lead btn btn-primary viewButton">View</a>
</div>
</div>
<ng-content></ng-content>
</div>

View File

@@ -6,6 +6,7 @@ import { Store } from '@ngrx/store';
import { of as observableOf } from 'rxjs';
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
import { ObjectCacheService } from '../../../../core/cache/object-cache.service';
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
import { CommunityDataService } from '../../../../core/data/community-data.service';
import { DefaultChangeAnalyzer } from '../../../../core/data/default-change-analyzer.service';
import { DSOChangeAnalyzer } from '../../../../core/data/dso-change-analyzer.service';
@@ -62,6 +63,7 @@ describe('CollectionSearchResultGridElementComponent', () => {
{ provide: UUIDService, useValue: {} },
{ provide: Store, useValue: {} },
{ provide: RemoteDataBuildService, useValue: {} },
{ provide: BitstreamDataService, useValue: {} },
{ provide: CommunityDataService, useValue: {} },
{ provide: HALEndpointService, useValue: {} },
{ provide: NotificationsService, useValue: {} },

View File

@@ -14,4 +14,5 @@
<a [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/communities/', dso.id]" class="lead btn btn-primary viewButton">View</a>
</div>
</div>
<ng-content></ng-content>
</div>

View File

@@ -6,6 +6,7 @@ import { Store } from '@ngrx/store';
import { of as observableOf } from 'rxjs';
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
import { ObjectCacheService } from '../../../../core/cache/object-cache.service';
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
import { CommunityDataService } from '../../../../core/data/community-data.service';
import { DefaultChangeAnalyzer } from '../../../../core/data/default-change-analyzer.service';
import { DSOChangeAnalyzer } from '../../../../core/data/dso-change-analyzer.service';
@@ -62,6 +63,7 @@ describe('CommunitySearchResultGridElementComponent', () => {
{ provide: UUIDService, useValue: {} },
{ provide: Store, useValue: {} },
{ provide: RemoteDataBuildService, useValue: {} },
{ provide: BitstreamDataService, useValue: {} },
{ provide: CommunityDataService, useValue: {} },
{ provide: HALEndpointService, useValue: {} },
{ provide: NotificationsService, useValue: {} },

View File

@@ -1,5 +1,6 @@
<ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
<ds-truncatable [id]="dso.id">
<ng-content></ng-content>
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]"
class="card-img-top full-width">
<div>
@@ -37,5 +38,7 @@
class="lead btn btn-primary viewButton">View</a>
</div>
</div>
</div>
</ds-truncatable>
<ng-content></ng-content>
</div>

View File

@@ -7,6 +7,7 @@ import { Item } from '../../../../../core/shared/item.model';
import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model';
@listableObjectComponent('PublicationSearchResult', ViewMode.GridElement)
@listableObjectComponent(ItemSearchResult, ViewMode.GridElement)
@Component({
selector: 'ds-publication-search-result-grid-element',
styleUrls: ['./publication-search-result-grid-element.component.scss'],

View File

@@ -27,9 +27,9 @@ describe('PaginatedSearchOptions', () => {
'query=search query&' +
'scope=0fde1ecb-82cc-425a-b600-ac3576d76b47&' +
'dsoType=ITEM&' +
'f.test=value,query&' +
'f.example=another value,query&' +
'f.example=second value,query'
'f.test=value&' +
'f.example=another value&' +
'f.example=second value'
);
});

View File

@@ -1,7 +1,6 @@
/**
* Represents a search filter
*/
import { hasValue } from '../empty.util';
export class SearchFilter {
key: string;
@@ -11,10 +10,6 @@ export class SearchFilter {
constructor(key: string, values: string[], operator?: string) {
this.key = key;
this.values = values;
if (hasValue(operator)) {
this.operator = operator;
} else {
this.operator = 'query';
}
}
}

View File

@@ -2,7 +2,9 @@
[routerLink]="[searchLink]"
[queryParams]="addQueryParams" queryParamsHandling="merge">
<input type="checkbox" [checked]="false" class="my-1 align-self-stretch"/>
<span class="filter-value px-1">{{filterValue.value}}</span>
<span class="filter-value px-1">
{{ 'search.filters.' + filterConfig.name + '.' + filterValue.value | translate: {default: filterValue.value} }}
</span>
<span class="float-right filter-value-count ml-auto">
<span class="badge badge-secondary badge-pill">{{filterValue.count}}</span>
</span>

View File

@@ -2,5 +2,7 @@
[routerLink]="[searchLink]"
[queryParams]="removeQueryParams" queryParamsHandling="merge">
<input type="checkbox" [checked]="true" class="my-1 align-self-stretch"/>
<span class="filter-value pl-1 text-capitalize">{{selectedValue.label}}</span>
<span class="filter-value pl-1 text-capitalize">
{{ 'search.filters.' + filterConfig.name + '.' + selectedValue.value | translate: {default: selectedValue.value} }}
</span>
</a>

View File

@@ -1,6 +1,6 @@
<a class="badge badge-primary mr-1 mb-1 text-capitalize"
[routerLink]="searchLink"
[queryParams]="(removeParameters | async)" queryParamsHandling="merge">
{{('search.filters.applied.' + key) | translate}}: {{normalizeFilterValue(value)}}
{{('search.filters.applied.' + key) | translate}}: {{'search.filters.' + filterName + '.' + value | translate: {default: normalizeFilterValue(value)} }}
<span> ×</span>
</a>

View File

@@ -22,6 +22,11 @@ export class SearchLabelComponent implements OnInit {
searchLink: string;
removeParameters: Observable<Params>;
/**
* The name of the filter without the f. prefix
*/
filterName: string;
/**
* Initialize the instance variable
*/
@@ -33,6 +38,7 @@ export class SearchLabelComponent implements OnInit {
ngOnInit(): void {
this.searchLink = this.getSearchLink();
this.removeParameters = this.getRemoveParams();
this.filterName = this.getFilterName();
}
/**
@@ -74,4 +80,8 @@ export class SearchLabelComponent implements OnInit {
const pattern = /,authority*$/g;
return value.replace(pattern, '');
}
private getFilterName(): string {
return this.key.startsWith('f.') ? this.key.substring(2) : this.key;
}
}

View File

@@ -21,9 +21,9 @@ describe('SearchOptions', () => {
'query=search query&' +
'scope=0fde1ecb-82cc-425a-b600-ac3576d76b47&' +
'dsoType=ITEM&' +
'f.test=value,query&' +
'f.example=another value,query&' +
'f.example=second value,query'
'f.test=value&' +
'f.example=another value&' +
'f.example=second value'
);
});

View File

@@ -50,7 +50,7 @@ export class SearchOptions {
if (isNotEmpty(this.filters)) {
this.filters.forEach((filter: SearchFilter) => {
filter.values.forEach((value) => {
const filterValue = value.includes(',') ? `${value}` : `${value},${filter.operator}`;
const filterValue = value.includes(',') ? `${value}` : value + (filter.operator ? ',' + filter.operator : '');
args.push(`${filter.key}=${filterValue}`)
});
});

View File

@@ -6,7 +6,7 @@ import { NouisliderModule } from 'ng2-nouislider';
import { NgbDatepickerModule, NgbModule, NgbTimepickerModule, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core';
import { MissingTranslationHandler, TranslateModule } from '@ngx-translate/core';
import { NgxPaginationModule } from 'ngx-pagination';
import { PublicationListElementComponent } from './object-list/item-list-element/item-types/publication/publication-list-element.component';
@@ -177,6 +177,7 @@ import { ImportableListItemControlComponent } from './object-collection/shared/i
import { DragDropModule } from '@angular/cdk/drag-drop';
import { ExistingMetadataListElementComponent } from './form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component';
import { SortablejsModule } from 'ngx-sortablejs';
import { MissingTranslationHelper } from './translate/missing-translation.helper';
const MODULES = [
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
@@ -194,7 +195,6 @@ const MODULES = [
NgxPaginationModule,
ReactiveFormsModule,
RouterModule,
TranslateModule,
NouisliderModule,
MomentModule,
TextMaskModule,
@@ -203,7 +203,11 @@ const MODULES = [
];
const ROOT_MODULES = [
TooltipModule.forRoot()
TranslateModule.forChild({
missingTranslationHandler: { provide: MissingTranslationHandler, useClass: MissingTranslationHelper },
useDefaultLang: true
}),
TooltipModule.forRoot(),
];
const PIPES = [
@@ -339,7 +343,8 @@ const COMPONENTS = [
SelectableListItemControlComponent,
ExternalSourceEntryImportModalComponent,
ImportableListItemControlComponent,
ExistingMetadataListElementComponent
ExistingMetadataListElementComponent,
PublicationSearchResultListElementComponent,
];
const ENTRY_COMPONENTS = [
@@ -402,7 +407,7 @@ const ENTRY_COMPONENTS = [
DsDynamicLookupRelationSearchTabComponent,
DsDynamicLookupRelationSelectionTabComponent,
DsDynamicLookupRelationExternalSourceTabComponent,
ExternalSourceEntryImportModalComponent
ExternalSourceEntryImportModalComponent,
];
const SHARED_ITEM_PAGE_COMPONENTS = [
@@ -435,8 +440,8 @@ const DIRECTIVES = [
@NgModule({
imports: [
...ROOT_MODULES,
...MODULES,
...ROOT_MODULES
],
declarations: [
...PIPES,
@@ -444,8 +449,7 @@ const DIRECTIVES = [
...DIRECTIVES,
...ENTRY_COMPONENTS,
...SHARED_ITEM_PAGE_COMPONENTS,
PublicationSearchResultListElementComponent,
ExistingMetadataListElementComponent
],
providers: [
...PROVIDERS

View File

@@ -0,0 +1,18 @@
import { MissingTranslationHandler, MissingTranslationHandlerParams } from '@ngx-translate/core';
/**
* Class to handle missing translations for the ngx-translate library
*/
export class MissingTranslationHelper implements MissingTranslationHandler {
/**
* Called when there is not translation for a specific key
* Will return the 'default' parameter of the translate pipe, if there is one available
* @param params
*/
handle(params: MissingTranslationHandlerParams) {
if (params.interpolateParams) {
return (params.interpolateParams as any).default || params.key;
}
return params.key;
}
}

View File

@@ -1,5 +1,6 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { of as observableOf } from 'rxjs';
import { mockTruncatableService } from '../mocks/mock-trucatable.service';
import { TruncatableComponent } from './truncatable.component';
import { TruncatableService } from './truncatable.service';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
@@ -10,29 +11,12 @@ describe('TruncatableComponent', () => {
let fixture: ComponentFixture<TruncatableComponent>;
const identifier = '1234567890';
let truncatableService;
const truncatableServiceStub: any = {
/* tslint:disable:no-empty */
isCollapsed: (id: string) => {
if (id === '1') {
return observableOf(true)
} else {
return observableOf(false);
}
},
expand: (id: string) => {
},
collapse: (id: string) => {
},
toggle: (id: string) => {
}
/* tslint:enable:no-empty */
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [NoopAnimationsModule],
declarations: [TruncatableComponent],
providers: [
{ provide: TruncatableService, useValue: truncatableServiceStub },
{ provide: TruncatableService, useValue: mockTruncatableService },
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(TruncatableComponent, {