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.
*/