diff --git a/src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.html b/src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.html
new file mode 100644
index 00000000000..b0ad0f80662
--- /dev/null
+++ b/src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.scss b/src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.scss
new file mode 100644
index 00000000000..4eeee69785d
--- /dev/null
+++ b/src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.scss
@@ -0,0 +1,9 @@
+:host{
+ img {
+ height: 80px;
+ width: 80px;
+ border: 1px solid #ccc;
+ border-radius: 50%;
+ object-fit: cover;
+ }
+}
diff --git a/src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.spec.ts b/src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.spec.ts
new file mode 100644
index 00000000000..79c06b00fc9
--- /dev/null
+++ b/src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.spec.ts
@@ -0,0 +1,47 @@
+/* tslint:disable:no-unused-variable */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MetadataLinkViewAvatarPopoverComponent } from './metadata-link-view-avatar-popover.component';
+import { of as observableOf } from 'rxjs';
+import { AuthService } from '../../../core/auth/auth.service';
+import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
+import { FileService } from '../../../core/shared/file.service';
+
+describe('MetadataLinkViewAvatarPopoverComponent', () => {
+ let component: MetadataLinkViewAvatarPopoverComponent;
+ let fixture: ComponentFixture;
+ let authService;
+ let authorizationService;
+ let fileService;
+
+ beforeEach(async(() => {
+ authService = jasmine.createSpyObj('AuthService', {
+ isAuthenticated: observableOf(true),
+ });
+ authorizationService = jasmine.createSpyObj('AuthorizationService', {
+ isAuthorized: observableOf(true),
+ });
+ fileService = jasmine.createSpyObj('FileService', {
+ retrieveFileDownloadLink: null
+ });
+ TestBed.configureTestingModule({
+ declarations: [ MetadataLinkViewAvatarPopoverComponent ],
+ providers: [
+ { provide: AuthService, useValue: authService },
+ { provide: AuthorizationDataService, useValue: authorizationService },
+ { provide: FileService, useValue: fileService }
+ ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(MetadataLinkViewAvatarPopoverComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.ts b/src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.ts
new file mode 100644
index 00000000000..20c2443c45b
--- /dev/null
+++ b/src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.ts
@@ -0,0 +1,15 @@
+import { Component } from '@angular/core';
+import { ThumbnailComponent } from 'src/app/thumbnail/thumbnail.component';
+
+@Component({
+ selector: 'ds-metadata-link-view-avatar-popover',
+ templateUrl: './metadata-link-view-avatar-popover.component.html',
+ styleUrls: ['./metadata-link-view-avatar-popover.component.scss']
+})
+export class MetadataLinkViewAvatarPopoverComponent extends ThumbnailComponent {
+
+ /**
+ * The fallback image to use when the thumbnail is not available
+ */
+ fallbackImage = 'assets/images/person-placeholder.svg';
+}
diff --git a/src/app/shared/metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component.html b/src/app/shared/metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component.html
new file mode 100644
index 00000000000..7a3ae54facb
--- /dev/null
+++ b/src/app/shared/metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component.html
@@ -0,0 +1,15 @@
+
+
+ {{ metadataValue }}
+
+
+ {{ metadataValue }}
+
+
+
+
diff --git a/src/app/shared/metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component.scss b/src/app/shared/metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component.scss
new file mode 100644
index 00000000000..b92a52cd35d
--- /dev/null
+++ b/src/app/shared/metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component.scss
@@ -0,0 +1,4 @@
+.orcid-icon {
+ height: 1.2rem;
+ padding-left: 0.3rem;
+}
diff --git a/src/app/shared/metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component.spec.ts b/src/app/shared/metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component.spec.ts
new file mode 100644
index 00000000000..2e4be5cafaa
--- /dev/null
+++ b/src/app/shared/metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component.spec.ts
@@ -0,0 +1,69 @@
+import { ConfigurationDataService } from './../../../core/data/configuration-data.service';
+import { Item } from 'src/app/core/shared/item.model';
+import { TranslateLoaderMock } from './../../testing/translate-loader.mock';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MetadataLinkViewOrcidComponent } from './metadata-link-view-orcid.component';
+import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
+import { MetadataValue } from 'src/app/core/shared/metadata.models';
+import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils';
+
+describe('MetadataLinkViewOrcidComponent', () => {
+ let component: MetadataLinkViewOrcidComponent;
+ let fixture: ComponentFixture;
+
+ const configurationDataService = jasmine.createSpyObj('configurationDataService', {
+ findByPropertyName: createSuccessfulRemoteDataObject$({ values: ['https://sandbox.orcid.org'] })
+ });
+
+
+ const metadataValue = Object.assign(new MetadataValue(), {
+ 'value': '0000-0001-8918-3592',
+ 'language': 'en_US',
+ 'authority': null,
+ 'confidence': -1,
+ 'place': 0
+ });
+
+ const testItem = Object.assign(new Item(),
+ {
+ type: 'item',
+ metadata: {
+ 'person.identifier.orcid': [metadataValue],
+ 'dspace.orcid.authenticated': [
+ {
+ language: null,
+ value: 'authenticated'
+ }
+ ]
+ },
+ uuid: 'test-item-uuid',
+ }
+ );
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ MetadataLinkViewOrcidComponent ],
+ imports: [TranslateModule.forRoot({
+ loader: {
+ provide: TranslateLoader,
+ useClass: TranslateLoaderMock
+ }
+ }), BrowserAnimationsModule],
+ providers: [
+ { provide: ConfigurationDataService, useValue: configurationDataService}
+ ],
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(MetadataLinkViewOrcidComponent);
+ component = fixture.componentInstance;
+ component.itemValue = testItem;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/shared/metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component.ts b/src/app/shared/metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component.ts
new file mode 100644
index 00000000000..a0252732c71
--- /dev/null
+++ b/src/app/shared/metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component.ts
@@ -0,0 +1,46 @@
+import { ConfigurationProperty } from './../../../core/shared/configuration-property.model';
+import { getFirstSucceededRemoteDataPayload } from './../../../core/shared/operators';
+import { ConfigurationDataService } from './../../../core/data/configuration-data.service';
+import { Component, Input, OnInit } from '@angular/core';
+import { Item } from '../../../core/shared/item.model';
+import { Observable, map } from 'rxjs';
+
+@Component({
+ selector: 'ds-metadata-link-view-orcid',
+ templateUrl: './metadata-link-view-orcid.component.html',
+ styleUrls: ['./metadata-link-view-orcid.component.scss'],
+})
+export class MetadataLinkViewOrcidComponent implements OnInit {
+ /**
+ * Item value to display the metadata for
+ */
+ @Input() itemValue: Item;
+
+ metadataValue: string;
+
+ orcidUrl$: Observable;
+
+ constructor(protected configurationService: ConfigurationDataService) {}
+
+ ngOnInit(): void {
+ this.orcidUrl$ = this.configurationService
+ .findByPropertyName('orcid.domain-url')
+ .pipe(
+ getFirstSucceededRemoteDataPayload(),
+ map((property: ConfigurationProperty) =>
+ property?.values?.length > 0 ? property.values[0] : null
+ )
+ );
+ this.metadataValue = this.itemValue.firstMetadataValue(
+ 'person.identifier.orcid'
+ );
+ }
+
+ public hasOrcid(): boolean {
+ return this.itemValue.hasMetadata('person.identifier.orcid');
+ }
+
+ public hasOrcidBadge(): boolean {
+ return this.itemValue.hasMetadata('dspace.orcid.authenticated');
+ }
+}
diff --git a/src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.html b/src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.html
new file mode 100644
index 00000000000..e53886331fe
--- /dev/null
+++ b/src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.html
@@ -0,0 +1,62 @@
+
+
+
+
+ {{item.firstMetadataValue('dc.title')}}
+
+
+
+
+
+ {{ "metadata-link-view.popover.label." + (isOtherEntityType ? "other" : item.entityType) + "." + metadata | translate }}
+
+
+
+
+
+
+
diff --git a/src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.scss b/src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.scss
new file mode 100644
index 00000000000..0ddfc98b6cd
--- /dev/null
+++ b/src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.scss
@@ -0,0 +1,6 @@
+.source-icon {
+ height: var(--ds-identifier-sybetype-icon-height);
+ min-height: 16px;
+ width: auto;
+ padding-left: 0.3rem;
+}
diff --git a/src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.spec.ts b/src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.spec.ts
new file mode 100644
index 00000000000..182902de7f7
--- /dev/null
+++ b/src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.spec.ts
@@ -0,0 +1,131 @@
+import { MetadataValueFilter } from 'src/app/core/shared/metadata.models';
+import { Item } from 'src/app/core/shared/item.model';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MetadataLinkViewPopoverComponent } from './metadata-link-view-popover.component';
+import { environment } from 'src/environments/environment.test';
+import { TranslateModule } from '@ngx-translate/core';
+import { NO_ERRORS_SCHEMA } from '@angular/core';
+import { By } from '@angular/platform-browser';
+import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils';
+import { Bitstream } from 'src/app/core/shared/bitstream.model';
+
+describe('MetadataLinkViewPopoverComponent', () => {
+ let component: MetadataLinkViewPopoverComponent;
+ let fixture: ComponentFixture;
+
+
+ const itemMock = Object.assign(new Item(), {
+ uuid: '1234-1234-1234-1234',
+
+ firstMetadataValue(keyOrKeys: string | string[], valueFilter?: MetadataValueFilter): string {
+ return itemMock.metadata[keyOrKeys as string][0].value;
+ },
+
+ metadata: {
+ 'dc.title': [
+ {
+ value: 'file name',
+ language: null
+ }
+ ],
+ 'dc.identifier.uri': [
+ {
+ value: 'http://example.com',
+ language: null
+ }
+ ],
+ 'dc.description.abstract': [
+ {
+ value: 'Long text description',
+ language: null
+ }
+ ],
+ 'organization.identifier.ror': [
+ {
+ value: 'https://ror.org/1234',
+ language: null
+ }
+ ],
+ 'person.identifier.orcid': [
+ {
+ value: 'https://orcid.org/0000-0000-0000-0000',
+ language: null
+ }
+ ],
+ 'dspace.entity.type': [
+ {
+ value: 'Person',
+ language: null
+ }
+ ]
+ },
+ thumbnail: createSuccessfulRemoteDataObject$(new Bitstream())
+ });
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ MetadataLinkViewPopoverComponent ],
+ imports: [TranslateModule.forRoot()],
+ schemas: [NO_ERRORS_SCHEMA]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(MetadataLinkViewPopoverComponent);
+ component = fixture.componentInstance;
+ component.item = itemMock;
+ itemMock.firstMetadataValue = jasmine.createSpy()
+ .withArgs('dspace.entity.type').and.returnValue('Person')
+ .withArgs('dc.title').and.returnValue('Test Title')
+ .withArgs('dc.identifier.uri').and.returnValue('http://example.com')
+ .withArgs('dc.description.abstract').and.returnValue('Long text description')
+ .withArgs('organization.identifier.ror').and.returnValue('https://ror.org/1234')
+ .withArgs('person.identifier.orcid').and.returnValue('https://orcid.org/0000-0000-0000-0000');
+
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should display the item title', () => {
+ const titleElement = fixture.debugElement.query(By.css('.font-weight-bold.h4'));
+ expect(titleElement.nativeElement.textContent).toContain('Test Title');
+ });
+
+ it('should display a link for each metadata field that is a valid link', () => {
+ component.entityMetdataFields = ['dc.identifier.uri'];
+ fixture.detectChanges();
+ const linkElement = fixture.debugElement.query(By.css('a[href="http://example.com"]'));
+ expect(linkElement).toBeTruthy();
+ });
+
+ it('should retrieve the identifier subtype configuration based on the given metadata value', () => {
+ const metadataValue = 'organization.identifier.ror';
+ const expectedSubtypeConfig = environment.identifierSubtypes.find((config) => config.name === 'ror');
+ expect(component.getSourceSubTypeIdentifier(metadataValue)).toEqual(expectedSubtypeConfig);
+ });
+
+
+ it('should check if a given metadata value is a valid link', () => {
+ const validLink = 'http://example.com';
+ const invalidLink = 'not a link';
+ expect(component.isLink(validLink)).toBeTrue();
+ expect(component.isLink(invalidLink)).toBeFalse();
+ });
+
+ it('should display the "more info" link with the correct router link', () => {
+ spyOn(component, 'getItemPageRoute').and.returnValue('/item/' + itemMock.uuid);
+ fixture.detectChanges();
+ const moreInfoLinkElement = fixture.debugElement.query(By.css('a[data-test="more-info-link"]'));
+ expect(moreInfoLinkElement.nativeElement.routerLink).toContain('/item/' + itemMock.uuid);
+ });
+
+ it('should display the avatar popover when item has a thumbnail', () => {
+ const avatarPopoverElement = fixture.debugElement.query(By.css('ds-metadata-link-view-avatar-popover'));
+ expect(avatarPopoverElement).toBeTruthy();
+ });
+});
diff --git a/src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.ts b/src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.ts
new file mode 100644
index 00000000000..9984621835e
--- /dev/null
+++ b/src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.ts
@@ -0,0 +1,94 @@
+import { IdentifierSubtypesConfig } from './../../../../config/identifier-subtypes-config.interface';
+import { MetadataLinkViewPopoverDataConfig } from 'src/config/metadata-link-view-popoverdata-config.interface';
+import { Item } from './../../../core/shared/item.model';
+import { Component, Input, OnInit } from '@angular/core';
+import { environment } from 'src/environments/environment';
+import { hasNoValue, hasValue } from '../../empty.util';
+
+import { AuthorithyIcon } from 'src/config/submission-config.interface';
+import { getItemPageRoute } from 'src/app/item-page/item-page-routing-paths';
+
+@Component({
+ selector: 'ds-metadata-link-view-popover',
+ templateUrl: './metadata-link-view-popover.component.html',
+ styleUrls: ['./metadata-link-view-popover.component.scss']
+})
+export class MetadataLinkViewPopoverComponent implements OnInit {
+
+ /**
+ * The item to display the metadata for
+ */
+ @Input() item: Item;
+
+ /**
+ * The metadata link view popover data configuration.
+ * This configuration is used to determine which metadata fields to display for the given entity type
+ */
+ metadataLinkViewPopoverData: MetadataLinkViewPopoverDataConfig = environment.metadataLinkViewPopoverData;
+
+ /**
+ * The metadata fields to display for the given entity type
+ */
+ entityMetdataFields: string[] = [];
+
+ /**
+ * The metadata fields including long text metadata values.
+ * These metadata values should be truncated to a certain length.
+ */
+ longTextMetadataList = ['dc.description.abstract', 'dc.description'];
+
+ /**
+ * The source icons configuration
+ */
+ sourceIcons: AuthorithyIcon[] = environment.submission.icons.authority.sourceIcons;
+
+ /**
+ * The identifier subtype configurations
+ */
+ identifierSubtypeConfig: IdentifierSubtypesConfig[] = environment.identifierSubtypes;
+
+ /**
+ * Whether the entity type is not found in the metadataLinkViewPopoverData configuration
+ */
+ isOtherEntityType = false;
+
+ /**
+ * If `metadataLinkViewPopoverData` is provided, it retrieves the metadata fields based on the entity type.
+ * If no metadata fields are found for the entity type, it falls back to the fallback metadata list.
+ */
+ ngOnInit() {
+ if (this.metadataLinkViewPopoverData) {
+ const metadataFields = this.metadataLinkViewPopoverData.entityDataConfig.find((config) => config.entityType === this.item.entityType);
+ this.entityMetdataFields = hasValue(metadataFields) ? metadataFields.metadataList : this.metadataLinkViewPopoverData.fallbackMetdataList;
+ this.isOtherEntityType = hasNoValue(metadataFields);
+ }
+ }
+
+ /**
+ * Checks if the given metadata value is a valid link.
+ */
+ isLink(metadataValue: string): boolean {
+ const urlRegex = /^(http|https):\/\/[^ "]+$/;
+ return urlRegex.test(metadataValue);
+ }
+
+ /**
+ * Returns the page route for the item.
+ * @returns The page route for the item.
+ */
+ getItemPageRoute(): string {
+ return getItemPageRoute(this.item);
+ }
+
+ /**
+ * Retrieves the identifier subtype configuration based on the given metadata value.
+ * @param metadataValue - The metadata value used to determine the identifier subtype.
+ * @returns The identifier subtype configuration object.
+ */
+ getSourceSubTypeIdentifier(metadataValue: string): IdentifierSubtypesConfig {
+ const metadataValueSplited = metadataValue.split('.');
+ const subtype = metadataValueSplited[metadataValueSplited.length - 1];
+ const identifierSubtype = this.identifierSubtypeConfig.find((config) => config.name === subtype);
+ return identifierSubtype;
+ }
+}
diff --git a/src/app/shared/metadata-link-view/metadata-link-view.component.html b/src/app/shared/metadata-link-view/metadata-link-view.component.html
index b9dc21d1b32..d7fb1150e7b 100644
--- a/src/app/shared/metadata-link-view/metadata-link-view.component.html
+++ b/src/app/shared/metadata-link-view/metadata-link-view.component.html
@@ -2,15 +2,24 @@
-
-
- {{metadataView.value}}
-
+
+
+
+ {{metadataView.value}}
+
+
+
{{normalizeValue(metadataView.value)}}
+
+
+
+
+
diff --git a/src/app/shared/metadata-link-view/metadata-link-view.component.scss b/src/app/shared/metadata-link-view/metadata-link-view.component.scss
index b92a52cd35d..e9051149d7f 100644
--- a/src/app/shared/metadata-link-view/metadata-link-view.component.scss
+++ b/src/app/shared/metadata-link-view/metadata-link-view.component.scss
@@ -2,3 +2,9 @@
height: 1.2rem;
padding-left: 0.3rem;
}
+
+
+::ng-deep .popover {
+ max-width: 400px !important;
+ min-width: 300px !important;
+}
diff --git a/src/app/shared/metadata-link-view/metadata-link-view.component.ts b/src/app/shared/metadata-link-view/metadata-link-view.component.ts
index 800897f3387..c38e1cc2dac 100644
--- a/src/app/shared/metadata-link-view/metadata-link-view.component.ts
+++ b/src/app/shared/metadata-link-view/metadata-link-view.component.ts
@@ -13,14 +13,8 @@ import { getFirstCompletedRemoteData } from '../../core/shared/operators';
import { Metadata } from '../../core/shared/metadata.utils';
import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { environment } from '../../../environments/environment';
-
-interface MetadataView {
- authority: string;
- value: string;
- orcidAuthenticated: string;
- entityType: string;
- entityStyle: string|string[];
-}
+import { followLink } from '../utils/follow-link-config.model';
+import { MetadataView } from './metadata-view.model';
@Component({
selector: 'ds-metadata-link-view',
@@ -58,6 +52,11 @@ export class MetadataLinkViewComponent implements OnInit {
*/
iconPosition = 'after';
+ /**
+ * Related item of the metadata value
+ */
+ relatedItem: Item;
+
/**
* Map all entities with the icons specified in the environment configuration file
*/
@@ -68,45 +67,67 @@ export class MetadataLinkViewComponent implements OnInit {
*/
ngOnInit(): void {
this.metadataView$ = observableOf(this.metadata).pipe(
- switchMap((metadataValue: MetadataValue) => {
- if (Metadata.hasValidAuthority(metadataValue.authority)) {
- return this.itemService.findById(metadataValue.authority).pipe(
- getFirstCompletedRemoteData(),
- map((itemRD: RemoteData- ) => {
- if (itemRD.hasSucceeded) {
- const entityStyleValue = this.getCrisRefMetadata(itemRD.payload?.entityType);
- return {
- authority: metadataValue.authority,
- value: metadataValue.value,
- orcidAuthenticated: this.getOrcid(itemRD.payload),
- entityType: itemRD.payload?.entityType,
- entityStyle: itemRD.payload?.firstMetadataValue(entityStyleValue)
- };
- } else {
- return {
- authority: null,
- value: metadataValue.value,
- orcidAuthenticated: null,
- entityType: 'PRIVATE',
- entityStyle: this.metadataName
- };
- }
- })
- );
- } else {
- return observableOf({
- authority: null,
- value: metadataValue.value,
- orcidAuthenticated: null,
- entityType: null,
- entityStyle: null
- });
- }
- }),
+ switchMap((metadataValue: MetadataValue) => this.getMetadataView(metadataValue)),
take(1)
);
}
+
+ /**
+ * Retrieves the metadata view for a given metadata value.
+ * If the metadata value has a valid authority, it retrieves the item using the authority and creates a metadata view.
+ * If the metadata value does not have a valid authority, it creates a metadata view with null values.
+ *
+ * @param metadataValue The metadata value for which to retrieve the metadata view.
+ * @returns An Observable that emits the metadata view.
+ */
+ private getMetadataView(metadataValue: MetadataValue): Observable {
+ const linksToFollow = [followLink('thumbnail')];
+
+ if (Metadata.hasValidAuthority(metadataValue.authority)) {
+ return this.itemService.findById(metadataValue.authority, true, false, ...linksToFollow).pipe(
+ getFirstCompletedRemoteData(),
+ map((itemRD: RemoteData
- ) => this.createMetadataView(itemRD, metadataValue))
+ );
+ } else {
+ return observableOf({
+ authority: null,
+ value: metadataValue.value,
+ orcidAuthenticated: null,
+ entityType: null,
+ entityStyle: null
+ });
+ }
+ }
+
+ /**
+ * Creates a MetadataView object based on the provided itemRD and metadataValue.
+ * @param itemRD - The RemoteData object containing the item information.
+ * @param metadataValue - The MetadataValue object containing the metadata information.
+ * @returns The created MetadataView object.
+ */
+ private createMetadataView(itemRD: RemoteData
- , metadataValue: MetadataValue): MetadataView {
+ if (itemRD.hasSucceeded) {
+ this.relatedItem = itemRD.payload;
+ const entityStyleValue = this.getCrisRefMetadata(itemRD.payload?.entityType);
+ return {
+ authority: metadataValue.authority,
+ value: metadataValue.value,
+ orcidAuthenticated: this.getOrcid(itemRD.payload),
+ entityType: itemRD.payload?.entityType,
+ entityStyle: itemRD.payload?.firstMetadataValue(entityStyleValue)
+ };
+ } else {
+ return {
+ authority: null,
+ value: metadataValue.value,
+ orcidAuthenticated: null,
+ entityType: 'PRIVATE',
+ entityStyle: this.metadataName
+ };
+ }
+ }
+
/**
* Returns the orcid for given item, or null if there is no metadata authenticated for person
*
diff --git a/src/app/shared/metadata-link-view/metadata-view.model.ts b/src/app/shared/metadata-link-view/metadata-view.model.ts
new file mode 100644
index 00000000000..569418d0b24
--- /dev/null
+++ b/src/app/shared/metadata-link-view/metadata-view.model.ts
@@ -0,0 +1,7 @@
+export interface MetadataView {
+ authority: string;
+ value: string;
+ orcidAuthenticated: string;
+ entityType: string;
+ entityStyle: string|string[];
+}
diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts
index e4e88c3c9ea..63337db4359 100644
--- a/src/app/shared/shared.module.ts
+++ b/src/app/shared/shared.module.ts
@@ -352,6 +352,9 @@ import { ItemCollectionComponent } from './object-collection/shared/mydspace-ite
import { ThemedItemListPreviewComponent } from './object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component';
import { ItemListPreviewComponent } from './object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component';
import { MarkdownDirective } from './utils/markdown.directive';
+import { MetadataLinkViewPopoverComponent } from './metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component';
+import { MetadataLinkViewAvatarPopoverComponent } from './metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component';
+import { MetadataLinkViewOrcidComponent } from './metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component';
const MODULES = [
CommonModule,
@@ -502,6 +505,9 @@ const COMPONENTS = [
BrowseMostElementsComponent,
EditMetadataSecurityComponent,
MetadataLinkViewComponent,
+ MetadataLinkViewPopoverComponent,
+ MetadataLinkViewAvatarPopoverComponent,
+ MetadataLinkViewOrcidComponent,
ExportExcelSelectorComponent,
ThemedBrowseMostElementsComponent,
SearchChartBarHorizontalComponent,
diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5
index 9532bf14ec0..94ca8ed18bc 100644
--- a/src/assets/i18n/en.json5
+++ b/src/assets/i18n/en.json5
@@ -7423,4 +7423,52 @@
"meta.tag.missing.description": "No description available",
+ "metadata-link-view.popover.label.Person.dc.title": "Fullname",
+
+ "metadata-link-view.popover.label.Person.person.affiliation.name": "Main affiliation",
+
+ "metadata-link-view.popover.label.Person.person.email": "Email",
+
+ "metadata-link-view.popover.label.Person.person.identifier.orcid": "ORCID",
+
+ "metadata-link-view.popover.label.Person.dc.description.abstract": "Abstract",
+
+ "metadata-link-view.popover.label.OrgUnit.dc.title": "Fullname",
+
+ "metadata-link-view.popover.label.OrgUnit.organization.identifier.ror": "ROR",
+
+ "metadata-link-view.popover.label.OrgUnit.crisou.director": "Director",
+
+ "metadata-link-view.popover.label.OrgUnit.organization.parentOrganization": "Parent Organization",
+
+ "metadata-link-view.popover.label.OrgUnit.dc.description.abstract": "Description",
+
+ "metadata-link-view.popover.label.Project.dc.title": "Title",
+
+ "metadata-link-view.popover.label.Project.oairecerif.project.status": "Status",
+
+ "metadata-link-view.popover.label.Project.dc.description.abstract": "Abstract",
+
+ "metadata-link-view.popover.label.Funding.dc.title": "Title",
+
+ "metadata-link-view.popover.label.Funding.oairecerif.funder": "Funder",
+
+ "metadata-link-view.popover.label.Funding.oairecerif.fundingProgram": "Funding program",
+
+ "metadata-link-view.popover.label.Funding.dc.description.abstract": "Abstract",
+
+ "metadata-link-view.popover.label.Publication.dc.title": "Title",
+
+ "metadata-link-view.popover.label.Publication.dc.identifier.doi": "DOI",
+
+ "metadata-link-view.popover.label.Publication.dc.identifier.uri": "URL",
+
+ "metadata-link-view.popover.label.Publication.dc.description.abstract": "Abstract",
+
+ "metadata-link-view.popover.label.other.dc.title": "Title",
+
+ "metadata-link-view.popover.label.other.dc.description.abstract": "Description",
+
+ "metadata-link-view.popover.label.more-info": "More info",
+
}
diff --git a/src/config/app-config.interface.ts b/src/config/app-config.interface.ts
index 5b21141695d..589999d4746 100644
--- a/src/config/app-config.interface.ts
+++ b/src/config/app-config.interface.ts
@@ -34,6 +34,7 @@ import { SearchResultConfig } from './search-result-config.interface';
import { MiradorConfig } from './mirador-config.interfaces';
import { LoaderConfig } from './loader-config.interfaces';
import { MetaTagsConfig } from './meta-tags.config';
+import { MetadataLinkViewPopoverDataConfig } from './metadata-link-view-popoverdata-config.interface';
import { IdentifierSubtypesConfig } from './identifier-subtypes-config.interface';
import { DatadogRumConfig } from './datadog-rum-config.interfaces';
@@ -76,6 +77,7 @@ interface AppConfig extends Config {
mirador: MiradorConfig;
loader: LoaderConfig;
metaTags: MetaTagsConfig;
+ metadataLinkViewPopoverData: MetadataLinkViewPopoverDataConfig;
identifierSubtypes: IdentifierSubtypesConfig[];
datadogRum?: DatadogRumConfig;
}
diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts
index 8ad8c884b71..e9c23e43cab 100644
--- a/src/config/default-app-config.ts
+++ b/src/config/default-app-config.ts
@@ -37,6 +37,7 @@ import { SearchResultConfig } from './search-result-config.interface';
import { MiradorConfig } from './mirador-config.interfaces';
import { LoaderConfig } from './loader-config.interfaces';
import { MetaTagsConfig } from './meta-tags.config';
+import { MetadataLinkViewPopoverDataConfig } from './metadata-link-view-popoverdata-config.interface';
import { IdentifierSubtypesConfig, IdentifierSubtypesIconPositionEnum } from './identifier-subtypes-config.interface';
import { DatadogRumConfig } from './datadog-rum-config.interfaces';
@@ -807,6 +808,35 @@ export class DefaultAppConfig implements AppConfig {
'DSpace-CRIS enables secure, integrated and interoperable research information and data management – in a single solution.'
};
+ // Configuration for the metadata link view popover
+ metadataLinkViewPopoverData: MetadataLinkViewPopoverDataConfig =
+ {
+ fallbackMetdataList: ['dc.description.abstract'],
+
+ entityDataConfig: [
+ {
+ entityType: 'Person',
+ metadataList: ['person.affiliation.name', 'person.email', 'person.identifier.orcid', 'dc.description.abstract']
+ },
+ {
+ entityType: 'OrgUnit',
+ metadataList: ['organization.parentOrganization', 'organization.identifier.ror', 'crisou.director', 'dc.description.abstract']
+ },
+ {
+ entityType: 'Project',
+ metadataList: ['oairecerif.project.status', 'dc.description.abstract']
+ },
+ {
+ entityType: 'Funding',
+ metadataList: ['oairecerif.funder', 'oairecerif.fundingProgram', 'dc.description.abstract']
+ },
+ {
+ entityType: 'Publication',
+ metadataList: ['dc.identifier.doi', 'dc.identifier.uri', 'dc.description.abstract']
+ },
+ ]
+ };
+
identifierSubtypes: IdentifierSubtypesConfig[] = [
{
name: 'ror',
diff --git a/src/config/metadata-link-view-popoverdata-config.interface.ts b/src/config/metadata-link-view-popoverdata-config.interface.ts
new file mode 100644
index 00000000000..01ee9a80723
--- /dev/null
+++ b/src/config/metadata-link-view-popoverdata-config.interface.ts
@@ -0,0 +1,23 @@
+export interface MetadataLinkViewPopoverDataConfig {
+ /**
+ * The list of entity types to display the metadata for
+ */
+ entityDataConfig: EntityDataConfig[];
+
+ /**
+ * The list of metadata keys to fallback to
+ */
+ fallbackMetdataList: string[];
+}
+
+
+export interface EntityDataConfig {
+ /**
+ * The metadata entity type
+ */
+ entityType: string;
+ /**
+ * The list of metadata keys to display
+ */
+ metadataList: string[];
+}
diff --git a/src/config/submission-config.interface.ts b/src/config/submission-config.interface.ts
index c180bfae014..935323ec777 100644
--- a/src/config/submission-config.interface.ts
+++ b/src/config/submission-config.interface.ts
@@ -10,7 +10,7 @@ interface TypeBindConfig extends Config {
field: string;
}
-interface AuthorithyIcon {
+export interface AuthorithyIcon {
source: string,
path: string
}
diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts
index 364590dab37..ce98bc1ae65 100644
--- a/src/environments/environment.test.ts
+++ b/src/environments/environment.test.ts
@@ -598,5 +598,33 @@ export const environment: BuildConfig = {
iconPosition: IdentifierSubtypesIconPositionEnum.LEFT,
link: 'https://ror.org'
}
- ]
+ ],
+ // Configuration for the metadata link view popover
+ metadataLinkViewPopoverData:
+ {
+ fallbackMetdataList: ['dc.description.abstract'],
+
+ entityDataConfig: [
+ {
+ entityType: 'Person',
+ metadataList: ['person.affiliation.name', 'person.email', 'person.identifier.orcid', 'dc.description.abstract']
+ },
+ {
+ entityType: 'OrgUnit',
+ metadataList: ['organization.parentOrganization', 'organization.identifier.ror', 'crisou.director', 'dc.description.abstract']
+ },
+ {
+ entityType: 'Project',
+ metadataList: ['oairecerif.project.status', 'dc.description.abstract']
+ },
+ {
+ entityType: 'Funding',
+ metadataList: ['oairecerif.funder', 'oairecerif.fundingProgram', 'dc.description.abstract']
+ },
+ {
+ entityType: 'Publication',
+ metadataList: ['dc.identifier.doi', 'dc.identifier.uri', 'dc.description.abstract']
+ },
+ ]
+ },
};