mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-08 18:44:14 +00:00
Merge pull request #930 from atmire/Admin-search-dialogs
Admin search dialogs
This commit is contained in:
@@ -3,37 +3,41 @@
|
|||||||
*ngVar="(collectionRD$ | async) as collectionRD">
|
*ngVar="(collectionRD$ | async) as collectionRD">
|
||||||
<div *ngIf="collectionRD?.hasSucceeded" @fadeInOut>
|
<div *ngIf="collectionRD?.hasSucceeded" @fadeInOut>
|
||||||
<div *ngIf="collectionRD?.payload as collection">
|
<div *ngIf="collectionRD?.payload as collection">
|
||||||
<ds-view-tracker [object]="collection"></ds-view-tracker>
|
<ds-view-tracker [object]="collection"></ds-view-tracker>
|
||||||
<header class="comcol-header border-bottom mb-4 pb-4">
|
<div class="d-flex flex-row border-bottom mb-4 pb-4">
|
||||||
|
<header class="comcol-header mr-auto">
|
||||||
<!-- Collection Name -->
|
<!-- Collection Name -->
|
||||||
<ds-comcol-page-header
|
<ds-comcol-page-header
|
||||||
[name]="collection.name">
|
[name]="collection.name">
|
||||||
</ds-comcol-page-header>
|
</ds-comcol-page-header>
|
||||||
<!-- Collection logo -->
|
<!-- Collection logo -->
|
||||||
<ds-comcol-page-logo *ngIf="logoRD$"
|
<ds-comcol-page-logo *ngIf="logoRD$"
|
||||||
[logo]="(logoRD$ | async)?.payload"
|
[logo]="(logoRD$ | async)?.payload"
|
||||||
[alternateText]="'Collection Logo'"
|
[alternateText]="'Collection Logo'"
|
||||||
[alternateText]="'Collection Logo'">
|
[alternateText]="'Collection Logo'">
|
||||||
</ds-comcol-page-logo>
|
</ds-comcol-page-logo>
|
||||||
|
|
||||||
<!-- Handle -->
|
<!-- Handle -->
|
||||||
<ds-comcol-page-handle
|
<ds-comcol-page-handle
|
||||||
[content]="collection.handle"
|
[content]="collection.handle"
|
||||||
[title]="'collection.page.handle'" >
|
[title]="'collection.page.handle'" >
|
||||||
</ds-comcol-page-handle>
|
</ds-comcol-page-handle>
|
||||||
<!-- Introductory text -->
|
<!-- Introductory text -->
|
||||||
<ds-comcol-page-content
|
<ds-comcol-page-content
|
||||||
[content]="collection.introductoryText"
|
[content]="collection.introductoryText"
|
||||||
[hasInnerHtml]="true">
|
[hasInnerHtml]="true">
|
||||||
</ds-comcol-page-content>
|
</ds-comcol-page-content>
|
||||||
<!-- News -->
|
<!-- News -->
|
||||||
<ds-comcol-page-content
|
<ds-comcol-page-content
|
||||||
[content]="collection.sidebarText"
|
[content]="collection.sidebarText"
|
||||||
[hasInnerHtml]="true"
|
[hasInnerHtml]="true"
|
||||||
[title]="'collection.page.news'">
|
[title]="'collection.page.news'">
|
||||||
</ds-comcol-page-content>
|
</ds-comcol-page-content>
|
||||||
|
</header>
|
||||||
</header>
|
<div class="pl-2">
|
||||||
|
<ds-dso-page-edit-button [pageRoutePrefix]="'collections'" [dso]="collection" [tooltipMsg]="'collection.page.edit'"></ds-dso-page-edit-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<section class="comcol-page-browse-section">
|
<section class="comcol-page-browse-section">
|
||||||
<!-- Browse-By Links -->
|
<!-- Browse-By Links -->
|
||||||
<ds-comcol-page-browse-by
|
<ds-comcol-page-browse-by
|
||||||
|
@@ -2,24 +2,28 @@
|
|||||||
<div class="community-page" *ngIf="communityRD?.hasSucceeded" @fadeInOut>
|
<div class="community-page" *ngIf="communityRD?.hasSucceeded" @fadeInOut>
|
||||||
<div *ngIf="communityRD?.payload; let communityPayload">
|
<div *ngIf="communityRD?.payload; let communityPayload">
|
||||||
<ds-view-tracker [object]="communityPayload"></ds-view-tracker>
|
<ds-view-tracker [object]="communityPayload"></ds-view-tracker>
|
||||||
<header class="comcol-header border-bottom mb-4 pb-4">
|
<div class="d-flex flex-row border-bottom mb-4 pb-4">
|
||||||
<!-- Community name -->
|
<header class="comcol-header mr-auto">
|
||||||
<ds-comcol-page-header [name]="communityPayload.name"></ds-comcol-page-header>
|
<!-- Community name -->
|
||||||
<!-- Community logo -->
|
<ds-comcol-page-header [name]="communityPayload.name"></ds-comcol-page-header>
|
||||||
<ds-comcol-page-logo *ngIf="logoRD$" [logo]="(logoRD$ | async)?.payload" [alternateText]="'Community Logo'">
|
<!-- Community logo -->
|
||||||
</ds-comcol-page-logo>
|
<ds-comcol-page-logo *ngIf="logoRD$" [logo]="(logoRD$ | async)?.payload" [alternateText]="'Community Logo'">
|
||||||
<!-- Handle -->
|
</ds-comcol-page-logo>
|
||||||
<ds-comcol-page-handle [content]="communityPayload.handle" [title]="'community.page.handle'">
|
<!-- Handle -->
|
||||||
</ds-comcol-page-handle>
|
<ds-comcol-page-handle [content]="communityPayload.handle" [title]="'community.page.handle'">
|
||||||
<!-- Introductory text -->
|
</ds-comcol-page-handle>
|
||||||
<ds-comcol-page-content [content]="communityPayload.introductoryText" [hasInnerHtml]="true">
|
<!-- Introductory text -->
|
||||||
</ds-comcol-page-content>
|
<ds-comcol-page-content [content]="communityPayload.introductoryText" [hasInnerHtml]="true">
|
||||||
<!-- News -->
|
</ds-comcol-page-content>
|
||||||
<ds-comcol-page-content [content]="communityPayload.sidebarText" [hasInnerHtml]="true"
|
<!-- News -->
|
||||||
[title]="'community.page.news'">
|
<ds-comcol-page-content [content]="communityPayload.sidebarText" [hasInnerHtml]="true"
|
||||||
</ds-comcol-page-content>
|
[title]="'community.page.news'">
|
||||||
|
</ds-comcol-page-content>
|
||||||
</header>
|
</header>
|
||||||
|
<div class="pl-2">
|
||||||
|
<ds-dso-page-edit-button [pageRoutePrefix]="'communities'" [dso]="communityPayload" [tooltipMsg]="'community.page.edit'"></ds-dso-page-edit-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<section class="comcol-page-browse-section">
|
<section class="comcol-page-browse-section">
|
||||||
<!-- Browse-By Links -->
|
<!-- Browse-By Links -->
|
||||||
<ds-comcol-page-browse-by [id]="communityPayload.id" [contentType]="communityPayload.type">
|
<ds-comcol-page-browse-by [id]="communityPayload.id" [contentType]="communityPayload.type">
|
||||||
|
@@ -48,7 +48,7 @@ export class AbstractItemUpdateComponent extends AbstractTrackableComponent impl
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
observableCombineLatest(this.route.data, this.route.parent.data).pipe(
|
observableCombineLatest(this.route.data, this.route.parent.data).pipe(
|
||||||
map(([data, parentData]) => Object.assign({}, data, parentData)),
|
map(([data, parentData]) => Object.assign({}, data, parentData)),
|
||||||
map((data) => data.item),
|
map((data) => data.dso),
|
||||||
first(),
|
first(),
|
||||||
map((data: RemoteData<Item>) => data.payload)
|
map((data: RemoteData<Item>) => data.payload)
|
||||||
).subscribe((item: Item) => {
|
).subscribe((item: Item) => {
|
||||||
|
@@ -47,7 +47,7 @@ export class EditItemPageComponent implements OnInit {
|
|||||||
this.pages = this.route.routeConfig.children
|
this.pages = this.route.routeConfig.children
|
||||||
.map((child: any) => child.path)
|
.map((child: any) => child.path)
|
||||||
.filter((path: string) => isNotEmpty(path)); // ignore reroutes
|
.filter((path: string) => isNotEmpty(path)); // ignore reroutes
|
||||||
this.itemRD$ = this.route.data.pipe(map((data) => data.item));
|
this.itemRD$ = this.route.data.pipe(map((data) => data.dso));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -74,7 +74,7 @@ describe('ItemAuthorizationsComponent test suite', () => {
|
|||||||
|
|
||||||
const routeStub = {
|
const routeStub = {
|
||||||
data: observableOf({
|
data: observableOf({
|
||||||
item: createSuccessfulRemoteDataObject(item)
|
dso: createSuccessfulRemoteDataObject(item)
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -75,7 +75,7 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.item$ = this.route.data.pipe(
|
this.item$ = this.route.data.pipe(
|
||||||
map((data) => data.item),
|
map((data) => data.dso),
|
||||||
getFirstSucceededRemoteDataWithNotEmptyPayload(),
|
getFirstSucceededRemoteDataWithNotEmptyPayload(),
|
||||||
map((item: Item) => this.linkService.resolveLink(
|
map((item: Item) => this.linkService.resolveLink(
|
||||||
item,
|
item,
|
||||||
|
@@ -140,7 +140,7 @@ describe('ItemBitstreamsComponent', () => {
|
|||||||
});
|
});
|
||||||
route = Object.assign({
|
route = Object.assign({
|
||||||
parent: {
|
parent: {
|
||||||
data: observableOf({ item: createMockRD(item) })
|
data: observableOf({ dso: createMockRD(item) })
|
||||||
},
|
},
|
||||||
data: observableOf({}),
|
data: observableOf({}),
|
||||||
url: url
|
url: url
|
||||||
|
@@ -89,7 +89,7 @@ describe('ItemCollectionMapperComponent', () => {
|
|||||||
clearDiscoveryRequests: () => {}
|
clearDiscoveryRequests: () => {}
|
||||||
/* tslint:enable:no-empty */
|
/* tslint:enable:no-empty */
|
||||||
});
|
});
|
||||||
const activatedRouteStub = new ActivatedRouteStub({}, { item: mockItemRD });
|
const activatedRouteStub = new ActivatedRouteStub({}, { dso: mockItemRD });
|
||||||
const translateServiceStub = {
|
const translateServiceStub = {
|
||||||
get: () => of('test-message of item ' + mockItem.name),
|
get: () => of('test-message of item ' + mockItem.name),
|
||||||
onLangChange: new EventEmitter(),
|
onLangChange: new EventEmitter(),
|
||||||
|
@@ -92,7 +92,7 @@ export class ItemCollectionMapperComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.itemRD$ = this.route.data.pipe(map((data) => data.item)).pipe(getSucceededRemoteData()) as Observable<RemoteData<Item>>;
|
this.itemRD$ = this.route.data.pipe(map((data) => data.dso)).pipe(getSucceededRemoteData()) as Observable<RemoteData<Item>>;
|
||||||
this.searchOptions$ = this.searchConfigService.paginatedSearchOptions;
|
this.searchOptions$ = this.searchConfigService.paginatedSearchOptions;
|
||||||
this.loadCollectionLists();
|
this.loadCollectionLists();
|
||||||
}
|
}
|
||||||
|
@@ -138,7 +138,7 @@ describe('ItemDeleteComponent', () => {
|
|||||||
|
|
||||||
routeStub = {
|
routeStub = {
|
||||||
data: observableOf({
|
data: observableOf({
|
||||||
item: createSuccessfulRemoteDataObject(mockItem)
|
dso: createSuccessfulRemoteDataObject(mockItem)
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -130,7 +130,7 @@ describe('ItemMetadataComponent', () => {
|
|||||||
routeStub = {
|
routeStub = {
|
||||||
data: observableOf({}),
|
data: observableOf({}),
|
||||||
parent: {
|
parent: {
|
||||||
data: observableOf({ item: createSuccessfulRemoteDataObject(item) })
|
data: observableOf({ dso: createSuccessfulRemoteDataObject(item) })
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
paginatedMetadataFields = new PaginatedList(undefined, [mdField1, mdField2, mdField3]);
|
paginatedMetadataFields = new PaginatedList(undefined, [mdField1, mdField2, mdField3]);
|
||||||
|
@@ -44,7 +44,7 @@ describe('ItemMoveComponent', () => {
|
|||||||
|
|
||||||
const routeStub = {
|
const routeStub = {
|
||||||
data: observableOf({
|
data: observableOf({
|
||||||
item: new RemoteData(false, false, true, null, {
|
dso: new RemoteData(false, false, true, null, {
|
||||||
id: 'item1'
|
id: 'item1'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@@ -55,7 +55,7 @@ export class ItemMoveComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.itemRD$ = this.route.data.pipe(map((data) => data.item), getSucceededRemoteData()) as Observable<RemoteData<Item>>;
|
this.itemRD$ = this.route.data.pipe(map((data) => data.dso), getSucceededRemoteData()) as Observable<RemoteData<Item>>;
|
||||||
this.itemRD$.subscribe((rd) => {
|
this.itemRD$.subscribe((rd) => {
|
||||||
this.itemId = rd.payload.id;
|
this.itemId = rd.payload.id;
|
||||||
}
|
}
|
||||||
|
@@ -51,7 +51,7 @@ describe('ItemPrivateComponent', () => {
|
|||||||
|
|
||||||
routeStub = {
|
routeStub = {
|
||||||
data: observableOf({
|
data: observableOf({
|
||||||
item: createSuccessfulRemoteDataObject({
|
dso: createSuccessfulRemoteDataObject({
|
||||||
id: 'fake-id'
|
id: 'fake-id'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@@ -51,7 +51,7 @@ describe('ItemPublicComponent', () => {
|
|||||||
|
|
||||||
routeStub = {
|
routeStub = {
|
||||||
data: observableOf({
|
data: observableOf({
|
||||||
item: createSuccessfulRemoteDataObject({
|
dso: createSuccessfulRemoteDataObject({
|
||||||
id: 'fake-id'
|
id: 'fake-id'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@@ -51,7 +51,7 @@ describe('ItemReinstateComponent', () => {
|
|||||||
|
|
||||||
routeStub = {
|
routeStub = {
|
||||||
data: observableOf({
|
data: observableOf({
|
||||||
item: createSuccessfulRemoteDataObject({
|
dso: createSuccessfulRemoteDataObject({
|
||||||
id: 'fake-id'
|
id: 'fake-id'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@@ -142,7 +142,7 @@ describe('ItemRelationshipsComponent', () => {
|
|||||||
routeStub = {
|
routeStub = {
|
||||||
data: observableOf({}),
|
data: observableOf({}),
|
||||||
parent: {
|
parent: {
|
||||||
data: observableOf({ item: new RemoteData(false, false, true, null, item) })
|
data: observableOf({ dso: new RemoteData(false, false, true, null, item) })
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -31,7 +31,7 @@ describe('ItemStatusComponent', () => {
|
|||||||
|
|
||||||
const routeStub = {
|
const routeStub = {
|
||||||
parent: {
|
parent: {
|
||||||
data: observableOf({ item: createSuccessfulRemoteDataObject(mockItem) })
|
data: observableOf({ dso: createSuccessfulRemoteDataObject(mockItem) })
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -56,7 +56,7 @@ export class ItemStatusComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.itemRD$ = this.route.parent.data.pipe(map((data) => data.item));
|
this.itemRD$ = this.route.parent.data.pipe(map((data) => data.dso));
|
||||||
this.itemRD$.pipe(
|
this.itemRD$.pipe(
|
||||||
first(),
|
first(),
|
||||||
map((data: RemoteData<Item>) => data.payload)
|
map((data: RemoteData<Item>) => data.payload)
|
||||||
|
@@ -23,7 +23,7 @@ describe('ItemVersionHistoryComponent', () => {
|
|||||||
declarations: [ItemVersionHistoryComponent, VarDirective],
|
declarations: [ItemVersionHistoryComponent, VarDirective],
|
||||||
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])],
|
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: ActivatedRoute, useValue: { parent: { data: observableOf({ item: createSuccessfulRemoteDataObject(item) }) } } }
|
{ provide: ActivatedRoute, useValue: { parent: { data: observableOf({ dso: createSuccessfulRemoteDataObject(item) }) } } }
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
@@ -30,6 +30,6 @@ export class ItemVersionHistoryComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.itemRD$ = this.route.parent.data.pipe(map((data) => data.item)).pipe(getSucceededRemoteData()) as Observable<RemoteData<Item>>;
|
this.itemRD$ = this.route.parent.data.pipe(map((data) => data.dso)).pipe(getSucceededRemoteData()) as Observable<RemoteData<Item>>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -51,7 +51,7 @@ describe('ItemWithdrawComponent', () => {
|
|||||||
|
|
||||||
routeStub = {
|
routeStub = {
|
||||||
data: observableOf({
|
data: observableOf({
|
||||||
item: createSuccessfulRemoteDataObject({
|
dso: createSuccessfulRemoteDataObject({
|
||||||
id: 'fake-id'
|
id: 'fake-id'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@@ -74,7 +74,7 @@ describe('AbstractSimpleItemActionComponent', () => {
|
|||||||
|
|
||||||
routeStub = {
|
routeStub = {
|
||||||
data: observableOf({
|
data: observableOf({
|
||||||
item: createSuccessfulRemoteDataObject({
|
dso: createSuccessfulRemoteDataObject({
|
||||||
id: 'fake-id'
|
id: 'fake-id'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@@ -42,7 +42,7 @@ export class AbstractSimpleItemActionComponent implements OnInit {
|
|||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.itemRD$ = this.route.data.pipe(
|
this.itemRD$ = this.route.data.pipe(
|
||||||
map((data) => data.item),
|
map((data) => data.dso),
|
||||||
getSucceededRemoteData()
|
getSucceededRemoteData()
|
||||||
)as Observable<RemoteData<Item>>;
|
)as Observable<RemoteData<Item>>;
|
||||||
|
|
||||||
|
@@ -3,7 +3,12 @@
|
|||||||
<div *ngIf="itemRD?.payload as item">
|
<div *ngIf="itemRD?.payload as item">
|
||||||
<ds-item-versions-notice [item]="item"></ds-item-versions-notice>
|
<ds-item-versions-notice [item]="item"></ds-item-versions-notice>
|
||||||
<ds-view-tracker [object]="item"></ds-view-tracker>
|
<ds-view-tracker [object]="item"></ds-view-tracker>
|
||||||
<ds-item-page-title-field [item]="item"></ds-item-page-title-field>
|
<div class="d-flex flex-row">
|
||||||
|
<ds-item-page-title-field class="mr-auto" [item]="item"></ds-item-page-title-field>
|
||||||
|
<div class="pl-2">
|
||||||
|
<ds-dso-page-edit-button [pageRoutePrefix]="'items'" [dso]="item" [tooltipMsg]="'item.page.edit'"></ds-dso-page-edit-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="simple-view-link my-3">
|
<div class="simple-view-link my-3">
|
||||||
<a class="btn btn-outline-primary" [routerLink]="['/items/' + item.id]">
|
<a class="btn btn-outline-primary" [routerLink]="['/items/' + item.id]">
|
||||||
{{"item.page.link.simple" | translate}}
|
{{"item.page.link.simple" | translate}}
|
||||||
|
@@ -34,7 +34,7 @@ const mockItem: Item = Object.assign(new Item(), {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const routeStub = Object.assign(new ActivatedRouteStub(), {
|
const routeStub = Object.assign(new ActivatedRouteStub(), {
|
||||||
data: observableOf({ item: createSuccessfulRemoteDataObject(mockItem) })
|
data: observableOf({ dso: createSuccessfulRemoteDataObject(mockItem) })
|
||||||
});
|
});
|
||||||
const metadataServiceStub = {
|
const metadataServiceStub = {
|
||||||
/* tslint:disable:no-empty */
|
/* tslint:disable:no-empty */
|
||||||
|
@@ -20,7 +20,7 @@ import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
|||||||
{
|
{
|
||||||
path: ':id',
|
path: ':id',
|
||||||
resolve: {
|
resolve: {
|
||||||
item: ItemPageResolver,
|
dso: ItemPageResolver,
|
||||||
breadcrumb: ItemBreadcrumbResolver
|
breadcrumb: ItemBreadcrumbResolver
|
||||||
},
|
},
|
||||||
runGuardsAndResolvers: 'always',
|
runGuardsAndResolvers: 'always',
|
||||||
|
@@ -37,7 +37,7 @@ describe('ItemPageComponent', () => {
|
|||||||
/* tslint:enable:no-empty */
|
/* tslint:enable:no-empty */
|
||||||
};
|
};
|
||||||
const mockRoute = Object.assign(new ActivatedRouteStub(), {
|
const mockRoute = Object.assign(new ActivatedRouteStub(), {
|
||||||
data: observableOf({ item: createSuccessfulRemoteDataObject(mockItem) })
|
data: observableOf({ dso: createSuccessfulRemoteDataObject(mockItem) })
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
|
@@ -55,7 +55,7 @@ export class ItemPageComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.itemRD$ = this.route.data.pipe(
|
this.itemRD$ = this.route.data.pipe(
|
||||||
map((data) => data.item as RemoteData<Item>),
|
map((data) => data.dso as RemoteData<Item>),
|
||||||
redirectOn404Or401(this.router)
|
redirectOn404Or401(this.router)
|
||||||
);
|
);
|
||||||
this.metadataService.processRemoteData(this.itemRD$);
|
this.metadataService.processRemoteData(this.itemRD$);
|
||||||
|
@@ -1,6 +1,11 @@
|
|||||||
<h2 class="item-page-title-field">
|
<div class="d-flex flex-row">
|
||||||
{{'publication.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
<h2 class="item-page-title-field mr-auto">
|
||||||
</h2>
|
{{'publication.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
||||||
|
</h2>
|
||||||
|
<div class="pl-2">
|
||||||
|
<ds-dso-page-edit-button [pageRoutePrefix]="'items'" [dso]="object" [tooltipMsg]="'publication.page.edit'"></ds-dso-page-edit-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12 col-md-4">
|
<div class="col-xs-12 col-md-4">
|
||||||
<ds-metadata-field-wrapper>
|
<ds-metadata-field-wrapper>
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<nav *ngIf="showBreadcrumbs" aria-label="breadcrumb">
|
<nav *ngIf="showBreadcrumbs" aria-label="breadcrumb">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<ng-container
|
<ng-container
|
||||||
*ngTemplateOutlet="breadcrumbs?.length > 0 ? breadcrumb : activeBreadcrumb; context: {text: 'Home', url: '/'}"></ng-container>
|
*ngTemplateOutlet="breadcrumbs?.length > 0 ? breadcrumb : activeBreadcrumb; context: {text: 'home.breadcrumbs', url: '/'}"></ng-container>
|
||||||
<ng-container *ngFor="let bc of breadcrumbs; let last = last;">
|
<ng-container *ngFor="let bc of breadcrumbs; let last = last;">
|
||||||
<ng-container *ngTemplateOutlet="!last ? breadcrumb : activeBreadcrumb; context: bc"></ng-container>
|
<ng-container *ngTemplateOutlet="!last ? breadcrumb : activeBreadcrumb; context: bc"></ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@@ -5,6 +5,7 @@ export enum FeatureID {
|
|||||||
LoginOnBehalfOf = 'loginOnBehalfOf',
|
LoginOnBehalfOf = 'loginOnBehalfOf',
|
||||||
AdministratorOf = 'administratorOf',
|
AdministratorOf = 'administratorOf',
|
||||||
CanDelete = 'canDelete',
|
CanDelete = 'canDelete',
|
||||||
|
CanEditMetadata = 'canEditMetadata',
|
||||||
WithdrawItem = 'withdrawItem',
|
WithdrawItem = 'withdrawItem',
|
||||||
ReinstateItem = 'reinstateItem',
|
ReinstateItem = 'reinstateItem',
|
||||||
EPersonRegistration = 'epersonRegistration',
|
EPersonRegistration = 'epersonRegistration',
|
||||||
|
@@ -13,4 +13,6 @@ export enum Context {
|
|||||||
EntitySearchModal = 'EntitySearchModal',
|
EntitySearchModal = 'EntitySearchModal',
|
||||||
AdminSearch = 'adminSearch',
|
AdminSearch = 'adminSearch',
|
||||||
AdminWorkflowSearch = 'adminWorkflowSearch',
|
AdminWorkflowSearch = 'adminWorkflowSearch',
|
||||||
|
SideBarSearchModal = 'sideBarSearchModal',
|
||||||
|
SideBarSearchModalCurrent = 'sideBarSearchModalCurrent',
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,42 @@
|
|||||||
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
|
import { Collection } from '../../../../../core/shared/collection.model';
|
||||||
|
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||||
|
import { createSidebarSearchListElementTests } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.spec';
|
||||||
|
import { JournalIssueSidebarSearchListElementComponent } from './journal-issue-sidebar-search-list-element.component';
|
||||||
|
|
||||||
|
const object = Object.assign(new ItemSearchResult(), {
|
||||||
|
indexableObject: Object.assign(new Item(), {
|
||||||
|
id: 'test-item',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{
|
||||||
|
value: 'title'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'publicationvolume.volumeNumber': [
|
||||||
|
{
|
||||||
|
value: '5'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'publicationissue.issueNumber': [
|
||||||
|
{
|
||||||
|
value: '7'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const parent = Object.assign(new Collection(), {
|
||||||
|
id: 'test-collection',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{
|
||||||
|
value: 'parent title'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('JournalIssueSidebarSearchListElementComponent',
|
||||||
|
createSidebarSearchListElementTests(JournalIssueSidebarSearchListElementComponent, object, parent, 'parent title', 'title', '5 - 7')
|
||||||
|
);
|
@@ -0,0 +1,39 @@
|
|||||||
|
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
||||||
|
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
||||||
|
import { Context } from '../../../../../core/shared/context.model';
|
||||||
|
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { SidebarSearchListElementComponent } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component';
|
||||||
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
|
import { isNotEmpty } from '../../../../../shared/empty.util';
|
||||||
|
|
||||||
|
@listableObjectComponent('JournalIssueSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
|
||||||
|
@listableObjectComponent('JournalIssueSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent)
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-journal-issue-sidebar-search-list-element',
|
||||||
|
templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component displaying a list element for a {@link ItemSearchResult} of type "JournalIssue" within the context of
|
||||||
|
* a sidebar search modal
|
||||||
|
*/
|
||||||
|
export class JournalIssueSidebarSearchListElementComponent extends SidebarSearchListElementComponent<ItemSearchResult, Item> {
|
||||||
|
/**
|
||||||
|
* Get the description of the Journal Issue by returning its volume number(s) and/or issue number(s)
|
||||||
|
*/
|
||||||
|
getDescription(): string {
|
||||||
|
const volumeNumbers = this.allMetadataValues(['publicationvolume.volumeNumber']);
|
||||||
|
const issueNumbers = this.allMetadataValues(['publicationissue.issueNumber']);
|
||||||
|
let description = '';
|
||||||
|
if (isNotEmpty(volumeNumbers)) {
|
||||||
|
description += volumeNumbers.join(', ');
|
||||||
|
}
|
||||||
|
if (isNotEmpty(description) && isNotEmpty(issueNumbers)) {
|
||||||
|
description += ' - ';
|
||||||
|
}
|
||||||
|
if (isNotEmpty(issueNumbers)) {
|
||||||
|
description += issueNumbers.join(', ');
|
||||||
|
}
|
||||||
|
return this.undefinedIfEmpty(description);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,45 @@
|
|||||||
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
|
import { Collection } from '../../../../../core/shared/collection.model';
|
||||||
|
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||||
|
import { createSidebarSearchListElementTests } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.spec';
|
||||||
|
import { JournalVolumeSidebarSearchListElementComponent } from './journal-volume-sidebar-search-list-element.component';
|
||||||
|
|
||||||
|
const object = Object.assign(new ItemSearchResult(), {
|
||||||
|
indexableObject: Object.assign(new Item(), {
|
||||||
|
id: 'test-item',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{
|
||||||
|
value: 'title'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'journal.title': [
|
||||||
|
{
|
||||||
|
value: 'journal title'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'publicationvolume.volumeNumber': [
|
||||||
|
{
|
||||||
|
value: '1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '2'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const parent = Object.assign(new Collection(), {
|
||||||
|
id: 'test-collection',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{
|
||||||
|
value: 'parent title'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('JournalVolumeSidebarSearchListElementComponent',
|
||||||
|
createSidebarSearchListElementTests(JournalVolumeSidebarSearchListElementComponent, object, parent, 'parent title', 'title', 'journal title (1) (2)')
|
||||||
|
);
|
@@ -0,0 +1,39 @@
|
|||||||
|
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
||||||
|
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
||||||
|
import { Context } from '../../../../../core/shared/context.model';
|
||||||
|
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { SidebarSearchListElementComponent } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component';
|
||||||
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
|
import { isNotEmpty } from '../../../../../shared/empty.util';
|
||||||
|
|
||||||
|
@listableObjectComponent('JournalVolumeSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
|
||||||
|
@listableObjectComponent('JournalVolumeSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent)
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-journal-volume-sidebar-search-list-element',
|
||||||
|
templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component displaying a list element for a {@link ItemSearchResult} of type "JournalVolume" within the context of
|
||||||
|
* a sidebar search modal
|
||||||
|
*/
|
||||||
|
export class JournalVolumeSidebarSearchListElementComponent extends SidebarSearchListElementComponent<ItemSearchResult, Item> {
|
||||||
|
/**
|
||||||
|
* Get the description of the Journal Volume by returning the journal title and volume number(s) (between parentheses)
|
||||||
|
*/
|
||||||
|
getDescription(): string {
|
||||||
|
const titles = this.allMetadataValues(['journal.title']);
|
||||||
|
const numbers = this.allMetadataValues(['publicationvolume.volumeNumber']);
|
||||||
|
let description = '';
|
||||||
|
if (isNotEmpty(titles)) {
|
||||||
|
description += titles.join(', ');
|
||||||
|
}
|
||||||
|
if (isNotEmpty(numbers)) {
|
||||||
|
if (isNotEmpty(description)) {
|
||||||
|
description += ' ';
|
||||||
|
}
|
||||||
|
description += numbers.map((n) => `(${n})`).join(' ');
|
||||||
|
}
|
||||||
|
return this.undefinedIfEmpty(description);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,40 @@
|
|||||||
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
|
import { Collection } from '../../../../../core/shared/collection.model';
|
||||||
|
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||||
|
import { createSidebarSearchListElementTests } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.spec';
|
||||||
|
import { JournalSidebarSearchListElementComponent } from './journal-sidebar-search-list-element.component';
|
||||||
|
|
||||||
|
const object = Object.assign(new ItemSearchResult(), {
|
||||||
|
indexableObject: Object.assign(new Item(), {
|
||||||
|
id: 'test-item',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{
|
||||||
|
value: 'title'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'creativeworkseries.issn': [
|
||||||
|
{
|
||||||
|
value: '1234'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '5678'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const parent = Object.assign(new Collection(), {
|
||||||
|
id: 'test-collection',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{
|
||||||
|
value: 'parent title'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('JournalSidebarSearchListElementComponent',
|
||||||
|
createSidebarSearchListElementTests(JournalSidebarSearchListElementComponent, object, parent, 'parent title', 'title', '1234, 5678')
|
||||||
|
);
|
@@ -0,0 +1,32 @@
|
|||||||
|
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
||||||
|
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
||||||
|
import { Context } from '../../../../../core/shared/context.model';
|
||||||
|
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { SidebarSearchListElementComponent } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component';
|
||||||
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
|
import { isNotEmpty } from '../../../../../shared/empty.util';
|
||||||
|
|
||||||
|
@listableObjectComponent('JournalSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
|
||||||
|
@listableObjectComponent('JournalSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent)
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-journal-sidebar-search-list-element',
|
||||||
|
templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component displaying a list element for a {@link ItemSearchResult} of type "Journal" within the context of
|
||||||
|
* a sidebar search modal
|
||||||
|
*/
|
||||||
|
export class JournalSidebarSearchListElementComponent extends SidebarSearchListElementComponent<ItemSearchResult, Item> {
|
||||||
|
/**
|
||||||
|
* Get the description of the Journal by returning its ISSN(s)
|
||||||
|
*/
|
||||||
|
getDescription(): string {
|
||||||
|
const issns = this.allMetadataValues(['creativeworkseries.issn']);
|
||||||
|
let description = '';
|
||||||
|
if (isNotEmpty(issns)) {
|
||||||
|
description += issns.join(', ');
|
||||||
|
}
|
||||||
|
return this.undefinedIfEmpty(description);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,11 @@
|
|||||||
<h2 class="item-page-title-field">
|
<div class="d-flex flex-row">
|
||||||
{{'journalissue.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
<h2 class="item-page-title-field mr-auto">
|
||||||
</h2>
|
{{'journalissue.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
||||||
|
</h2>
|
||||||
|
<div class="pl-2">
|
||||||
|
<ds-dso-page-edit-button [pageRoutePrefix]="'items'" [dso]="object" [tooltipMsg]="'journalissue.page.edit'"></ds-dso-page-edit-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12 col-md-4">
|
<div class="col-xs-12 col-md-4">
|
||||||
<ds-metadata-field-wrapper>
|
<ds-metadata-field-wrapper>
|
||||||
|
@@ -1,6 +1,11 @@
|
|||||||
<h2 class="item-page-title-field">
|
<div class="d-flex flex-row">
|
||||||
{{'journalvolume.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
<h2 class="item-page-title-field mr-auto">
|
||||||
</h2>
|
{{'journalvolume.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
||||||
|
</h2>
|
||||||
|
<div class="pl-2">
|
||||||
|
<ds-dso-page-edit-button [pageRoutePrefix]="'items'" [dso]="object" [tooltipMsg]="'journalvolume.page.edit'"></ds-dso-page-edit-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12 col-md-4">
|
<div class="col-xs-12 col-md-4">
|
||||||
<ds-metadata-field-wrapper>
|
<ds-metadata-field-wrapper>
|
||||||
|
@@ -1,6 +1,11 @@
|
|||||||
<h2 class="item-page-title-field">
|
<div class="d-flex flex-row">
|
||||||
{{'journal.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
<h2 class="item-page-title-field mr-auto">
|
||||||
</h2>
|
{{'journal.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
||||||
|
</h2>
|
||||||
|
<div class="pl-2">
|
||||||
|
<ds-dso-page-edit-button [pageRoutePrefix]="'items'" [dso]="object" [tooltipMsg]="'journal.page.edit'"></ds-dso-page-edit-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12 col-md-4">
|
<div class="col-xs-12 col-md-4">
|
||||||
<ds-metadata-field-wrapper>
|
<ds-metadata-field-wrapper>
|
||||||
|
@@ -18,6 +18,9 @@ import { JournalIssueSearchResultListElementComponent } from './item-list-elemen
|
|||||||
import { JournalVolumeSearchResultListElementComponent } from './item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component';
|
import { JournalVolumeSearchResultListElementComponent } from './item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component';
|
||||||
import { JournalIssueSearchResultGridElementComponent } from './item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component';
|
import { JournalIssueSearchResultGridElementComponent } from './item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component';
|
||||||
import { JournalVolumeSearchResultGridElementComponent } from './item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component';
|
import { JournalVolumeSearchResultGridElementComponent } from './item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component';
|
||||||
|
import { JournalVolumeSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/journal-volume/journal-volume-sidebar-search-list-element.component';
|
||||||
|
import { JournalIssueSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/journal-issue/journal-issue-sidebar-search-list-element.component';
|
||||||
|
import { JournalSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/journal/journal-sidebar-search-list-element.component';
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
JournalComponent,
|
JournalComponent,
|
||||||
@@ -34,7 +37,10 @@ const ENTRY_COMPONENTS = [
|
|||||||
JournalVolumeSearchResultListElementComponent,
|
JournalVolumeSearchResultListElementComponent,
|
||||||
JournalIssueSearchResultGridElementComponent,
|
JournalIssueSearchResultGridElementComponent,
|
||||||
JournalVolumeSearchResultGridElementComponent,
|
JournalVolumeSearchResultGridElementComponent,
|
||||||
JournalSearchResultGridElementComponent
|
JournalSearchResultGridElementComponent,
|
||||||
|
JournalVolumeSidebarSearchListElementComponent,
|
||||||
|
JournalIssueSidebarSearchListElementComponent,
|
||||||
|
JournalSidebarSearchListElementComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@@ -0,0 +1,37 @@
|
|||||||
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
|
import { Collection } from '../../../../../core/shared/collection.model';
|
||||||
|
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||||
|
import { createSidebarSearchListElementTests } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.spec';
|
||||||
|
import { OrgUnitSidebarSearchListElementComponent } from './org-unit-sidebar-search-list-element.component';
|
||||||
|
|
||||||
|
const object = Object.assign(new ItemSearchResult(), {
|
||||||
|
indexableObject: Object.assign(new Item(), {
|
||||||
|
id: 'test-item',
|
||||||
|
metadata: {
|
||||||
|
'organization.legalName': [
|
||||||
|
{
|
||||||
|
value: 'title'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'dc.description': [
|
||||||
|
{
|
||||||
|
value: 'description'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const parent = Object.assign(new Collection(), {
|
||||||
|
id: 'test-collection',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{
|
||||||
|
value: 'parent title'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('OrgUnitSidebarSearchListElementComponent',
|
||||||
|
createSidebarSearchListElementTests(OrgUnitSidebarSearchListElementComponent, object, parent, 'parent title', 'title', 'description')
|
||||||
|
);
|
@@ -0,0 +1,33 @@
|
|||||||
|
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
||||||
|
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
||||||
|
import { Context } from '../../../../../core/shared/context.model';
|
||||||
|
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { SidebarSearchListElementComponent } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component';
|
||||||
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
|
|
||||||
|
@listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
|
||||||
|
@listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent)
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-org-unit-sidebar-search-list-element',
|
||||||
|
templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component displaying a list element for a {@link ItemSearchResult} of type "OrgUnit" within the context of
|
||||||
|
* a sidebar search modal
|
||||||
|
*/
|
||||||
|
export class OrgUnitSidebarSearchListElementComponent extends SidebarSearchListElementComponent<ItemSearchResult, Item> {
|
||||||
|
/**
|
||||||
|
* Get the title of the Org Unit by returning its legal name
|
||||||
|
*/
|
||||||
|
getTitle(): string {
|
||||||
|
return this.firstMetadataValue('organization.legalName');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the description of the Org Unit by returning its dc.description
|
||||||
|
*/
|
||||||
|
getDescription(): string {
|
||||||
|
return this.firstMetadataValue('dc.description');
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,45 @@
|
|||||||
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
|
import { Collection } from '../../../../../core/shared/collection.model';
|
||||||
|
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||||
|
import { createSidebarSearchListElementTests } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.spec';
|
||||||
|
import { PersonSidebarSearchListElementComponent } from './person-sidebar-search-list-element.component';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
const object = Object.assign(new ItemSearchResult(), {
|
||||||
|
indexableObject: Object.assign(new Item(), {
|
||||||
|
id: 'test-item',
|
||||||
|
metadata: {
|
||||||
|
'person.familyName': [
|
||||||
|
{
|
||||||
|
value: 'family name'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'person.givenName': [
|
||||||
|
{
|
||||||
|
value: 'given name'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'person.jobTitle': [
|
||||||
|
{
|
||||||
|
value: 'job title'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const parent = Object.assign(new Collection(), {
|
||||||
|
id: 'test-collection',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{
|
||||||
|
value: 'parent title'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PersonSidebarSearchListElementComponent',
|
||||||
|
createSidebarSearchListElementTests(PersonSidebarSearchListElementComponent, object, parent, 'parent title', 'family name, given name', 'job title', [
|
||||||
|
{ provide: TranslateService, useValue: jasmine.createSpyObj('translate', { instant: '' }) }
|
||||||
|
])
|
||||||
|
);
|
@@ -0,0 +1,60 @@
|
|||||||
|
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
||||||
|
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
||||||
|
import { Context } from '../../../../../core/shared/context.model';
|
||||||
|
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { SidebarSearchListElementComponent } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component';
|
||||||
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
|
import { isNotEmpty } from '../../../../../shared/empty.util';
|
||||||
|
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
||||||
|
import { LinkService } from '../../../../../core/cache/builders/link.service';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
@listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
|
||||||
|
@listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent)
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-person-sidebar-search-list-element',
|
||||||
|
templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component displaying a list element for a {@link ItemSearchResult} of type "Person" within the context of
|
||||||
|
* a sidebar search modal
|
||||||
|
*/
|
||||||
|
export class PersonSidebarSearchListElementComponent extends SidebarSearchListElementComponent<ItemSearchResult, Item> {
|
||||||
|
constructor(protected truncatableService: TruncatableService,
|
||||||
|
protected linkService: LinkService,
|
||||||
|
protected translateService: TranslateService) {
|
||||||
|
super(truncatableService, linkService);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the title of the Person by returning a combination of its family name and given name (or "No name found")
|
||||||
|
*/
|
||||||
|
getTitle(): string {
|
||||||
|
const familyName = this.firstMetadataValue('person.familyName');
|
||||||
|
const givenName = this.firstMetadataValue('person.givenName');
|
||||||
|
let title = '';
|
||||||
|
if (isNotEmpty(familyName)) {
|
||||||
|
title = familyName;
|
||||||
|
}
|
||||||
|
if (isNotEmpty(title)) {
|
||||||
|
title += ', ';
|
||||||
|
}
|
||||||
|
if (isNotEmpty(givenName)) {
|
||||||
|
title += givenName;
|
||||||
|
}
|
||||||
|
return this.defaultIfEmpty(title, this.translateService.instant('person.listelement.no-title'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the description of the Person by returning its job title(s)
|
||||||
|
*/
|
||||||
|
getDescription(): string {
|
||||||
|
const titles = this.allMetadataValues(['person.jobTitle']);
|
||||||
|
let description = '';
|
||||||
|
if (isNotEmpty(titles)) {
|
||||||
|
description += titles.join(', ');
|
||||||
|
}
|
||||||
|
return this.undefinedIfEmpty(description);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,32 @@
|
|||||||
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
|
import { Collection } from '../../../../../core/shared/collection.model';
|
||||||
|
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||||
|
import { createSidebarSearchListElementTests } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.spec';
|
||||||
|
import { ProjectSidebarSearchListElementComponent } from './project-sidebar-search-list-element.component';
|
||||||
|
|
||||||
|
const object = Object.assign(new ItemSearchResult(), {
|
||||||
|
indexableObject: Object.assign(new Item(), {
|
||||||
|
id: 'test-item',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{
|
||||||
|
value: 'title'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const parent = Object.assign(new Collection(), {
|
||||||
|
id: 'test-collection',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{
|
||||||
|
value: 'parent title'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ProjectSidebarSearchListElementComponent',
|
||||||
|
createSidebarSearchListElementTests(ProjectSidebarSearchListElementComponent, object, parent, 'parent title', 'title', undefined)
|
||||||
|
);
|
@@ -0,0 +1,26 @@
|
|||||||
|
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
||||||
|
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
||||||
|
import { Context } from '../../../../../core/shared/context.model';
|
||||||
|
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { SidebarSearchListElementComponent } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component';
|
||||||
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
|
|
||||||
|
@listableObjectComponent('ProjectSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
|
||||||
|
@listableObjectComponent('ProjectSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent)
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-project-sidebar-search-list-element',
|
||||||
|
templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component displaying a list element for a {@link ItemSearchResult} of type "Project" within the context of
|
||||||
|
* a sidebar search modal
|
||||||
|
*/
|
||||||
|
export class ProjectSidebarSearchListElementComponent extends SidebarSearchListElementComponent<ItemSearchResult, Item> {
|
||||||
|
/**
|
||||||
|
* Projects currently don't support a description
|
||||||
|
*/
|
||||||
|
getDescription(): string {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,11 @@
|
|||||||
<h2 class="item-page-title-field">
|
<div class="d-flex flex-row">
|
||||||
{{'orgunit.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['organization.legalName'])"></ds-metadata-values>
|
<h2 class="item-page-title-field mr-auto">
|
||||||
</h2>
|
{{'orgunit.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['organization.legalName'])"></ds-metadata-values>
|
||||||
|
</h2>
|
||||||
|
<div class="pl-2">
|
||||||
|
<ds-dso-page-edit-button [pageRoutePrefix]="'items'" [dso]="object" [tooltipMsg]="'orgunit.page.edit'"></ds-dso-page-edit-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12 col-md-4">
|
<div class="col-xs-12 col-md-4">
|
||||||
<ds-metadata-field-wrapper>
|
<ds-metadata-field-wrapper>
|
||||||
|
@@ -1,6 +1,11 @@
|
|||||||
<h2 class="item-page-title-field">
|
<div class="d-flex flex-row">
|
||||||
{{'person.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="[object?.firstMetadata('person.familyName'), object?.firstMetadata('person.givenName')]" [separator]="', '"></ds-metadata-values>
|
<h2 class="item-page-title-field mr-auto">
|
||||||
</h2>
|
{{'person.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="[object?.firstMetadata('person.familyName'), object?.firstMetadata('person.givenName')]" [separator]="', '"></ds-metadata-values>
|
||||||
|
</h2>
|
||||||
|
<div class="pl-2">
|
||||||
|
<ds-dso-page-edit-button [pageRoutePrefix]="'items'" [dso]="object" [tooltipMsg]="'person.page.edit'"></ds-dso-page-edit-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12 col-md-4">
|
<div class="col-xs-12 col-md-4">
|
||||||
<ds-metadata-field-wrapper>
|
<ds-metadata-field-wrapper>
|
||||||
|
@@ -1,6 +1,11 @@
|
|||||||
<h2 class="item-page-title-field">
|
<div class="d-flex flex-row">
|
||||||
{{'project.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
<h2 class="item-page-title-field mr-auto">
|
||||||
</h2>
|
{{'project.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
||||||
|
</h2>
|
||||||
|
<div class="pl-2">
|
||||||
|
<ds-dso-page-edit-button [pageRoutePrefix]="'items'" [dso]="object" [tooltipMsg]="'project.page.edit'"></ds-dso-page-edit-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12 col-md-4">
|
<div class="col-xs-12 col-md-4">
|
||||||
<ds-metadata-field-wrapper>
|
<ds-metadata-field-wrapper>
|
||||||
|
@@ -26,6 +26,9 @@ import { NameVariantModalComponent } from './submission/name-variant-modal/name-
|
|||||||
import { OrgUnitInputSuggestionsComponent } from './submission/item-list-elements/org-unit/org-unit-suggestions/org-unit-input-suggestions.component';
|
import { OrgUnitInputSuggestionsComponent } from './submission/item-list-elements/org-unit/org-unit-suggestions/org-unit-input-suggestions.component';
|
||||||
import { OrgUnitSearchResultListSubmissionElementComponent } from './submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component';
|
import { OrgUnitSearchResultListSubmissionElementComponent } from './submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component';
|
||||||
import { ExternalSourceEntryListSubmissionElementComponent } from './submission/item-list-elements/external-source-entry/external-source-entry-list-submission-element.component';
|
import { ExternalSourceEntryListSubmissionElementComponent } from './submission/item-list-elements/external-source-entry/external-source-entry-list-submission-element.component';
|
||||||
|
import { OrgUnitSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/org-unit/org-unit-sidebar-search-list-element.component';
|
||||||
|
import { PersonSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/person/person-sidebar-search-list-element.component';
|
||||||
|
import { ProjectSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/project/project-sidebar-search-list-element.component';
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
OrgUnitComponent,
|
OrgUnitComponent,
|
||||||
@@ -50,7 +53,10 @@ const ENTRY_COMPONENTS = [
|
|||||||
NameVariantModalComponent,
|
NameVariantModalComponent,
|
||||||
OrgUnitSearchResultListSubmissionElementComponent,
|
OrgUnitSearchResultListSubmissionElementComponent,
|
||||||
OrgUnitInputSuggestionsComponent,
|
OrgUnitInputSuggestionsComponent,
|
||||||
ExternalSourceEntryListSubmissionElementComponent
|
ExternalSourceEntryListSubmissionElementComponent,
|
||||||
|
OrgUnitSidebarSearchListElementComponent,
|
||||||
|
PersonSidebarSearchListElementComponent,
|
||||||
|
ProjectSidebarSearchListElementComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
<a *ngIf="isAuthorized$ | async"
|
||||||
|
[routerLink]="['/' + pageRoutePrefix, dso.id, 'edit']"
|
||||||
|
class="edit-button btn btn-dark text-light btn-sm"
|
||||||
|
[tooltip]="tooltipMsg | translate">
|
||||||
|
<i class="fas fa-pencil-alt fa-fw"></i>
|
||||||
|
</a>
|
@@ -0,0 +1,3 @@
|
|||||||
|
.btn-dark {
|
||||||
|
background-color: $admin-sidebar-bg;
|
||||||
|
}
|
@@ -0,0 +1,76 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { DsoPageEditButtonComponent } from './dso-page-edit-button.component';
|
||||||
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
|
import { Item } from '../../../core/shared/item.model';
|
||||||
|
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { TooltipModule } from 'ngx-bootstrap';
|
||||||
|
|
||||||
|
describe('DsoPageEditButtonComponent', () => {
|
||||||
|
let component: DsoPageEditButtonComponent;
|
||||||
|
let fixture: ComponentFixture<DsoPageEditButtonComponent>;
|
||||||
|
|
||||||
|
let authorizationService: AuthorizationDataService;
|
||||||
|
let dso: DSpaceObject;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
dso = Object.assign(new Item(), {
|
||||||
|
id: 'test-item',
|
||||||
|
_links: {
|
||||||
|
self: { href: 'test-item-selflink' }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
authorizationService = jasmine.createSpyObj('authorizationService', {
|
||||||
|
isAuthorized: observableOf(true)
|
||||||
|
});
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ DsoPageEditButtonComponent ],
|
||||||
|
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), TooltipModule.forRoot()],
|
||||||
|
providers: [
|
||||||
|
{ provide: AuthorizationDataService, useValue: authorizationService }
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(DsoPageEditButtonComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
component.dso = dso;
|
||||||
|
component.pageRoutePrefix = 'test';
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check the authorization of the current user', () => {
|
||||||
|
expect(authorizationService.isAuthorized).toHaveBeenCalledWith(FeatureID.CanEditMetadata, dso.self);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the user is authorized', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
(authorizationService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(true));
|
||||||
|
component.ngOnInit();
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render a link', () => {
|
||||||
|
const link = fixture.debugElement.query(By.css('a'));
|
||||||
|
expect(link).not.toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the user is not authorized', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
(authorizationService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false));
|
||||||
|
component.ngOnInit();
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not render a link', () => {
|
||||||
|
const link = fixture.debugElement.query(By.css('a'));
|
||||||
|
expect(link).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,43 @@
|
|||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
|
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-dso-page-edit-button',
|
||||||
|
templateUrl: './dso-page-edit-button.component.html',
|
||||||
|
styleUrls: ['./dso-page-edit-button.component.scss']
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Display a button linking to the edit page of a DSpaceObject
|
||||||
|
*/
|
||||||
|
export class DsoPageEditButtonComponent implements OnInit {
|
||||||
|
/**
|
||||||
|
* The DSpaceObject to display a button to the edit page for
|
||||||
|
*/
|
||||||
|
@Input() dso: DSpaceObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The prefix of the route to the edit page (before the object's UUID, e.g. "items")
|
||||||
|
*/
|
||||||
|
@Input() pageRoutePrefix: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message for the tooltip on the button
|
||||||
|
* Supports i18n keys
|
||||||
|
*/
|
||||||
|
@Input() tooltipMsg: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the current user is authorized to edit the DSpaceObject
|
||||||
|
*/
|
||||||
|
isAuthorized$: Observable<boolean>;
|
||||||
|
|
||||||
|
constructor(protected authorizationService: AuthorizationDataService) { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.isAuthorized$ = this.authorizationService.isAuthorized(FeatureID.CanEditMetadata, this.dso.self);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,56 @@
|
|||||||
|
import { AuthorizedCollectionSelectorComponent } from './authorized-collection-selector.component';
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { VarDirective } from '../../../utils/var.directive';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { SearchService } from '../../../../core/shared/search/search.service';
|
||||||
|
import { CollectionDataService } from '../../../../core/data/collection-data.service';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../../../remote-data.utils';
|
||||||
|
import { createPaginatedList } from '../../../testing/utils.test';
|
||||||
|
import { Collection } from '../../../../core/shared/collection.model';
|
||||||
|
import { DSpaceObjectType } from '../../../../core/shared/dspace-object-type.model';
|
||||||
|
|
||||||
|
describe('AuthorizedCollectionSelectorComponent', () => {
|
||||||
|
let component: AuthorizedCollectionSelectorComponent;
|
||||||
|
let fixture: ComponentFixture<AuthorizedCollectionSelectorComponent>;
|
||||||
|
|
||||||
|
let collectionService;
|
||||||
|
let collection;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
collection = Object.assign(new Collection(), {
|
||||||
|
id: 'authorized-collection'
|
||||||
|
});
|
||||||
|
collectionService = jasmine.createSpyObj('collectionService', {
|
||||||
|
getAuthorizedCollection: createSuccessfulRemoteDataObject$(createPaginatedList([collection]))
|
||||||
|
});
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [AuthorizedCollectionSelectorComponent, VarDirective],
|
||||||
|
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])],
|
||||||
|
providers: [
|
||||||
|
{ provide: SearchService, useValue: {} },
|
||||||
|
{ provide: CollectionDataService, useValue: collectionService }
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(AuthorizedCollectionSelectorComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
component.types = [DSpaceObjectType.COLLECTION];
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('search', () => {
|
||||||
|
it('should call getAuthorizedCollection and return the authorized collection in a SearchResult', (done) => {
|
||||||
|
component.search('', 1).subscribe((result) => {
|
||||||
|
expect(collectionService.getAuthorizedCollection).toHaveBeenCalled();
|
||||||
|
expect(result.page.length).toEqual(1);
|
||||||
|
expect(result.page[0].indexableObject).toEqual(collection);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,48 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { DSOSelectorComponent } from '../dso-selector.component';
|
||||||
|
import { SearchService } from '../../../../core/shared/search/search.service';
|
||||||
|
import { CollectionDataService } from '../../../../core/data/collection-data.service';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
|
import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { CollectionSearchResult } from '../../../object-collection/shared/collection-search-result.model';
|
||||||
|
import { SearchResult } from '../../../search/search-result.model';
|
||||||
|
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-authorized-collection-selector',
|
||||||
|
styleUrls: ['../dso-selector.component.scss'],
|
||||||
|
templateUrl: '../dso-selector.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component rendering a list of collections to select from
|
||||||
|
*/
|
||||||
|
export class AuthorizedCollectionSelectorComponent extends DSOSelectorComponent {
|
||||||
|
constructor(protected searchService: SearchService,
|
||||||
|
protected collectionDataService: CollectionDataService) {
|
||||||
|
super(searchService);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a query to send for retrieving the current DSO
|
||||||
|
*/
|
||||||
|
getCurrentDSOQuery(): string {
|
||||||
|
return this.currentDSOId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a search for authorized collections with the current query and page
|
||||||
|
* @param query Query to search objects for
|
||||||
|
* @param page Page to retrieve
|
||||||
|
*/
|
||||||
|
search(query: string, page: number): Observable<PaginatedList<SearchResult<DSpaceObject>>> {
|
||||||
|
return this.collectionDataService.getAuthorizedCollection(query, Object.assign({
|
||||||
|
currentPage: page,
|
||||||
|
elementsPerPage: this.defaultPagination.pageSize
|
||||||
|
})).pipe(
|
||||||
|
getFirstSucceededRemoteDataPayload(),
|
||||||
|
map((list) => new PaginatedList(list.pageInfo, list.page.map((col) => Object.assign(new CollectionSearchResult(), { indexableObject: col }))))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -7,15 +7,29 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
<div class="scrollable-menu list-group">
|
<div class="scrollable-menu list-group">
|
||||||
|
<div
|
||||||
|
infiniteScroll
|
||||||
|
[infiniteScrollDistance]="1"
|
||||||
|
[infiniteScrollThrottle]="300"
|
||||||
|
[infiniteScrollContainer]="'.scrollable-menu'"
|
||||||
|
[fromRoot]="true"
|
||||||
|
(scrolled)="onScrollDown()">
|
||||||
<button class="list-group-item list-group-item-action border-0 disabled"
|
<button class="list-group-item list-group-item-action border-0 disabled"
|
||||||
*ngIf="(listEntries$ | async)?.payload.page.length == 0">
|
*ngIf="listEntries.length == 0">
|
||||||
{{'dso-selector.no-results' | translate: { type: typesString } }}
|
{{'dso-selector.no-results' | translate: { type: typesString } }}
|
||||||
</button>
|
</button>
|
||||||
<button *ngFor="let listEntry of (listEntries$ | async)?.payload.page"
|
<button *ngFor="let listEntry of listEntries"
|
||||||
class="list-group-item list-group-item-action border-0 list-entry"
|
class="list-group-item list-group-item-action border-0 list-entry"
|
||||||
|
[ngClass]="{'bg-primary': listEntry.indexableObject.id === currentDSOId}"
|
||||||
title="{{ listEntry.indexableObject.name }}"
|
title="{{ listEntry.indexableObject.name }}"
|
||||||
|
dsHoverClass="ds-hover"
|
||||||
(click)="onSelect.emit(listEntry.indexableObject)" #listEntryElement>
|
(click)="onSelect.emit(listEntry.indexableObject)" #listEntryElement>
|
||||||
<ds-listable-object-component-loader [object]="listEntry" [viewMode]="viewMode"
|
<ds-listable-object-component-loader [object]="listEntry" [viewMode]="viewMode"
|
||||||
[linkType]=linkTypes.None></ds-listable-object-component-loader>
|
[linkType]=linkTypes.None [context]="getContext(listEntry.indexableObject.id)"></ds-listable-object-component-loader>
|
||||||
</button>
|
</button>
|
||||||
|
<button *ngIf="hasNextPage"
|
||||||
|
class="list-group-item list-group-item-action border-0 list-entry">
|
||||||
|
<ds-loading [showMessage]="false"></ds-loading>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -0,0 +1,5 @@
|
|||||||
|
.scrollable-menu {
|
||||||
|
height: auto;
|
||||||
|
max-height: $dso-selector-list-max-height;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
@@ -6,10 +6,10 @@ import { SearchService } from '../../../core/shared/search/search.service';
|
|||||||
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
|
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
|
||||||
import { ItemSearchResult } from '../../object-collection/shared/item-search-result.model';
|
import { ItemSearchResult } from '../../object-collection/shared/item-search-result.model';
|
||||||
import { Item } from '../../../core/shared/item.model';
|
import { Item } from '../../../core/shared/item.model';
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
|
||||||
import { MetadataValue } from '../../../core/shared/metadata.models';
|
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils';
|
||||||
import { PaginatedSearchOptions } from '../../search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../search/paginated-search-options.model';
|
||||||
|
import { hasValue } from '../../empty.util';
|
||||||
|
import { createPaginatedList } from '../../testing/utils.test';
|
||||||
|
|
||||||
describe('DSOSelectorComponent', () => {
|
describe('DSOSelectorComponent', () => {
|
||||||
let component: DSOSelectorComponent;
|
let component: DSOSelectorComponent;
|
||||||
@@ -18,19 +18,46 @@ describe('DSOSelectorComponent', () => {
|
|||||||
|
|
||||||
const currentDSOId = 'test-uuid-ford-sose';
|
const currentDSOId = 'test-uuid-ford-sose';
|
||||||
const type = DSpaceObjectType.ITEM;
|
const type = DSpaceObjectType.ITEM;
|
||||||
const searchResult = new ItemSearchResult();
|
const searchResult = createSearchResult('current');
|
||||||
const item = new Item();
|
|
||||||
item.metadata = {
|
const firstPageResults = [
|
||||||
'dc.title': [Object.assign(new MetadataValue(), {
|
createSearchResult('1'),
|
||||||
value: 'Item title',
|
createSearchResult('2'),
|
||||||
language: undefined
|
createSearchResult('3'),
|
||||||
})]
|
];
|
||||||
};
|
|
||||||
searchResult.indexableObject = item;
|
const nextPageResults = [
|
||||||
searchResult.hitHighlights = {};
|
createSearchResult('4'),
|
||||||
const searchService = jasmine.createSpyObj('searchService', {
|
createSearchResult('5'),
|
||||||
search: createSuccessfulRemoteDataObject$(new PaginatedList(undefined, [searchResult]))
|
createSearchResult('6'),
|
||||||
});
|
];
|
||||||
|
|
||||||
|
const searchService = {
|
||||||
|
search: (options: PaginatedSearchOptions) => {
|
||||||
|
if (hasValue(options.query) && options.query.startsWith('search.resourceid')) {
|
||||||
|
return createSuccessfulRemoteDataObject$(createPaginatedList([searchResult]));
|
||||||
|
} else if (options.pagination.currentPage === 1) {
|
||||||
|
return createSuccessfulRemoteDataObject$(createPaginatedList(firstPageResults));
|
||||||
|
} else {
|
||||||
|
return createSuccessfulRemoteDataObject$(createPaginatedList(nextPageResults));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSearchResult(name: string): ItemSearchResult {
|
||||||
|
return Object.assign(new ItemSearchResult(), {
|
||||||
|
indexableObject: Object.assign(new Item(), {
|
||||||
|
id: `test-result-${name}`,
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{
|
||||||
|
value: `test result - ${name}`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@@ -58,13 +85,23 @@ describe('DSOSelectorComponent', () => {
|
|||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should initially call the search method on the SearchService with the given DSO uuid', () => {
|
describe('populating listEntries', () => {
|
||||||
const searchOptions = new PaginatedSearchOptions({
|
it('should not be empty', () => {
|
||||||
query: currentDSOId,
|
expect(component.listEntries.length).toBeGreaterThan(0);
|
||||||
dsoTypes: [type],
|
|
||||||
pagination: (component as any).defaultPagination
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(searchService.search).toHaveBeenCalledWith(searchOptions);
|
it('should contain a combination of the current DSO and first page results', () => {
|
||||||
|
expect(component.listEntries).toEqual([searchResult, ...firstPageResults]);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when current page increases', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
component.currentPage$.next(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain a combination of the current DSO, as well as first and second page results', () => {
|
||||||
|
expect(component.listEntries).toEqual([searchResult, ...firstPageResults, ...nextPageResults]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -3,28 +3,33 @@ import {
|
|||||||
ElementRef,
|
ElementRef,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
Input,
|
Input,
|
||||||
|
OnDestroy,
|
||||||
OnInit,
|
OnInit,
|
||||||
Output,
|
Output,
|
||||||
QueryList,
|
QueryList,
|
||||||
ViewChildren
|
ViewChildren
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { FormControl } from '@angular/forms';
|
import { FormControl } from '@angular/forms';
|
||||||
|
import { debounceTime, map, startWith, switchMap, tap } from 'rxjs/operators';
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { debounceTime, startWith, switchMap } from 'rxjs/operators';
|
|
||||||
import { SearchService } from '../../../core/shared/search/search.service';
|
import { SearchService } from '../../../core/shared/search/search.service';
|
||||||
import { CollectionElementLinkType } from '../../object-collection/collection-element-link.type';
|
import { CollectionElementLinkType } from '../../object-collection/collection-element-link.type';
|
||||||
import { PaginatedSearchOptions } from '../../search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../search/paginated-search-options.model';
|
||||||
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
|
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
|
||||||
import { SearchResult } from '../../search/search-result.model';
|
|
||||||
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
import { ViewMode } from '../../../core/shared/view-mode.model';
|
import { ViewMode } from '../../../core/shared/view-mode.model';
|
||||||
|
import { Context } from '../../../core/shared/context.model';
|
||||||
|
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
|
||||||
|
import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators';
|
||||||
|
import { Subscription } from 'rxjs/internal/Subscription';
|
||||||
|
import { hasValue, isEmpty, isNotEmpty } from '../../empty.util';
|
||||||
|
import { combineLatest as observableCombineLatest, of as observableOf } from 'rxjs';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
|
import { SearchResult } from '../../search/search-result.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-dso-selector',
|
selector: 'ds-dso-selector',
|
||||||
// styleUrls: ['./dso-selector.component.scss'],
|
styleUrls: ['./dso-selector.component.scss'],
|
||||||
templateUrl: './dso-selector.component.html'
|
templateUrl: './dso-selector.component.html'
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -32,7 +37,7 @@ import { ViewMode } from '../../../core/shared/view-mode.model';
|
|||||||
* Component to render a list of DSO's of which one can be selected
|
* Component to render a list of DSO's of which one can be selected
|
||||||
* The user can search the list by using the input field
|
* The user can search the list by using the input field
|
||||||
*/
|
*/
|
||||||
export class DSOSelectorComponent implements OnInit {
|
export class DSOSelectorComponent implements OnInit, OnDestroy {
|
||||||
/**
|
/**
|
||||||
* The view mode of the listed objects
|
* The view mode of the listed objects
|
||||||
*/
|
*/
|
||||||
@@ -63,12 +68,29 @@ export class DSOSelectorComponent implements OnInit {
|
|||||||
/**
|
/**
|
||||||
* Default pagination for this feature
|
* Default pagination for this feature
|
||||||
*/
|
*/
|
||||||
private defaultPagination = { id: 'dso-selector', currentPage: 1, pageSize: 5 } as any;
|
defaultPagination = { id: 'dso-selector', currentPage: 1, pageSize: 10 } as any;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List with search results of DSpace objects for the current query
|
* List with search results of DSpace objects for the current query
|
||||||
*/
|
*/
|
||||||
listEntries$: Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>>;
|
listEntries: Array<SearchResult<DSpaceObject>> = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current page to load
|
||||||
|
* Dynamically goes up as the user scrolls down until it reaches the last page possible
|
||||||
|
*/
|
||||||
|
currentPage$ = new BehaviorSubject(1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the list contains a next page to load
|
||||||
|
* This allows us to avoid next pages from trying to load when there are none
|
||||||
|
*/
|
||||||
|
hasNextPage = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the list should be reset next time it receives a page to load
|
||||||
|
*/
|
||||||
|
resetList = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of element references to all elements
|
* List of element references to all elements
|
||||||
@@ -85,31 +107,107 @@ export class DSOSelectorComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
linkTypes = CollectionElementLinkType;
|
linkTypes = CollectionElementLinkType;
|
||||||
|
|
||||||
constructor(private searchService: SearchService) {
|
/**
|
||||||
|
* Track whether the element has the mouse over it
|
||||||
|
*/
|
||||||
|
isMouseOver = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
public subs: Subscription[] = [];
|
||||||
|
|
||||||
|
constructor(protected searchService: SearchService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fills the listEntries$ variable with search results based on the input field's current value
|
* Fills the listEntries variable with search results based on the input field's current value and the current page
|
||||||
* The search will always start with the initial currentDSOId value
|
* The search will always start with the initial currentDSOId value
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.input.setValue(this.currentDSOId);
|
|
||||||
this.typesString = this.types.map((type: string) => type.toString().toLowerCase()).join(', ');
|
this.typesString = this.types.map((type: string) => type.toString().toLowerCase()).join(', ');
|
||||||
this.listEntries$ = this.input.valueChanges
|
|
||||||
.pipe(
|
// Create an observable searching for the current DSO (return empty list if there's no current DSO)
|
||||||
|
let currentDSOResult$;
|
||||||
|
if (isNotEmpty(this.currentDSOId)) {
|
||||||
|
currentDSOResult$ = this.search(this.getCurrentDSOQuery(), 1);
|
||||||
|
} else {
|
||||||
|
currentDSOResult$ = observableOf(new PaginatedList(undefined, []));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine current DSO, query and page
|
||||||
|
this.subs.push(observableCombineLatest(
|
||||||
|
currentDSOResult$,
|
||||||
|
this.input.valueChanges.pipe(
|
||||||
debounceTime(this.debounceTime),
|
debounceTime(this.debounceTime),
|
||||||
startWith(this.currentDSOId),
|
startWith(''),
|
||||||
switchMap((query) => {
|
tap(() => this.currentPage$.next(1))
|
||||||
return this.searchService.search(
|
),
|
||||||
new PaginatedSearchOptions({
|
this.currentPage$
|
||||||
query: query,
|
).pipe(
|
||||||
dsoTypes: this.types,
|
switchMap(([currentDSOResult, query, page]: [PaginatedList<SearchResult<DSpaceObject>>, string, number]) => {
|
||||||
pagination: this.defaultPagination
|
if (page === 1) {
|
||||||
})
|
// The first page is loading, this means we should reset the list instead of adding to it
|
||||||
)
|
this.resetList = true;
|
||||||
}
|
}
|
||||||
)
|
return this.search(query, page).pipe(
|
||||||
)
|
map((list) => {
|
||||||
|
// If it's the first page and no query is entered, add the current DSO to the start of the list
|
||||||
|
// If no query is entered, filter out the current DSO from the results, as it'll be displayed at the start of the list already
|
||||||
|
list.page = [
|
||||||
|
...((isEmpty(query) && page === 1) ? currentDSOResult.page : []),
|
||||||
|
...list.page.filter((result) => isNotEmpty(query) || result.indexableObject.id !== this.currentDSOId)
|
||||||
|
];
|
||||||
|
return list;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
).subscribe((list) => {
|
||||||
|
if (this.resetList) {
|
||||||
|
this.listEntries = list.page;
|
||||||
|
this.resetList = false;
|
||||||
|
} else {
|
||||||
|
this.listEntries.push(...list.page);
|
||||||
|
}
|
||||||
|
// Check if there are more pages available after the current one
|
||||||
|
this.hasNextPage = list.totalElements > this.listEntries.length;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a query to send for retrieving the current DSO
|
||||||
|
*/
|
||||||
|
getCurrentDSOQuery(): string {
|
||||||
|
return `search.resourceid:${this.currentDSOId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a search for the current query and page
|
||||||
|
* @param query Query to search objects for
|
||||||
|
* @param page Page to retrieve
|
||||||
|
*/
|
||||||
|
search(query: string, page: number): Observable<PaginatedList<SearchResult<DSpaceObject>>> {
|
||||||
|
return this.searchService.search(
|
||||||
|
new PaginatedSearchOptions({
|
||||||
|
query: query,
|
||||||
|
dsoTypes: this.types,
|
||||||
|
pagination: Object.assign({}, this.defaultPagination, {
|
||||||
|
currentPage: page
|
||||||
|
})
|
||||||
|
})
|
||||||
|
).pipe(
|
||||||
|
getFirstSucceededRemoteDataPayload()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the user reaches the bottom of the page (or almost) and there's a next page available, increase the current page
|
||||||
|
*/
|
||||||
|
onScrollDown() {
|
||||||
|
if (this.hasNextPage) {
|
||||||
|
this.currentPage$.next(this.currentPage$.value + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -120,4 +218,22 @@ export class DSOSelectorComponent implements OnInit {
|
|||||||
this.listElements.first.nativeElement.click();
|
this.listElements.first.nativeElement.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the context for element with the given id
|
||||||
|
*/
|
||||||
|
getContext(id: string) {
|
||||||
|
if (id === this.currentDSOId) {
|
||||||
|
return Context.SideBarSearchModalCurrent;
|
||||||
|
} else {
|
||||||
|
return Context.SideBarSearchModal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe from all subscriptions
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,6 +22,7 @@ export class CreateCollectionParentSelectorComponent extends DSOSelectorModalWra
|
|||||||
objectType = DSpaceObjectType.COLLECTION;
|
objectType = DSpaceObjectType.COLLECTION;
|
||||||
selectorTypes = [DSpaceObjectType.COMMUNITY];
|
selectorTypes = [DSpaceObjectType.COMMUNITY];
|
||||||
action = SelectorActionType.CREATE;
|
action = SelectorActionType.CREATE;
|
||||||
|
header = 'dso-selector.create.collection.sub-level';
|
||||||
|
|
||||||
constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router) {
|
constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router) {
|
||||||
super(activeModal, route);
|
super(activeModal, route);
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<ds-collection-dropdown (selectionChange)="selectObject($event.collection)">
|
<h5 *ngIf="header" class="px-2">{{header | translate}}</h5>
|
||||||
</ds-collection-dropdown>
|
<ds-authorized-collection-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" (onSelect)="selectObject($event)"></ds-authorized-collection-selector>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -20,6 +20,7 @@ export class CreateItemParentSelectorComponent extends DSOSelectorModalWrapperCo
|
|||||||
objectType = DSpaceObjectType.ITEM;
|
objectType = DSpaceObjectType.ITEM;
|
||||||
selectorTypes = [DSpaceObjectType.COLLECTION];
|
selectorTypes = [DSpaceObjectType.COLLECTION];
|
||||||
action = SelectorActionType.CREATE;
|
action = SelectorActionType.CREATE;
|
||||||
|
header = 'dso-selector.create.item.sub-level';
|
||||||
|
|
||||||
constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router) {
|
constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router) {
|
||||||
super(activeModal, route);
|
super(activeModal, route);
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<ds-dso-selector [currentDSOId]="dsoRD?.payload.uuid ? 'search.resourceid:' + dsoRD?.payload.uuid : null" [types]="selectorTypes" (onSelect)="selectObject($event)"></ds-dso-selector>
|
<h5 *ngIf="header" class="px-2">{{header | translate}}</h5>
|
||||||
|
<ds-dso-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" (onSelect)="selectObject($event)"></ds-dso-selector>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -23,6 +23,12 @@ export abstract class DSOSelectorModalWrapperComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
@Input() dsoRD: RemoteData<DSpaceObject>;
|
@Input() dsoRD: RemoteData<DSpaceObject>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional header to display above the selection list
|
||||||
|
* Supports i18n keys
|
||||||
|
*/
|
||||||
|
@Input() header: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of the DSO that's being edited, created or exported
|
* The type of the DSO that's being edited, created or exported
|
||||||
*/
|
*/
|
||||||
|
35
src/app/shared/hover-class.directive.spec.ts
Normal file
35
src/app/shared/hover-class.directive.spec.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { Component, DebugElement } from '@angular/core';
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { HoverClassDirective } from './hover-class.directive';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `<div dsHoverClass="ds-hover"></div>`
|
||||||
|
})
|
||||||
|
class TestComponent { }
|
||||||
|
|
||||||
|
describe('HoverClassDirective', () => {
|
||||||
|
let component: TestComponent;
|
||||||
|
let fixture: ComponentFixture<TestComponent>;
|
||||||
|
let el: DebugElement;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.configureTestingModule({
|
||||||
|
declarations: [TestComponent, HoverClassDirective]
|
||||||
|
}).createComponent(TestComponent);
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
el = fixture.debugElement.query(By.css('div'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add the class on mouseenter and remove on mouseleave', () => {
|
||||||
|
el.triggerEventHandler('mouseenter', null);
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(el.nativeElement.classList).toContain('ds-hover');
|
||||||
|
|
||||||
|
el.triggerEventHandler('mouseleave', null);
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(el.nativeElement.classList).not.toContain('ds-hover');
|
||||||
|
});
|
||||||
|
});
|
31
src/app/shared/hover-class.directive.ts
Normal file
31
src/app/shared/hover-class.directive.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[dsHoverClass]'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* A directive adding a class to an element when hovered over
|
||||||
|
*/
|
||||||
|
export class HoverClassDirective {
|
||||||
|
/**
|
||||||
|
* The name of the class to add on hover
|
||||||
|
*/
|
||||||
|
@Input('dsHoverClass') hoverClass: string;
|
||||||
|
|
||||||
|
constructor(public elementRef: ElementRef) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On mouse enter, add the class to the element's class list
|
||||||
|
*/
|
||||||
|
@HostListener('mouseenter') onMouseEnter() {
|
||||||
|
this.elementRef.nativeElement.classList.add(this.hoverClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On mouse leave, remove the class from the element's class list
|
||||||
|
*/
|
||||||
|
@HostListener('mouseleave') onMouseLeave() {
|
||||||
|
this.elementRef.nativeElement.classList.remove(this.hoverClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,37 @@
|
|||||||
|
import { CollectionSidebarSearchListElementComponent } from './collection-sidebar-search-list-element.component';
|
||||||
|
import { CollectionSearchResult } from '../../../object-collection/shared/collection-search-result.model';
|
||||||
|
import { Collection } from '../../../../core/shared/collection.model';
|
||||||
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
|
import { createSidebarSearchListElementTests } from '../sidebar-search-list-element.component.spec';
|
||||||
|
|
||||||
|
const object = Object.assign(new CollectionSearchResult(), {
|
||||||
|
indexableObject: Object.assign(new Collection(), {
|
||||||
|
id: 'test-collection',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{
|
||||||
|
value: 'title'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'dc.description.abstract': [
|
||||||
|
{
|
||||||
|
value: 'description'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const parent = Object.assign(new Community(), {
|
||||||
|
id: 'test-community',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{
|
||||||
|
value: 'parent title'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('CollectionSidebarSearchListElementComponent',
|
||||||
|
createSidebarSearchListElementTests(CollectionSidebarSearchListElementComponent, object, parent, 'parent title', 'title', 'description')
|
||||||
|
);
|
@@ -0,0 +1,25 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { CollectionSearchResult } from '../../../object-collection/shared/collection-search-result.model';
|
||||||
|
import { Collection } from '../../../../core/shared/collection.model';
|
||||||
|
import { listableObjectComponent } from '../../../object-collection/shared/listable-object/listable-object.decorator';
|
||||||
|
import { Context } from '../../../../core/shared/context.model';
|
||||||
|
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||||
|
import { SidebarSearchListElementComponent } from '../sidebar-search-list-element.component';
|
||||||
|
|
||||||
|
@listableObjectComponent(CollectionSearchResult, ViewMode.ListElement, Context.SideBarSearchModal)
|
||||||
|
@listableObjectComponent(CollectionSearchResult, ViewMode.ListElement, Context.SideBarSearchModalCurrent)
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-collection-sidebar-search-list-element',
|
||||||
|
templateUrl: '../sidebar-search-list-element.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component displaying a list element for a {@link CollectionSearchResult} within the context of a sidebar search modal
|
||||||
|
*/
|
||||||
|
export class CollectionSidebarSearchListElementComponent extends SidebarSearchListElementComponent<CollectionSearchResult, Collection> {
|
||||||
|
/**
|
||||||
|
* Get the description of the Collection by returning its abstract
|
||||||
|
*/
|
||||||
|
getDescription(): string {
|
||||||
|
return this.firstMetadataValue('dc.description.abstract');
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,36 @@
|
|||||||
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
|
import { createSidebarSearchListElementTests } from '../sidebar-search-list-element.component.spec';
|
||||||
|
import { CommunitySidebarSearchListElementComponent } from './community-sidebar-search-list-element.component';
|
||||||
|
import { CommunitySearchResult } from '../../../object-collection/shared/community-search-result.model';
|
||||||
|
|
||||||
|
const object = Object.assign(new CommunitySearchResult(), {
|
||||||
|
indexableObject: Object.assign(new Community(), {
|
||||||
|
id: 'test-community',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{
|
||||||
|
value: 'title'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'dc.description.abstract': [
|
||||||
|
{
|
||||||
|
value: 'description'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const parent = Object.assign(new Community(), {
|
||||||
|
id: 'test-parent-community',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{
|
||||||
|
value: 'parent title'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('CommunitySidebarSearchListElementComponent',
|
||||||
|
createSidebarSearchListElementTests(CommunitySidebarSearchListElementComponent, object, parent, 'parent title', 'title', 'description')
|
||||||
|
);
|
@@ -0,0 +1,25 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { listableObjectComponent } from '../../../object-collection/shared/listable-object/listable-object.decorator';
|
||||||
|
import { Context } from '../../../../core/shared/context.model';
|
||||||
|
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||||
|
import { SidebarSearchListElementComponent } from '../sidebar-search-list-element.component';
|
||||||
|
import { CommunitySearchResult } from '../../../object-collection/shared/community-search-result.model';
|
||||||
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
|
|
||||||
|
@listableObjectComponent(CommunitySearchResult, ViewMode.ListElement, Context.SideBarSearchModal)
|
||||||
|
@listableObjectComponent(CommunitySearchResult, ViewMode.ListElement, Context.SideBarSearchModalCurrent)
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-collection-sidebar-search-list-element',
|
||||||
|
templateUrl: '../sidebar-search-list-element.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component displaying a list element for a {@link CommunitySearchResult} within the context of a sidebar search modal
|
||||||
|
*/
|
||||||
|
export class CommunitySidebarSearchListElementComponent extends SidebarSearchListElementComponent<CommunitySearchResult, Community> {
|
||||||
|
/**
|
||||||
|
* Get the description of the Community by returning its abstract
|
||||||
|
*/
|
||||||
|
getDescription(): string {
|
||||||
|
return this.firstMetadataValue('dc.description.abstract');
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,47 @@
|
|||||||
|
import { createSidebarSearchListElementTests } from '../../sidebar-search-list-element.component.spec';
|
||||||
|
import { PublicationSidebarSearchListElementComponent } from './publication-sidebar-search-list-element.component';
|
||||||
|
import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model';
|
||||||
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
|
import { Collection } from '../../../../../core/shared/collection.model';
|
||||||
|
|
||||||
|
const object = Object.assign(new ItemSearchResult(), {
|
||||||
|
indexableObject: Object.assign(new Item(), {
|
||||||
|
id: 'test-item',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{
|
||||||
|
value: 'title'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'dc.publisher': [
|
||||||
|
{
|
||||||
|
value: 'publisher'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'dc.date.issued': [
|
||||||
|
{
|
||||||
|
value: 'date'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'dc.contributor.author': [
|
||||||
|
{
|
||||||
|
value: 'author'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const parent = Object.assign(new Collection(), {
|
||||||
|
id: 'test-collection',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{
|
||||||
|
value: 'parent title'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PublicationSidebarSearchListElementComponent',
|
||||||
|
createSidebarSearchListElementTests(PublicationSidebarSearchListElementComponent, object, parent, 'parent title', 'title', '(publisher, date) author')
|
||||||
|
);
|
@@ -0,0 +1,23 @@
|
|||||||
|
import { listableObjectComponent } from '../../../../object-collection/shared/listable-object/listable-object.decorator';
|
||||||
|
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { Context } from '../../../../../core/shared/context.model';
|
||||||
|
import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model';
|
||||||
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
|
import { SidebarSearchListElementComponent } from '../../sidebar-search-list-element.component';
|
||||||
|
|
||||||
|
@listableObjectComponent('PublicationSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
|
||||||
|
@listableObjectComponent('PublicationSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent)
|
||||||
|
@listableObjectComponent(ItemSearchResult, ViewMode.ListElement, Context.SideBarSearchModal)
|
||||||
|
@listableObjectComponent(ItemSearchResult, ViewMode.ListElement, Context.SideBarSearchModalCurrent)
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-publication-sidebar-search-list-element',
|
||||||
|
templateUrl: '../../sidebar-search-list-element.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component displaying a list element for a {@link ItemSearchResult} of type "Publication" within the context of
|
||||||
|
* a sidebar search modal
|
||||||
|
*/
|
||||||
|
export class PublicationSidebarSearchListElementComponent extends SidebarSearchListElementComponent<ItemSearchResult, Item> {
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,14 @@
|
|||||||
|
<ds-truncatable-part [maxLines]="1" [background]="isCurrent() ? 'primary' : 'default'">
|
||||||
|
<div [ngClass]="isCurrent() ? 'text-light' : 'text-body'"
|
||||||
|
[innerHTML]="(parentTitle$ && parentTitle$ | async) ? (parentTitle$ | async) : ('home.breadcrumbs' | translate)"></div>
|
||||||
|
</ds-truncatable-part>
|
||||||
|
<ds-truncatable-part *ngIf="title" [maxLines]="1" [background]="isCurrent() ? 'primary' : 'default'">
|
||||||
|
<div class="font-weight-bold"
|
||||||
|
[ngClass]="isCurrent() ? 'text-light' : 'text-primary'"
|
||||||
|
[innerHTML]="title"></div>
|
||||||
|
</ds-truncatable-part>
|
||||||
|
<ds-truncatable-part *ngIf="description" [maxLines]="1" [background]="isCurrent() ? 'primary' : 'default'">
|
||||||
|
<div class="text-secondary"
|
||||||
|
[ngClass]="isCurrent() ? 'text-light' : 'text-secondary'"
|
||||||
|
[innerHTML]="description"></div>
|
||||||
|
</ds-truncatable-part>
|
@@ -0,0 +1,69 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { VarDirective } from '../../utils/var.directive';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { SearchResult } from '../../search/search-result.model';
|
||||||
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
|
import { TruncatableService } from '../../truncatable/truncatable.service';
|
||||||
|
import { LinkService } from '../../../core/cache/builders/link.service';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils';
|
||||||
|
import { HALResource } from '../../../core/shared/hal-resource.model';
|
||||||
|
import { ChildHALResource } from '../../../core/shared/child-hal-resource.model';
|
||||||
|
|
||||||
|
export function createSidebarSearchListElementTests(
|
||||||
|
componentClass: any,
|
||||||
|
object: SearchResult<DSpaceObject & ChildHALResource>,
|
||||||
|
parent: DSpaceObject,
|
||||||
|
expectedParentTitle: string,
|
||||||
|
expectedTitle: string,
|
||||||
|
expectedDescription: string,
|
||||||
|
extraProviders: any[] = []
|
||||||
|
) {
|
||||||
|
return () => {
|
||||||
|
let component;
|
||||||
|
let fixture: ComponentFixture<any>;
|
||||||
|
|
||||||
|
let linkService;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
linkService = jasmine.createSpyObj('linkService', {
|
||||||
|
resolveLink: Object.assign(new HALResource(), {
|
||||||
|
[object.indexableObject.getParentLinkKey()]: createSuccessfulRemoteDataObject$(parent)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [componentClass, VarDirective],
|
||||||
|
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])],
|
||||||
|
providers: [
|
||||||
|
{ provide: TruncatableService, useValue: {} },
|
||||||
|
{ provide: LinkService, useValue: linkService },
|
||||||
|
...extraProviders
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(componentClass);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
component.object = object;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain the correct parent title', (done) => {
|
||||||
|
component.parentTitle$.subscribe((title) => {
|
||||||
|
expect(title).toEqual(expectedParentTitle);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain the correct title', () => {
|
||||||
|
expect(component.title).toEqual(expectedTitle);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain the correct description', () => {
|
||||||
|
expect(component.description).toEqual(expectedDescription);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
@@ -0,0 +1,153 @@
|
|||||||
|
import { SearchResult } from '../../search/search-result.model';
|
||||||
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
|
import { SearchResultListElementComponent } from '../search-result-list-element/search-result-list-element.component';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { hasValue, isNotEmpty } from '../../empty.util';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { TruncatableService } from '../../truncatable/truncatable.service';
|
||||||
|
import { LinkService } from '../../../core/cache/builders/link.service';
|
||||||
|
import { find, map } from 'rxjs/operators';
|
||||||
|
import { ChildHALResource } from '../../../core/shared/child-hal-resource.model';
|
||||||
|
import { followLink } from '../../utils/follow-link-config.model';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { Context } from '../../../core/shared/context.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-sidebar-search-list-element',
|
||||||
|
templateUrl: './sidebar-search-list-element.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component displaying a list element for a {@link SearchResult} in the sidebar search modal
|
||||||
|
* It displays the name of the parent, title and description of the object. All of which are customizable in the child
|
||||||
|
* component by overriding the relevant methods of this component
|
||||||
|
*/
|
||||||
|
export class SidebarSearchListElementComponent<T extends SearchResult<K>, K extends DSpaceObject> extends SearchResultListElementComponent<T, K> {
|
||||||
|
/**
|
||||||
|
* Observable for the title of the parent object (displayed above the object's title)
|
||||||
|
*/
|
||||||
|
parentTitle$: Observable<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The title for the object to display
|
||||||
|
*/
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A description to display below the title
|
||||||
|
*/
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
public constructor(protected truncatableService: TruncatableService,
|
||||||
|
protected linkService: LinkService) {
|
||||||
|
super(truncatableService);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise the component variables
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
super.ngOnInit();
|
||||||
|
if (hasValue(this.dso)) {
|
||||||
|
this.parentTitle$ = this.getParentTitle();
|
||||||
|
this.title = this.getTitle();
|
||||||
|
this.description = this.getDescription();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns true if this element represents the current dso
|
||||||
|
*/
|
||||||
|
isCurrent(): boolean {
|
||||||
|
return this.context === Context.SideBarSearchModalCurrent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the title of the object's parent
|
||||||
|
* Retrieve the parent by using the object's parent link and retrieving its 'dc.title' metadata
|
||||||
|
*/
|
||||||
|
getParentTitle(): Observable<string> {
|
||||||
|
return this.getParent().pipe(
|
||||||
|
map((parentRD: RemoteData<DSpaceObject>) => {
|
||||||
|
return hasValue(parentRD) && hasValue(parentRD.payload) ? parentRD.payload.firstMetadataValue('dc.title') : undefined;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the parent of the object
|
||||||
|
*/
|
||||||
|
getParent(): Observable<RemoteData<DSpaceObject>> {
|
||||||
|
if (typeof (this.dso as any).getParentLinkKey === 'function') {
|
||||||
|
const propertyName = (this.dso as any).getParentLinkKey();
|
||||||
|
return this.linkService.resolveLink(this.dso, followLink(propertyName))[propertyName].pipe(
|
||||||
|
find((parentRD: RemoteData<ChildHALResource & DSpaceObject>) => parentRD.hasSucceeded || parentRD.statusCode === 204)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return observableOf(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the title of the object
|
||||||
|
* Default: "dc.title"
|
||||||
|
*/
|
||||||
|
getTitle(): string {
|
||||||
|
return this.firstMetadataValue('dc.title');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the description of the object
|
||||||
|
* Default: "(dc.publisher, dc.date.issued) authors"
|
||||||
|
*/
|
||||||
|
getDescription(): string {
|
||||||
|
const publisher = this.firstMetadataValue('dc.publisher');
|
||||||
|
const date = this.firstMetadataValue('dc.date.issued');
|
||||||
|
const authors = this.allMetadataValues(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']);
|
||||||
|
let description = '';
|
||||||
|
if (isNotEmpty(publisher) || isNotEmpty(date)) {
|
||||||
|
description += '(';
|
||||||
|
}
|
||||||
|
if (isNotEmpty(publisher)) {
|
||||||
|
description += publisher;
|
||||||
|
}
|
||||||
|
if (isNotEmpty(date)) {
|
||||||
|
if (isNotEmpty(publisher)) {
|
||||||
|
description += ', ';
|
||||||
|
}
|
||||||
|
description += date;
|
||||||
|
}
|
||||||
|
if (isNotEmpty(description)) {
|
||||||
|
description += ') ';
|
||||||
|
}
|
||||||
|
if (isNotEmpty(authors)) {
|
||||||
|
authors.forEach((author, i) => {
|
||||||
|
description += author;
|
||||||
|
if (i < (authors.length - 1)) {
|
||||||
|
description += '; ';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this.undefinedIfEmpty(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return undefined if the provided string is empty
|
||||||
|
* @param value Value to check
|
||||||
|
*/
|
||||||
|
undefinedIfEmpty(value: string) {
|
||||||
|
return this.defaultIfEmpty(value, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a default value if the provided string is empty
|
||||||
|
* @param value Value to check
|
||||||
|
* @param def Default in case value is empty
|
||||||
|
*/
|
||||||
|
defaultIfEmpty(value: string, def: string) {
|
||||||
|
if (isNotEmpty(value)) {
|
||||||
|
return value;
|
||||||
|
} else {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -210,6 +210,13 @@ import { CollectionDropdownComponent } from './collection-dropdown/collection-dr
|
|||||||
import { DsSelectComponent } from './ds-select/ds-select.component';
|
import { DsSelectComponent } from './ds-select/ds-select.component';
|
||||||
import { VocabularyTreeviewComponent } from './vocabulary-treeview/vocabulary-treeview.component';
|
import { VocabularyTreeviewComponent } from './vocabulary-treeview/vocabulary-treeview.component';
|
||||||
import { CurationFormComponent } from '../curation-form/curation-form.component';
|
import { CurationFormComponent } from '../curation-form/curation-form.component';
|
||||||
|
import { PublicationSidebarSearchListElementComponent } from './object-list/sidebar-search-list-element/item-types/publication/publication-sidebar-search-list-element.component';
|
||||||
|
import { SidebarSearchListElementComponent } from './object-list/sidebar-search-list-element/sidebar-search-list-element.component';
|
||||||
|
import { CollectionSidebarSearchListElementComponent } from './object-list/sidebar-search-list-element/collection/collection-sidebar-search-list-element.component';
|
||||||
|
import { CommunitySidebarSearchListElementComponent } from './object-list/sidebar-search-list-element/community/community-sidebar-search-list-element.component';
|
||||||
|
import { AuthorizedCollectionSelectorComponent } from './dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component';
|
||||||
|
import { DsoPageEditButtonComponent } from './dso-page/dso-page-edit-button/dso-page-edit-button.component';
|
||||||
|
import { HoverClassDirective } from './hover-class.directive';
|
||||||
|
|
||||||
const MODULES = [
|
const MODULES = [
|
||||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||||
@@ -403,7 +410,8 @@ const COMPONENTS = [
|
|||||||
CollectionDropdownComponent,
|
CollectionDropdownComponent,
|
||||||
ExportMetadataSelectorComponent,
|
ExportMetadataSelectorComponent,
|
||||||
ConfirmationModalComponent,
|
ConfirmationModalComponent,
|
||||||
VocabularyTreeviewComponent
|
VocabularyTreeviewComponent,
|
||||||
|
AuthorizedCollectionSelectorComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
@@ -483,12 +491,18 @@ const ENTRY_COMPONENTS = [
|
|||||||
CurationFormComponent,
|
CurationFormComponent,
|
||||||
ExportMetadataSelectorComponent,
|
ExportMetadataSelectorComponent,
|
||||||
ConfirmationModalComponent,
|
ConfirmationModalComponent,
|
||||||
VocabularyTreeviewComponent
|
VocabularyTreeviewComponent,
|
||||||
|
SidebarSearchListElementComponent,
|
||||||
|
PublicationSidebarSearchListElementComponent,
|
||||||
|
CollectionSidebarSearchListElementComponent,
|
||||||
|
CommunitySidebarSearchListElementComponent,
|
||||||
|
AuthorizedCollectionSelectorComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
const SHARED_ITEM_PAGE_COMPONENTS = [
|
const SHARED_ITEM_PAGE_COMPONENTS = [
|
||||||
MetadataFieldWrapperComponent,
|
MetadataFieldWrapperComponent,
|
||||||
MetadataValuesComponent,
|
MetadataValuesComponent,
|
||||||
|
DsoPageEditButtonComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
const PROVIDERS = [
|
const PROVIDERS = [
|
||||||
@@ -519,7 +533,8 @@ const DIRECTIVES = [
|
|||||||
FileValidator,
|
FileValidator,
|
||||||
ClaimedTaskActionsDirective,
|
ClaimedTaskActionsDirective,
|
||||||
NgForTrackByIdDirective,
|
NgForTrackByIdDirective,
|
||||||
MetadataFieldValidator
|
MetadataFieldValidator,
|
||||||
|
HoverClassDirective
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -532,19 +547,19 @@ const DIRECTIVES = [
|
|||||||
...COMPONENTS,
|
...COMPONENTS,
|
||||||
...DIRECTIVES,
|
...DIRECTIVES,
|
||||||
...ENTRY_COMPONENTS,
|
...ENTRY_COMPONENTS,
|
||||||
...SHARED_ITEM_PAGE_COMPONENTS
|
...SHARED_ITEM_PAGE_COMPONENTS,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
...PROVIDERS
|
...PROVIDERS
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
...MODULES,
|
...MODULES,
|
||||||
...PIPES,
|
...PIPES,
|
||||||
...COMPONENTS,
|
...COMPONENTS,
|
||||||
...SHARED_ITEM_PAGE_COMPONENTS,
|
...SHARED_ITEM_PAGE_COMPONENTS,
|
||||||
...DIRECTIVES,
|
...DIRECTIVES,
|
||||||
CurationFormComponent
|
CurationFormComponent
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
...ENTRY_COMPONENTS
|
...ENTRY_COMPONENTS
|
||||||
]
|
]
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<div class="clamp-{{lines}} min-{{minLines}} {{type}} {{fixedHeight ? 'fixedHeight' : ''}}">
|
<div class="clamp-{{background}}-{{lines}} min-{{minLines}} {{type}} {{fixedHeight ? 'fixedHeight' : ''}}">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
@mixin clamp($lines, $size-factor: 1, $line-height: $line-height-base) {
|
@mixin clamp($lines, $bg, $size-factor: 1, $line-height: $line-height-base) {
|
||||||
$height: $line-height * $font-size-base * $size-factor;
|
$height: $line-height * $font-size-base * $size-factor;
|
||||||
&.fixedHeight {
|
&.fixedHeight {
|
||||||
height: $lines * $height;
|
height: $lines * $height;
|
||||||
@@ -19,10 +19,11 @@
|
|||||||
min-width: 75px;
|
min-width: 75px;
|
||||||
max-width: 150px;
|
max-width: 150px;
|
||||||
height: $height;
|
height: $height;
|
||||||
background: linear-gradient(to right, rgba(255, 255, 255, 0), $body-bg 70%);
|
background: linear-gradient(to right, rgba(255, 255, 255, 0), $bg 70%);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin min($lines, $size-factor: 1, $line-height: $line-height-base) {
|
@mixin min($lines, $size-factor: 1, $line-height: $line-height-base) {
|
||||||
@@ -31,16 +32,32 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$h4-factor: strip-unit($h4-font-size);
|
$h4-factor: strip-unit($h4-font-size);
|
||||||
|
|
||||||
|
@mixin clamp-with-titles($i, $bg) {
|
||||||
|
transition: height 1s;
|
||||||
|
@include clamp($i, $bg);
|
||||||
|
&.title {
|
||||||
|
@include clamp($i, $bg, 1.25);
|
||||||
|
}
|
||||||
|
&.h4 {
|
||||||
|
@include clamp($i, $bg, $h4-factor, $headings-line-height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@for $i from 1 through 15 {
|
@for $i from 1 through 15 {
|
||||||
.clamp-#{$i} {
|
.clamp-default-#{$i} {
|
||||||
transition: height 1s;
|
@include clamp-with-titles($i, $body-bg);
|
||||||
@include clamp($i);
|
}
|
||||||
&.title {
|
:host-context(.ds-hover) .clamp-default-#{$i} {
|
||||||
@include clamp($i, 1.25);
|
@include clamp-with-titles($i, $list-group-hover-bg);
|
||||||
}
|
}
|
||||||
&.h4 {
|
|
||||||
@include clamp($i, $h4-factor, $headings-line-height);
|
.clamp-primary-#{$i} {
|
||||||
}
|
@include clamp-with-titles($i, $primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
:host-context(.ds-hover) .clamp-primary-#{$i} {
|
||||||
|
@include clamp-with-titles($i, darken($primary, 10%));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -38,6 +38,8 @@ export class TruncatablePartComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
@Input() fixedHeight = false;
|
@Input() fixedHeight = false;
|
||||||
|
|
||||||
|
@Input() background = 'default';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current amount of lines shown of this part
|
* Current amount of lines shown of this part
|
||||||
*/
|
*/
|
||||||
|
@@ -764,6 +764,8 @@
|
|||||||
|
|
||||||
"collection.page.browse.recent.empty": "No items to show",
|
"collection.page.browse.recent.empty": "No items to show",
|
||||||
|
|
||||||
|
"collection.page.edit": "Edit this collection",
|
||||||
|
|
||||||
"collection.page.handle": "Permanent URI for this collection",
|
"collection.page.handle": "Permanent URI for this collection",
|
||||||
|
|
||||||
"collection.page.license": "License",
|
"collection.page.license": "License",
|
||||||
@@ -927,6 +929,8 @@
|
|||||||
|
|
||||||
"community.form.title": "Name",
|
"community.form.title": "Name",
|
||||||
|
|
||||||
|
"community.page.edit": "Edit this community",
|
||||||
|
|
||||||
"community.page.handle": "Permanent URI for this community",
|
"community.page.handle": "Permanent URI for this community",
|
||||||
|
|
||||||
"community.page.license": "License",
|
"community.page.license": "License",
|
||||||
@@ -1037,6 +1041,8 @@
|
|||||||
|
|
||||||
"dso-selector.create.collection.head": "New collection",
|
"dso-selector.create.collection.head": "New collection",
|
||||||
|
|
||||||
|
"dso-selector.create.collection.sub-level": "Create a new collection in",
|
||||||
|
|
||||||
"dso-selector.create.community.head": "New community",
|
"dso-selector.create.community.head": "New community",
|
||||||
|
|
||||||
"dso-selector.create.community.sub-level": "Create a new community in",
|
"dso-selector.create.community.sub-level": "Create a new community in",
|
||||||
@@ -1045,6 +1051,8 @@
|
|||||||
|
|
||||||
"dso-selector.create.item.head": "New item",
|
"dso-selector.create.item.head": "New item",
|
||||||
|
|
||||||
|
"dso-selector.create.item.sub-level": "Create a new item in",
|
||||||
|
|
||||||
"dso-selector.create.submission.head": "New submission",
|
"dso-selector.create.submission.head": "New submission",
|
||||||
|
|
||||||
"dso-selector.edit.collection.head": "Edit collection",
|
"dso-selector.edit.collection.head": "Edit collection",
|
||||||
@@ -1246,6 +1254,8 @@
|
|||||||
|
|
||||||
"home.description": "",
|
"home.description": "",
|
||||||
|
|
||||||
|
"home.breadcrumbs": "Home",
|
||||||
|
|
||||||
"home.title": "DSpace Angular :: Home",
|
"home.title": "DSpace Angular :: Home",
|
||||||
|
|
||||||
"home.top-level-communities.head": "Communities in DSpace",
|
"home.top-level-communities.head": "Communities in DSpace",
|
||||||
@@ -1672,6 +1682,8 @@
|
|||||||
|
|
||||||
"item.page.date": "Date",
|
"item.page.date": "Date",
|
||||||
|
|
||||||
|
"item.page.edit": "Edit this item",
|
||||||
|
|
||||||
"item.page.files": "Files",
|
"item.page.files": "Files",
|
||||||
|
|
||||||
"item.page.filesection.description": "Description:",
|
"item.page.filesection.description": "Description:",
|
||||||
@@ -1778,6 +1790,8 @@
|
|||||||
|
|
||||||
"journal.page.description": "Description",
|
"journal.page.description": "Description",
|
||||||
|
|
||||||
|
"journal.page.edit": "Edit this item",
|
||||||
|
|
||||||
"journal.page.editor": "Editor-in-Chief",
|
"journal.page.editor": "Editor-in-Chief",
|
||||||
|
|
||||||
"journal.page.issn": "ISSN",
|
"journal.page.issn": "ISSN",
|
||||||
@@ -1796,6 +1810,8 @@
|
|||||||
|
|
||||||
"journalissue.page.description": "Description",
|
"journalissue.page.description": "Description",
|
||||||
|
|
||||||
|
"journalissue.page.edit": "Edit this item",
|
||||||
|
|
||||||
"journalissue.page.issuedate": "Issue Date",
|
"journalissue.page.issuedate": "Issue Date",
|
||||||
|
|
||||||
"journalissue.page.journal-issn": "Journal ISSN",
|
"journalissue.page.journal-issn": "Journal ISSN",
|
||||||
@@ -1814,6 +1830,8 @@
|
|||||||
|
|
||||||
"journalvolume.page.description": "Description",
|
"journalvolume.page.description": "Description",
|
||||||
|
|
||||||
|
"journalvolume.page.edit": "Edit this item",
|
||||||
|
|
||||||
"journalvolume.page.issuedate": "Issue Date",
|
"journalvolume.page.issuedate": "Issue Date",
|
||||||
|
|
||||||
"journalvolume.page.titleprefix": "Journal Volume: ",
|
"journalvolume.page.titleprefix": "Journal Volume: ",
|
||||||
@@ -2176,6 +2194,8 @@
|
|||||||
|
|
||||||
"orgunit.page.description": "Description",
|
"orgunit.page.description": "Description",
|
||||||
|
|
||||||
|
"orgunit.page.edit": "Edit this item",
|
||||||
|
|
||||||
"orgunit.page.id": "ID",
|
"orgunit.page.id": "ID",
|
||||||
|
|
||||||
"orgunit.page.titleprefix": "Organizational Unit: ",
|
"orgunit.page.titleprefix": "Organizational Unit: ",
|
||||||
@@ -2194,8 +2214,12 @@
|
|||||||
|
|
||||||
"person.listelement.badge": "Person",
|
"person.listelement.badge": "Person",
|
||||||
|
|
||||||
|
"person.listelement.no-title": "No name found",
|
||||||
|
|
||||||
"person.page.birthdate": "Birth Date",
|
"person.page.birthdate": "Birth Date",
|
||||||
|
|
||||||
|
"person.page.edit": "Edit this item",
|
||||||
|
|
||||||
"person.page.email": "Email Address",
|
"person.page.email": "Email Address",
|
||||||
|
|
||||||
"person.page.firstname": "First Name",
|
"person.page.firstname": "First Name",
|
||||||
@@ -2427,6 +2451,8 @@
|
|||||||
|
|
||||||
"project.page.description": "Description",
|
"project.page.description": "Description",
|
||||||
|
|
||||||
|
"project.page.edit": "Edit this item",
|
||||||
|
|
||||||
"project.page.expectedcompletion": "Expected Completion",
|
"project.page.expectedcompletion": "Expected Completion",
|
||||||
|
|
||||||
"project.page.funder": "Funders",
|
"project.page.funder": "Funders",
|
||||||
@@ -2447,6 +2473,8 @@
|
|||||||
|
|
||||||
"publication.page.description": "Description",
|
"publication.page.description": "Description",
|
||||||
|
|
||||||
|
"publication.page.edit": "Edit this item",
|
||||||
|
|
||||||
"publication.page.journal-issn": "Journal ISSN",
|
"publication.page.journal-issn": "Journal ISSN",
|
||||||
|
|
||||||
"publication.page.journal-title": "Journal Title",
|
"publication.page.journal-title": "Journal Title",
|
||||||
|
@@ -37,3 +37,7 @@ $edit-item-metadata-field-width: 190px !default;
|
|||||||
$edit-item-language-field-width: 43px !default;
|
$edit-item-language-field-width: 43px !default;
|
||||||
|
|
||||||
$thumbnail-max-width: 175px !default;
|
$thumbnail-max-width: 175px !default;
|
||||||
|
|
||||||
|
$dso-selector-list-max-height: 475px !default;
|
||||||
|
$dso-selector-current-background-color: #eeeeee;
|
||||||
|
$dso-selector-current-background-hover-color: darken($dso-selector-current-background-color, 10%);
|
||||||
|
Reference in New Issue
Block a user