diff --git a/src/app/item-page/full/full-item-page.component.html b/src/app/item-page/full/full-item-page.component.html index 0e0db894b1d..63ff0a2441b 100644 --- a/src/app/item-page/full/full-item-page.component.html +++ b/src/app/item-page/full/full-item-page.component.html @@ -24,7 +24,13 @@ @for (mdValue of mdEntry.value; track mdValue) { {{mdEntry.key}} - {{mdValue.value}} + + @if (isHttpUrl(mdValue.value)) { + {{mdValue.value.trim()}} + } @else { + {{mdValue.value}} + } + {{mdValue.language}} } diff --git a/src/app/item-page/full/full-item-page.component.spec.ts b/src/app/item-page/full/full-item-page.component.spec.ts index ac2b4634a83..cab8dd68ff2 100644 --- a/src/app/item-page/full/full-item-page.component.spec.ts +++ b/src/app/item-page/full/full-item-page.component.spec.ts @@ -65,6 +65,30 @@ const mockItem: Item = Object.assign(new Item(), { }, }); +const mockItemWithUrl: Item = Object.assign(new Item(), { + bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])), + metadata: { + 'dc.title': [ + { + language: 'en_US', + value: 'test item', + }, + ], + 'dc.identifier.uri': [ + { + language: null, + value: 'https://hdl.handle.net/123456789/1', + }, + ], + 'dc.subject': [ + { + language: 'en_US', + value: 'plain text value', + }, + ], + }, +}); + const mockWithdrawnItem: Item = Object.assign(new Item(), { bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])), metadata: [], @@ -265,4 +289,51 @@ describe('FullItemPageComponent', () => { expect(linkHeadService.addTag).toHaveBeenCalledTimes(3); }); }); + + describe('isHttpUrl', () => { + it('should return true for https URLs', () => { + expect(comp.isHttpUrl('https://example.com')).toBeTrue(); + }); + + it('should return true for http URLs', () => { + expect(comp.isHttpUrl('http://example.com')).toBeTrue(); + }); + + it('should return false for plain text', () => { + expect(comp.isHttpUrl('just some text')).toBeFalse(); + }); + + it('should return false for null', () => { + expect(comp.isHttpUrl(null)).toBeFalse(); + }); + + it('should return false for undefined', () => { + expect(comp.isHttpUrl(undefined)).toBeFalse(); + }); + }); + + describe('metadata URL rendering', () => { + beforeEach(() => { + routeData.dso = createSuccessfulRemoteDataObject(mockItemWithUrl); + comp.ngOnInit(); + fixture.detectChanges(); + }); + + it('should render URL metadata values as clickable links', () => { + const links = fixture.debugElement.queryAll(By.css('table a')); + const urlLink = links.find(l => l.nativeElement.textContent.includes('https://hdl.handle.net/123456789/1')); + expect(urlLink).toBeTruthy(); + expect(urlLink.nativeElement.getAttribute('href')).toBe('https://hdl.handle.net/123456789/1'); + expect(urlLink.nativeElement.getAttribute('target')).toBe('_blank'); + expect(urlLink.nativeElement.getAttribute('rel')).toBe('noopener noreferrer'); + }); + + it('should render non-URL metadata values as plain text', () => { + const table = fixture.debugElement.query(By.css('table')); + const links = fixture.debugElement.queryAll(By.css('table a')); + expect(table.nativeElement.innerHTML).toContain('plain text value'); + const plainTextLink = links.find(l => l.nativeElement.textContent.includes('plain text value')); + expect(plainTextLink).toBeFalsy(); + }); + }); }); diff --git a/src/app/item-page/full/full-item-page.component.ts b/src/app/item-page/full/full-item-page.component.ts index 87b01f21e97..10ccafdc762 100644 --- a/src/app/item-page/full/full-item-page.component.ts +++ b/src/app/item-page/full/full-item-page.component.ts @@ -119,6 +119,14 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit, ); } + /** + * Check if a metadata value is an HTTP(S) URL. + */ + isHttpUrl(value: string | null | undefined): boolean { + const v = value?.trim().toLowerCase(); + return !!v && (v.startsWith('http://') || v.startsWith('https://')); + } + /** * Navigate back in browser history. */