mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-13 13:03:04 +00:00
Merge branch 'main' into w2p-93963-Add_support_for_line_breaks_markdown_and_mathjax_in_metadata
# Conflicts: # src/app/shared/shared.module.ts
This commit is contained in:
@@ -14,6 +14,14 @@ import { By } from '@angular/platform-browser';
|
|||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { getCollectionEditRoute } from '../../../../../collection-page/collection-page-routing-paths';
|
import { getCollectionEditRoute } from '../../../../../collection-page/collection-page-routing-paths';
|
||||||
import { LinkService } from '../../../../../core/cache/builders/link.service';
|
import { LinkService } from '../../../../../core/cache/builders/link.service';
|
||||||
|
import { AuthService } from '../../../../../core/auth/auth.service';
|
||||||
|
import { AuthServiceStub } from '../../../../../shared/testing/auth-service.stub';
|
||||||
|
import { FileService } from '../../../../../core/shared/file.service';
|
||||||
|
import { FileServiceStub } from '../../../../../shared/testing/file-service.stub';
|
||||||
|
import { AuthorizationDataService } from '../../../../../core/data/feature-authorization/authorization-data.service';
|
||||||
|
import { AuthorizationDataServiceStub } from '../../../../../shared/testing/authorization-service.stub';
|
||||||
|
import { ThemeService } from '../../../../../shared/theme-support/theme.service';
|
||||||
|
import { getMockThemeService } from '../../../../../shared/mocks/theme-service.mock';
|
||||||
|
|
||||||
describe('CollectionAdminSearchResultGridElementComponent', () => {
|
describe('CollectionAdminSearchResultGridElementComponent', () => {
|
||||||
let component: CollectionAdminSearchResultGridElementComponent;
|
let component: CollectionAdminSearchResultGridElementComponent;
|
||||||
@@ -45,7 +53,11 @@ describe('CollectionAdminSearchResultGridElementComponent', () => {
|
|||||||
providers: [
|
providers: [
|
||||||
{ provide: TruncatableService, useValue: mockTruncatableService },
|
{ provide: TruncatableService, useValue: mockTruncatableService },
|
||||||
{ provide: BitstreamDataService, useValue: {} },
|
{ provide: BitstreamDataService, useValue: {} },
|
||||||
{ provide: LinkService, useValue: linkService }
|
{ provide: LinkService, useValue: linkService },
|
||||||
|
{ provide: AuthService, useClass: AuthServiceStub },
|
||||||
|
{ provide: FileService, useClass: FileServiceStub },
|
||||||
|
{ provide: AuthorizationDataService, useClass: AuthorizationDataServiceStub },
|
||||||
|
{ provide: ThemeService, useValue: getMockThemeService() },
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
@@ -16,6 +16,14 @@ import { CommunitySearchResult } from '../../../../../shared/object-collection/s
|
|||||||
import { Community } from '../../../../../core/shared/community.model';
|
import { Community } from '../../../../../core/shared/community.model';
|
||||||
import { getCommunityEditRoute } from '../../../../../community-page/community-page-routing-paths';
|
import { getCommunityEditRoute } from '../../../../../community-page/community-page-routing-paths';
|
||||||
import { LinkService } from '../../../../../core/cache/builders/link.service';
|
import { LinkService } from '../../../../../core/cache/builders/link.service';
|
||||||
|
import { AuthService } from '../../../../../core/auth/auth.service';
|
||||||
|
import { AuthServiceStub } from '../../../../../shared/testing/auth-service.stub';
|
||||||
|
import { FileService } from '../../../../../core/shared/file.service';
|
||||||
|
import { FileServiceStub } from '../../../../../shared/testing/file-service.stub';
|
||||||
|
import { AuthorizationDataService } from '../../../../../core/data/feature-authorization/authorization-data.service';
|
||||||
|
import { AuthorizationDataServiceStub } from '../../../../../shared/testing/authorization-service.stub';
|
||||||
|
import { ThemeService } from '../../../../../shared/theme-support/theme.service';
|
||||||
|
import { getMockThemeService } from '../../../../../shared/mocks/theme-service.mock';
|
||||||
|
|
||||||
describe('CommunityAdminSearchResultGridElementComponent', () => {
|
describe('CommunityAdminSearchResultGridElementComponent', () => {
|
||||||
let component: CommunityAdminSearchResultGridElementComponent;
|
let component: CommunityAdminSearchResultGridElementComponent;
|
||||||
@@ -47,7 +55,11 @@ describe('CommunityAdminSearchResultGridElementComponent', () => {
|
|||||||
providers: [
|
providers: [
|
||||||
{ provide: TruncatableService, useValue: mockTruncatableService },
|
{ provide: TruncatableService, useValue: mockTruncatableService },
|
||||||
{ provide: BitstreamDataService, useValue: {} },
|
{ provide: BitstreamDataService, useValue: {} },
|
||||||
{ provide: LinkService, useValue: linkService }
|
{ provide: LinkService, useValue: linkService },
|
||||||
|
{ provide: AuthService, useClass: AuthServiceStub },
|
||||||
|
{ provide: FileService, useClass: FileServiceStub },
|
||||||
|
{ provide: AuthorizationDataService, useClass: AuthorizationDataServiceStub },
|
||||||
|
{ provide: ThemeService, useValue: getMockThemeService() },
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
})
|
})
|
||||||
|
@@ -20,6 +20,12 @@ import { getMockThemeService } from '../../../../../shared/mocks/theme-service.m
|
|||||||
import { ThemeService } from '../../../../../shared/theme-support/theme.service';
|
import { ThemeService } from '../../../../../shared/theme-support/theme.service';
|
||||||
import { AccessStatusDataService } from '../../../../../core/data/access-status-data.service';
|
import { AccessStatusDataService } from '../../../../../core/data/access-status-data.service';
|
||||||
import { AccessStatusObject } from '../../../../../shared/object-list/access-status-badge/access-status.model';
|
import { AccessStatusObject } from '../../../../../shared/object-list/access-status-badge/access-status.model';
|
||||||
|
import { AuthService } from '../../../../../core/auth/auth.service';
|
||||||
|
import { AuthServiceStub } from '../../../../../shared/testing/auth-service.stub';
|
||||||
|
import { FileService } from '../../../../../core/shared/file.service';
|
||||||
|
import { FileServiceStub } from '../../../../../shared/testing/file-service.stub';
|
||||||
|
import { AuthorizationDataService } from '../../../../../core/data/feature-authorization/authorization-data.service';
|
||||||
|
import { AuthorizationDataServiceStub } from '../../../../../shared/testing/authorization-service.stub';
|
||||||
|
|
||||||
describe('ItemAdminSearchResultGridElementComponent', () => {
|
describe('ItemAdminSearchResultGridElementComponent', () => {
|
||||||
let component: ItemAdminSearchResultGridElementComponent;
|
let component: ItemAdminSearchResultGridElementComponent;
|
||||||
@@ -64,6 +70,9 @@ describe('ItemAdminSearchResultGridElementComponent', () => {
|
|||||||
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
|
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
|
||||||
{ provide: ThemeService, useValue: mockThemeService },
|
{ provide: ThemeService, useValue: mockThemeService },
|
||||||
{ provide: AccessStatusDataService, useValue: mockAccessStatusDataService },
|
{ provide: AccessStatusDataService, useValue: mockAccessStatusDataService },
|
||||||
|
{ provide: AuthService, useClass: AuthServiceStub },
|
||||||
|
{ provide: FileService, useClass: FileServiceStub },
|
||||||
|
{ provide: AuthorizationDataService, useClass: AuthorizationDataServiceStub },
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
})
|
})
|
||||||
|
@@ -31,6 +31,8 @@ export class DSONameService {
|
|||||||
const givenName = dso.firstMetadataValue('person.givenName');
|
const givenName = dso.firstMetadataValue('person.givenName');
|
||||||
if (isEmpty(familyName) && isEmpty(givenName)) {
|
if (isEmpty(familyName) && isEmpty(givenName)) {
|
||||||
return dso.firstMetadataValue('dc.title') || dso.name;
|
return dso.firstMetadataValue('dc.title') || dso.name;
|
||||||
|
} else if (isEmpty(familyName) || isEmpty(givenName)) {
|
||||||
|
return familyName || givenName;
|
||||||
} else {
|
} else {
|
||||||
return `${familyName}, ${givenName}`;
|
return `${familyName}, ${givenName}`;
|
||||||
}
|
}
|
||||||
@@ -55,11 +57,14 @@ export class DSONameService {
|
|||||||
.filter((type) => typeof type === 'string')
|
.filter((type) => typeof type === 'string')
|
||||||
.find((type: string) => Object.keys(this.factories).includes(type)) as string;
|
.find((type: string) => Object.keys(this.factories).includes(type)) as string;
|
||||||
|
|
||||||
|
let name;
|
||||||
if (hasValue(match)) {
|
if (hasValue(match)) {
|
||||||
return this.factories[match](dso);
|
name = this.factories[match](dso);
|
||||||
} else {
|
|
||||||
return this.factories.Default(dso);
|
|
||||||
}
|
}
|
||||||
|
if (isEmpty(name)) {
|
||||||
|
name = this.factories.Default(dso);
|
||||||
|
}
|
||||||
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -21,7 +21,7 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
||||||
<h4 class="card-title" [innerHTML]="firstMetadataValue('dc.title')"></h4>
|
<h4 class="card-title" [innerHTML]="dsoTitle"></h4>
|
||||||
</ds-truncatable-part>
|
</ds-truncatable-part>
|
||||||
<p *ngIf="dso.hasMetadata('creativework.datePublished')"
|
<p *ngIf="dso.hasMetadata('creativework.datePublished')"
|
||||||
class="item-date card-text text-muted">
|
class="item-date card-text text-muted">
|
||||||
|
@@ -21,7 +21,7 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
||||||
<h4 class="card-title" [innerHTML]="dso.firstMetadataValue('dc.title')"></h4>
|
<h4 class="card-title" [innerHTML]="dsoTitle"></h4>
|
||||||
</ds-truncatable-part>
|
</ds-truncatable-part>
|
||||||
<p *ngIf="dso.hasMetadata('creativework.datePublished')"
|
<p *ngIf="dso.hasMetadata('creativework.datePublished')"
|
||||||
class="item-date card-text text-muted">
|
class="item-date card-text text-muted">
|
||||||
|
@@ -21,7 +21,7 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
||||||
<h4 class="card-title" [innerHTML]="firstMetadataValue('dc.title')"></h4>
|
<h4 class="card-title" [innerHTML]="dsoTitle"></h4>
|
||||||
</ds-truncatable-part>
|
</ds-truncatable-part>
|
||||||
<p *ngIf="dso.hasMetadata('creativework.editor')"
|
<p *ngIf="dso.hasMetadata('creativework.editor')"
|
||||||
class="item-publisher card-text text-muted">
|
class="item-publisher card-text text-muted">
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
<div class="d-flex flex-row">
|
<div class="d-flex flex-row">
|
||||||
<h2 class="item-page-title-field mr-auto">
|
<ds-item-page-title-field [item]="object" class="mr-auto">
|
||||||
{{'journalissue.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
</ds-item-page-title-field>
|
||||||
</h2>
|
|
||||||
<div class="pl-2 space-children-mr">
|
<div class="pl-2 space-children-mr">
|
||||||
<ds-dso-page-version-button (newVersionEvent)="onCreateNewVersion()" [dso]="object"
|
<ds-dso-page-version-button (newVersionEvent)="onCreateNewVersion()" [dso]="object"
|
||||||
[tooltipMsgCreate]="'item.page.version.create'"
|
[tooltipMsgCreate]="'item.page.version.create'"
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
<div class="d-flex flex-row">
|
<div class="d-flex flex-row">
|
||||||
<h2 class="item-page-title-field mr-auto">
|
<ds-item-page-title-field [item]="object" class="mr-auto">
|
||||||
{{'journalvolume.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
</ds-item-page-title-field>
|
||||||
</h2>
|
|
||||||
<div class="pl-2 space-children-mr">
|
<div class="pl-2 space-children-mr">
|
||||||
<ds-dso-page-version-button (newVersionEvent)="onCreateNewVersion()" [dso]="object"
|
<ds-dso-page-version-button (newVersionEvent)="onCreateNewVersion()" [dso]="object"
|
||||||
[tooltipMsgCreate]="'item.page.version.create'"
|
[tooltipMsgCreate]="'item.page.version.create'"
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
<div class="d-flex flex-row">
|
<div class="d-flex flex-row">
|
||||||
<h2 class="item-page-title-field mr-auto">
|
<ds-item-page-title-field [item]="object" class="mr-auto">
|
||||||
{{'journal.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
</ds-item-page-title-field>
|
||||||
</h2>
|
|
||||||
<div class="pl-2 space-children-mr">
|
<div class="pl-2 space-children-mr">
|
||||||
<ds-dso-page-version-button (newVersionEvent)="onCreateNewVersion()" [dso]="object"
|
<ds-dso-page-version-button (newVersionEvent)="onCreateNewVersion()" [dso]="object"
|
||||||
[tooltipMsgCreate]="'item.page.version.create'"
|
[tooltipMsgCreate]="'item.page.version.create'"
|
||||||
|
@@ -21,7 +21,7 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
||||||
<h4 class="card-title" [innerHTML]="firstMetadataValue('organization.legalName')"></h4>
|
<h4 class="card-title" [innerHTML]="dsoTitle"></h4>
|
||||||
</ds-truncatable-part>
|
</ds-truncatable-part>
|
||||||
<p *ngIf="dso.hasMetadata('organization.foundingDate')"
|
<p *ngIf="dso.hasMetadata('organization.foundingDate')"
|
||||||
class="item-date card-text text-muted">
|
class="item-date card-text text-muted">
|
||||||
|
@@ -21,8 +21,7 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
||||||
<h4 class="card-title"
|
<h4 class="card-title" [innerHTML]="dsoTitle"></h4>
|
||||||
[innerHTML]="firstMetadataValue('person.familyName') + ', ' + firstMetadataValue('person.givenName')"></h4>
|
|
||||||
</ds-truncatable-part>
|
</ds-truncatable-part>
|
||||||
<p *ngIf="dso.hasMetadata('person.email')" class="item-email card-text text-muted">
|
<p *ngIf="dso.hasMetadata('person.email')" class="item-email card-text text-muted">
|
||||||
<ds-truncatable-part [id]="dso.id" [minLines]="1">
|
<ds-truncatable-part [id]="dso.id" [minLines]="1">
|
||||||
|
@@ -21,7 +21,7 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
||||||
<h4 class="card-title" [innerHTML]="firstMetadataValue('dc.title')"></h4>
|
<h4 class="card-title" [innerHTML]="dsoTitle"></h4>
|
||||||
</ds-truncatable-part>
|
</ds-truncatable-part>
|
||||||
<p *ngIf="dso.hasMetadata('dc.description')" class="item-description card-text text-muted">
|
<p *ngIf="dso.hasMetadata('dc.description')" class="item-description card-text text-muted">
|
||||||
<ds-truncatable-part [id]="dso.id" [minLines]="3">
|
<ds-truncatable-part [id]="dso.id" [minLines]="3">
|
||||||
|
@@ -16,10 +16,10 @@
|
|||||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
|
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
[routerLink]="[itemPageRoute]" class="lead"
|
[routerLink]="[itemPageRoute]" class="lead"
|
||||||
[innerHTML]="firstMetadataValue('organization.legalName')"></a>
|
[innerHTML]="dsoTitle"></a>
|
||||||
<span *ngIf="linkType == linkTypes.None"
|
<span *ngIf="linkType == linkTypes.None"
|
||||||
class="lead"
|
class="lead"
|
||||||
[innerHTML]="firstMetadataValue('organization.legalName')"></span>
|
[innerHTML]="dsoTitle"></span>
|
||||||
<span class="text-muted">
|
<span class="text-muted">
|
||||||
<span *ngIf="dso.allMetadata(['dc.description']).length > 0"
|
<span *ngIf="dso.allMetadata(['dc.description']).length > 0"
|
||||||
class="item-list-org-unit-description">
|
class="item-list-org-unit-description">
|
||||||
|
@@ -16,10 +16,10 @@
|
|||||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
|
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
[routerLink]="[itemPageRoute]" class="lead"
|
[routerLink]="[itemPageRoute]" class="lead"
|
||||||
[innerHTML]="name"></a>
|
[innerHTML]="dsoTitle"></a>
|
||||||
<span *ngIf="linkType == linkTypes.None"
|
<span *ngIf="linkType == linkTypes.None"
|
||||||
class="lead"
|
class="lead"
|
||||||
[innerHTML]="name"></span>
|
[innerHTML]="dsoTitle"></span>
|
||||||
<span class="text-muted">
|
<span class="text-muted">
|
||||||
<ds-truncatable-part [id]="dso.id" [minLines]="1">
|
<ds-truncatable-part [id]="dso.id" [minLines]="1">
|
||||||
<span *ngIf="dso.allMetadata(['person.jobTitle']).length > 0"
|
<span *ngIf="dso.allMetadata(['person.jobTitle']).length > 0"
|
||||||
|
@@ -21,9 +21,11 @@ import { APP_CONFIG, AppConfig } from '../../../../../../config/app-config.inter
|
|||||||
*/
|
*/
|
||||||
export class PersonSearchResultListElementComponent extends ItemSearchResultListElementComponent {
|
export class PersonSearchResultListElementComponent extends ItemSearchResultListElementComponent {
|
||||||
|
|
||||||
public constructor(protected truncatableService: TruncatableService,
|
public constructor(
|
||||||
|
protected truncatableService: TruncatableService,
|
||||||
protected dsoNameService: DSONameService,
|
protected dsoNameService: DSONameService,
|
||||||
@Inject(APP_CONFIG) protected appConfig: AppConfig ) {
|
@Inject(APP_CONFIG) protected appConfig: AppConfig
|
||||||
|
) {
|
||||||
super(truncatableService, dsoNameService, appConfig);
|
super(truncatableService, dsoNameService, appConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,11 +38,4 @@ export class PersonSearchResultListElementComponent extends ItemSearchResultList
|
|||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
this.showThumbnails = this.appConfig.browseBy.showThumbnails;
|
this.showThumbnails = this.appConfig.browseBy.showThumbnails;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the person name
|
|
||||||
*/
|
|
||||||
get name() {
|
|
||||||
return this.dsoNameService.getName(this.dso);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -8,6 +8,11 @@ const object = Object.assign(new ItemSearchResult(), {
|
|||||||
indexableObject: Object.assign(new Item(), {
|
indexableObject: Object.assign(new Item(), {
|
||||||
id: 'test-item',
|
id: 'test-item',
|
||||||
metadata: {
|
metadata: {
|
||||||
|
'dspace.entity.type': [
|
||||||
|
{
|
||||||
|
value: 'OrgUnit'
|
||||||
|
}
|
||||||
|
],
|
||||||
'organization.legalName': [
|
'organization.legalName': [
|
||||||
{
|
{
|
||||||
value: 'title'
|
value: 'title'
|
||||||
|
@@ -17,12 +17,6 @@ import { Item } from '../../../../../core/shared/item.model';
|
|||||||
* a sidebar search modal
|
* a sidebar search modal
|
||||||
*/
|
*/
|
||||||
export class OrgUnitSidebarSearchListElementComponent extends SidebarSearchListElementComponent<ItemSearchResult, Item> {
|
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
|
* Get the description of the Org Unit by returning its dc.description
|
||||||
|
@@ -3,12 +3,16 @@ import { Collection } from '../../../../../core/shared/collection.model';
|
|||||||
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.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 { 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 { PersonSidebarSearchListElementComponent } from './person-sidebar-search-list-element.component';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
|
||||||
|
|
||||||
const object = Object.assign(new ItemSearchResult(), {
|
const object = Object.assign(new ItemSearchResult(), {
|
||||||
indexableObject: Object.assign(new Item(), {
|
indexableObject: Object.assign(new Item(), {
|
||||||
id: 'test-item',
|
id: 'test-item',
|
||||||
metadata: {
|
metadata: {
|
||||||
|
'dspace.entity.type': [
|
||||||
|
{
|
||||||
|
value: 'Person',
|
||||||
|
}
|
||||||
|
],
|
||||||
'person.familyName': [
|
'person.familyName': [
|
||||||
{
|
{
|
||||||
value: 'family name'
|
value: 'family name'
|
||||||
@@ -40,6 +44,5 @@ const parent = Object.assign(new Collection(), {
|
|||||||
|
|
||||||
describe('PersonSidebarSearchListElementComponent',
|
describe('PersonSidebarSearchListElementComponent',
|
||||||
createSidebarSearchListElementTests(PersonSidebarSearchListElementComponent, object, parent, 'parent title', 'family name, given name', 'job title', [
|
createSidebarSearchListElementTests(PersonSidebarSearchListElementComponent, object, parent, 'parent title', 'family name, given name', 'job title', [
|
||||||
{ provide: TranslateService, useValue: jasmine.createSpyObj('translate', { instant: '' }) }
|
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
@@ -30,25 +30,6 @@ export class PersonSidebarSearchListElementComponent extends SidebarSearchListEl
|
|||||||
super(truncatableService, linkService, dsoNameService);
|
super(truncatableService, linkService, dsoNameService);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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)
|
* Get the description of the Person by returning its job title(s)
|
||||||
*/
|
*/
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
<div class="d-flex flex-row">
|
<div class="d-flex flex-row">
|
||||||
<h2 class="item-page-title-field mr-auto">
|
<ds-item-page-title-field [item]="object" class="mr-auto">
|
||||||
{{'orgunit.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['organization.legalName'])"></ds-metadata-values>
|
</ds-item-page-title-field>
|
||||||
</h2>
|
|
||||||
<div class="pl-2 space-children-mr">
|
<div class="pl-2 space-children-mr">
|
||||||
<ds-dso-page-version-button (newVersionEvent)="onCreateNewVersion()" [dso]="object"
|
<ds-dso-page-version-button (newVersionEvent)="onCreateNewVersion()" [dso]="object"
|
||||||
[tooltipMsgCreate]="'item.page.version.create'"
|
[tooltipMsgCreate]="'item.page.version.create'"
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
<div class="d-flex flex-row">
|
<div class="d-flex flex-row">
|
||||||
<h2 class="item-page-title-field mr-auto">
|
<ds-item-page-title-field class="mr-auto" [item]="object">
|
||||||
{{'person.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="getTitleMetadataValues()" [separator]="', '"></ds-metadata-values>
|
</ds-item-page-title-field>
|
||||||
</h2>
|
|
||||||
<div class="pl-2 space-children-mr">
|
<div class="pl-2 space-children-mr">
|
||||||
<ds-dso-page-orcid-button [pageRoute]="itemPageRoute" [dso]="object" class="mr-2"></ds-dso-page-orcid-button>
|
<ds-dso-page-orcid-button [pageRoute]="itemPageRoute" [dso]="object" class="mr-2"></ds-dso-page-orcid-button>
|
||||||
<ds-dso-page-version-button (newVersionEvent)="onCreateNewVersion()" [dso]="object"
|
<ds-dso-page-version-button (newVersionEvent)="onCreateNewVersion()" [dso]="object"
|
||||||
|
@@ -2,7 +2,6 @@ import { Component } from '@angular/core';
|
|||||||
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||||
import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
||||||
import { VersionedItemComponent } from '../../../../item-page/simple/item-types/versioned-item/versioned-item.component';
|
import { VersionedItemComponent } from '../../../../item-page/simple/item-types/versioned-item/versioned-item.component';
|
||||||
import { MetadataValue } from '../../../../core/shared/metadata.models';
|
|
||||||
|
|
||||||
@listableObjectComponent('Person', ViewMode.StandalonePage)
|
@listableObjectComponent('Person', ViewMode.StandalonePage)
|
||||||
@Component({
|
@Component({
|
||||||
@@ -14,25 +13,4 @@ import { MetadataValue } from '../../../../core/shared/metadata.models';
|
|||||||
* The component for displaying metadata and relations of an item of the type Person
|
* The component for displaying metadata and relations of an item of the type Person
|
||||||
*/
|
*/
|
||||||
export class PersonComponent extends VersionedItemComponent {
|
export class PersonComponent extends VersionedItemComponent {
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the metadata values to be used for the page title.
|
|
||||||
*/
|
|
||||||
getTitleMetadataValues(): MetadataValue[] {
|
|
||||||
const metadataValues = [];
|
|
||||||
const familyName = this.object?.firstMetadata('person.familyName');
|
|
||||||
const givenName = this.object?.firstMetadata('person.givenName');
|
|
||||||
const title = this.object?.firstMetadata('dc.title');
|
|
||||||
if (familyName) {
|
|
||||||
metadataValues.push(familyName);
|
|
||||||
}
|
|
||||||
if (givenName) {
|
|
||||||
metadataValues.push(givenName);
|
|
||||||
}
|
|
||||||
if (metadataValues.length === 0 && title) {
|
|
||||||
metadataValues.push(title);
|
|
||||||
}
|
|
||||||
return metadataValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
<div class="d-flex flex-row">
|
<div class="d-flex flex-row">
|
||||||
<h2 class="item-page-title-field mr-auto">
|
<ds-item-page-title-field [item]="object" class="mr-auto">
|
||||||
{{'project.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
</ds-item-page-title-field>
|
||||||
</h2>
|
|
||||||
<div class="pl-2 space-children-mr">
|
<div class="pl-2 space-children-mr">
|
||||||
<ds-dso-page-version-button (newVersionEvent)="onCreateNewVersion()" [dso]="object"
|
<ds-dso-page-version-button (newVersionEvent)="onCreateNewVersion()" [dso]="object"
|
||||||
[tooltipMsgCreate]="'item.page.version.create'"
|
[tooltipMsgCreate]="'item.page.version.create'"
|
||||||
|
@@ -6,9 +6,7 @@
|
|||||||
<ds-org-unit-input-suggestions *ngIf="useNameVariants" [suggestions]="allSuggestions" [(ngModel)]="selectedName" (clickSuggestion)="select($event)"
|
<ds-org-unit-input-suggestions *ngIf="useNameVariants" [suggestions]="allSuggestions" [(ngModel)]="selectedName" (clickSuggestion)="select($event)"
|
||||||
(submitSuggestion)="selectCustom($event)"></ds-org-unit-input-suggestions>
|
(submitSuggestion)="selectCustom($event)"></ds-org-unit-input-suggestions>
|
||||||
|
|
||||||
<div *ngIf="!useNameVariants"
|
<div *ngIf="!useNameVariants" class="lead" [innerHTML]="dsoTitle"></div>
|
||||||
class="lead"
|
|
||||||
[innerHTML]="firstMetadataValue('organization.legalName')"></div>
|
|
||||||
|
|
||||||
<span class="text-muted">
|
<span class="text-muted">
|
||||||
<span *ngIf="dso.allMetadata('organization.address.addressLocality').length > 0"
|
<span *ngIf="dso.allMetadata('organization.address.addressLocality').length > 0"
|
||||||
|
@@ -61,7 +61,7 @@ export class OrgUnitSearchResultListSubmissionElementComponent extends SearchRes
|
|||||||
this.useNameVariants = this.context === Context.EntitySearchModalWithNameVariants;
|
this.useNameVariants = this.context === Context.EntitySearchModalWithNameVariants;
|
||||||
|
|
||||||
if (this.useNameVariants) {
|
if (this.useNameVariants) {
|
||||||
const defaultValue = this.firstMetadataValue('organization.legalName');
|
const defaultValue = this.dsoTitle;
|
||||||
const alternatives = this.allMetadataValues(this.alternativeField);
|
const alternatives = this.allMetadataValues(this.alternativeField);
|
||||||
this.allSuggestions = [defaultValue, ...alternatives];
|
this.allSuggestions = [defaultValue, ...alternatives];
|
||||||
|
|
||||||
|
@@ -55,7 +55,7 @@ export class PersonSearchResultListSubmissionElementComponent extends SearchResu
|
|||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
const defaultValue = this.firstMetadataValue('person.familyName') + ', ' + this.firstMetadataValue('person.givenName');
|
const defaultValue = this.dsoTitle;
|
||||||
const alternatives = this.allMetadataValues(this.alternativeField);
|
const alternatives = this.allMetadataValues(this.alternativeField);
|
||||||
this.allSuggestions = [defaultValue, ...alternatives];
|
this.allSuggestions = [defaultValue, ...alternatives];
|
||||||
|
|
||||||
|
@@ -16,9 +16,6 @@ import {
|
|||||||
ItemPageAbstractFieldComponent
|
ItemPageAbstractFieldComponent
|
||||||
} from './simple/field-components/specific-field/abstract/item-page-abstract-field.component';
|
} from './simple/field-components/specific-field/abstract/item-page-abstract-field.component';
|
||||||
import { ItemPageUriFieldComponent } from './simple/field-components/specific-field/uri/item-page-uri-field.component';
|
import { ItemPageUriFieldComponent } from './simple/field-components/specific-field/uri/item-page-uri-field.component';
|
||||||
import {
|
|
||||||
ItemPageTitleFieldComponent
|
|
||||||
} from './simple/field-components/specific-field/title/item-page-title-field.component';
|
|
||||||
import { ItemPageFieldComponent } from './simple/field-components/specific-field/item-page-field.component';
|
import { ItemPageFieldComponent } from './simple/field-components/specific-field/item-page-field.component';
|
||||||
import { CollectionsComponent } from './field-components/collections/collections.component';
|
import { CollectionsComponent } from './field-components/collections/collections.component';
|
||||||
import { FullItemPageComponent } from './full/full-item-page.component';
|
import { FullItemPageComponent } from './full/full-item-page.component';
|
||||||
@@ -68,7 +65,6 @@ const DECLARATIONS = [
|
|||||||
ItemPageDateFieldComponent,
|
ItemPageDateFieldComponent,
|
||||||
ItemPageAbstractFieldComponent,
|
ItemPageAbstractFieldComponent,
|
||||||
ItemPageUriFieldComponent,
|
ItemPageUriFieldComponent,
|
||||||
ItemPageTitleFieldComponent,
|
|
||||||
ItemPageFieldComponent,
|
ItemPageFieldComponent,
|
||||||
CollectionsComponent,
|
CollectionsComponent,
|
||||||
FullFileSectionComponent,
|
FullFileSectionComponent,
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<h2 class="item-page-title-field">
|
<h2 class="item-page-title-field">
|
||||||
<div *ngIf="item.firstMetadataValue('dspace.entity.type') as type">
|
<div *ngIf="item.firstMetadataValue('dspace.entity.type') as type" class="d-inline">
|
||||||
{{ type.toLowerCase() + '.page.titleprefix' | translate }}
|
{{ type.toLowerCase() + '.page.titleprefix' | translate }}
|
||||||
</div>
|
</div>
|
||||||
<ds-metadata-values [mdValues]="item?.allMetadata(fields)"></ds-metadata-values>
|
<span class="dont-break-out">{{ dsoNameService.getName(item) }}</span>
|
||||||
</h2>
|
</h2>
|
||||||
|
@@ -1,34 +1,25 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
|
|
||||||
import { Item } from '../../../../../core/shared/item.model';
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
import { ItemPageFieldComponent } from '../item-page-field.component';
|
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-item-page-title-field',
|
selector: 'ds-item-page-title-field',
|
||||||
templateUrl: './item-page-title-field.component.html'
|
templateUrl: './item-page-title-field.component.html'
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* This component is used for displaying the title (dc.title) of an item
|
* This component is used for displaying the title (defined by the {@link DSONameService}) of an item
|
||||||
*/
|
*/
|
||||||
export class ItemPageTitleFieldComponent extends ItemPageFieldComponent {
|
export class ItemPageTitleFieldComponent {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The item to display metadata for
|
* The item to display metadata for
|
||||||
*/
|
*/
|
||||||
@Input() item: Item;
|
@Input() item: Item;
|
||||||
|
|
||||||
/**
|
constructor(
|
||||||
* Separator string between multiple values of the metadata fields defined
|
public dsoNameService: DSONameService,
|
||||||
* @type {string}
|
) {
|
||||||
*/
|
}
|
||||||
separator: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fields (schema.element.qualifier) used to render their values.
|
|
||||||
* In this component, we want to display values for metadata 'dc.title'
|
|
||||||
*/
|
|
||||||
fields: string[] = [
|
|
||||||
'dc.title'
|
|
||||||
];
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -8,9 +8,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-row">
|
<div class="d-flex flex-row">
|
||||||
<h2 class="item-page-title-field mr-auto">
|
<ds-item-page-title-field [item]="object" class="mr-auto">
|
||||||
{{'publication.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
</ds-item-page-title-field>
|
||||||
</h2>
|
|
||||||
<div class="pl-2 space-children-mr">
|
<div class="pl-2 space-children-mr">
|
||||||
<ds-dso-page-version-button (newVersionEvent)="onCreateNewVersion()" [dso]="object"
|
<ds-dso-page-version-button (newVersionEvent)="onCreateNewVersion()" [dso]="object"
|
||||||
[tooltipMsgCreate]="'item.page.version.create'"
|
[tooltipMsgCreate]="'item.page.version.create'"
|
||||||
|
@@ -8,9 +8,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-row">
|
<div class="d-flex flex-row">
|
||||||
<h2 class="item-page-title-field mr-auto">
|
<ds-item-page-title-field [item]="object" class="mr-auto">
|
||||||
<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
</ds-item-page-title-field>
|
||||||
</h2>
|
|
||||||
<div class="pl-2 space-children-mr">
|
<div class="pl-2 space-children-mr">
|
||||||
<ds-dso-page-version-button (newVersionEvent)="onCreateNewVersion()" [dso]="object"
|
<ds-dso-page-version-button (newVersionEvent)="onCreateNewVersion()" [dso]="object"
|
||||||
[tooltipMsgCreate]="'item.page.version.create'"
|
[tooltipMsgCreate]="'item.page.version.create'"
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<div>
|
<div *ngIf="!spinner">
|
||||||
<label *ngIf="showMessage && message">{{ message }}</label>
|
<label *ngIf="showMessage && message">{{ message }}</label>
|
||||||
<div class="loader">
|
<div class="loader">
|
||||||
<span class="l-1"></span>
|
<span class="l-1"></span>
|
||||||
@@ -13,3 +13,6 @@
|
|||||||
<span class="l-10"></span>
|
<span class="l-10"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div *ngIf='spinner' class="spinner spinner-border" role="status">
|
||||||
|
<span class="sr-only">{{ message }}</span>
|
||||||
|
</div>
|
||||||
|
@@ -71,3 +71,7 @@ span.l-10 {-webkit-animation-delay: 0s;animation-delay: 0s;-ms-animation-delay:
|
|||||||
50% {-ms-transform: translateX(30px); opacity: 0;}
|
50% {-ms-transform: translateX(30px); opacity: 0;}
|
||||||
100% {opacity: 0;}
|
100% {opacity: 0;}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
color: var(--bs-gray-600);
|
||||||
|
}
|
||||||
|
@@ -15,6 +15,11 @@ export class LoadingComponent implements OnDestroy, OnInit {
|
|||||||
@Input() message: string;
|
@Input() message: string;
|
||||||
@Input() showMessage = true;
|
@Input() showMessage = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a more compact spinner animation instead of the default one
|
||||||
|
*/
|
||||||
|
@Input() spinner = false;
|
||||||
|
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
|
|
||||||
constructor(private translate: TranslateService) {
|
constructor(private translate: TranslateService) {
|
||||||
|
@@ -15,8 +15,9 @@ export class ThemedLoadingComponent extends ThemedComponent<LoadingComponent> {
|
|||||||
|
|
||||||
@Input() message: string;
|
@Input() message: string;
|
||||||
@Input() showMessage = true;
|
@Input() showMessage = true;
|
||||||
|
@Input() spinner = false;
|
||||||
|
|
||||||
protected inAndOutputNames: (keyof LoadingComponent & keyof this)[] = ['message', 'showMessage'];
|
protected inAndOutputNames: (keyof LoadingComponent & keyof this)[] = ['message', 'showMessage', 'spinner'];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected resolver: ComponentFactoryResolver,
|
protected resolver: ComponentFactoryResolver,
|
||||||
|
@@ -3,10 +3,8 @@
|
|||||||
<ds-mydspace-item-status [status]="status"></ds-mydspace-item-status>
|
<ds-mydspace-item-status [status]="status"></ds-mydspace-item-status>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<div *ngIf="item">
|
<div *ngIf="item">
|
||||||
<h2 class="item-page-title-field">
|
<ds-item-page-title-field [item]="item">
|
||||||
<ds-metadata-values *ngIf="item.hasMetadata('dc.title')" [mdValues]="item?.allMetadata('dc.title')"></ds-metadata-values>
|
</ds-item-page-title-field>
|
||||||
<span class="text-muted" *ngIf="!item.hasMetadata('dc.title')">{{('mydspace.results.no-title' | translate)}}</span>
|
|
||||||
</h2>
|
|
||||||
<div class="row mb-1">
|
<div class="row mb-1">
|
||||||
<div class="col-xs-12 col-md-4">
|
<div class="col-xs-12 col-md-4">
|
||||||
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
|
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
<ds-access-status-badge [item]="dso"></ds-access-status-badge>
|
<ds-access-status-badge [item]="dso"></ds-access-status-badge>
|
||||||
<ds-truncatable [id]="dso.id">
|
<ds-truncatable [id]="dso.id">
|
||||||
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
||||||
<h4 class="card-title" [innerHTML]="firstMetadataValue('dc.title')"></h4>
|
<h4 class="card-title" [innerHTML]="dsoTitle"></h4>
|
||||||
</ds-truncatable-part>
|
</ds-truncatable-part>
|
||||||
<ds-truncatable-part [id]="dso.id" [minLines]="1" *ngIf="dso.hasMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])">
|
<ds-truncatable-part [id]="dso.id" [minLines]="1" *ngIf="dso.hasMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])">
|
||||||
<p class="item-authors card-text text-muted">
|
<p class="item-authors card-text text-muted">
|
||||||
|
@@ -1,11 +1,16 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { focusShadow } from '../../../../animations/focus';
|
import { focusShadow } from '../../../../animations/focus';
|
||||||
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
||||||
import { listableObjectComponent } from '../../../../object-collection/shared/listable-object/listable-object.decorator';
|
import {
|
||||||
|
listableObjectComponent
|
||||||
|
} from '../../../../object-collection/shared/listable-object/listable-object.decorator';
|
||||||
import { SearchResultGridElementComponent } from '../../search-result-grid-element.component';
|
import { SearchResultGridElementComponent } from '../../search-result-grid-element.component';
|
||||||
import { Item } from '../../../../../core/shared/item.model';
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model';
|
import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model';
|
||||||
import { getItemPageRoute } from '../../../../../item-page/item-page-routing-paths';
|
import { getItemPageRoute } from '../../../../../item-page/item-page-routing-paths';
|
||||||
|
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { TruncatableService } from '../../../../truncatable/truncatable.service';
|
||||||
|
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
|
||||||
|
|
||||||
@listableObjectComponent('PublicationSearchResult', ViewMode.GridElement)
|
@listableObjectComponent('PublicationSearchResult', ViewMode.GridElement)
|
||||||
@listableObjectComponent(ItemSearchResult, ViewMode.GridElement)
|
@listableObjectComponent(ItemSearchResult, ViewMode.GridElement)
|
||||||
@@ -24,8 +29,19 @@ export class ItemSearchResultGridElementComponent extends SearchResultGridElemen
|
|||||||
*/
|
*/
|
||||||
itemPageRoute: string;
|
itemPageRoute: string;
|
||||||
|
|
||||||
|
dsoTitle: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected truncatableService: TruncatableService,
|
||||||
|
protected bitstreamDataService: BitstreamDataService,
|
||||||
|
private dsoNameService: DSONameService,
|
||||||
|
) {
|
||||||
|
super(truncatableService, bitstreamDataService);
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
this.itemPageRoute = getItemPageRoute(this.dso);
|
this.itemPageRoute = getItemPageRoute(this.dso);
|
||||||
|
this.dsoTitle = this.dsoNameService.getName(this.dso);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -13,8 +13,7 @@
|
|||||||
<ds-access-status-badge [item]="item" class="pl-1"></ds-access-status-badge>
|
<ds-access-status-badge [item]="item" class="pl-1"></ds-access-status-badge>
|
||||||
</div>
|
</div>
|
||||||
<ds-truncatable [id]="item.id">
|
<ds-truncatable [id]="item.id">
|
||||||
<h3 [innerHTML]="item.firstMetadataValue('dc.title') || ('mydspace.results.no-title' | translate)"
|
<h3 [innerHTML]="dsoTitle" [ngClass]="{'lead': true,'text-muted': !item.firstMetadataValue('dc.title')}"></h3>
|
||||||
[ngClass]="{'lead': true,'text-muted': !item.firstMetadataValue('dc.title')}"></h3>
|
|
||||||
<div>
|
<div>
|
||||||
<span class="text-muted">
|
<span class="text-muted">
|
||||||
<ds-truncatable-part [id]="item.id" [minLines]="1">
|
<ds-truncatable-part [id]="item.id" [minLines]="1">
|
||||||
|
@@ -2,9 +2,12 @@ import { Component, Inject, Input, OnInit } from '@angular/core';
|
|||||||
|
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
import { fadeInOut } from '../../../animations/fade';
|
import { fadeInOut } from '../../../animations/fade';
|
||||||
import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type';
|
import {
|
||||||
|
MyDspaceItemStatusType
|
||||||
|
} from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type';
|
||||||
import { SearchResult } from '../../../search/models/search-result.model';
|
import { SearchResult } from '../../../search/models/search-result.model';
|
||||||
import { APP_CONFIG, AppConfig } from '../../../../../config/app-config.interface';
|
import { APP_CONFIG, AppConfig } from '../../../../../config/app-config.interface';
|
||||||
|
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component show metadata for the given item object in the list view.
|
* This component show metadata for the given item object in the list view.
|
||||||
@@ -37,16 +40,23 @@ export class ItemListPreviewComponent implements OnInit{
|
|||||||
*/
|
*/
|
||||||
@Input() showSubmitter = false;
|
@Input() showSubmitter = false;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display thumbnails if required by configuration
|
* Display thumbnails if required by configuration
|
||||||
*/
|
*/
|
||||||
showThumbnails: boolean;
|
showThumbnails: boolean;
|
||||||
|
|
||||||
constructor(@Inject(APP_CONFIG) protected appConfig: AppConfig) {
|
dsoTitle: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||||
|
private dsoNameService: DSONameService,
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.showThumbnails = this.appConfig.browseBy.showThumbnails;
|
this.showThumbnails = this.appConfig.browseBy.showThumbnails;
|
||||||
|
this.dsoTitle = this.dsoNameService.getName(this.item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -2,10 +2,10 @@
|
|||||||
<div [ngClass]="isCurrent() ? 'text-light' : 'text-body'"
|
<div [ngClass]="isCurrent() ? 'text-light' : 'text-body'"
|
||||||
[innerHTML]="(parentTitle$ && parentTitle$ | async) ? (parentTitle$ | async) : ('home.breadcrumbs' | translate)"></div>
|
[innerHTML]="(parentTitle$ && parentTitle$ | async) ? (parentTitle$ | async) : ('home.breadcrumbs' | translate)"></div>
|
||||||
</ds-truncatable-part>
|
</ds-truncatable-part>
|
||||||
<ds-truncatable-part *ngIf="title" [maxLines]="1" [background]="isCurrent() ? 'primary' : 'default'" [showToggle]="false">
|
<ds-truncatable-part [maxLines]="1" [background]="isCurrent() ? 'primary' : 'default'" [showToggle]="false">
|
||||||
<div class="font-weight-bold"
|
<div class="font-weight-bold"
|
||||||
[ngClass]="isCurrent() ? 'text-light' : 'text-primary'"
|
[ngClass]="isCurrent() ? 'text-light' : 'text-primary'"
|
||||||
[innerHTML]="title"></div>
|
[innerHTML]="dsoTitle"></div>
|
||||||
</ds-truncatable-part>
|
</ds-truncatable-part>
|
||||||
<ds-truncatable-part *ngIf="description" [maxLines]="1" [background]="isCurrent() ? 'primary' : 'default'" [showToggle]="false">
|
<ds-truncatable-part *ngIf="description" [maxLines]="1" [background]="isCurrent() ? 'primary' : 'default'" [showToggle]="false">
|
||||||
<div class="text-secondary"
|
<div class="text-secondary"
|
||||||
|
@@ -40,7 +40,7 @@ export function createSidebarSearchListElementTests(
|
|||||||
providers: [
|
providers: [
|
||||||
{ provide: TruncatableService, useValue: {} },
|
{ provide: TruncatableService, useValue: {} },
|
||||||
{ provide: LinkService, useValue: linkService },
|
{ provide: LinkService, useValue: linkService },
|
||||||
{ provide: DSONameService, useClass: DSONameServiceMock },
|
DSONameService,
|
||||||
...extraProviders
|
...extraProviders
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
@@ -51,6 +51,7 @@ export function createSidebarSearchListElementTests(
|
|||||||
fixture = TestBed.createComponent(componentClass);
|
fixture = TestBed.createComponent(componentClass);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
component.object = object;
|
component.object = object;
|
||||||
|
component.ngOnInit();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -62,7 +63,7 @@ export function createSidebarSearchListElementTests(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should contain the correct title', () => {
|
it('should contain the correct title', () => {
|
||||||
expect(component.title).toEqual(expectedTitle);
|
expect(component.dsoTitle).toEqual(expectedTitle);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should contain the correct description', () => {
|
it('should contain the correct description', () => {
|
||||||
|
@@ -28,11 +28,6 @@ export class SidebarSearchListElementComponent<T extends SearchResult<K>, K exte
|
|||||||
*/
|
*/
|
||||||
parentTitle$: Observable<string>;
|
parentTitle$: Observable<string>;
|
||||||
|
|
||||||
/**
|
|
||||||
* The title for the object to display
|
|
||||||
*/
|
|
||||||
title: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A description to display below the title
|
* A description to display below the title
|
||||||
*/
|
*/
|
||||||
@@ -52,7 +47,6 @@ export class SidebarSearchListElementComponent<T extends SearchResult<K>, K exte
|
|||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
if (hasValue(this.dso)) {
|
if (hasValue(this.dso)) {
|
||||||
this.parentTitle$ = this.getParentTitle();
|
this.parentTitle$ = this.getParentTitle();
|
||||||
this.title = this.getTitle();
|
|
||||||
this.description = this.getDescription();
|
this.description = this.getDescription();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,7 +65,7 @@ export class SidebarSearchListElementComponent<T extends SearchResult<K>, K exte
|
|||||||
getParentTitle(): Observable<string> {
|
getParentTitle(): Observable<string> {
|
||||||
return this.getParent().pipe(
|
return this.getParent().pipe(
|
||||||
map((parentRD: RemoteData<DSpaceObject>) => {
|
map((parentRD: RemoteData<DSpaceObject>) => {
|
||||||
return hasValue(parentRD) && hasValue(parentRD.payload) ? parentRD.payload.firstMetadataValue('dc.title') : undefined;
|
return hasValue(parentRD) && hasValue(parentRD.payload) ? this.dsoNameService.getName(parentRD.payload) : undefined;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -89,14 +83,6 @@ export class SidebarSearchListElementComponent<T extends SearchResult<K>, K exte
|
|||||||
return observableOf(undefined);
|
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
|
* Get the description of the object
|
||||||
* Default: "(dc.publisher, dc.date.issued) authors"
|
* Default: "(dc.publisher, dc.date.issued) authors"
|
||||||
|
@@ -311,6 +311,9 @@ import { BrowserOnlyPipe } from './utils/browser-only.pipe';
|
|||||||
import { ThemedLoadingComponent } from './loading/themed-loading.component';
|
import { ThemedLoadingComponent } from './loading/themed-loading.component';
|
||||||
import { PersonPageClaimButtonComponent } from './dso-page/person-page-claim-button/person-page-claim-button.component';
|
import { PersonPageClaimButtonComponent } from './dso-page/person-page-claim-button/person-page-claim-button.component';
|
||||||
import { SearchExportCsvComponent } from './search/search-export-csv/search-export-csv.component';
|
import { SearchExportCsvComponent } from './search/search-export-csv/search-export-csv.component';
|
||||||
|
import {
|
||||||
|
ItemPageTitleFieldComponent
|
||||||
|
} from '../item-page/simple/field-components/specific-field/title/item-page-title-field.component';
|
||||||
import { MarkdownPipe } from './utils/markdown.pipe';
|
import { MarkdownPipe } from './utils/markdown.pipe';
|
||||||
|
|
||||||
const MODULES = [
|
const MODULES = [
|
||||||
@@ -493,7 +496,8 @@ const COMPONENTS = [
|
|||||||
CollectionSidebarSearchListElementComponent,
|
CollectionSidebarSearchListElementComponent,
|
||||||
CommunitySidebarSearchListElementComponent,
|
CommunitySidebarSearchListElementComponent,
|
||||||
SearchNavbarComponent,
|
SearchNavbarComponent,
|
||||||
ScopeSelectorModalComponent
|
ScopeSelectorModalComponent,
|
||||||
|
ItemPageTitleFieldComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
|
8
src/app/shared/testing/authorization-service.stub.ts
Normal file
8
src/app/shared/testing/authorization-service.stub.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
|
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
||||||
|
|
||||||
|
export class AuthorizationDataServiceStub {
|
||||||
|
isAuthorized(featureId?: FeatureID, objectUrl?: string, ePersonUuid?: string): Observable<boolean> {
|
||||||
|
return observableOf(false);
|
||||||
|
}
|
||||||
|
}
|
7
src/app/shared/testing/file-service.stub.ts
Normal file
7
src/app/shared/testing/file-service.stub.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
|
export class FileServiceStub {
|
||||||
|
retrieveFileDownloadLink() {
|
||||||
|
return observableOf(null);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,14 +1,21 @@
|
|||||||
<div class="thumbnail" [class.limit-width]="limitWidth">
|
<div class="thumbnail" [class.limit-width]="limitWidth" *ngVar="(isLoading$ | async) as isLoading">
|
||||||
<ds-themed-loading *ngIf="isLoading; else showThumbnail" class="thumbnail-content" [showMessage]="false">
|
<div *ngIf="isLoading" class="thumbnail-content outer">
|
||||||
text-content
|
|
||||||
</ds-themed-loading>
|
|
||||||
<ng-template #showThumbnail>
|
|
||||||
<img *ngIf="src !== null" class="thumbnail-content img-fluid"
|
|
||||||
[src]="src | dsSafeUrl" [alt]="alt | translate" (error)="errorHandler()">
|
|
||||||
<div *ngIf="src === null" class="thumbnail-content outer">
|
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<div class="thumbnail-placeholder w-100 h-100 lead">{{ placeholder | translate }}</div>
|
<div class="centered">
|
||||||
|
<ds-themed-loading [spinner]="true"></ds-themed-loading>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</div>
|
||||||
|
<ng-container *ngVar="(src$ | async) as src">
|
||||||
|
<!-- don't use *ngIf="!isLoading" so the thumbnail can load in while the animation is playing -->
|
||||||
|
<img *ngIf="src !== null" class="thumbnail-content img-fluid" [ngClass]="{'d-none': isLoading}"
|
||||||
|
[src]="src | dsSafeUrl" [alt]="alt | translate" (error)="errorHandler()" (load)="successHandler()">
|
||||||
|
<div *ngIf="src === null && !isLoading" class="thumbnail-content outer">
|
||||||
|
<div class="inner">
|
||||||
|
<div class="thumbnail-placeholder centered lead">
|
||||||
|
{{ placeholder | translate }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -26,6 +26,10 @@ img {
|
|||||||
border: var(--ds-thumbnail-placeholder-border);
|
border: var(--ds-thumbnail-placeholder-border);
|
||||||
color: var(--ds-thumbnail-placeholder-color);
|
color: var(--ds-thumbnail-placeholder-color);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
> .centered {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@@ -3,12 +3,15 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
|||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { Bitstream } from '../core/shared/bitstream.model';
|
import { Bitstream } from '../core/shared/bitstream.model';
|
||||||
import { SafeUrlPipe } from '../shared/utils/safe-url-pipe';
|
import { SafeUrlPipe } from '../shared/utils/safe-url-pipe';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
import { ThumbnailComponent } from './thumbnail.component';
|
import { ThumbnailComponent } from './thumbnail.component';
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
import {
|
import { createFailedRemoteDataObject, createSuccessfulRemoteDataObject } from '../shared/remote-data.utils';
|
||||||
createFailedRemoteDataObject, createPendingRemoteDataObject, createSuccessfulRemoteDataObject,
|
import { AuthService } from '../core/auth/auth.service';
|
||||||
} from '../shared/remote-data.utils';
|
import { FileService } from '../core/shared/file.service';
|
||||||
|
import { VarDirective } from '../shared/utils/var.directive';
|
||||||
|
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
|
||||||
|
|
||||||
// eslint-disable-next-line @angular-eslint/pipe-prefix
|
// eslint-disable-next-line @angular-eslint/pipe-prefix
|
||||||
@Pipe({ name: 'translate' })
|
@Pipe({ name: 'translate' })
|
||||||
@@ -18,143 +21,311 @@ class MockTranslatePipe implements PipeTransform {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CONTENT = 'content.url';
|
||||||
|
|
||||||
describe('ThumbnailComponent', () => {
|
describe('ThumbnailComponent', () => {
|
||||||
let comp: ThumbnailComponent;
|
let comp: ThumbnailComponent;
|
||||||
let fixture: ComponentFixture<ThumbnailComponent>;
|
let fixture: ComponentFixture<ThumbnailComponent>;
|
||||||
let de: DebugElement;
|
let de: DebugElement;
|
||||||
let el: HTMLElement;
|
let el: HTMLElement;
|
||||||
|
let authService;
|
||||||
|
let authorizationService;
|
||||||
|
let fileService;
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
|
authService = jasmine.createSpyObj('AuthService', {
|
||||||
|
isAuthenticated: observableOf(true),
|
||||||
|
});
|
||||||
|
authorizationService = jasmine.createSpyObj('AuthorizationService', {
|
||||||
|
isAuthorized: observableOf(true),
|
||||||
|
});
|
||||||
|
fileService = jasmine.createSpyObj('FileService', {
|
||||||
|
retrieveFileDownloadLink: null
|
||||||
|
});
|
||||||
|
fileService.retrieveFileDownloadLink.and.callFake((url) => observableOf(`${url}?authentication-token=fake`));
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [ThumbnailComponent, SafeUrlPipe, MockTranslatePipe],
|
declarations: [ThumbnailComponent, SafeUrlPipe, MockTranslatePipe, VarDirective],
|
||||||
|
providers: [
|
||||||
|
{ provide: AuthService, useValue: authService },
|
||||||
|
{ provide: AuthorizationDataService, useValue: authorizationService },
|
||||||
|
{ provide: FileService, useValue: fileService }
|
||||||
|
]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(ThumbnailComponent);
|
fixture = TestBed.createComponent(ThumbnailComponent);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
authService = TestBed.inject(AuthService);
|
||||||
|
|
||||||
comp = fixture.componentInstance; // ThumbnailComponent test instance
|
comp = fixture.componentInstance; // ThumbnailComponent test instance
|
||||||
de = fixture.debugElement.query(By.css('div.thumbnail'));
|
de = fixture.debugElement.query(By.css('div.thumbnail'));
|
||||||
el = de.nativeElement;
|
el = de.nativeElement;
|
||||||
});
|
});
|
||||||
|
|
||||||
const withoutThumbnail = () => {
|
describe('loading', () => {
|
||||||
describe('and there is a default image', () => {
|
it('should start out with isLoading$ true', () => {
|
||||||
it('should display the default image', () => {
|
expect(comp.isLoading$.getValue()).toBeTrue();
|
||||||
comp.src = 'http://bit.stream';
|
|
||||||
comp.defaultImage = 'http://default.img';
|
|
||||||
comp.errorHandler();
|
|
||||||
expect(comp.src).toBe(comp.defaultImage);
|
|
||||||
});
|
});
|
||||||
it('should include the alt text', () => {
|
|
||||||
comp.src = 'http://bit.stream';
|
|
||||||
comp.defaultImage = 'http://default.img';
|
|
||||||
comp.errorHandler();
|
|
||||||
comp.ngOnChanges();
|
|
||||||
fixture.detectChanges();
|
|
||||||
const image: HTMLElement = de.query(By.css('img')).nativeElement;
|
|
||||||
expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('and there is no default image', () => {
|
|
||||||
it('should display the placeholder', () => {
|
|
||||||
comp.src = 'http://default.img';
|
|
||||||
comp.errorHandler();
|
|
||||||
expect(comp.src).toBe(null);
|
|
||||||
|
|
||||||
comp.ngOnChanges();
|
it('should set isLoading$ to false once an image is successfully loaded', () => {
|
||||||
|
comp.setSrc('http://bit.stream');
|
||||||
|
fixture.debugElement.query(By.css('img.thumbnail-content')).triggerEventHandler('load', new Event('load'));
|
||||||
|
expect(comp.isLoading$.getValue()).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set isLoading$ to false once the src is set to null', () => {
|
||||||
|
comp.setSrc(null);
|
||||||
|
expect(comp.isLoading$.getValue()).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show a loading animation while isLoading$ is true', () => {
|
||||||
|
expect(de.query(By.css('ds-themed-loading'))).toBeTruthy();
|
||||||
|
|
||||||
|
comp.isLoading$.next(false);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const placeholder = fixture.debugElement.query(By.css('div.thumbnail-placeholder')).nativeElement;
|
expect(fixture.debugElement.query(By.css('ds-themed-loading'))).toBeFalsy();
|
||||||
expect(placeholder.innerHTML).toBe('TRANSLATED ' + comp.placeholder);
|
});
|
||||||
|
|
||||||
|
describe('with a thumbnail image', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.src$.next('https://bit.stream');
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render but hide the image while loading and show it once done', () => {
|
||||||
|
let img = fixture.debugElement.query(By.css('img.thumbnail-content'));
|
||||||
|
expect(img).toBeTruthy();
|
||||||
|
expect(img.classes['d-none']).toBeTrue();
|
||||||
|
|
||||||
|
comp.isLoading$.next(false);
|
||||||
|
fixture.detectChanges();
|
||||||
|
img = fixture.debugElement.query(By.css('img.thumbnail-content'));
|
||||||
|
expect(img).toBeTruthy();
|
||||||
|
expect(img.classes['d-none']).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('without a thumbnail image', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.src$.next(null);
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should only show the HTML placeholder once done loading', () => {
|
||||||
|
expect(fixture.debugElement.query(By.css('div.thumbnail-placeholder'))).toBeFalsy();
|
||||||
|
|
||||||
|
comp.isLoading$.next(false);
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.debugElement.query(By.css('div.thumbnail-placeholder'))).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
const errorHandler = () => {
|
||||||
|
let fallbackSpy;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fallbackSpy = spyOn(comp, 'showFallback').and.callThrough();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('retry with authentication token', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// disconnect error handler to be sure it's only called once
|
||||||
|
const img = fixture.debugElement.query(By.css('img.thumbnail-content'));
|
||||||
|
img.nativeNode.onerror = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remember that it already retried once', () => {
|
||||||
|
expect(comp.retriedWithToken).toBeFalse();
|
||||||
|
comp.errorHandler();
|
||||||
|
expect(comp.retriedWithToken).toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('if not logged in', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
authService.isAuthenticated.and.returnValue(observableOf(false));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fall back to default', () => {
|
||||||
|
comp.errorHandler();
|
||||||
|
expect(fallbackSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('if logged in', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
authService.isAuthenticated.and.returnValue(observableOf(true));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('and authorized to download the thumbnail', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
authorizationService.isAuthorized.and.returnValue(observableOf(true));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add an authentication token to the thumbnail URL', () => {
|
||||||
|
comp.errorHandler();
|
||||||
|
|
||||||
|
if ((comp.thumbnail as RemoteData<Bitstream>)?.hasFailed) {
|
||||||
|
// If we failed to retrieve the Bitstream in the first place, fall back to the default
|
||||||
|
expect(comp.src$.getValue()).toBe(null);
|
||||||
|
expect(fallbackSpy).toHaveBeenCalled();
|
||||||
|
} else {
|
||||||
|
expect(comp.src$.getValue()).toBe(CONTENT + '?authentication-token=fake');
|
||||||
|
expect(fallbackSpy).not.toHaveBeenCalled();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('but not authorized to download the thumbnail', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
authorizationService.isAuthorized.and.returnValue(observableOf(false));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fall back to default', () => {
|
||||||
|
comp.errorHandler();
|
||||||
|
|
||||||
|
expect(comp.src$.getValue()).toBe(null);
|
||||||
|
expect(fallbackSpy).toHaveBeenCalled();
|
||||||
|
|
||||||
|
// We don't need to check authorization if we failed to retrieve the Bitstreamin the first place
|
||||||
|
if (!(comp.thumbnail as RemoteData<Bitstream>)?.hasFailed) {
|
||||||
|
expect(authorizationService.isAuthorized).toHaveBeenCalled();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('after retrying with token', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.retriedWithToken = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fall back to default', () => {
|
||||||
|
comp.errorHandler();
|
||||||
|
expect(authService.isAuthenticated).not.toHaveBeenCalled();
|
||||||
|
expect(fileService.retrieveFileDownloadLink).not.toHaveBeenCalled();
|
||||||
|
expect(fallbackSpy).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
describe('fallback', () => {
|
||||||
|
describe('if there is a default image', () => {
|
||||||
|
it('should display the default image', () => {
|
||||||
|
comp.src$.next('http://bit.stream');
|
||||||
|
comp.defaultImage = 'http://default.img';
|
||||||
|
comp.errorHandler();
|
||||||
|
expect(comp.src$.getValue()).toBe(comp.defaultImage);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include the alt text', () => {
|
||||||
|
comp.src$.next('http://bit.stream');
|
||||||
|
comp.defaultImage = 'http://default.img';
|
||||||
|
comp.errorHandler();
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
const image: HTMLElement = fixture.debugElement.query(By.css('img')).nativeElement;
|
||||||
|
expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('if there is no default image', () => {
|
||||||
|
it('should display the HTML placeholder', () => {
|
||||||
|
comp.src$.next('http://default.img');
|
||||||
|
comp.defaultImage = null;
|
||||||
|
comp.errorHandler();
|
||||||
|
expect(comp.src$.getValue()).toBe(null);
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
const placeholder = fixture.debugElement.query(By.css('div.thumbnail-placeholder')).nativeElement;
|
||||||
|
expect(placeholder.innerHTML).toContain('TRANSLATED ' + comp.placeholder);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('with thumbnail as Bitstream', () => {
|
describe('with thumbnail as Bitstream', () => {
|
||||||
let thumbnail: Bitstream;
|
let thumbnail;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
thumbnail = new Bitstream();
|
thumbnail = new Bitstream();
|
||||||
thumbnail._links = {
|
thumbnail._links = {
|
||||||
self: { href: 'self.url' },
|
self: { href: 'self.url' },
|
||||||
bundle: { href: 'bundle.url' },
|
bundle: { href: 'bundle.url' },
|
||||||
format: { href: 'format.url' },
|
format: { href: 'format.url' },
|
||||||
content: { href: 'content.url' },
|
content: { href: CONTENT },
|
||||||
thumbnail: undefined,
|
thumbnail: undefined,
|
||||||
};
|
};
|
||||||
|
comp.thumbnail = thumbnail;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display an image', () => {
|
it('should display an image', () => {
|
||||||
comp.thumbnail = thumbnail;
|
|
||||||
comp.ngOnChanges();
|
comp.ngOnChanges();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const image: HTMLElement = de.query(By.css('img')).nativeElement;
|
const image: HTMLElement = fixture.debugElement.query(By.css('img')).nativeElement;
|
||||||
expect(image.getAttribute('src')).toBe(comp.thumbnail._links.content.href);
|
expect(image.getAttribute('src')).toBe(thumbnail._links.content.href);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should include the alt text', () => {
|
it('should include the alt text', () => {
|
||||||
comp.thumbnail = thumbnail;
|
|
||||||
comp.ngOnChanges();
|
comp.ngOnChanges();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const image: HTMLElement = de.query(By.css('img')).nativeElement;
|
const image: HTMLElement = fixture.debugElement.query(By.css('img')).nativeElement;
|
||||||
expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt);
|
expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when there is no thumbnail', () => {
|
describe('when there is no thumbnail', () => {
|
||||||
withoutThumbnail();
|
errorHandler();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with thumbnail as RemoteData<Bitstream>', () => {
|
describe('with thumbnail as RemoteData<Bitstream>', () => {
|
||||||
let thumbnail: RemoteData<Bitstream>;
|
let thumbnail: Bitstream;
|
||||||
|
|
||||||
describe('while loading', () => {
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
thumbnail = createPendingRemoteDataObject();
|
thumbnail = new Bitstream();
|
||||||
});
|
thumbnail._links = {
|
||||||
|
self: { href: 'self.url' },
|
||||||
it('should show a loading animation', () => {
|
bundle: { href: 'bundle.url' },
|
||||||
comp.thumbnail = thumbnail;
|
format: { href: 'format.url' },
|
||||||
comp.ngOnChanges();
|
content: { href: CONTENT },
|
||||||
fixture.detectChanges();
|
thumbnail: undefined
|
||||||
expect(de.query(By.css('ds-themed-loading'))).toBeTruthy();
|
};
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when there is a thumbnail', () => {
|
describe('when there is a thumbnail', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const bitstream = new Bitstream();
|
comp.thumbnail = createSuccessfulRemoteDataObject(thumbnail);
|
||||||
bitstream._links = {
|
|
||||||
self: { href: 'self.url' },
|
|
||||||
bundle: { href: 'bundle.url' },
|
|
||||||
format: { href: 'format.url' },
|
|
||||||
content: { href: 'content.url' },
|
|
||||||
thumbnail: undefined,
|
|
||||||
};
|
|
||||||
thumbnail = createSuccessfulRemoteDataObject(bitstream);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display an image', () => {
|
it('should display an image', () => {
|
||||||
comp.thumbnail = thumbnail;
|
|
||||||
comp.ngOnChanges();
|
comp.ngOnChanges();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const image: HTMLElement = de.query(By.css('img')).nativeElement;
|
const image: HTMLElement = de.query(By.css('img')).nativeElement;
|
||||||
expect(image.getAttribute('src')).toBe(comp.thumbnail.payload._links.content.href);
|
expect(image.getAttribute('src')).toBe(thumbnail._links.content.href);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display the alt text', () => {
|
it('should display the alt text', () => {
|
||||||
comp.thumbnail = thumbnail;
|
|
||||||
comp.ngOnChanges();
|
comp.ngOnChanges();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const image: HTMLElement = de.query(By.css('img')).nativeElement;
|
const image: HTMLElement = de.query(By.css('img')).nativeElement;
|
||||||
expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt);
|
expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('but it can\'t be loaded', () => {
|
||||||
|
errorHandler();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when there is no thumbnail', () => {
|
describe('when there is no thumbnail', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
thumbnail = createFailedRemoteDataObject();
|
comp.thumbnail = createFailedRemoteDataObject();
|
||||||
});
|
});
|
||||||
|
|
||||||
withoutThumbnail();
|
errorHandler();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,13 @@
|
|||||||
import { Component, Input, OnChanges } from '@angular/core';
|
import { Component, Input, OnChanges } from '@angular/core';
|
||||||
import { Bitstream } from '../core/shared/bitstream.model';
|
import { Bitstream } from '../core/shared/bitstream.model';
|
||||||
import { hasValue } from '../shared/empty.util';
|
import { hasNoValue, hasValue } from '../shared/empty.util';
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
|
import { BehaviorSubject, of as observableOf } from 'rxjs';
|
||||||
|
import { switchMap } from 'rxjs/operators';
|
||||||
|
import { FeatureID } from '../core/data/feature-authorization/feature-id';
|
||||||
|
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
|
||||||
|
import { AuthService } from '../core/auth/auth.service';
|
||||||
|
import { FileService } from '../core/shared/file.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders a given Bitstream as a thumbnail.
|
* This component renders a given Bitstream as a thumbnail.
|
||||||
@@ -14,7 +20,6 @@ import { RemoteData } from '../core/data/remote-data';
|
|||||||
templateUrl: './thumbnail.component.html',
|
templateUrl: './thumbnail.component.html',
|
||||||
})
|
})
|
||||||
export class ThumbnailComponent implements OnChanges {
|
export class ThumbnailComponent implements OnChanges {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The thumbnail Bitstream
|
* The thumbnail Bitstream
|
||||||
*/
|
*/
|
||||||
@@ -29,7 +34,9 @@ export class ThumbnailComponent implements OnChanges {
|
|||||||
/**
|
/**
|
||||||
* The src attribute used in the template to render the image.
|
* The src attribute used in the template to render the image.
|
||||||
*/
|
*/
|
||||||
src: string = null;
|
src$ = new BehaviorSubject<string>(undefined);
|
||||||
|
|
||||||
|
retriedWithToken = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* i18n key of thumbnail alt text
|
* i18n key of thumbnail alt text
|
||||||
@@ -46,50 +53,123 @@ export class ThumbnailComponent implements OnChanges {
|
|||||||
*/
|
*/
|
||||||
@Input() limitWidth? = true;
|
@Input() limitWidth? = true;
|
||||||
|
|
||||||
isLoading: boolean;
|
/**
|
||||||
|
* Whether the thumbnail is currently loading
|
||||||
|
* Start out as true to avoid flashing the alt text while a thumbnail is being loaded.
|
||||||
|
*/
|
||||||
|
isLoading$ = new BehaviorSubject(true);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected auth: AuthService,
|
||||||
|
protected authorizationService: AuthorizationDataService,
|
||||||
|
protected fileService: FileService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve the thumbnail.
|
* Resolve the thumbnail.
|
||||||
* Use a default image if no actual image is available.
|
* Use a default image if no actual image is available.
|
||||||
*/
|
*/
|
||||||
ngOnChanges(): void {
|
ngOnChanges(): void {
|
||||||
if (this.thumbnail === undefined || this.thumbnail === null) {
|
if (hasNoValue(this.thumbnail)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.thumbnail instanceof Bitstream) {
|
|
||||||
this.resolveThumbnail(this.thumbnail as Bitstream);
|
const thumbnail = this.bitstream;
|
||||||
|
if (hasValue(thumbnail?._links?.content?.href)) {
|
||||||
|
this.setSrc(thumbnail?._links?.content?.href);
|
||||||
} else {
|
} else {
|
||||||
const thumbnailRD = this.thumbnail as RemoteData<Bitstream>;
|
this.showFallback();
|
||||||
if (thumbnailRD.isLoading) {
|
|
||||||
this.isLoading = true;
|
|
||||||
} else {
|
|
||||||
this.resolveThumbnail(thumbnailRD.payload as Bitstream);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private resolveThumbnail(thumbnail: Bitstream): void {
|
/**
|
||||||
if (hasValue(thumbnail) && hasValue(thumbnail._links)
|
* The current thumbnail Bitstream
|
||||||
&& hasValue(thumbnail._links.content)
|
* @private
|
||||||
&& thumbnail._links.content.href) {
|
*/
|
||||||
this.src = thumbnail._links.content.href;
|
private get bitstream(): Bitstream {
|
||||||
} else {
|
if (this.thumbnail instanceof Bitstream) {
|
||||||
this.src = this.defaultImage;
|
return this.thumbnail as Bitstream;
|
||||||
|
} else if (this.thumbnail instanceof RemoteData) {
|
||||||
|
return (this.thumbnail as RemoteData<Bitstream>).payload;
|
||||||
}
|
}
|
||||||
this.isLoading = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle image download errors.
|
* Handle image download errors.
|
||||||
* If the image can't be found, use the defaultImage instead.
|
* If the image can't be loaded, try re-requesting it with an authorization token in case it's a restricted Bitstream
|
||||||
* If that also can't be found, use null to fall back to the HTML placeholder.
|
* Otherwise, fall back to the default image or a HTML placeholder
|
||||||
*/
|
*/
|
||||||
errorHandler() {
|
errorHandler() {
|
||||||
if (this.src !== this.defaultImage) {
|
if (!this.retriedWithToken && hasValue(this.thumbnail)) {
|
||||||
this.src = this.defaultImage;
|
// the thumbnail may have failed to load because it's restricted
|
||||||
|
// → retry with an authorization token
|
||||||
|
// only do this once; fall back to the default if it still fails
|
||||||
|
this.retriedWithToken = true;
|
||||||
|
|
||||||
|
const thumbnail = this.bitstream;
|
||||||
|
this.auth.isAuthenticated().pipe(
|
||||||
|
switchMap((isLoggedIn) => {
|
||||||
|
if (isLoggedIn && hasValue(thumbnail)) {
|
||||||
|
return this.authorizationService.isAuthorized(FeatureID.CanDownload, thumbnail.self);
|
||||||
} else {
|
} else {
|
||||||
this.src = null;
|
return observableOf(false);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
switchMap((isAuthorized) => {
|
||||||
|
if (isAuthorized) {
|
||||||
|
return this.fileService.retrieveFileDownloadLink(thumbnail._links.content.href);
|
||||||
|
} else {
|
||||||
|
return observableOf(null);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).subscribe((url: string) => {
|
||||||
|
if (hasValue(url)) {
|
||||||
|
// If we got a URL, try to load it
|
||||||
|
// (if it still fails this method will be called again, and we'll fall back to the default)
|
||||||
|
// Otherwise, fall back to the default image right now
|
||||||
|
this.setSrc(url);
|
||||||
|
} else {
|
||||||
|
this.showFallback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.showFallback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To be called when the requested thumbnail could not be found
|
||||||
|
* - If the current src is not the default image, try that first
|
||||||
|
* - If this was already the case and the default image could not be found either,
|
||||||
|
* show an HTML placecholder by setting src to null
|
||||||
|
*
|
||||||
|
* Also stops the loading animation.
|
||||||
|
*/
|
||||||
|
showFallback() {
|
||||||
|
if (this.src$.getValue() !== this.defaultImage) {
|
||||||
|
this.setSrc(this.defaultImage);
|
||||||
|
} else {
|
||||||
|
this.setSrc(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the thumbnail.
|
||||||
|
* Stop the loading animation if setting to null.
|
||||||
|
* @param src
|
||||||
|
*/
|
||||||
|
setSrc(src: string): void {
|
||||||
|
this.src$.next(src);
|
||||||
|
if (src === null) {
|
||||||
|
this.isLoading$.next(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the loading animation once the thumbnail is successfully loaded
|
||||||
|
*/
|
||||||
|
successHandler() {
|
||||||
|
this.isLoading$.next(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -910,7 +910,7 @@
|
|||||||
|
|
||||||
"collection.edit.tabs.source.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button",
|
"collection.edit.tabs.source.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button",
|
||||||
|
|
||||||
"collection.edit.tabs.source.notifications.discarded.title": "Changed discarded",
|
"collection.edit.tabs.source.notifications.discarded.title": "Changes discarded",
|
||||||
|
|
||||||
"collection.edit.tabs.source.notifications.invalid.content": "Your changes were not saved. Please make sure all fields are valid before you save.",
|
"collection.edit.tabs.source.notifications.invalid.content": "Your changes were not saved. Please make sure all fields are valid before you save.",
|
||||||
|
|
||||||
@@ -1915,7 +1915,7 @@
|
|||||||
|
|
||||||
"item.edit.metadata.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button",
|
"item.edit.metadata.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button",
|
||||||
|
|
||||||
"item.edit.metadata.notifications.discarded.title": "Changed discarded",
|
"item.edit.metadata.notifications.discarded.title": "Changes discarded",
|
||||||
|
|
||||||
"item.edit.metadata.notifications.error.title": "An error occurred",
|
"item.edit.metadata.notifications.error.title": "An error occurred",
|
||||||
|
|
||||||
@@ -1925,7 +1925,7 @@
|
|||||||
|
|
||||||
"item.edit.metadata.notifications.outdated.content": "The item you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts",
|
"item.edit.metadata.notifications.outdated.content": "The item you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts",
|
||||||
|
|
||||||
"item.edit.metadata.notifications.outdated.title": "Changed outdated",
|
"item.edit.metadata.notifications.outdated.title": "Changes outdated",
|
||||||
|
|
||||||
"item.edit.metadata.notifications.saved.content": "Your changes to this item's metadata were saved.",
|
"item.edit.metadata.notifications.saved.content": "Your changes to this item's metadata were saved.",
|
||||||
|
|
||||||
|
@@ -3274,7 +3274,7 @@
|
|||||||
"mydspace.status.workspace": "Työtila",
|
"mydspace.status.workspace": "Työtila",
|
||||||
|
|
||||||
// "mydspace.title": "MyDSpace",
|
// "mydspace.title": "MyDSpace",
|
||||||
"mydspace.title": "Omat tiedot",
|
"mydspace.title": "Oma DSpace",
|
||||||
|
|
||||||
// "mydspace.upload.upload-failed": "Error creating new workspace. Please verify the content uploaded before retry.",
|
// "mydspace.upload.upload-failed": "Error creating new workspace. Please verify the content uploaded before retry.",
|
||||||
"mydspace.upload.upload-failed": "Virhe uutta työtilaa luotaessa. Tarkista ladattava sisältö ennen kuin yrität uudelleen.",
|
"mydspace.upload.upload-failed": "Virhe uutta työtilaa luotaessa. Tarkista ladattava sisältö ennen kuin yrität uudelleen.",
|
||||||
@@ -4345,13 +4345,13 @@
|
|||||||
"statistics.table.title.TotalDownloads": "Tiedostokäyntejä",
|
"statistics.table.title.TotalDownloads": "Tiedostokäyntejä",
|
||||||
|
|
||||||
// "statistics.table.title.TopCountries": "Top country views",
|
// "statistics.table.title.TopCountries": "Top country views",
|
||||||
"statistics.table.title.TopCountries": "Eniten latauksia maittain",
|
"statistics.table.title.TopCountries": "Eniten katseluita maittain",
|
||||||
|
|
||||||
// "statistics.table.title.TopCities": "Top city views",
|
// "statistics.table.title.TopCities": "Top city views",
|
||||||
"statistics.table.title.TopCities": "Eniten latauksia kaupungeittain",
|
"statistics.table.title.TopCities": "Eniten katseluita kaupungeittain",
|
||||||
|
|
||||||
// "statistics.table.header.views": "Views",
|
// "statistics.table.header.views": "Views",
|
||||||
"statistics.table.header.views": "Latauksia",
|
"statistics.table.header.views": "Katseluita",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user