diff --git a/src/app/item-page/clarin-ref-citation/clarin-ref-citation.component.html b/src/app/item-page/clarin-ref-citation/clarin-ref-citation.component.html index 9c205dce27a..407f1963e93 100644 --- a/src/app/item-page/clarin-ref-citation/clarin-ref-citation.component.html +++ b/src/app/item-page/clarin-ref-citation/clarin-ref-citation.component.html @@ -15,7 +15,7 @@ {{citationText + ', '}} {{itemNameText + ', '}} {{repositoryNameText + ', '}} - {{identifierURI}} + {{prettifiedIdentifier | async}}
diff --git a/src/app/item-page/clarin-ref-citation/clarin-ref-citation.component.ts b/src/app/item-page/clarin-ref-citation/clarin-ref-citation.component.ts index ee24711cd5e..37edb07f702 100644 --- a/src/app/item-page/clarin-ref-citation/clarin-ref-citation.component.ts +++ b/src/app/item-page/clarin-ref-citation/clarin-ref-citation.component.ts @@ -10,6 +10,11 @@ import { GetRequest } from '../../core/data/request.models'; import { RequestService } from '../../core/data/request.service'; import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service'; import { HALEndpointService } from '../../core/shared/hal-endpoint.service'; +import { BehaviorSubject } from 'rxjs'; +import { + DOI_METADATA_FIELD, HANDLE_METADATA_FIELD, +} from '../simple/field-components/clarin-generic-item-field/clarin-generic-item-field.component'; +import { ItemIdentifierService } from '../../shared/item-identifier.service'; /** * If the item has more authors do not add all authors to the citation but add there a shortcut. @@ -57,6 +62,14 @@ export class ClarinRefCitationComponent implements OnInit { * The nam of the organization which provides the repository */ repositoryNameText: string; + /** + * BehaviorSubject to store the prettified identifier. + */ + prettifiedIdentifier: BehaviorSubject = new BehaviorSubject(null); + /** + * The item has DOI or not. + */ + hasDoi = false; constructor(private configurationService: ConfigurationDataService, private clipboard: Clipboard, @@ -64,7 +77,8 @@ export class ClarinRefCitationComponent implements OnInit { private modalService: NgbModal, private requestService: RequestService, protected rdbService: RemoteDataBuildService, - protected halService: HALEndpointService,) { + protected halService: HALEndpointService, + private itemIdentifierService: ItemIdentifierService) { // Configure the tooltip to show on click - `Copied` message config.triggers = 'click'; } @@ -79,10 +93,15 @@ export class ClarinRefCitationComponent implements OnInit { return textValue !== null; }); + this.hasDoi = this.hasItemDoi(); this.citationText = citationArray.join(', '); this.itemNameText = this.getTitle(); - this.identifierURI = this.getIdentifierUri(); - this.getRepositoryName().then(res => { + this.identifierURI = this.getIdentifierUri(this.whichIdentifierMetadataField()); + void this.itemIdentifierService.prettifyIdentifier(this.identifierURI, [this.whichIdentifierMetadataField()]) + .then((value: string) => { + this.prettifiedIdentifier.next(value); + }); + void this.getRepositoryName().then(res => { this.repositoryNameText = res?.payload?.values?.[0]; }); } @@ -104,18 +123,30 @@ export class ClarinRefCitationComponent implements OnInit { .pipe(getFirstSucceededRemoteData()).toPromise(); } - getIdentifierUri() { - const handleMetadata = this.item.metadata['dc.identifier.uri']; - if (isUndefined(handleMetadata) || isNull(handleMetadata)) { - return null; - } + /** + * Get the identifier URI from the item metadata. If the item has DOI, return the DOI, otherwise return the handle. + */ + getIdentifierUri(identifierMetadataField) { + return this.item.firstMetadataValue(identifierMetadataField); + } + + /** + * Check if the item has DOI. + */ + hasItemDoi() { + return this.item?.allMetadata(DOI_METADATA_FIELD)?.length > 0; + } - return handleMetadata?.[0]?.value; + /** + * If the item has DOI, return the DOI metadata field, otherwise return the handle metadata field. + */ + whichIdentifierMetadataField() { + return this.hasDoi ? DOI_METADATA_FIELD : HANDLE_METADATA_FIELD; } getHandle() { // Separate the handle from the full URI - const fullUri = this.getIdentifierUri(); + const fullUri = this.getIdentifierUri(this.whichIdentifierMetadataField()); const handleWord = 'handle/'; const startHandleIndex = fullUri.indexOf('handle/') + handleWord.length; return fullUri.substr(startHandleIndex); diff --git a/src/app/item-page/simple/field-components/clarin-generic-item-field/clarin-generic-item-field.component.ts b/src/app/item-page/simple/field-components/clarin-generic-item-field/clarin-generic-item-field.component.ts index ec73516b638..e8174206450 100644 --- a/src/app/item-page/simple/field-components/clarin-generic-item-field/clarin-generic-item-field.component.ts +++ b/src/app/item-page/simple/field-components/clarin-generic-item-field/clarin-generic-item-field.component.ts @@ -10,8 +10,8 @@ import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/oper import { map } from 'rxjs/operators'; export const DOI_METADATA_FIELD = 'dc.identifier.doi'; +export const HANDLE_METADATA_FIELD = 'dc.identifier.uri'; const SHOW_HANDLE_AND_DOI_PROPERTY_NAME = 'item-page.show-handle-and-doi'; -const HANDLE_METADATA_FIELD = 'dc.identifier.uri'; @Component({ selector: 'ds-clarin-generic-item-field', diff --git a/src/app/item-page/simple/field-components/clarin-identifier-item-field/clarin-identifier-item-field.component.spec.ts b/src/app/item-page/simple/field-components/clarin-identifier-item-field/clarin-identifier-item-field.component.spec.ts index 31cde074d2b..70269c8bedf 100644 --- a/src/app/item-page/simple/field-components/clarin-identifier-item-field/clarin-identifier-item-field.component.spec.ts +++ b/src/app/item-page/simple/field-components/clarin-identifier-item-field/clarin-identifier-item-field.component.spec.ts @@ -1,16 +1,16 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ClarinIdentifierItemFieldComponent } from './clarin-identifier-item-field.component'; -import { ConfigurationDataService } from '../../../../core/data/configuration-data.service'; import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils'; -import { ConfigurationProperty } from '../../../../core/shared/configuration-property.model'; import { Item } from '../../../../core/shared/item.model'; import { createPaginatedList } from '../../../../shared/testing/utils.test'; import { DOI_METADATA_FIELD } from '../clarin-generic-item-field/clarin-generic-item-field.component'; +import { ItemIdentifierService } from '../../../../shared/item-identifier.service'; describe('ClarinIdentifierItemFieldComponent', () => { let component: ClarinIdentifierItemFieldComponent; let fixture: ComponentFixture; + let itemIdentifierService: ItemIdentifierService; const mockItem: Item = Object.assign(new Item(), { bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])), @@ -25,13 +25,8 @@ describe('ClarinIdentifierItemFieldComponent', () => { }); beforeEach(async () => { - const configurationServiceSpy = jasmine.createSpyObj('configurationService', { - findByPropertyName: createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), { - name: 'test', - values: [ - true - ] - })), + itemIdentifierService = jasmine.createSpyObj('itemIdentifierService', { + prettifyIdentifier: new Promise((res, rej) => { return 'awesome identifier'; }), }); await TestBed.configureTestingModule({ @@ -40,7 +35,7 @@ describe('ClarinIdentifierItemFieldComponent', () => { ], declarations: [ ClarinIdentifierItemFieldComponent ], providers: [ - { provide: ConfigurationDataService, useValue: configurationServiceSpy } + { provide: ItemIdentifierService, useValue: itemIdentifierService } ] }) .compileComponents(); diff --git a/src/app/item-page/simple/field-components/clarin-identifier-item-field/clarin-identifier-item-field.component.ts b/src/app/item-page/simple/field-components/clarin-identifier-item-field/clarin-identifier-item-field.component.ts index a75eae240b6..13964247b13 100644 --- a/src/app/item-page/simple/field-components/clarin-identifier-item-field/clarin-identifier-item-field.component.ts +++ b/src/app/item-page/simple/field-components/clarin-identifier-item-field/clarin-identifier-item-field.component.ts @@ -1,16 +1,10 @@ import { Component, Input, OnInit, ViewChild } from '@angular/core'; import { Item } from '../../../../core/shared/item.model'; -import { BehaviorSubject, firstValueFrom } from 'rxjs'; +import { BehaviorSubject } from 'rxjs'; import { Clipboard } from '@angular/cdk/clipboard'; import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; -import { ConfigurationDataService } from '../../../../core/data/configuration-data.service'; -import { isEmpty, isNotEmpty } from '../../../../shared/empty.util'; -import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; -import { map } from 'rxjs/operators'; -import { DOI_METADATA_FIELD } from '../clarin-generic-item-field/clarin-generic-item-field.component'; +import { ItemIdentifierService } from '../../../../shared/item-identifier.service'; -const DEFAULT_DOI_RESOLVER = 'https://doi.org/'; -const DOI_RESOLVER_CFG_PROPERTY = 'identifier.doi.resolver'; @Component({ selector: 'ds-clarin-identifier-item-field', @@ -34,11 +28,6 @@ export class ClarinIdentifierItemFieldComponent implements OnInit { */ @Input() fields: string[]; - /** - * The DOI resolver URL. It is a `identifier.doi.resolver` property or the default resolver (constant). - */ - doiResolver: string; - /** * The identifier of the item. DOI or handle. */ @@ -49,16 +38,18 @@ export class ClarinIdentifierItemFieldComponent implements OnInit { */ prettifiedIdentifier: BehaviorSubject = new BehaviorSubject(null); - constructor(private configurationService: ConfigurationDataService, + constructor(private itemIdentifierService: ItemIdentifierService, private clipboard: Clipboard) { } ngOnInit(): void { this.identifier = this.item?.firstMetadataValue(this.fields); - void this.prettifyIdentifier(this.identifier); + void this.itemIdentifierService.prettifyIdentifier(this.identifier, this.fields) + .then((value: string) => { + this.prettifiedIdentifier.next(value); + }); } - /** * Copy the metadata value to the clipboard. After clicking on the `Copy` icon the message `Copied` is popped up. * @@ -70,56 +61,4 @@ export class ClarinIdentifierItemFieldComponent implements OnInit { this.copyButtonRef.close(); }, 700); } - - /** - * Prettify the identifier. If the identifier is DOI, remove the DOI resolver from it. - * - * @param identifier - */ - async prettifyIdentifier(identifier: string) { - if (isEmpty(identifier)) { - this.prettifiedIdentifier.next(null); - return; - } - - // Do not prettify the identifier if it is not DOI. - if (!this.fields.includes(DOI_METADATA_FIELD)) { - this.prettifiedIdentifier.next(identifier); - return; - } - - // If the DOI resolver is set, use it. It is not set by default. - if (isNotEmpty(this.doiResolver)) { - this.removeDoiResolverFromIdentifier(identifier); - return; - } - - // Get the DOI resolver from the configuration. - const cfgDoiResolver = await firstValueFrom(this.loadDoiResolverConfiguration()); - - // If the configuration is not set, use the default resolver. - this.doiResolver = isEmpty(cfgDoiResolver) ? DEFAULT_DOI_RESOLVER : cfgDoiResolver; - - // Remove the DOI resolver from the identifier. - this.removeDoiResolverFromIdentifier(identifier); - } - - /** - * Remove the DOI resolver from the identifier. - * - * @param identifier - */ - removeDoiResolverFromIdentifier(identifier: string) { - this.prettifiedIdentifier.next(identifier.replace(this.doiResolver, '')); - } - - /** - * Load the DOI resolver from the configuration. It is a `identifier.doi.resolver` property. - */ - loadDoiResolverConfiguration() { - return this.configurationService.findByPropertyName(DOI_RESOLVER_CFG_PROPERTY) - .pipe( - getFirstCompletedRemoteData(), - map((config) => config?.payload?.values?.[0])); - } } diff --git a/src/app/shared/item-identifier.service.ts b/src/app/shared/item-identifier.service.ts new file mode 100644 index 00000000000..3a040bb7615 --- /dev/null +++ b/src/app/shared/item-identifier.service.ts @@ -0,0 +1,68 @@ +import { Injectable } from '@angular/core'; +import { ConfigurationDataService } from '../core/data/configuration-data.service'; +import { isEmpty } from './empty.util'; +import { firstValueFrom } from 'rxjs'; +import { + DOI_METADATA_FIELD +} from '../item-page/simple/field-components/clarin-generic-item-field/clarin-generic-item-field.component'; +import { getFirstCompletedRemoteData } from '../core/shared/operators'; +import { map } from 'rxjs/operators'; + +export const DEFAULT_DOI_RESOLVER = 'https://doi.org/'; +export const DOI_RESOLVER_CFG_PROPERTY = 'identifier.doi.resolver'; + +/** + * Service to handle the item identifier. It prettifies the identifier and removes the DOI resolver from it. + */ +@Injectable() +export class ItemIdentifierService { + constructor(private configurationService: ConfigurationDataService) { + } + + /** + * Prettify the identifier. If the identifier is DOI, remove the DOI resolver from it. + * + * @param identifier + * @param metadataFields + */ + async prettifyIdentifier(identifier: string, metadataFields: string[] = []) { + let prettifiedIdentifier = identifier; + if (isEmpty(identifier)) { + return prettifiedIdentifier; + } + + // Do not prettify the identifier if it is not DOI. + if (!metadataFields?.includes(DOI_METADATA_FIELD)) { + return prettifiedIdentifier; + } + + // Get the DOI resolver from the configuration. + const cfgDoiResolver = await firstValueFrom(this.loadDoiResolverConfiguration()); + + // If the configuration is not set, use the default resolver. + const doiResolver = isEmpty(cfgDoiResolver) ? DEFAULT_DOI_RESOLVER : cfgDoiResolver; + + // Remove the DOI resolver from the identifier. + return this.removeDoiResolverFromIdentifier(identifier, doiResolver); + } + + /** + * Remove the DOI resolver from the identifier. + * + * @param identifier + * @param doiResolver + */ + removeDoiResolverFromIdentifier(identifier: string, doiResolver: string) { + return identifier.replace(doiResolver, ''); + } + + /** + * Load the DOI resolver from the configuration. It is a `identifier.doi.resolver` property. + */ + loadDoiResolverConfiguration() { + return this.configurationService.findByPropertyName(DOI_RESOLVER_CFG_PROPERTY) + .pipe( + getFirstCompletedRemoteData(), + map((config) => config?.payload?.values?.[0])); + } +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 2f5a4c9f154..b8ee124f70a 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -295,6 +295,7 @@ import { HtmlContentService } from './html-content.service'; import { ClarinSafeHtmlPipe } from './utils/clarin-safehtml.pipe'; import { ReplacePipe } from './utils/replace.pipe'; import { ClarinDateService } from './clarin-date.service'; +import { ItemIdentifierService } from './item-identifier.service'; const MODULES = [ CommonModule, @@ -495,7 +496,8 @@ const PROVIDERS = [ MockAdminGuard, AbstractTrackableComponent, HtmlContentService, - ClarinDateService + ClarinDateService, + ItemIdentifierService ]; const DIRECTIVES = [