From ca86b4d9a66bf6273aa047db4e0378fac6d6f9f5 Mon Sep 17 00:00:00 2001 From: Jose Blanco Date: Mon, 6 May 2024 13:21:39 -0400 Subject: [PATCH] add altmetrics and more. --- config/config.example.yml | 3 + config/config.yml | 2 + .../forward-client-ip.interceptor.ts | 16 ++- src/app/item-page/item-page.module.ts | 22 ++++ .../item-page-dimensions-field.component.html | 9 ++ .../item-page-dimensions-field.component.ts | 71 ++++++++++++ .../item-page-dimensions-field.directive.ts | 83 ++++++++++++++ ...ge-metrics-dimensions-field.component.html | 12 ++ ...page-metrics-dimensions-field.component.ts | 24 ++++ .../item-page-altmetric-field.component.html | 10 ++ .../item-page-altmetric-field.component.ts | 62 +++++++++++ .../item-page-altmetric-field.directive.ts | 83 ++++++++++++++ .../item-page-metrics-field.component.html | 12 ++ .../item-page-metrics-field.component.ts | 24 ++++ .../publication/publication.component.html | 1 + .../untyped-item/untyped-item.component.html | 31 ++++-- .../untyped-item/untyped-item.component.ts | 51 --------- .../user-menu/user-menu.component.html | 8 +- .../shared/listfiles/listfiles.component.html | 3 + .../metadata-field-wrapper.component.html | 2 +- src/app/shared/rss-feed/rss.component.html | 3 + src/app/shared/shared.module.ts | 7 +- ...ternal-script-loader-dimensions.service.ts | 103 ++++++++++++++++++ .../external-script.model.ts | 22 ++++ .../external-script-loader.service.ts | 103 ++++++++++++++++++ .../scripts-loader/external-script.model.ts | 22 ++++ src/assets/i18n/en.json5 | 38 +++++-- src/config/default-app-config.ts | 3 +- src/config/item-config.interface.ts | 2 + src/environments/environment.test.ts | 3 +- src/styles/_global-styles.scss | 4 +- .../dspace/app/header/header.component.html | 21 ---- .../dspace/app/header/header.component.scss | 2 +- .../dspace/app/navbar/navbar.component.html | 3 +- .../dspace/app/navbar/navbar.component.scss | 10 +- 35 files changed, 764 insertions(+), 111 deletions(-) create mode 100644 src/app/item-page/simple/field-components/specific-field/metrics-dimensions/dimensions/item-page-dimensions-field.component.html create mode 100644 src/app/item-page/simple/field-components/specific-field/metrics-dimensions/dimensions/item-page-dimensions-field.component.ts create mode 100644 src/app/item-page/simple/field-components/specific-field/metrics-dimensions/dimensions/item-page-dimensions-field.directive.ts create mode 100644 src/app/item-page/simple/field-components/specific-field/metrics-dimensions/item-page-metrics-dimensions-field.component.html create mode 100644 src/app/item-page/simple/field-components/specific-field/metrics-dimensions/item-page-metrics-dimensions-field.component.ts create mode 100644 src/app/item-page/simple/field-components/specific-field/metrics/altmetric/item-page-altmetric-field.component.html create mode 100644 src/app/item-page/simple/field-components/specific-field/metrics/altmetric/item-page-altmetric-field.component.ts create mode 100644 src/app/item-page/simple/field-components/specific-field/metrics/altmetric/item-page-altmetric-field.directive.ts create mode 100644 src/app/item-page/simple/field-components/specific-field/metrics/item-page-metrics-field.component.html create mode 100644 src/app/item-page/simple/field-components/specific-field/metrics/item-page-metrics-field.component.ts create mode 100644 src/app/shared/utils/scripts-loader-dimensions/external-script-loader-dimensions.service.ts create mode 100644 src/app/shared/utils/scripts-loader-dimensions/external-script.model.ts create mode 100644 src/app/shared/utils/scripts-loader/external-script-loader.service.ts create mode 100644 src/app/shared/utils/scripts-loader/external-script.model.ts diff --git a/config/config.example.yml b/config/config.example.yml index ea38303fa36..f3716b51b29 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -252,6 +252,9 @@ browseBy: # settings menu. pageSize: 20 + # Show the Altmetric badge in the simple item page. + showAltmetricBadge: true + communityList: # No. of communities to list per expansion (show more) pageSize: 20 diff --git a/config/config.yml b/config/config.yml index b5eecd112f0..0955f27c809 100644 --- a/config/config.yml +++ b/config/config.yml @@ -3,3 +3,5 @@ rest: host: api7.dspace.org port: 443 nameSpace: /server +item: + showAltmetricBadge: true \ No newline at end of file diff --git a/src/app/core/forward-client-ip/forward-client-ip.interceptor.ts b/src/app/core/forward-client-ip/forward-client-ip.interceptor.ts index 2e2e59fa6b8..13e384cb5ba 100644 --- a/src/app/core/forward-client-ip/forward-client-ip.interceptor.ts +++ b/src/app/core/forward-client-ip/forward-client-ip.interceptor.ts @@ -18,6 +18,20 @@ export class ForwardClientIpInterceptor implements HttpInterceptor { */ intercept(httpRequest: HttpRequest, next: HttpHandler): Observable> { const clientIp = this.req.get('x-forwarded-for') || this.req.connection.remoteAddress; - return next.handle(httpRequest.clone({ setHeaders: { 'X-Forwarded-For': clientIp } })); +// return next.handle(httpRequest.clone({ setHeaders: { 'X-Forwarded-For': clientIp } })); + +// This came from here: https://github.com/DSpace/dspace-angular/issues/2902 +// UM Change. I was having issues getting the referer, aske Tim about it and +// he pointed me to this. This is not solve the issue, but seems like it would +// be good to have anyway + const headers = { 'X-Forwarded-For': clientIp }; + + // if the request has a user-agent retain it + const userAgent = this.req.get('user-agent'); + if (userAgent) { + headers['User-Agent'] = userAgent; + } + + return next.handle(httpRequest.clone({ setHeaders: headers })); } } diff --git a/src/app/item-page/item-page.module.ts b/src/app/item-page/item-page.module.ts index a8d41d15352..ae605234814 100644 --- a/src/app/item-page/item-page.module.ts +++ b/src/app/item-page/item-page.module.ts @@ -61,6 +61,17 @@ import { ThemedFullFileSectionComponent } from './full/field-components/file-section/themed-full-file-section.component'; +import { ItemPageAltmetricFieldComponent } from './simple/field-components/specific-field/metrics/altmetric/item-page-altmetric-field.component'; +import { AltmetricDirective } from './simple/field-components/specific-field/metrics/altmetric/item-page-altmetric-field.directive'; +import { ItemPageMetricsFieldComponent } from './simple/field-components/specific-field/metrics/item-page-metrics-field.component'; + +import { ItemPageDimensionsFieldComponent } from './simple/field-components/specific-field/metrics-dimensions/dimensions/item-page-dimensions-field.component'; +import { DimensionsDirective } from './simple/field-components/specific-field/metrics-dimensions/dimensions/item-page-dimensions-field.directive'; +import { ItemPageMetricsDimensionsFieldComponent } from './simple/field-components/specific-field/metrics-dimensions/item-page-metrics-dimensions-field.component'; + + + + const ENTRY_COMPONENTS = [ // put only entry components that use custom decorator PublicationComponent, @@ -103,6 +114,15 @@ const DECLARATIONS = [ ItemAlertsComponent, ThemedItemAlertsComponent, BitstreamRequestACopyPageComponent, + ItemPageMetricsFieldComponent, + ItemPageAltmetricFieldComponent, + ItemPageMetricsDimensionsFieldComponent, + ItemPageDimensionsFieldComponent, +]; + +const DIRECTIVES = [ + AltmetricDirective, + DimensionsDirective, ]; @NgModule({ @@ -124,10 +144,12 @@ const DECLARATIONS = [ ], declarations: [ ...DECLARATIONS, + ...DIRECTIVES, ], exports: [ ...DECLARATIONS, + ...DIRECTIVES, ] }) export class ItemPageModule { diff --git a/src/app/item-page/simple/field-components/specific-field/metrics-dimensions/dimensions/item-page-dimensions-field.component.html b/src/app/item-page/simple/field-components/specific-field/metrics-dimensions/dimensions/item-page-dimensions-field.component.html new file mode 100644 index 00000000000..013b5b177ab --- /dev/null +++ b/src/app/item-page/simple/field-components/specific-field/metrics-dimensions/dimensions/item-page-dimensions-field.component.html @@ -0,0 +1,9 @@ + + + diff --git a/src/app/item-page/simple/field-components/specific-field/metrics-dimensions/dimensions/item-page-dimensions-field.component.ts b/src/app/item-page/simple/field-components/specific-field/metrics-dimensions/dimensions/item-page-dimensions-field.component.ts new file mode 100644 index 00000000000..b0ea666ac6a --- /dev/null +++ b/src/app/item-page/simple/field-components/specific-field/metrics-dimensions/dimensions/item-page-dimensions-field.component.ts @@ -0,0 +1,71 @@ +import { AfterViewInit, Component, EventEmitter, HostListener, Inject, Input, Output } from '@angular/core'; +import { ExternalScriptLoaderDimensionsService } from 'src/app/shared/utils/scripts-loader-dimensions/external-script-loader-dimensions.service'; +import { + ExternalScriptsNames, + ExternalScriptsStatus, +} from 'src/app/shared/utils/scripts-loader-dimensions/external-script.model'; +import { Item } from '../../../../../../core/shared/item.model'; +import { APP_CONFIG, AppConfig } from 'src/config/app-config.interface'; + +@Component({ + selector: 'ds-item-page-dimensions-field', + templateUrl: './item-page-dimensions-field.component.html', +}) +export class ItemPageDimensionsFieldComponent implements AfterViewInit { + @Input() item: Item; + + @Output() widgetLoaded = new EventEmitter(); + + constructor( + @Inject(APP_CONFIG) protected appConfig: AppConfig, + private scriptLoader: ExternalScriptLoaderDimensionsService + ) {} + + ngAfterViewInit() { + if (!this.appConfig.item.showAltmetricBadge) { + return; + } + this.scriptLoader + .load(ExternalScriptsNames.DIMENSIONS) + .then((data) => this.reloadBadge(data)) + .catch((error) => console.error(error)); + } + + /** + * We ensure that the badge is visible after the script is loaded + * @param data The data returned from the promise + */ + // private reloadBadge(data: any[]) { + // if (data.find((element) => this.isLoaded(element))) { + // const initMethod = '__dimensions_embed_installed__'; + // window[initMethod](); + // } + // } + + // I got this method from the community. The init method has to be different. + private reloadBadge(data: any[]) { + if (data.find((element) => this.isLoaded(element))) { + const initClass = '__dimensions_embed'; + const initMethod = 'addBadges'; + window[initClass][initMethod](); + } + } + + + /** + * Check if the script has been previously loaded in the DOM + * @param element The resolve element from the promise + * @returns true if the script has been already loaded, false if not + */ + private isLoaded(element: any): unknown { + return ( + element.script === ExternalScriptsNames.DIMENSIONS && + element.status === ExternalScriptsStatus.ALREADY_LOADED + ); + } + + @HostListener('window:altmetric:show', ['$event']) + private onWidgetShow(event: Event) { + this.widgetLoaded.emit(true); + } +} diff --git a/src/app/item-page/simple/field-components/specific-field/metrics-dimensions/dimensions/item-page-dimensions-field.directive.ts b/src/app/item-page/simple/field-components/specific-field/metrics-dimensions/dimensions/item-page-dimensions-field.directive.ts new file mode 100644 index 00000000000..65eb62aaedf --- /dev/null +++ b/src/app/item-page/simple/field-components/specific-field/metrics-dimensions/dimensions/item-page-dimensions-field.directive.ts @@ -0,0 +1,83 @@ +import { Directive, ElementRef, Input, OnInit, Renderer2 } from '@angular/core'; +import { Item } from 'src/app/core/shared/item.model'; + +/** + * This directive adds the data-* attribute for the Altmetric badge dependening on the first identifier found in the item + */ +@Directive({ + selector: '[dsDimensionsData]', +}) +export class DimensionsDirective implements OnInit { + @Input() item: Item; + + constructor(private renderer: Renderer2, private elementRef: ElementRef) {} + + ngOnInit(): void { + const identifier = this.obtainFirstValidID(this.initItemIdentifiers()); + if (identifier !== undefined) { + this.renderer.setAttribute( + this.elementRef.nativeElement, + identifier.name, + this.applyRegex(identifier.value, identifier.regex) + ); + } + } + + /** + * This initialize an array of identifiers founded in the item. + * It search for DOI, Handle, PMID, ISBN, ARXIV and URI. + * Some identifiers may be stored with more text than the ID so this objects has a regex property to clean it + */ + private initItemIdentifiers(): any[] { + return [ + { + name: 'data-doi', + value: this.item.firstMetadataValue('dc.identifier.doi'), + regex: /https?:\/\/(dx\.)?doi\.org\//gi, + }, + { + name: 'data-handle', + value: this.item.firstMetadataValue('dc.identifier.uri'), + regex: /http?:\/\/hdl\.handle\.net\//gi, + }, + { + name: 'data-pmid', + value: this.item.firstMetadataValue('dc.identifier.pmid'), + regex: '', + }, + { + name: 'data-isbn', + value: this.item.firstMetadataValue('dc.identifier.isbn'), + regex: '', + }, + { + name: 'data-arxiv-id', + value: this.item.firstMetadataValue('dc.identifier.arxiv'), + regex: '', + }, + { + name: 'data-uri', + value: this.item.firstMetadataValue('dc.identifier.uri'), + regex: '', + }, + ]; + } + + /** + * This function obtains the first valid ID from the item + * @returns Returns first valid identifier (not undefined), undefined otherwise + */ + private obtainFirstValidID(itemIdentifiers: any[]): any { + return itemIdentifiers.find((element) => element.value !== undefined); + } + + /** + * Apply the specified regex to clean the metadata and obtain only the ID + * @param value The metadata value + * @param regex The regex to apply + * @returns The result is the ID clean + */ + private applyRegex(value: string, regex: string): string { + return value.replace(regex, ''); + } +} diff --git a/src/app/item-page/simple/field-components/specific-field/metrics-dimensions/item-page-metrics-dimensions-field.component.html b/src/app/item-page/simple/field-components/specific-field/metrics-dimensions/item-page-metrics-dimensions-field.component.html new file mode 100644 index 00000000000..a22c6aefbac --- /dev/null +++ b/src/app/item-page/simple/field-components/specific-field/metrics-dimensions/item-page-metrics-dimensions-field.component.html @@ -0,0 +1,12 @@ +
+ +
+ +
+ +
+
+
+
diff --git a/src/app/item-page/simple/field-components/specific-field/metrics-dimensions/item-page-metrics-dimensions-field.component.ts b/src/app/item-page/simple/field-components/specific-field/metrics-dimensions/item-page-metrics-dimensions-field.component.ts new file mode 100644 index 00000000000..48a95a254a8 --- /dev/null +++ b/src/app/item-page/simple/field-components/specific-field/metrics-dimensions/item-page-metrics-dimensions-field.component.ts @@ -0,0 +1,24 @@ +import { Component, Input } from '@angular/core'; +import { ItemPageFieldComponent } from '../item-page-field.component'; +import { Item } from 'src/app/core/shared/item.model'; + +@Component({ + selector: 'ds-item-page-metrics-dimensions-field', + templateUrl: './item-page-metrics-dimensions-field.component.html', + styleUrls: [ + '../../../../../shared/metadata-field-wrapper/metadata-field-wrapper.component.scss', + ], +}) +export class ItemPageMetricsDimensionsFieldComponent extends ItemPageFieldComponent { + + @Input() item: Item; + + public showTitle = false; + + public someWidgetHasLoaded(widgetLoaded: boolean) { + if (widgetLoaded) { + this.showTitle = true; + } + } + +} diff --git a/src/app/item-page/simple/field-components/specific-field/metrics/altmetric/item-page-altmetric-field.component.html b/src/app/item-page/simple/field-components/specific-field/metrics/altmetric/item-page-altmetric-field.component.html new file mode 100644 index 00000000000..169f445d66f --- /dev/null +++ b/src/app/item-page/simple/field-components/specific-field/metrics/altmetric/item-page-altmetric-field.component.html @@ -0,0 +1,10 @@ + diff --git a/src/app/item-page/simple/field-components/specific-field/metrics/altmetric/item-page-altmetric-field.component.ts b/src/app/item-page/simple/field-components/specific-field/metrics/altmetric/item-page-altmetric-field.component.ts new file mode 100644 index 00000000000..cde3c3757ab --- /dev/null +++ b/src/app/item-page/simple/field-components/specific-field/metrics/altmetric/item-page-altmetric-field.component.ts @@ -0,0 +1,62 @@ +import { AfterViewInit, Component, EventEmitter, HostListener, Inject, Input, Output } from '@angular/core'; +import { ExternalScriptLoaderService } from 'src/app/shared/utils/scripts-loader/external-script-loader.service'; +import { + ExternalScriptsNames, + ExternalScriptsStatus, +} from 'src/app/shared/utils/scripts-loader/external-script.model'; +import { Item } from '../../../../../../core/shared/item.model'; +import { APP_CONFIG, AppConfig } from 'src/config/app-config.interface'; + +@Component({ + selector: 'ds-item-page-altmetric-field', + templateUrl: './item-page-altmetric-field.component.html', +}) +export class ItemPageAltmetricFieldComponent implements AfterViewInit { + @Input() item: Item; + + @Output() widgetLoaded = new EventEmitter(); + + constructor( + @Inject(APP_CONFIG) protected appConfig: AppConfig, + private scriptLoader: ExternalScriptLoaderService + ) {} + + ngAfterViewInit() { + if (!this.appConfig.item.showAltmetricBadge) { + return; + } + + this.scriptLoader + .load(ExternalScriptsNames.ALTMETRIC) + .then((data) => this.reloadBadge(data)) + .catch((error) => console.error(error)); + } + + /** + * We ensure that the badge is visible after the script is loaded + * @param data The data returned from the promise + */ + private reloadBadge(data: any[]) { + if (data.find((element) => this.isLoaded(element))) { + const initMethod = '_altmetric_embed_init'; + window[initMethod](); + } + } + + /** + * Check if the script has been previously loaded in the DOM + * @param element The resolve element from the promise + * @returns true if the script has been already loaded, false if not + */ + private isLoaded(element: any): unknown { + return ( + element.script === ExternalScriptsNames.ALTMETRIC && + element.status === ExternalScriptsStatus.ALREADY_LOADED + ); + } + + @HostListener('window:altmetric:show', ['$event']) + private onWidgetShow(event: Event) { + this.widgetLoaded.emit(true); + } +} diff --git a/src/app/item-page/simple/field-components/specific-field/metrics/altmetric/item-page-altmetric-field.directive.ts b/src/app/item-page/simple/field-components/specific-field/metrics/altmetric/item-page-altmetric-field.directive.ts new file mode 100644 index 00000000000..c2063eda370 --- /dev/null +++ b/src/app/item-page/simple/field-components/specific-field/metrics/altmetric/item-page-altmetric-field.directive.ts @@ -0,0 +1,83 @@ +import { Directive, ElementRef, Input, OnInit, Renderer2 } from '@angular/core'; +import { Item } from 'src/app/core/shared/item.model'; + +/** + * This directive adds the data-* attribute for the Altmetric badge dependening on the first identifier found in the item + */ +@Directive({ + selector: '[dsAltmetricData]', +}) +export class AltmetricDirective implements OnInit { + @Input() item: Item; + + constructor(private renderer: Renderer2, private elementRef: ElementRef) {} + + ngOnInit(): void { + const identifier = this.obtainFirstValidID(this.initItemIdentifiers()); + if (identifier !== undefined) { + this.renderer.setAttribute( + this.elementRef.nativeElement, + identifier.name, + this.applyRegex(identifier.value, identifier.regex) + ); + } + } + + /** + * This initialize an array of identifiers founded in the item. + * It search for DOI, Handle, PMID, ISBN, ARXIV and URI. + * Some identifiers may be stored with more text than the ID so this objects has a regex property to clean it + */ + private initItemIdentifiers(): any[] { + return [ + { + name: 'data-doi', + value: this.item.firstMetadataValue('dc.identifier.doi'), + regex: /https?:\/\/(dx\.)?doi\.org\//gi, + }, + { + name: 'data-handle', + value: this.item.firstMetadataValue('dc.identifier.uri'), + regex: /http?:\/\/hdl\.handle\.net\//gi, + }, + { + name: 'data-pmid', + value: this.item.firstMetadataValue('dc.identifier.pmid'), + regex: '', + }, + { + name: 'data-isbn', + value: this.item.firstMetadataValue('dc.identifier.isbn'), + regex: '', + }, + { + name: 'data-arxiv-id', + value: this.item.firstMetadataValue('dc.identifier.arxiv'), + regex: '', + }, + { + name: 'data-uri', + value: this.item.firstMetadataValue('dc.identifier.uri'), + regex: '', + }, + ]; + } + + /** + * This function obtains the first valid ID from the item + * @returns Returns first valid identifier (not undefined), undefined otherwise + */ + private obtainFirstValidID(itemIdentifiers: any[]): any { + return itemIdentifiers.find((element) => element.value !== undefined); + } + + /** + * Apply the specified regex to clean the metadata and obtain only the ID + * @param value The metadata value + * @param regex The regex to apply + * @returns The result is the ID clean + */ + private applyRegex(value: string, regex: string): string { + return value.replace(regex, ''); + } +} diff --git a/src/app/item-page/simple/field-components/specific-field/metrics/item-page-metrics-field.component.html b/src/app/item-page/simple/field-components/specific-field/metrics/item-page-metrics-field.component.html new file mode 100644 index 00000000000..81e9630d0a9 --- /dev/null +++ b/src/app/item-page/simple/field-components/specific-field/metrics/item-page-metrics-field.component.html @@ -0,0 +1,12 @@ +
+ +
+ +
+ +
+
+
+
diff --git a/src/app/item-page/simple/field-components/specific-field/metrics/item-page-metrics-field.component.ts b/src/app/item-page/simple/field-components/specific-field/metrics/item-page-metrics-field.component.ts new file mode 100644 index 00000000000..95f8a267675 --- /dev/null +++ b/src/app/item-page/simple/field-components/specific-field/metrics/item-page-metrics-field.component.ts @@ -0,0 +1,24 @@ +import { Component, Input } from '@angular/core'; +import { ItemPageFieldComponent } from '../item-page-field.component'; +import { Item } from 'src/app/core/shared/item.model'; + +@Component({ + selector: 'ds-item-page-metrics-field', + templateUrl: './item-page-metrics-field.component.html', + styleUrls: [ + '../../../../../shared/metadata-field-wrapper/metadata-field-wrapper.component.scss', + ], +}) +export class ItemPageMetricsFieldComponent extends ItemPageFieldComponent { + + @Input() item: Item; + + public showTitle = false; + + public someWidgetHasLoaded(widgetLoaded: boolean) { + if (widgetLoaded) { + this.showTitle = true; + } + } + +} diff --git a/src/app/item-page/simple/item-types/publication/publication.component.html b/src/app/item-page/simple/item-types/publication/publication.component.html index 3749f639644..1741d1cfaf2 100644 --- a/src/app/item-page/simple/item-types/publication/publication.component.html +++ b/src/app/item-page/simple/item-types/publication/publication.component.html @@ -47,6 +47,7 @@ [fields]="['dc.publisher']" [label]="'publication.page.publisher'"> +
--> + +
- - + + + + + + + + + @@ -60,16 +69,20 @@ [fields]="['dc.publisher']" [label]="'item.page.publisher'"> +
-
+
-
Abstract
+

Abstract

+ + +
@@ -101,7 +114,7 @@
Abstract
-
Handle
+

Handle

{{uri}}
@@ -113,7 +126,7 @@
0" > -
Other identifiers
+

Other identifiers

{{uri}}
@@ -127,7 +140,7 @@
0" > -
Deep Blue DOI
+

Deep Blue DOI

{{doi}} http://dx.doi.org/{{doi}} @@ -140,7 +153,7 @@
0" > -
Other DOIs
+

Other DOIs

{{doi}} http://dx.doi.org/{{doi}}
@@ -485,7 +498,7 @@
0" > -
Videos
+

Videos

diff --git a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts index 2bd690b2aa0..fbd1740c606 100644 --- a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts +++ b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts @@ -24,57 +24,6 @@ export class UntypedItemComponent extends ItemComponent { private sanitizer = inject(DomSanitizer); trustedUrl: any = ''; - theDoi: any = '10.1002/jbmr.493'; - - ngOnInit() { - - //this.hasLink = isNotEmpty(this.item.href); - let node = document.createElement('script'); - node.src = 'https://d1bxh8uas1mnw7.cloudfront.net/assets/embed.js'; - node.type = 'text/javascript'; - node.id = 'cloudflarescript'; - document.getElementsByTagName('head')[0].appendChild(node); - - - let node2 = document.createElement('script'); - node2.src = 'https://badge.dimensions.ai/badge.js'; - node2.type = 'text/javascript'; - node2.id = 'cloudflarescript2'; - document.getElementsByTagName('head')[0].appendChild(node2); - -// let check3 = document.querySelector("link[href='https://badge.dimensions.ai/badge.css']"); -// if ( check3 ) -// { -// console.log ('ngOnDestroy removing link to dimensions'); -// check3.remove(); -// } - -} - - -ngOnDestroy() { - let check2 = document.getElementById('cloudflarescript2'); - if ( check2 ) - { - //console.log ('ngOnDestroy removing'); - check2.remove(); - } - - let check = document.getElementById('cloudflarescript'); - if ( check ) - { - //console.log ('ngOnDestroy removing'); - check.remove(); - } - -// let check3 = document.querySelector("link[href='https://badge.dimensions.ai/badge.css']"); -// if ( check3 ) -// { -// console.log ('ngOnDestroy removing link to dimensions'); -// check3.remove(); -// } - -} public getSelectedCcLicense(ccLicense: String): String { if ( ccLicense.startsWith("http://creativecommons.org/licenses/by/") ) diff --git a/src/app/shared/auth-nav-menu/user-menu/user-menu.component.html b/src/app/shared/auth-nav-menu/user-menu/user-menu.component.html index 85b9d8aece2..06d57ffccaa 100644 --- a/src/app/shared/auth-nav-menu/user-menu/user-menu.component.html +++ b/src/app/shared/auth-nav-menu/user-menu/user-menu.component.html @@ -1,12 +1,12 @@
- + {{dsoNameService.getName(user$ | async)}}
{{(user$ | async)?.email}}
- {{'nav.profile' | translate}} - {{'nav.mydspace' | translate}} - {{'nav.subscriptions' | translate}} + {{'nav.profile' | translate}} + {{'nav.mydspace' | translate}} + {{'nav.subscriptions' | translate}} diff --git a/src/app/shared/listfiles/listfiles.component.html b/src/app/shared/listfiles/listfiles.component.html index 26a572ec678..7709cfbccdc 100644 --- a/src/app/shared/listfiles/listfiles.component.html +++ b/src/app/shared/listfiles/listfiles.component.html @@ -58,3 +58,6 @@
+ + + diff --git a/src/app/shared/metadata-field-wrapper/metadata-field-wrapper.component.html b/src/app/shared/metadata-field-wrapper/metadata-field-wrapper.component.html index d69f87883bb..7748e385ca4 100644 --- a/src/app/shared/metadata-field-wrapper/metadata-field-wrapper.component.html +++ b/src/app/shared/metadata-field-wrapper/metadata-field-wrapper.component.html @@ -1,5 +1,5 @@
-
{{ label }}
+

{{ label }}

diff --git a/src/app/shared/rss-feed/rss.component.html b/src/app/shared/rss-feed/rss.component.html index 91140c50c5a..987be743ad0 100644 --- a/src/app/shared/rss-feed/rss.component.html +++ b/src/app/shared/rss-feed/rss.component.html @@ -1,5 +1,8 @@ + diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index b0556b99dae..2086adcb2f3 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -286,6 +286,9 @@ import { } from '../item-page/simple/field-components/specific-field/title/themed-item-page-field.component'; import { BitstreamListItemComponent } from './object-list/bitstream-list-item/bitstream-list-item.component'; import { NgxPaginationModule } from 'ngx-pagination'; +import { ExternalScriptLoaderService } from './utils/scripts-loader/external-script-loader.service'; +import { ExternalScriptLoaderDimensionsService } from './utils/scripts-loader-dimensions/external-script-loader-dimensions.service'; + const MODULES = [ CommonModule, @@ -475,7 +478,9 @@ const ENTRY_COMPONENTS = [ const PROVIDERS = [ TruncatableService, MockAdminGuard, - AbstractTrackableComponent + AbstractTrackableComponent, + ExternalScriptLoaderService, + ExternalScriptLoaderDimensionsService, ]; const DIRECTIVES = [ diff --git a/src/app/shared/utils/scripts-loader-dimensions/external-script-loader-dimensions.service.ts b/src/app/shared/utils/scripts-loader-dimensions/external-script-loader-dimensions.service.ts new file mode 100644 index 00000000000..e1adfe7635e --- /dev/null +++ b/src/app/shared/utils/scripts-loader-dimensions/external-script-loader-dimensions.service.ts @@ -0,0 +1,103 @@ +import { Injectable } from '@angular/core'; +import { + ExternalScriptsList, + ExternalScriptsStatus, +} from './external-script.model'; + +declare const document: any; + +/** + * Service used to load external scripts in the DOM when it cannot be loaded from the standard angular methods + */ +@Injectable() +export class ExternalScriptLoaderDimensionsService { + private scriptsStored: any = {}; + + constructor() { + ExternalScriptsList.forEach( + ({ name, src }) => (this.scriptsStored[name] = { loaded: false, src }) + ); + } + + /** + * Load the scripts in the DOM and return every {@link Promise} + * @param scriptsToLoad Scripts to load, see {@link scriptsToLoad} + * @returns An array of the scripts promises + */ + load(...scriptsToLoad: string[]): Promise { + let promises: any[] = []; + scriptsToLoad.forEach((script) => promises.push(this.loadScript(script))); + return Promise.all(promises); + } + + private loadScript(name: string) { + return new Promise((resolve) => { + if (this.isAlreadyLoaded(name)) { + resolve( + this.createResolveResult( + name, + true, + ExternalScriptsStatus.ALREADY_LOADED + ) + ); + } else { + let script = this.createScriptHTMLElement(name); + if (this.areWeInIE(script)) { + this.configureOnReadyState(name, script, resolve); + } else { + this.configureOnLoad(name, script, resolve); + } + this.configureOnError(name, script, resolve); + document.getElementsByTagName('head')[0].appendChild(script); + } + }); + } + + private isAlreadyLoaded(name: string): boolean { + return this.scriptsStored[name].loaded; + } + + private areWeInIE(script: any): boolean { + return script.readyState; + } + + private createResolveResult(script: string, loaded: boolean, status: string) { + return { script, loaded, status }; + } + + private createScriptHTMLElement(name: string): any { + let script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = this.scriptsStored[name].src; + + return script; + } + + private configureOnReadyState(name: string, script: any, resolve: any) { + script.onreadystatechange = () => { + if (script.readyState === 'loaded' || script.readyState === 'complete') { + script.onreadystatechange = null; + this.scriptsStored[name].loaded = true; + resolve( + this.createResolveResult(name, true, ExternalScriptsStatus.LOADED) + ); + } + }; + } + + private configureOnLoad(name: string, script: any, resolve: any) { + script.onload = () => { + this.scriptsStored[name].loaded = true; + resolve( + this.createResolveResult(name, true, ExternalScriptsStatus.LOADED) + ); + }; + } + + private configureOnError(name: string, script: any, resolve: any) { + script.onerror = (error: any) => + resolve( + this.createResolveResult(name, false, ExternalScriptsStatus.NOT_LOADED) + ); + } +} diff --git a/src/app/shared/utils/scripts-loader-dimensions/external-script.model.ts b/src/app/shared/utils/scripts-loader-dimensions/external-script.model.ts new file mode 100644 index 00000000000..85269baf5dd --- /dev/null +++ b/src/app/shared/utils/scripts-loader-dimensions/external-script.model.ts @@ -0,0 +1,22 @@ +export interface ExternalScript { + name: string; + src: string; +} + +export enum ExternalScriptsNames { + DIMENSIONS = 'dimensions-embed', +} + +export enum ExternalScriptsStatus { + LOADED = 'loaded', + ALREADY_LOADED = 'already loaded', + NOT_LOADED = 'not loaded', +} + +export const ExternalScriptsList: ExternalScript[] = [ + { + name: ExternalScriptsNames.DIMENSIONS, + src: 'https://badge.dimensions.ai/badge.js', + + }, +]; diff --git a/src/app/shared/utils/scripts-loader/external-script-loader.service.ts b/src/app/shared/utils/scripts-loader/external-script-loader.service.ts new file mode 100644 index 00000000000..60fa29a8c56 --- /dev/null +++ b/src/app/shared/utils/scripts-loader/external-script-loader.service.ts @@ -0,0 +1,103 @@ +import { Injectable } from '@angular/core'; +import { + ExternalScriptsList, + ExternalScriptsStatus, +} from './external-script.model'; + +declare const document: any; + +/** + * Service used to load external scripts in the DOM when it cannot be loaded from the standard angular methods + */ +@Injectable() +export class ExternalScriptLoaderService { + private scriptsStored: any = {}; + + constructor() { + ExternalScriptsList.forEach( + ({ name, src }) => (this.scriptsStored[name] = { loaded: false, src }) + ); + } + + /** + * Load the scripts in the DOM and return every {@link Promise} + * @param scriptsToLoad Scripts to load, see {@link scriptsToLoad} + * @returns An array of the scripts promises + */ + load(...scriptsToLoad: string[]): Promise { + let promises: any[] = []; + scriptsToLoad.forEach((script) => promises.push(this.loadScript(script))); + return Promise.all(promises); + } + + private loadScript(name: string) { + return new Promise((resolve) => { + if (this.isAlreadyLoaded(name)) { + resolve( + this.createResolveResult( + name, + true, + ExternalScriptsStatus.ALREADY_LOADED + ) + ); + } else { + let script = this.createScriptHTMLElement(name); + if (this.areWeInIE(script)) { + this.configureOnReadyState(name, script, resolve); + } else { + this.configureOnLoad(name, script, resolve); + } + this.configureOnError(name, script, resolve); + document.getElementsByTagName('head')[0].appendChild(script); + } + }); + } + + private isAlreadyLoaded(name: string): boolean { + return this.scriptsStored[name].loaded; + } + + private areWeInIE(script: any): boolean { + return script.readyState; + } + + private createResolveResult(script: string, loaded: boolean, status: string) { + return { script, loaded, status }; + } + + private createScriptHTMLElement(name: string): any { + let script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = this.scriptsStored[name].src; + + return script; + } + + private configureOnReadyState(name: string, script: any, resolve: any) { + script.onreadystatechange = () => { + if (script.readyState === 'loaded' || script.readyState === 'complete') { + script.onreadystatechange = null; + this.scriptsStored[name].loaded = true; + resolve( + this.createResolveResult(name, true, ExternalScriptsStatus.LOADED) + ); + } + }; + } + + private configureOnLoad(name: string, script: any, resolve: any) { + script.onload = () => { + this.scriptsStored[name].loaded = true; + resolve( + this.createResolveResult(name, true, ExternalScriptsStatus.LOADED) + ); + }; + } + + private configureOnError(name: string, script: any, resolve: any) { + script.onerror = (error: any) => + resolve( + this.createResolveResult(name, false, ExternalScriptsStatus.NOT_LOADED) + ); + } +} diff --git a/src/app/shared/utils/scripts-loader/external-script.model.ts b/src/app/shared/utils/scripts-loader/external-script.model.ts new file mode 100644 index 00000000000..a6bae54959a --- /dev/null +++ b/src/app/shared/utils/scripts-loader/external-script.model.ts @@ -0,0 +1,22 @@ +export interface ExternalScript { + name: string; + src: string; +} + +export enum ExternalScriptsNames { + ALTMETRIC = 'altmetric-embed', +} + +export enum ExternalScriptsStatus { + LOADED = 'loaded', + ALREADY_LOADED = 'already loaded', + NOT_LOADED = 'not loaded', +} + +export const ExternalScriptsList: ExternalScript[] = [ + { + name: ExternalScriptsNames.ALTMETRIC, + src: 'https://d1bxh8uas1mnw7.cloudfront.net/assets/embed.js', + + }, +]; diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 2ec85cb8522..d5065240c99 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -780,7 +780,7 @@ "browse.comcol.by.subject": "By Subject", - "browse.comcol.by.srsc": "By Subject Category", + "browse.comcol.by.srsc": "By Subject Category 123", "browse.comcol.by.title": "By Title", @@ -856,7 +856,7 @@ "browse.startsWith.type_date.label": "Or type in a date (year-month) and click on the Browse button", - "browse.startsWith.type_text": "Filter results by typing the first few letters", + "browse.startsWith.type_text": "Type the starting word(s) to filter results", "browse.startsWith.input": "Filter", @@ -1470,7 +1470,7 @@ "dso-selector.set-scope.community.head": "Select a search scope", - "dso-selector.set-scope.community.button": "Search all of DSpace", + "dso-selector.set-scope.community.button": "Search all of DB Docs", "dso-selector.set-scope.community.or-divider": "or", @@ -2454,6 +2454,8 @@ "item.page.claim.tooltip": "Claim this item as profile", + "item.page.metrics": "Metrics", + "item.preview.dc.identifier.uri": "Identifier:", "item.preview.dc.contributor.author": "Authors:", @@ -2824,17 +2826,17 @@ "menu.section.browse_community_by_title": "By Title", - "menu.section.browse_global": "All of Deep Blue Documents", + "menu.section.browse_global": "Browse", - "menu.section.browse_global_by_author": "By Author", + "menu.section.browse_global_by_author": "Author", - "menu.section.browse_global_by_dateissued": "By Issue Date", + "menu.section.browse_global_by_dateissued": "Issue Date", - "menu.section.browse_global_by_subject": "By Subject", + "menu.section.browse_global_by_subject": "Subject", - "menu.section.browse_global_by_srsc": "By Subject Category", + "menu.section.browse_global_by_srsc": "By Subject Category 456", - "menu.section.browse_global_by_title": "By Title", + "menu.section.browse_global_by_title": "Title", "menu.section.browse_global_communities_and_collections": "Communities & Collections", @@ -3042,7 +3044,7 @@ "mydspace.view-btn": "View", - "nav.browse.header": "All of DSpace", + "nav.browse.header": "All of DB Docs", "nav.community-browse.header": "By Community", @@ -3680,6 +3682,22 @@ "search.filters.filter.author.label": "Search author name", + "search.filters.filter.peerreviewed.placeholder": "PeerReviewed", + + "search.filters.filter.peerreviewed.label": "Search PeerReviewed", + + "search.filters.filter.identifiersource.placeholder": "Source", + + "search.filters.filter.identifiersource.label": "Search Source", + + "search.filters.filter.hlbtoplevel.placeholder": "Top Level Subject", + + "search.filters.filter.hlbtoplevel.label": "Search Top Level Subject", + + "search.filters.filter.hlbsecondlevel.placeholder": "Second Level Subject", + + "search.filters.filter.hlbsecondlevel.label": "Search Second Level Subject", + "search.filters.filter.birthDate.head": "Birth Date", "search.filters.filter.birthDate.placeholder": "Birth Date", diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index c85f416012c..7db902d1079 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -284,7 +284,8 @@ export class DefaultAppConfig implements AppConfig { // Rounded to the nearest size in the list of selectable sizes on the // settings menu. See pageSizeOptions in 'pagination-component-options.model.ts'. pageSize: 5 - } + }, + showAltmetricBadge: true, }; // Collection Page Config diff --git a/src/config/item-config.interface.ts b/src/config/item-config.interface.ts index 35cb5260aea..d0ca671b9d3 100644 --- a/src/config/item-config.interface.ts +++ b/src/config/item-config.interface.ts @@ -13,4 +13,6 @@ export interface ItemConfig extends Config { // settings menu. See pageSizeOptions in 'pagination-component-options.model.ts'. pageSize: number; } + + showAltmetricBadge: boolean; } diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index cb9d2c71303..05b9d650c3f 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -255,7 +255,8 @@ export const environment: BuildConfig = { // Rounded to the nearest size in the list of selectable sizes on the // settings menu. See pageSizeOptions in 'pagination-component-options.model.ts'. pageSize: 5 - } + }, + showAltmetricBadge: true, }, collection: { edit: { diff --git a/src/styles/_global-styles.scss b/src/styles/_global-styles.scss index 47079eec802..5f45d6b588e 100644 --- a/src/styles/_global-styles.scss +++ b/src/styles/_global-styles.scss @@ -17,9 +17,7 @@ h1 { } h2 { - font-size: 1.75rem; - font-weight: var(--bold); - line-height: var(--line-height-heading); + font-size: 1.25rem; } h3 { diff --git a/src/themes/dspace/app/header/header.component.html b/src/themes/dspace/app/header/header.component.html index 8cc58fdc071..d952a440322 100644 --- a/src/themes/dspace/app/header/header.component.html +++ b/src/themes/dspace/app/header/header.component.html @@ -3,28 +3,7 @@
-
diff --git a/src/themes/dspace/app/header/header.component.scss b/src/themes/dspace/app/header/header.component.scss index 2fc857826f9..37f59f6189a 100644 --- a/src/themes/dspace/app/header/header.component.scss +++ b/src/themes/dspace/app/header/header.component.scss @@ -1,6 +1,6 @@ @media screen and (min-width: map-get($grid-breakpoints, md)) { nav.navbar { - display: none; + } .header { background-color: var(--ds-header-bg); diff --git a/src/themes/dspace/app/navbar/navbar.component.html b/src/themes/dspace/app/navbar/navbar.component.html index c114a5dc11a..b611a6db655 100644 --- a/src/themes/dspace/app/navbar/navbar.component.html +++ b/src/themes/dspace/app/navbar/navbar.component.html @@ -18,7 +18,8 @@
- + + diff --git a/src/themes/dspace/app/navbar/navbar.component.scss b/src/themes/dspace/app/navbar/navbar.component.scss index 2f62a237e26..f40a62a84a6 100644 --- a/src/themes/dspace/app/navbar/navbar.component.scss +++ b/src/themes/dspace/app/navbar/navbar.component.scss @@ -6,11 +6,9 @@ nav.navbar { /** Mobile menu styling **/ @media screen and (max-width: map-get($grid-breakpoints, md)-0.02) { .navbar { - width: 100%; - background-color: var(--bs-white); - position: absolute; - overflow: hidden; - height: 0; + width: 100%; + overflow: hidden; + height: auto !important; &.open { height: 100vh; //doesn't matter because wrapper is sticky } @@ -33,7 +31,7 @@ nav.navbar { display: none; } .navbar-collapsed { - display: none; + /* display: none; This is so that the Login shows up when the page is made smaller */ } } padding: 0;