grid view for admin search results

This commit is contained in:
lotte
2020-03-10 10:49:18 +01:00
parent 6c7780ca56
commit 401393c8bd
32 changed files with 608 additions and 82 deletions

View File

@@ -170,6 +170,8 @@
"admin.search.breadcrumbs": "Administrative Search",
"admin.search.collection.edit": "Edit", "admin.search.collection.edit": "Edit",
"admin.search.community.edit": "Edit", "admin.search.community.edit": "Edit",
@@ -192,7 +194,7 @@
"admin.search.item.withdrawn": "Withdrawn", "admin.search.item.withdrawn": "Withdrawn",
"admin.search.title": "Admin Search", "admin.search.title": "Administrative Search",
@@ -1520,6 +1522,8 @@
"search.filters.applied.f.dateSubmitted": "Date submitted", "search.filters.applied.f.dateSubmitted": "Date submitted",
"search.filters.applied.f.discoverable": "Private",
"search.filters.applied.f.entityType": "Item Type", "search.filters.applied.f.entityType": "Item Type",
"search.filters.applied.f.has_content_in_original_bundle": "Has files", "search.filters.applied.f.has_content_in_original_bundle": "Has files",
@@ -1531,10 +1535,15 @@
"search.filters.applied.f.subject": "Subject", "search.filters.applied.f.subject": "Subject",
"search.filters.applied.f.submitter": "Submitter", "search.filters.applied.f.submitter": "Submitter",
"search.filters.applied.f.jobTitle": "Job Title", "search.filters.applied.f.jobTitle": "Job Title",
"search.filters.applied.f.birthDate.max": "End birth date", "search.filters.applied.f.birthDate.max": "End birth date",
"search.filters.applied.f.birthDate.min": "Start birth date", "search.filters.applied.f.birthDate.min": "Start birth date",
"search.filters.applied.f.withdrawn": "Withdrawn",
"search.filters.filter.author.head": "Author", "search.filters.filter.author.head": "Author",
@@ -1571,6 +1580,10 @@
"search.filters.filter.dateSubmitted.placeholder": "Date submitted", "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.head": "Item Type",
"search.filters.filter.entityType.placeholder": "Item Type", "search.filters.filter.entityType.placeholder": "Item Type",
@@ -1637,6 +1650,14 @@
"search.filters.has_content_in_original_bundle.false": "No", "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.head": "Filters",
@@ -2017,7 +2038,7 @@
"title": "DSpace", "title": "DSpace",
"discoverableAndUndiscoverableItems.search.results.head": "Admin Search", "discoverableAndUndiscoverableItems.search.results.head": "Administrative Search",
"uploader.browse": "browse", "uploader.browse": "browse",

View File

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

View File

@@ -3,7 +3,6 @@ import { RouterModule } from '@angular/router';
import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component'; import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
import { AuthenticatedGuard } from './core/auth/authenticated.guard'; import { AuthenticatedGuard } from './core/auth/authenticated.guard';
import { Breadcrumb } from './breadcrumbs/breadcrumb/breadcrumb.model';
import { DSpaceObject } from './core/shared/dspace-object.model'; import { DSpaceObject } from './core/shared/dspace-object.model';
import { Community } from './core/shared/community.model'; import { Community } from './core/shared/community.model';
import { getCommunityPageRoute } from './+community-page/community-page-routing.module'; 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 { Item } from './core/shared/item.model';
import { getItemPageRoute } from './+item-page/item-page-routing.module'; import { getItemPageRoute } from './+item-page/item-page-routing.module';
import { getCollectionPageRoute } from './+collection-page/collection-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'; const ITEM_MODULE_PATH = 'items';

View File

@@ -1,6 +1,8 @@
<ds-truncatable [id]="dso.id"> <ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'"> <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"> <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> <div>
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async"> <ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
</ds-grid-thumbnail> </ds-grid-thumbnail>
@@ -32,5 +34,6 @@
class="lead btn btn-primary viewButton">View</a> class="lead btn btn-primary viewButton">View</a>
</div> </div>
</div> </div>
<ng-content></ng-content>
</div> </div>
</ds-truncatable> </ds-truncatable>

View File

@@ -1,6 +1,8 @@
<ds-truncatable [id]="dso.id"> <ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'"> <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"> <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> <div>
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async"> <ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
</ds-grid-thumbnail> </ds-grid-thumbnail>
@@ -32,5 +34,6 @@
class="lead btn btn-primary viewButton">View</a> class="lead btn btn-primary viewButton">View</a>
</div> </div>
</div> </div>
<ng-content></ng-content>
</div> </div>
</ds-truncatable> </ds-truncatable>

View File

@@ -1,6 +1,8 @@
<ds-truncatable [id]="dso.id"> <ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'"> <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"> <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> <div>
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async"> <ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
</ds-grid-thumbnail> </ds-grid-thumbnail>
@@ -37,5 +39,6 @@
class="lead btn btn-primary viewButton">View</a> class="lead btn btn-primary viewButton">View</a>
</div> </div>
</div> </div>
<ng-content></ng-content>
</div> </div>
</ds-truncatable> </ds-truncatable>

View File

@@ -1,6 +1,8 @@
<ds-truncatable [id]="dso.id"> <ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'"> <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"> <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> <div>
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async"> <ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
</ds-grid-thumbnail> </ds-grid-thumbnail>
@@ -37,5 +39,6 @@
class="lead btn btn-primary viewButton">View</a> class="lead btn btn-primary viewButton">View</a>
</div> </div>
</div> </div>
<ng-content></ng-content>
</div> </div>
</ds-truncatable> </ds-truncatable>

View File

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

View File

@@ -1,6 +1,8 @@
<ds-truncatable [id]="dso.id"> <ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'"> <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"> <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> <div>
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async"> <ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
</ds-grid-thumbnail> </ds-grid-thumbnail>
@@ -27,5 +29,6 @@
class="lead btn btn-primary viewButton">View</a> class="lead btn btn-primary viewButton">View</a>
</div> </div>
</div> </div>
<ng-content></ng-content>
</div> </div>
</ds-truncatable> </ds-truncatable>

View File

@@ -0,0 +1,11 @@
<ds-collection-search-result-grid-element [object]="object"
[index]="index"
[linkType]="linkType"
[listID]="listID">
<div class="card-footer text-center">
<a class="btn btn-light btn-sm btn-auto my-1" [routerLink]="[getEditPath()]">
<i class="fa fa-edit"></i>
</a>
</div>
</ds-collection-search-result-grid-element>

View File

@@ -0,0 +1,60 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { CollectionAdminSearchResultGridElementComponent } from './collection-admin-search-result-grid-element.component';
import { TranslateModule } from '@ngx-translate/core';
import { TruncatableService } from '../../../truncatable/truncatable.service';
import { CollectionElementLinkType } from '../../../object-collection/collection-element-link.type';
import { ViewMode } from '../../../../core/shared/view-mode.model';
import { CollectionSearchResult } from '../../../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: 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: [
TranslateModule.forRoot(),
RouterTestingModule.withRoutes([])
],
declarations: [CollectionAdminSearchResultGridElementComponent],
providers: [{ provide: TruncatableService, useValue: {} }],
schemas: [NO_ERRORS_SCHEMA]
})
.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'));
const link = a.nativeElement.href;
expect(link).toContain(getCollectionEditPath(id));
})
});

View File

@@ -0,0 +1,27 @@
import { Component } from '@angular/core';
import { ViewMode } from '../../../../core/shared/view-mode.model';
import { listableObjectComponent } from '../../../object-collection/shared/listable-object/listable-object.decorator';
import { Context } from '../../../../core/shared/context.model';
import { CollectionSearchResult } from '../../../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 '../../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> {
/**
* Returns the path to the edit page of this collection
*/
getEditPath(): string {
return getCollectionEditPath(this.dso.uuid)
}
}

View File

@@ -0,0 +1,11 @@
<ds-community-search-result-grid-element [object]="object"
[index]="index"
[linkType]="linkType"
[listID]="listID">
<div class="card-footer text-center">
<a class="btn btn-light btn-sm btn-auto my-1" [routerLink]="[getEditPath()]">
<i class="fa fa-edit"></i>
</a>
</div>
</ds-community-search-result-grid-element>

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 '../../../truncatable/truncatable.service';
import { CollectionElementLinkType } from '../../../object-collection/collection-element-link.type';
import { ViewMode } from '../../../../core/shared/view-mode.model';
import { Collection } from '../../../../core/shared/collection.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 '../../../object-collection/shared/community-search-result.model';
import { getCommunityEditPath } from '../../../../+community-page/community-page-routing.module';
describe('CommunityAdminSearchResultListElementComponent', () => {
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 Collection();
searchResult.indexableObject.uuid = id;
}
beforeEach(async(() => {
init();
TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot(),
RouterTestingModule.withRoutes([])
],
declarations: [CommunityAdminSearchResultGridElementComponent],
providers: [{ provide: TruncatableService, 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'));
const link = a.nativeElement.href;
expect(link).toContain(getCommunityEditPath(id));
})
});

View File

@@ -0,0 +1,27 @@
import { Component } from '@angular/core';
import { ViewMode } from '../../../../core/shared/view-mode.model';
import { listableObjectComponent } from '../../../object-collection/shared/listable-object/listable-object.decorator';
import { Context } from '../../../../core/shared/context.model';
import { CommunitySearchResult } from '../../../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 '../../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> {
/**
* Returns the path to the edit page of this community
*/
getEditPath(): string {
return getCommunityEditPath(this.dso.uuid)
}
}

View File

@@ -0,0 +1,39 @@
<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>
<div #buttons class="card-footer d-flex justify-content-between">
<a class="btn btn-light btn-sm my-1 edit-link" [routerLink]="[getEditPath()]" [title]="'admin.search.item.edit' | translate">
<i class="fa fa-edit"></i>
</a>
<a *ngIf="dso && !dso.isWithdrawn" class="btn btn-light btn-sm my-1 withdraw-link" [routerLink]="[getWithdrawPath()]" [title]="'admin.search.item.withdraw' | translate">
<i class="fa fa-box"></i>
</a>
<a *ngIf="dso && dso.isWithdrawn" class="btn btn-light btn-sm my-1 reinstate-link" [routerLink]="[getReinstatePath()]" [title]="'admin.search.item.reinstate' | translate">
<i class="fa fa-box-open"></i>
</a>
<a *ngIf="dso && dso.isDiscoverable" class="btn btn-light btn-sm my-1 private-link" [routerLink]="[getPrivatePath()]" [title]="'admin.search.item.make-private' | translate">
<i class="fa fa-eye-slash"></i>
</a>
<a *ngIf="dso && !dso.isDiscoverable" class="btn btn-light btn-sm my-1 public-link" [routerLink]="[getPublicPath()]" [title]="'admin.search.item.make-public' | translate">
<i class="fa fa-eye"></i>
</a>
<a class="btn btn-light btn-sm my-1 delete-link" [routerLink]="[getDeletePath()]" [title]="'admin.search.item.delete' | translate">
<i class="fa fa-trash"></i>
</a>
<a class="btn btn-light btn-sm my-1 move-link" [routerLink]="[getMovePath()]" [title]="'admin.search.item.move' | translate">
<i class="fa fa-arrow-circle-right"></i>
</a>
</div>

View File

@@ -0,0 +1,120 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { TruncatableService } from '../../../truncatable/truncatable.service';
import { CollectionElementLinkType } from '../../../object-collection/collection-element-link.type';
import { ViewMode } from '../../../../core/shared/view-mode.model';
import { Collection } from '../../../../core/shared/collection.model';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
import { ItemAdminSearchResultGridElementComponent } from './item-admin-search-result-grid-element.component';
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_REINSTATE_PATH, ITEM_EDIT_WITHDRAW_PATH } from '../../../../+item-page/edit-item-page/edit-item-page.routing.module';
describe('ItemAdminSearchResultListElementComponent', () => {
let component: ItemAdminSearchResultGridElementComponent;
let fixture: ComponentFixture<ItemAdminSearchResultGridElementComponent>;
let id;
let searchResult;
function init() {
id = '780b2588-bda5-4112-a1cd-0b15000a5339';
searchResult = new ItemSearchResult();
searchResult.indexableObject = new Collection();
searchResult.indexableObject.uuid = id;
}
beforeEach(async(() => {
init();
TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot(),
RouterTestingModule.withRoutes([])
],
declarations: [ItemAdminSearchResultGridElementComponent],
providers: [{ provide: TruncatableService, useValue: {} }],
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();
});
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.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();
});
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.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();
});
it('should render a withdraw button with the correct link', () => {
const a = fixture.debugElement.query(By.css('a.withdraw-link'));
expect(a).toBeNull();
});
it('should not 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());
});
})
});

View File

@@ -0,0 +1,124 @@
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 '../../../object-collection/shared/listable-object/listable-object.decorator';
import { Context } from '../../../../core/shared/context.model';
import { ItemSearchResult } from '../../../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 '../../search-result-grid-element/search-result-grid-element.component';
import { TruncatableService } from '../../../truncatable/truncatable.service';
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
import { GenericConstructor } from '../../../../core/shared/generic-constructor';
import { ListableObjectDirective } from '../../../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,
private viewContainerRef: ViewContainerRef) {
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)
}
/**
* Returns the path to the edit page of this item
*/
getEditPath(): string {
return getItemEditPath(this.dso.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

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

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> <a [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/communities/', dso.id]" class="lead btn btn-primary viewButton">View</a>
</div> </div>
</div> </div>
<ng-content></ng-content>
</div> </div>

View File

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

View File

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

View File

@@ -1,8 +1,9 @@
<ds-listable-object-component-loader [object]="object" <ds-collection-search-result-list-element [object]="object"
[viewMode]="viewModes.ListElement"
[index]="index" [index]="index"
[linkType]="linkType" [linkType]="linkType"
[listID]="listID"></ds-listable-object-component-loader> [listID]="listID"></ds-collection-search-result-list-element>
<a class="btn btn-primary mt-1 mb-3" [routerLink]="[getEditPath()]"> <div>
<a class="btn btn-light mt-1" [routerLink]="[getEditPath()]">
<i class="fa fa-edit"></i> {{"admin.search.collection.edit" | translate}} <i class="fa fa-edit"></i> {{"admin.search.collection.edit" | translate}}
</a> </a>
</div>

View File

@@ -1,8 +1,9 @@
<ds-listable-object-component-loader [object]="object" <ds-community-search-result-list-element [object]="object"
[viewMode]="viewModes.ListElement"
[index]="index" [index]="index"
[linkType]="linkType" [linkType]="linkType"
[listID]="listID"></ds-listable-object-component-loader> [listID]="listID"></ds-community-search-result-list-element>
<a class="btn btn-primary mt-1 mb-3" [routerLink]="[getEditPath()]"> <div>
<a class="btn btn-light mt-1" [routerLink]="[getEditPath()]">
<i class="fa fa-edit"></i> {{"admin.search.community.edit" | translate}} <i class="fa fa-edit"></i> {{"admin.search.community.edit" | translate}}
</a> </a>
</div>

View File

@@ -10,30 +10,30 @@
[linkType]="linkType" [linkType]="linkType"
[listID]="listID"></ds-listable-object-component-loader> [listID]="listID"></ds-listable-object-component-loader>
<a class="btn btn-light my-1 edit-link" [routerLink]="[getEditPath()]"> <a class="btn btn-light my-1 edit-link" [routerLink]="[getEditPath()]" [title]="'admin.search.item.edit' | translate">
<i class="fa fa-edit"></i><span class="d-none d-sm-inline"> {{"admin.search.item.edit" | translate}}</span> <i class="fa fa-edit"></i><span class="d-none d-sm-inline"> {{"admin.search.item.edit" | translate}}</span>
</a> </a>
<a *ngIf="dso && !dso.isWithdrawn" class="btn btn-light my-1 withdraw-link" [routerLink]="[getWithdrawPath()]"> <a *ngIf="dso && !dso.isWithdrawn" class="btn btn-light my-1 withdraw-link" [routerLink]="[getWithdrawPath()]" [title]="'admin.search.item.withdraw' | translate">
<i class="fa fa-box"></i><span class="d-none d-sm-inline"> {{"admin.search.item.withdraw" | translate}}</span> <i class="fa fa-box"></i><span class="d-none d-sm-inline"> {{"admin.search.item.withdraw" | translate}}</span>
</a> </a>
<a *ngIf="dso && dso.isWithdrawn" class="btn btn-light my-1 reinstate-link" [routerLink]="[getReinstatePath()]"> <a *ngIf="dso && dso.isWithdrawn" class="btn btn-light my-1 reinstate-link" [routerLink]="[getReinstatePath()]" [title]="'admin.search.item.reinstate' | translate">
<i class="fa fa-box-open"></i><span class="d-none d-sm-inline"> {{"admin.search.item.reinstate" | translate}}</span> <i class="fa fa-box-open"></i><span class="d-none d-sm-inline"> {{"admin.search.item.reinstate" | translate}}</span>
</a> </a>
<a *ngIf="dso && dso.isDiscoverable" class="btn btn-light my-1 private-link" [routerLink]="[getPrivatePath()]"> <a *ngIf="dso && dso.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 class="d-none d-sm-inline"> {{"admin.search.item.make-private" | translate}}</span> <i class="fa fa-eye-slash"></i><span class="d-none d-sm-inline"> {{"admin.search.item.make-private" | translate}}</span>
</a> </a>
<a *ngIf="dso && !dso.isDiscoverable" class="btn btn-light my-1 public-link" [routerLink]="[getPublicPath()]"> <a *ngIf="dso && !dso.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 class="d-none d-sm-inline"> {{"admin.search.item.make-public" | translate}}</span> <i class="fa fa-eye"></i><span class="d-none d-sm-inline"> {{"admin.search.item.make-public" | translate}}</span>
</a> </a>
<a class="btn btn-light my-1 delete-link" [routerLink]="[getDeletePath()]"> <a class="btn btn-light my-1 delete-link" [routerLink]="[getDeletePath()]" [title]="'admin.search.item.delete' | translate">
<i class="fa fa-trash"></i><span class="d-none d-sm-inline"> {{"admin.search.item.delete" | translate}}</span> <i class="fa fa-trash"></i><span class="d-none d-sm-inline"> {{"admin.search.item.delete" | translate}}</span>
</a> </a>
<a class="btn btn-light my-1 move-link" [routerLink]="[getMovePath()]"> <a 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 class="d-none d-sm-inline"> {{"admin.search.item.move" | translate}}</span> <i class="fa fa-arrow-circle-right"></i><span class="d-none d-sm-inline"> {{"admin.search.item.move" | translate}}</span>
</a> </a>

View File

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

View File

@@ -50,7 +50,7 @@ export class SearchOptions {
if (isNotEmpty(this.filters)) { if (isNotEmpty(this.filters)) {
this.filters.forEach((filter: SearchFilter) => { this.filters.forEach((filter: SearchFilter) => {
filter.values.forEach((value) => { 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}`) args.push(`${filter.key}=${filterValue}`)
}); });
}); });

View File

@@ -181,6 +181,9 @@ import { ItemAdminSearchResultListElementComponent } from './object-list/admin-s
import { CommunityAdminSearchResultListElementComponent } from './object-list/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component'; import { CommunityAdminSearchResultListElementComponent } from './object-list/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component';
import { CollectionAdminSearchResultListElementComponent } from './object-list/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component'; import { CollectionAdminSearchResultListElementComponent } from './object-list/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component';
import { MissingTranslationHelper } from './translate/missing-translation.helper'; import { MissingTranslationHelper } from './translate/missing-translation.helper';
import { ItemAdminSearchResultGridElementComponent } from './object-grid/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component';
import { CommunityAdminSearchResultGridElementComponent } from './object-grid/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component';
import { CollectionAdminSearchResultGridElementComponent } from './object-grid/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component';
const MODULES = [ const MODULES = [
// Do NOT include UniversalModule, HttpModule, or JsonpModule here // Do NOT include UniversalModule, HttpModule, or JsonpModule here
@@ -347,11 +350,13 @@ const COMPONENTS = [
ExternalSourceEntryImportModalComponent, ExternalSourceEntryImportModalComponent,
ImportableListItemControlComponent, ImportableListItemControlComponent,
ExistingMetadataListElementComponent, ExistingMetadataListElementComponent,
PublicationSearchResultListElementComponent,
ItemAdminSearchResultListElementComponent, ItemAdminSearchResultListElementComponent,
CommunityAdminSearchResultListElementComponent, CommunityAdminSearchResultListElementComponent,
CollectionAdminSearchResultListElementComponent, CollectionAdminSearchResultListElementComponent,
PublicationSearchResultListElementComponent, ItemAdminSearchResultGridElementComponent,
ExistingMetadataListElementComponent CommunityAdminSearchResultGridElementComponent,
CollectionAdminSearchResultGridElementComponent
]; ];
const ENTRY_COMPONENTS = [ const ENTRY_COMPONENTS = [
@@ -418,6 +423,9 @@ const ENTRY_COMPONENTS = [
ItemAdminSearchResultListElementComponent, ItemAdminSearchResultListElementComponent,
CommunityAdminSearchResultListElementComponent, CommunityAdminSearchResultListElementComponent,
CollectionAdminSearchResultListElementComponent, CollectionAdminSearchResultListElementComponent,
ItemAdminSearchResultGridElementComponent,
CommunityAdminSearchResultGridElementComponent,
CollectionAdminSearchResultGridElementComponent
]; ];
const SHARED_ITEM_PAGE_COMPONENTS = [ const SHARED_ITEM_PAGE_COMPONENTS = [

View File

@@ -1,4 +1,4 @@
import {MissingTranslationHandler, MissingTranslationHandlerParams} from '@ngx-translate/core'; import { MissingTranslationHandler, MissingTranslationHandlerParams } from '@ngx-translate/core';
export class MissingTranslationHelper implements MissingTranslationHandler { export class MissingTranslationHelper implements MissingTranslationHandler {
handle(params: MissingTranslationHandlerParams) { handle(params: MissingTranslationHandlerParams) {