Skip to content

Commit b29dc1e

Browse files
[DURACOM-426] improve title configurability in popover, add link view in item preview
1 parent 5b6fa80 commit b29dc1e

File tree

11 files changed

+86
-87
lines changed

11 files changed

+86
-87
lines changed

src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
[entityType]="item.entityType"
88
></ds-metadata-link-view-avatar-popover>
99
}
10-
<span class="font-weight-bold h4 text-truncate"> {{item.firstMetadataValue('dc.title')}} </span>
10+
<div class="font-weight-bold h4 text-truncate"> {{title}} </div>
1111
</div>
1212

1313
@for (metadata of entityMetdataFields; track metadata) {

src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.spec.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ describe('MetadataLinkViewPopoverComponent', () => {
9292
.withArgs('dc.identifier.uri').and.returnValue('http://example.com')
9393
.withArgs('dc.description.abstract').and.returnValue('Long text description')
9494
.withArgs('organization.identifier.ror').and.returnValue('https://ror.org/1234')
95-
.withArgs('person.identifier.orcid').and.returnValue('https://orcid.org/0000-0000-0000-0000');
95+
.withArgs('person.identifier.orcid').and.returnValue('https://orcid.org/0000-0000-0000-0000')
96+
.withArgs('dc.nonexistent').and.returnValue(null);
9697

9798
fixture.detectChanges();
9899
});
@@ -138,4 +139,40 @@ describe('MetadataLinkViewPopoverComponent', () => {
138139
const avatarPopoverElement = fixture.debugElement.query(By.css('ds-metadata-link-view-avatar-popover'));
139140
expect(avatarPopoverElement).toBeTruthy();
140141
});
142+
143+
describe('getTitleFromMetadataList', () => {
144+
145+
it('should return title from configured metadata when available', () => {
146+
component.metadataLinkViewPopoverData = {
147+
entityDataConfig: [
148+
{
149+
entityType: 'Publication',
150+
metadataList: ['dc.title', 'dc.identifier.uri'],
151+
titleMetadataList: ['dc.title', 'dc.identifier.uri'],
152+
},
153+
],
154+
fallbackMetdataList: [],
155+
};
156+
157+
const title = component.getTitleFromMetadataList();
158+
expect(title).toBe('Test Title, http://example.com');
159+
});
160+
161+
it('should fallback to defaultTitleMetadataList when no configured title is present', () => {
162+
component.metadataLinkViewPopoverData = {
163+
entityDataConfig: [
164+
{
165+
entityType: 'Publication',
166+
metadataList: ['dc.title', 'dc.identifier.uri'],
167+
titleMetadataList: ['dc.nonexistent'],
168+
},
169+
],
170+
fallbackMetdataList: [],
171+
};
172+
173+
const title = component.getTitleFromMetadataList();
174+
expect(title).toBe('Test Title');
175+
});
176+
});
177+
141178
});

src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,14 @@ export class MetadataLinkViewPopoverComponent implements OnInit {
8080
*/
8181
isOtherEntityType = false;
8282

83+
/**
84+
* The title to be displayed
85+
*/
86+
title: string;
87+
88+
private readonly titleSeparator = ', ';
89+
private readonly defaultTitleMetadataList = ['dc.title'];
90+
8391
/**
8492
* If `metadataLinkViewPopoverData` is provided, it retrieves the metadata fields based on the entity type.
8593
* If no metadata fields are found for the entity type, it falls back to the fallback metadata list.
@@ -89,6 +97,7 @@ export class MetadataLinkViewPopoverComponent implements OnInit {
8997
const metadataFields = this.metadataLinkViewPopoverData.entityDataConfig.find((config) => config.entityType === this.item.entityType);
9098
this.entityMetdataFields = hasValue(metadataFields) ? metadataFields.metadataList : this.metadataLinkViewPopoverData.fallbackMetdataList;
9199
this.isOtherEntityType = hasNoValue(metadataFields);
100+
this.title = this.getTitleFromMetadataList();
92101
}
93102
}
94103

@@ -119,4 +128,15 @@ export class MetadataLinkViewPopoverComponent implements OnInit {
119128
const identifierSubtype = this.identifierSubtypeConfig.find((config) => config.name === subtype);
120129
return identifierSubtype;
121130
}
131+
132+
/**
133+
* Generates the title for the popover based on the title metadata list.
134+
* @returns The generated title as a string.
135+
*/
136+
getTitleFromMetadataList(): string {
137+
const titleMetadataList = this.metadataLinkViewPopoverData.entityDataConfig.find((config) => config.entityType === this.item.entityType)?.titleMetadataList;
138+
const itemHasConfiguredTitle = titleMetadataList?.length && titleMetadataList.map(metadata => this.item.firstMetadataValue(metadata)).some(value => hasValue(value));
139+
return (itemHasConfiguredTitle ? titleMetadataList : this.defaultTitleMetadataList)
140+
.map(metadataField => this.item.firstMetadataValue(metadataField)).join(this.titleSeparator);
141+
}
122142
}

src/app/shared/metadata-link-view/metadata-link-view.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export class MetadataLinkViewComponent implements OnInit {
131131
* @returns The created MetadataView object.
132132
*/
133133
private createMetadataView(itemRD: RemoteData<Item>, metadataValue: MetadataValue): MetadataView {
134-
if (itemRD.hasSucceeded) {
134+
if (itemRD.hasSucceeded && itemRD.payload) {
135135
this.relatedItem = itemRD.payload;
136136
this.relatedDsoRoute = this.getItemPageRoute(this.relatedItem);
137137
return {

src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component.html

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,9 @@ <h2 [innerHTML]="dsoTitle" [ngClass]="{'lead': true,'text-muted': !item.hasMetad
3030
>{{'mydspace.results.no-authors'
3131
| translate}}</span>
3232
}
33-
@for (author of item.allMetadataValues(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'], undefined, true); track author; let last = $last) {
34-
<span
35-
>
36-
<span [innerHTML]="author"><span [innerHTML]="author"></span></span>
33+
@for (author of authorMetadataList; track author.uuid; let last = $last) {
34+
<span class="item-list-authors">
35+
<ds-metadata-link-view [metadata]="author"></ds-metadata-link-view>
3736
@if (!last) {
3837
<span>; </span>
3938
}

src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component.spec.ts

Lines changed: 9 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
import { of } from 'rxjs';
2020

2121
import { ThemedThumbnailComponent } from '../../../../thumbnail/themed-thumbnail.component';
22+
import { MetadataLinkViewComponent } from '../../../metadata-link-view/metadata-link-view.component';
2223
import { ThemedBadgesComponent } from '../../../object-collection/shared/badges/themed-badges.component';
2324
import { ItemCollectionComponent } from '../../../object-collection/shared/mydspace-item-collection/item-collection.component';
2425
import { ItemSubmitterComponent } from '../../../object-collection/shared/mydspace-item-submitter/item-submitter.component';
@@ -119,6 +120,7 @@ describe('ItemListPreviewComponent', () => {
119120
ThemedThumbnailComponent, ThemedBadgesComponent,
120121
TruncatableComponent, TruncatablePartComponent,
121122
ItemSubmitterComponent, ItemCollectionComponent,
123+
MetadataLinkViewComponent,
122124
],
123125
},
124126
}).compileComponents();
@@ -127,66 +129,44 @@ describe('ItemListPreviewComponent', () => {
127129
beforeEach(waitForAsync(() => {
128130
fixture = TestBed.createComponent(ItemListPreviewComponent);
129131
component = fixture.componentInstance;
130-
131-
}));
132-
133-
beforeEach(() => {
134132
component.object = { hitHighlights: {} } as any;
135-
});
133+
component.item = mockItemWithAuthorAndDate;
134+
fixture.detectChanges();
135+
}));
136136

137137
describe('When showThumbnails is true', () => {
138-
beforeEach(() => {
139-
component.item = mockItemWithAuthorAndDate;
140-
fixture.detectChanges();
141-
});
142138
it('should add the thumbnail element', () => {
143139
const thumbnail = fixture.debugElement.query(By.css('ds-thumbnail'));
144140
expect(thumbnail).toBeTruthy();
145141
});
146142
});
147143

148144
describe('When the item has an author', () => {
149-
beforeEach(() => {
150-
component.item = mockItemWithAuthorAndDate;
151-
fixture.detectChanges();
152-
});
153-
154145
it('should show the author paragraph', () => {
155-
const itemAuthorField = fixture.debugElement.query(By.css('span.item-list-authors'));
146+
const itemAuthorField = fixture.debugElement.query(By.css('span.item-list-authors ds-metadata-link-view'));
156147
expect(itemAuthorField).not.toBeNull();
157148
});
158149
});
159150

160151
describe('When the item has no author', () => {
161-
beforeEach(() => {
152+
beforeEach(waitForAsync(() => {
162153
component.item = mockItemWithoutAuthorAndDate;
163154
fixture.detectChanges();
164-
});
165-
155+
}));
166156
it('should not show the author paragraph', () => {
167-
const itemAuthorField = fixture.debugElement.query(By.css('span.item-list-authors'));
157+
const itemAuthorField = fixture.debugElement.query(By.css('span.item-list-authors ds-metadata-link-view'));
168158
expect(itemAuthorField).toBeNull();
169159
});
170160
});
171161

172162
describe('When the item has an issuedate', () => {
173-
beforeEach(() => {
174-
component.item = mockItemWithAuthorAndDate;
175-
fixture.detectChanges();
176-
});
177-
178163
it('should show the issuedate span', () => {
179164
const dateField = fixture.debugElement.query(By.css('span.item-list-date'));
180165
expect(dateField).not.toBeNull();
181166
});
182167
});
183168

184169
describe('When the item has no issuedate', () => {
185-
beforeEach(() => {
186-
component.item = mockItemWithoutAuthorAndDate;
187-
fixture.detectChanges();
188-
});
189-
190170
it('should show the issuedate empty placeholder', () => {
191171
const dateField = fixture.debugElement.query(By.css('span.item-list-date'));
192172
expect(dateField).not.toBeNull();
@@ -205,54 +185,3 @@ describe('ItemListPreviewComponent', () => {
205185
});
206186
});
207187
});
208-
209-
describe('ItemListPreviewComponent', () => {
210-
beforeEach(waitForAsync(() => {
211-
TestBed.configureTestingModule({
212-
imports: [
213-
TranslateModule.forRoot({
214-
loader: {
215-
provide: TranslateLoader,
216-
useClass: TranslateLoaderMock,
217-
},
218-
}),
219-
NoopAnimationsModule,
220-
ItemListPreviewComponent, TruncatePipe,
221-
],
222-
providers: [
223-
{ provide: 'objectElementProvider', useValue: { mockItemWithAuthorAndDate } },
224-
{ provide: APP_CONFIG, useValue: enviromentNoThumbs },
225-
],
226-
schemas: [NO_ERRORS_SCHEMA],
227-
}).overrideComponent(ItemListPreviewComponent, {
228-
add: { changeDetection: ChangeDetectionStrategy.Default },
229-
remove: {
230-
imports: [
231-
ThemedThumbnailComponent, ThemedBadgesComponent,
232-
TruncatableComponent, TruncatablePartComponent,
233-
ItemSubmitterComponent, ItemCollectionComponent,
234-
],
235-
},
236-
}).compileComponents();
237-
}));
238-
beforeEach(waitForAsync(() => {
239-
fixture = TestBed.createComponent(ItemListPreviewComponent);
240-
component = fixture.componentInstance;
241-
242-
}));
243-
244-
beforeEach(() => {
245-
component.object = { hitHighlights: {} } as any;
246-
});
247-
248-
describe('When showThumbnails is true', () => {
249-
beforeEach(() => {
250-
component.item = mockItemWithAuthorAndDate;
251-
fixture.detectChanges();
252-
});
253-
it('should add the thumbnail element', () => {
254-
const thumbnail = fixture.debugElement.query(By.css('ds-thumbnail'));
255-
expect(thumbnail).toBeFalsy();
256-
});
257-
});
258-
});

src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ import {
1515
import { DSONameService } from '@dspace/core/breadcrumbs/dso-name.service';
1616
import { Context } from '@dspace/core/shared/context.model';
1717
import { Item } from '@dspace/core/shared/item.model';
18+
import { MetadataValue } from '@dspace/core/shared/metadata.models';
1819
import { SearchResult } from '@dspace/core/shared/search/models/search-result.model';
1920
import { WorkflowItem } from '@dspace/core/submission/models/workflowitem.model';
2021
import { TranslateModule } from '@ngx-translate/core';
2122

2223
import { ThemedThumbnailComponent } from '../../../../thumbnail/themed-thumbnail.component';
2324
import { fadeInOut } from '../../../animations/fade';
25+
import { MetadataLinkViewComponent } from '../../../metadata-link-view/metadata-link-view.component';
2426
import { ThemedBadgesComponent } from '../../../object-collection/shared/badges/themed-badges.component';
2527
import { ItemCollectionComponent } from '../../../object-collection/shared/mydspace-item-collection/item-collection.component';
2628
import { ItemSubmitterComponent } from '../../../object-collection/shared/mydspace-item-submitter/item-submitter.component';
@@ -39,6 +41,7 @@ import { TruncatablePartComponent } from '../../../truncatable/truncatable-part/
3941
AsyncPipe,
4042
ItemCollectionComponent,
4143
ItemSubmitterComponent,
44+
MetadataLinkViewComponent,
4245
NgClass,
4346
ThemedBadgesComponent,
4447
ThemedThumbnailComponent,
@@ -81,6 +84,8 @@ export class ItemListPreviewComponent implements OnInit {
8184

8285
dsoTitle: string;
8386

87+
authorMetadataList: MetadataValue[] = [];
88+
8489
constructor(
8590
@Inject(APP_CONFIG) protected appConfig: AppConfig,
8691
public dsoNameService: DSONameService,
@@ -90,6 +95,7 @@ export class ItemListPreviewComponent implements OnInit {
9095
ngOnInit(): void {
9196
this.showThumbnails = this.appConfig.browseBy.showThumbnails;
9297
this.dsoTitle = this.dsoNameService.getHitHighlights(this.object, this.item, true);
98+
this.authorMetadataList = this.item.allMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'], undefined, true);
9399
}
94100

95101

src/config/default-app-config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,7 @@ export class DefaultAppConfig implements AppConfig {
732732
{
733733
entityType: 'Person',
734734
metadataList: ['person.affiliation.name', 'person.email', 'person.identifier.orcid', 'dc.description.abstract'],
735+
titleMetadataList: ['person.givenName', 'person.familyName' ],
735736
},
736737
{
737738
entityType: 'OrgUnit',

src/config/metadata-link-view-popoverdata-config.interface.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,8 @@ export interface EntityDataConfig {
2020
* The list of metadata keys to display
2121
*/
2222
metadataList: string[];
23+
/**
24+
* The list of title metadata keys to display as title (optional as default is on dc.title)
25+
**/
26+
titleMetadataList?: string[];
2327
}

src/environments/environment.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,7 @@ export const environment: BuildConfig = {
529529
{
530530
entityType: 'Person',
531531
metadataList: ['person.affiliation.name', 'person.email', 'person.identifier.orcid', 'dc.description.abstract'],
532+
titleMetadataList: ['person.givenName', 'person.familyName' ],
532533
},
533534
{
534535
entityType: 'OrgUnit',

0 commit comments

Comments
 (0)