diff --git a/.eslintrc.json b/.eslintrc.json index 4cc8c6dbc5..5fb4c12171 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -165,6 +165,7 @@ "@angular-eslint/no-output-native": "warn", "@angular-eslint/no-output-on-prefix": "warn", "@angular-eslint/no-conflicting-lifecycle": "warn", + "@angular-eslint/use-lifecycle-interface": "error", "@typescript-eslint/no-inferrable-types":[ "error", diff --git a/Dockerfile.dist b/Dockerfile.dist index e4b467ae26..4c47b0cb40 100644 --- a/Dockerfile.dist +++ b/Dockerfile.dist @@ -4,7 +4,7 @@ # Test build: # docker build -f Dockerfile.dist -t dspace/dspace-angular:latest-dist . -FROM node:18-alpine as build +FROM node:18-alpine AS build # Ensure Python and other build tools are available # These are needed to install some node modules, especially on linux/arm64 diff --git a/docker/README.md b/docker/README.md index 64d8ddc2c5..3dc5fd5055 100644 --- a/docker/README.md +++ b/docker/README.md @@ -59,19 +59,19 @@ A default/demo version of this image is built *automatically*. ## To refresh / pull DSpace images from Dockerhub ``` -docker-compose -f docker/docker-compose.yml pull +docker compose -f docker/docker-compose.yml pull ``` ## To build DSpace images using code in your branch ``` -docker-compose -f docker/docker-compose.yml build +docker compose -f docker/docker-compose.yml build ``` ## To start DSpace (REST and Angular) from your branch This command provides a quick way to start both the frontend & backend from this single codebase ``` -docker-compose -p d8 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml up -d +docker compose -p d8 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml up -d ``` Keep in mind, you may also start the backend by cloning the 'DSpace/DSpace' GitHub repository separately. See the next section. @@ -86,14 +86,14 @@ _The system will be started in 2 steps. Each step shares the same docker network From 'DSpace/DSpace' clone (build first as needed): ``` -docker-compose -p d8 up -d +docker compose -p d8 up -d ``` NOTE: More detailed instructions on starting the backend via Docker can be found in the [Docker Compose instructions for the Backend](https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/README.md). From 'DSpace/dspace-angular' clone (build first as needed) ``` -docker-compose -p d8 -f docker/docker-compose.yml up -d +docker compose -p d8 -f docker/docker-compose.yml up -d ``` At this point, you should be able to access the UI from http://localhost:4000, @@ -105,21 +105,21 @@ This allows you to run the Angular UI in *production* mode, pointing it at the d (https://demo.dspace.org/server/ or https://sandbox.dspace.org/server/). ``` -docker-compose -f docker/docker-compose-dist.yml pull -docker-compose -f docker/docker-compose-dist.yml build -docker-compose -p d8 -f docker/docker-compose-dist.yml up -d +docker compose -f docker/docker-compose-dist.yml pull +docker compose -f docker/docker-compose-dist.yml build +docker compose -p d8 -f docker/docker-compose-dist.yml up -d ``` ## Ingest test data from AIPDIR Create an administrator ``` -docker-compose -p d8 -f docker/cli.yml run --rm dspace-cli create-administrator -e test@test.edu -f admin -l user -p admin -c en +docker compose -p d8 -f docker/cli.yml run --rm dspace-cli create-administrator -e test@test.edu -f admin -l user -p admin -c en ``` Load content from AIP files ``` -docker-compose -p d8 -f docker/cli.yml -f ./docker/cli.ingest.yml run --rm dspace-cli +docker compose -p d8 -f docker/cli.yml -f ./docker/cli.ingest.yml run --rm dspace-cli ``` ## Alternative Ingest - Use Entities dataset @@ -127,12 +127,12 @@ _Delete your docker volumes or use a unique project (-p) name_ Start DSpace with Database Content from a database dump ``` -docker-compose -p d8 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml -f docker/db.entities.yml up -d +docker compose -p d8 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml -f docker/db.entities.yml up -d ``` Load assetstore content and trigger a re-index of the repository ``` -docker-compose -p d8 -f docker/cli.yml -f docker/cli.assetstore.yml run --rm dspace-cli +docker compose -p d8 -f docker/cli.yml -f docker/cli.assetstore.yml run --rm dspace-cli ``` ## End to end testing of the REST API (runs in GitHub Actions CI). @@ -140,5 +140,5 @@ _In this instance, only the REST api runs in Docker using the Entities dataset. This command is only really useful for testing our Continuous Integration process. ``` -docker-compose -p d8ci -f docker/docker-compose-ci.yml up -d +docker compose -p d8ci -f docker/docker-compose-ci.yml up -d ``` diff --git a/docker/db.entities.yml b/docker/db.entities.yml index ae1ac77346..b3cf5bd86f 100644 --- a/docker/db.entities.yml +++ b/docker/db.entities.yml @@ -14,7 +14,7 @@ # # Therefore, it should be kept in sync with that file services: dspacedb: - image: dspace/dspace-postgres-pgcrypto::${DSPACE_VER:-latest}-loadsql + image: "${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-latest}-loadsql" environment: # This LOADSQL should be kept in sync with the URL in DSpace/DSpace # This SQL is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data @@ -34,4 +34,4 @@ services: - | while (! /dev/null 2>&1; do sleep 1; done; /dspace/bin/dspace database migrate ignored - java -jar /dspace/webapps/server-boot.jar --dspace.dir=/dspace \ No newline at end of file + java -jar /dspace/webapps/server-boot.jar --dspace.dir=/dspace diff --git a/docker/docker-compose-rest.yml b/docker/docker-compose-rest.yml index c04948e4b2..09dfcf2a5f 100644 --- a/docker/docker-compose-rest.yml +++ b/docker/docker-compose-rest.yml @@ -101,7 +101,7 @@ services: # * First, run precreate-core to create the core (if it doesn't yet exist). If exists already, this is a no-op # * Second, copy configsets to this core: # Updates to Solr configs require the container to be rebuilt/restarted: - # `docker-compose -p d7 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml up -d --build dspacesolr` + # `docker compose -p d7 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml up -d --build dspacesolr` entrypoint: - /bin/bash - '-c' diff --git a/package.json b/package.json index 3679208516..ada0525149 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dspace-angular", - "version": "8.0.0", + "version": "8.1.0-next", "scripts": { "ng": "ng", "config:watch": "nodemon", diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.ts b/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.ts index a148559bd2..be989d49e2 100644 --- a/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.ts +++ b/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.ts @@ -4,7 +4,10 @@ import { NgForOf, NgIf, } from '@angular/common'; -import { Component } from '@angular/core'; +import { + Component, + OnDestroy, +} from '@angular/core'; import { Router, RouterLink, @@ -60,7 +63,7 @@ import { MetadataSchemaFormComponent } from './metadata-schema-form/metadata-sch * A component used for managing all existing metadata schemas within the repository. * The admin can create, edit or delete metadata schemas here. */ -export class MetadataRegistryComponent { +export class MetadataRegistryComponent implements OnDestroy { /** * A list of all the current metadata schemas within the repository diff --git a/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.ts b/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.ts index b4bebfc924..e1f54bd8d3 100644 --- a/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.ts +++ b/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.ts @@ -4,6 +4,7 @@ import { } from '@angular/common'; import { Component, + OnInit, ViewChild, } from '@angular/core'; import { @@ -40,7 +41,7 @@ import { FilteredCollections } from './filtered-collections.model'; ], standalone: true, }) -export class FilteredCollectionsComponent { +export class FilteredCollectionsComponent implements OnInit { queryForm: FormGroup; results: FilteredCollections = new FilteredCollections(); diff --git a/src/app/admin/admin-reports/filtered-items/filtered-items.component.ts b/src/app/admin/admin-reports/filtered-items/filtered-items.component.ts index 52454f2e8e..73b0eeb771 100644 --- a/src/app/admin/admin-reports/filtered-items/filtered-items.component.ts +++ b/src/app/admin/admin-reports/filtered-items/filtered-items.component.ts @@ -5,6 +5,7 @@ import { } from '@angular/common'; import { Component, + OnInit, ViewChild, } from '@angular/core'; import { @@ -68,7 +69,7 @@ import { QueryPredicate } from './query-predicate.model'; ], standalone: true, }) -export class FilteredItemsComponent { +export class FilteredItemsComponent implements OnInit { collections: OptionVO[]; presetQueries: PresetQuery[]; @@ -92,7 +93,7 @@ export class FilteredItemsComponent { private formBuilder: FormBuilder, private restService: DspaceRestService) {} - ngOnInit() { + ngOnInit(): void { this.loadCollections(); this.loadPresetQueries(); this.loadMetadataFields(); diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.ts index 172226dd07..38352e7816 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.ts @@ -1,4 +1,7 @@ -import { Component } from '@angular/core'; +import { + Component, + OnInit, +} from '@angular/core'; import { RouterLink } from '@angular/router'; import { getCollectionEditRoute } from '../../../../../collection-page/collection-page-routing-paths'; @@ -21,10 +24,10 @@ import { SearchResultGridElementComponent } from '../../../../../shared/object-g /** * The component for displaying a list element for a collection search result on the admin search page */ -export class CollectionAdminSearchResultGridElementComponent extends SearchResultGridElementComponent { +export class CollectionAdminSearchResultGridElementComponent extends SearchResultGridElementComponent implements OnInit { editPath: string; - ngOnInit() { + ngOnInit(): void { super.ngOnInit(); this.editPath = getCollectionEditRoute(this.dso.uuid); } diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.ts index 50be35229d..509c1e9266 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.ts @@ -1,4 +1,7 @@ -import { Component } from '@angular/core'; +import { + Component, + OnInit, +} from '@angular/core'; import { RouterLink } from '@angular/router'; import { getCommunityEditRoute } from '../../../../../community-page/community-page-routing-paths'; @@ -21,10 +24,10 @@ import { SearchResultGridElementComponent } from '../../../../../shared/object-g /** * The component for displaying a list element for a community search result on the admin search page */ -export class CommunityAdminSearchResultGridElementComponent extends SearchResultGridElementComponent { +export class CommunityAdminSearchResultGridElementComponent extends SearchResultGridElementComponent implements OnInit { editPath: string; - ngOnInit() { + ngOnInit(): void { super.ngOnInit(); this.editPath = getCommunityEditRoute(this.dso.uuid); } diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.ts index 37afbf14fe..0924379ea5 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.ts @@ -1,4 +1,7 @@ -import { Component } from '@angular/core'; +import { + Component, + OnInit, +} from '@angular/core'; import { RouterLink } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; @@ -22,10 +25,10 @@ import { SearchResultListElementComponent } from '../../../../../shared/object-l /** * The component for displaying a list element for a collection search result on the admin search page */ -export class CollectionAdminSearchResultListElementComponent extends SearchResultListElementComponent { +export class CollectionAdminSearchResultListElementComponent extends SearchResultListElementComponent implements OnInit { editPath: string; - ngOnInit() { + ngOnInit(): void { super.ngOnInit(); this.editPath = getCollectionEditRoute(this.dso.uuid); } diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.ts index 5861f15c1f..c4146dbd60 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.ts @@ -1,4 +1,7 @@ -import { Component } from '@angular/core'; +import { + Component, + OnInit, +} from '@angular/core'; import { RouterLink } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; @@ -22,10 +25,10 @@ import { SearchResultListElementComponent } from '../../../../../shared/object-l /** * The component for displaying a list element for a community search result on the admin search page */ -export class CommunityAdminSearchResultListElementComponent extends SearchResultListElementComponent { +export class CommunityAdminSearchResultListElementComponent extends SearchResultListElementComponent implements OnInit { editPath: string; - ngOnInit() { + ngOnInit(): void { super.ngOnInit(); this.editPath = getCommunityEditRoute(this.dso.uuid); } diff --git a/src/app/collection-page/edit-collection-page/collection-curate/collection-curate.component.ts b/src/app/collection-page/edit-collection-page/collection-curate/collection-curate.component.ts index 14ba01421b..370506e473 100644 --- a/src/app/collection-page/edit-collection-page/collection-curate/collection-curate.component.ts +++ b/src/app/collection-page/edit-collection-page/collection-curate/collection-curate.component.ts @@ -1,5 +1,8 @@ import { AsyncPipe } from '@angular/common'; -import { Component } from '@angular/core'; +import { + Component, + OnInit, +} from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; import { Observable } from 'rxjs'; @@ -28,7 +31,7 @@ import { hasValue } from '../../../shared/empty.util'; ], standalone: true, }) -export class CollectionCurateComponent { +export class CollectionCurateComponent implements OnInit { dsoRD$: Observable>; collectionName$: Observable; diff --git a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts index fef7868561..06dd537d9d 100644 --- a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts +++ b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts @@ -97,7 +97,7 @@ export class CollectionSourceControlsComponent implements OnInit, OnDestroy { ) { } - ngOnInit() { + ngOnInit(): void { // ensure the contentSource gets updated after being set to stale this.contentSource$ = this.collectionService.findByHref(this.collection._links.self.href, false).pipe( getAllSucceededRemoteDataPayload(), 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 037f690057..7fe4263d46 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 @@ -27,6 +27,14 @@ 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 } })); + 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/core/shared/client-math.service.ts b/src/app/core/shared/client-math.service.ts index 2fde521ba9..0630ec3246 100644 --- a/src/app/core/shared/client-math.service.ts +++ b/src/app/core/shared/client-math.service.ts @@ -31,7 +31,7 @@ export class ClientMathService extends MathService { protected mathJaxOptions = { tex: { - inlineMath: [['$', '$'], ['\\(', '\\)']], + inlineMath: [['$', '$'], ['$$', '$$'], ['\\(', '\\)']], }, svg: { fontCache: 'global', @@ -108,7 +108,7 @@ export class ClientMathService extends MathService { */ render(element: HTMLElement) { if (environment.markdown.mathjax) { - this._window.nativeWindow.MathJax.typesetPromise([element]); + return (window as any).MathJax.typesetPromise([element]) as Promise; } } } diff --git a/src/app/core/shared/math.service.spec.ts b/src/app/core/shared/math.service.spec.ts index cc0b2814f2..75d8bf1024 100644 --- a/src/app/core/shared/math.service.spec.ts +++ b/src/app/core/shared/math.service.spec.ts @@ -22,8 +22,8 @@ export class MockMathService extends MathService { return of(true); } - render(element: HTMLElement): void { - return; + render(element: HTMLElement): Promise { + return Promise.resolve(); } } diff --git a/src/app/core/shared/math.service.ts b/src/app/core/shared/math.service.ts index c06ce06220..909e039564 100644 --- a/src/app/core/shared/math.service.ts +++ b/src/app/core/shared/math.service.ts @@ -15,5 +15,5 @@ export abstract class MathService { protected abstract registerMathJaxAsync(config: MathJaxConfig): Promise; abstract ready(): Observable; - abstract render(element: HTMLElement): void; + abstract render(element: HTMLElement): Promise; } diff --git a/src/app/core/shared/server-math.service.ts b/src/app/core/shared/server-math.service.ts index 75fa775fee..62070dbafc 100644 --- a/src/app/core/shared/server-math.service.ts +++ b/src/app/core/shared/server-math.service.ts @@ -47,6 +47,6 @@ export class ServerMathService extends MathService { } render(element: HTMLElement) { - return; + return Promise.resolve(); } } diff --git a/src/app/core/submission/submission-duplicate-data.service.ts b/src/app/core/submission/submission-duplicate-data.service.ts index 755fec05c2..b327342469 100644 --- a/src/app/core/submission/submission-duplicate-data.service.ts +++ b/src/app/core/submission/submission-duplicate-data.service.ts @@ -1,5 +1,8 @@ /* eslint-disable max-classes-per-file */ -import { Injectable } from '@angular/core'; +import { + Injectable, + OnDestroy, +} from '@angular/core'; import { Observable } from 'rxjs'; import { Duplicate } from '../../shared/object-list/duplicate-data/duplicate.model'; @@ -33,7 +36,8 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; * */ @Injectable({ providedIn: 'root' }) -export class SubmissionDuplicateDataService extends BaseDataService implements SearchData { +export class SubmissionDuplicateDataService extends BaseDataService + implements SearchData, OnDestroy { /** * The ResponseParsingService constructor name diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.html b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.html index f288b39d6e..05d578324d 100644 --- a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.html +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.html @@ -1,21 +1,25 @@ diff --git a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts index 1ddac22dd3..ac524739d4 100644 --- a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts @@ -91,6 +91,11 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple */ range: [number | undefined, number | undefined]; + /** + * The range currently selected by the slider + */ + sliderRange: [number | undefined, number | undefined]; + /** * Whether the sider is being controlled by the keyboard. * Supresses any changes until the key is released. @@ -145,6 +150,15 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple }; } + /** + * Updates the sliderRange property with the current slider range. + * This method is called whenever the slider value changes, but it does not immediately apply the changes. + * @param range - The current range selected by the slider + */ + onSliderChange(range: [number | undefined, number | undefined]): void { + this.sliderRange = range; + } + /** * Submits new custom range values to the range filter from the widget */ @@ -182,5 +196,4 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple shouldShowSlider(): boolean { return isPlatformBrowser(this.platformId); } - } diff --git a/src/app/shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.component.html b/src/app/shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.component.html index babfa91f0b..38c2af97fc 100644 --- a/src/app/shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.component.html +++ b/src/app/shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.component.html @@ -19,9 +19,9 @@ {{ ((fileObject === null || fileObject === undefined) ? dropMessageLabel : dropMessageLabelReplacement) | translate}} {{'uploader.or' | translate}}

diff --git a/src/app/shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.component.ts b/src/app/shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.component.ts index 8c04bd264b..e937b1ea0f 100644 --- a/src/app/shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.component.ts +++ b/src/app/shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.component.ts @@ -54,7 +54,7 @@ export class FileDropzoneNoUploaderComponent implements OnInit { /** * The function to call when file is added */ - @Output() onFileAdded: EventEmitter = new EventEmitter(); + @Output() onFileAdded: EventEmitter = new EventEmitter(); /** * The uploader configuration options @@ -83,15 +83,17 @@ export class FileDropzoneNoUploaderComponent implements OnInit { } @HostListener('window:drop', ['$event']) - onDrop(event: any) { + onDrop(event: DragEvent) { event.preventDefault(); + event.stopPropagation(); } @HostListener('window:dragover', ['$event']) - onDragOver(event: any) { + onDragOver(event: DragEvent) { // Show drop area on the page event.preventDefault(); - if ((event.target as any).tagName !== 'HTML') { + event.stopPropagation(); + if ((event.target as HTMLElement).tagName !== 'HTML') { this.isOverDocumentDropZone = observableOf(true); } } @@ -105,11 +107,18 @@ export class FileDropzoneNoUploaderComponent implements OnInit { } } + public handleFileInput(event: Event) { + const input = event.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + this.setFile(input.files); + } + } + /** * Set file * @param files */ - setFile(files) { + public setFile(files: FileList) { this.fileObject = files.length > 0 ? files[0] : undefined; this.onFileAdded.emit(this.fileObject); } diff --git a/src/app/shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.scss b/src/app/shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.scss index 5d06cd5718..91eb2ae868 100644 --- a/src/app/shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.scss +++ b/src/app/shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.scss @@ -6,10 +6,16 @@ top: 0; left: 0; z-index: -1; + display: block; + opacity: 0; + visibility: hidden; + transition: opacity 0.3s ease, visibility 0.3s ease; } .ds-document-drop-zone-active { z-index: var(--ds-drop-zone-area-z-index) !important; + opacity: 1; + visibility: visible; } .ds-document-drop-zone-inner { diff --git a/src/app/shared/upload/uploader/uploader.component.ts b/src/app/shared/upload/uploader/uploader.component.ts index fe8fb6d038..ec66981f16 100644 --- a/src/app/shared/upload/uploader/uploader.component.ts +++ b/src/app/shared/upload/uploader/uploader.component.ts @@ -1,12 +1,14 @@ import { CommonModule } from '@angular/common'; import { HttpXsrfTokenExtractor } from '@angular/common/http'; import { + AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, HostListener, Input, + OnInit, Output, ViewEncapsulation, } from '@angular/core'; @@ -43,7 +45,7 @@ import { UploaderProperties } from './uploader-properties.model'; standalone: true, imports: [TranslateModule, FileUploadModule, CommonModule, DisabledDirective], }) -export class UploaderComponent { +export class UploaderComponent implements OnInit, AfterViewInit { /** * The message to show when drag files on the drop zone @@ -123,7 +125,7 @@ export class UploaderComponent { /** * Method provided by Angular. Invoked after the constructor. */ - ngOnInit() { + ngOnInit(): void { this.uploaderId = 'ds-drag-and-drop-uploader' + uniqueId(); this.checkConfig(this.uploadFilesOptions); this.uploader = new FileUploader({ @@ -148,7 +150,7 @@ export class UploaderComponent { } } - ngAfterViewInit() { + ngAfterViewInit(): void { this.uploader.onAfterAddingAll = ((items) => { this.onFileSelected.emit(items); }); diff --git a/src/app/shared/utils/markdown.directive.ts b/src/app/shared/utils/markdown.directive.ts index a38b079ffd..0540e25cc5 100644 --- a/src/app/shared/utils/markdown.directive.ts +++ b/src/app/shared/utils/markdown.directive.ts @@ -55,30 +55,40 @@ export class MarkdownDirective implements OnInit, OnDestroy { async render(value: string, forcePreview = false): Promise { if (isEmpty(value) || (!environment.markdown.enabled && !forcePreview)) { - return value; + this.el.innerHTML = value; + return; + } else { + if (environment.markdown.mathjax) { + this.renderMathjaxThenMarkdown(value); + } else { + this.renderMarkdown(value); + } } + } + + private renderMathjaxThenMarkdown(value: string) { + const sanitized = this.sanitizer.sanitize(SecurityContext.HTML, value); + this.el.innerHTML = sanitized; + this.mathService.ready().pipe( + filter((ready) => ready), + take(1), + takeUntil(this.alive$), + ).subscribe(() => { + this.mathService.render(this.el)?.then(_ => { + this.renderMarkdown(this.el.innerHTML, true); + }); + }); + } + + private async renderMarkdown(value: string, alreadySanitized = false) { const MarkdownIt = await this.markdownIt; const md = new MarkdownIt({ html: true, linkify: true, }); - const html = this.sanitizer.sanitize(SecurityContext.HTML, md.render(value)); + const html = alreadySanitized ? md.render(value) : this.sanitizer.sanitize(SecurityContext.HTML, md.render(value)); this.el.innerHTML = html; - - if (environment.markdown.mathjax) { - this.renderMathjax(); - } - } - - private renderMathjax() { - this.mathService.ready().pipe( - filter((ready) => ready), - take(1), - takeUntil(this.alive$), - ).subscribe(() => { - this.mathService.render(this.el); - }); } ngOnDestroy() { diff --git a/src/app/submission/form/collection/submission-form-collection.component.ts b/src/app/submission/form/collection/submission-form-collection.component.ts index 1749e9165a..7e574a00d5 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.ts +++ b/src/app/submission/form/collection/submission-form-collection.component.ts @@ -5,6 +5,7 @@ import { EventEmitter, Input, OnChanges, + OnDestroy, OnInit, Output, SimpleChanges, @@ -60,7 +61,7 @@ import { SubmissionService } from '../../submission.service'; DisabledDirective, ], }) -export class SubmissionFormCollectionComponent implements OnChanges, OnInit { +export class SubmissionFormCollectionComponent implements OnDestroy, OnChanges, OnInit { /** * The current collection id this submission belonging to diff --git a/src/app/submission/form/submission-upload-files/submission-upload-files.component.ts b/src/app/submission/form/submission-upload-files/submission-upload-files.component.ts index e694a868ca..3632ec6760 100644 --- a/src/app/submission/form/submission-upload-files/submission-upload-files.component.ts +++ b/src/app/submission/form/submission-upload-files/submission-upload-files.component.ts @@ -3,6 +3,7 @@ import { Component, Input, OnChanges, + OnDestroy, } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { @@ -43,7 +44,7 @@ import parseSectionErrors from '../../utils/parseSectionErrors'; ], standalone: true, }) -export class SubmissionUploadFilesComponent implements OnChanges { +export class SubmissionUploadFilesComponent implements OnChanges, OnDestroy { /** * The collection id this submission belonging to diff --git a/src/app/submission/sections/duplicates/section-duplicates.component.ts b/src/app/submission/sections/duplicates/section-duplicates.component.ts index d90a0ca85a..885511ca52 100644 --- a/src/app/submission/sections/duplicates/section-duplicates.component.ts +++ b/src/app/submission/sections/duplicates/section-duplicates.component.ts @@ -7,6 +7,7 @@ import { ChangeDetectionStrategy, Component, Inject, + OnInit, } from '@angular/core'; import { TranslateModule, @@ -48,7 +49,7 @@ import { SectionsService } from '../sections.service'; standalone: true, }) -export class SubmissionSectionDuplicatesComponent extends SectionModelComponent { +export class SubmissionSectionDuplicatesComponent extends SectionModelComponent implements OnInit { protected readonly Metadata = Metadata; /** * The Alert categories. diff --git a/src/app/submission/sections/identifiers/section-identifiers.component.ts b/src/app/submission/sections/identifiers/section-identifiers.component.ts index 1483ea47b7..e297c795df 100644 --- a/src/app/submission/sections/identifiers/section-identifiers.component.ts +++ b/src/app/submission/sections/identifiers/section-identifiers.component.ts @@ -7,6 +7,7 @@ import { ChangeDetectionStrategy, Component, Inject, + OnInit, } from '@angular/core'; import { TranslateModule, @@ -47,7 +48,7 @@ import { SectionsService } from '../sections.service'; standalone: true, }) -export class SubmissionSectionIdentifiersComponent extends SectionModelComponent { +export class SubmissionSectionIdentifiersComponent extends SectionModelComponent implements OnInit { /** * The Alert categories. * @type {AlertType} @@ -76,7 +77,6 @@ export class SubmissionSectionIdentifiersComponent extends SectionModelComponent /** * Initialize instance variables. * - * @param {PaginationService} paginationService * @param {TranslateService} translate * @param {SectionsService} sectionService * @param {SubmissionService} submissionService @@ -93,7 +93,7 @@ export class SubmissionSectionIdentifiersComponent extends SectionModelComponent super(injectedCollectionId, injectedSectionData, injectedSubmissionId); } - ngOnInit() { + ngOnInit(): void { super.ngOnInit(); } diff --git a/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routes.ts b/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routes.ts index 8f07c24edc..35cf2521c7 100644 --- a/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routes.ts +++ b/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routes.ts @@ -50,16 +50,6 @@ export const ROUTES: Route[] = [ }, data: { title: 'workspace-item.delete', breadcrumbKey: 'workspace-item.delete' }, }, - { - canActivate: [authenticatedGuard], - path: 'delete', - component: ThemedWorkspaceItemsDeletePageComponent, - resolve: { - dso: itemFromWorkspaceResolver, - breadcrumb: i18nBreadcrumbResolver, - }, - data: { title: 'workspace-item.delete', breadcrumbKey: 'workspace-item.delete' }, - }, ], }, ]; diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index 7f2485ab29..285bbfa43d 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -5881,7 +5881,7 @@ "workflow-item.delete.notification.success.title": "Gelöscht", // "workflow-item.delete.notification.success.content": "This workflow item was successfully deleted", - "workflow-item.delete.notification.success.content": "Dieses Workflow-Item wurde gelöscht.", + "workflow-item.delete.notification.success.content": "Dieses Workflow-Item wurde erfolgreich gelöscht.", // "workflow-item.delete.notification.error.title": "Something went wrong", "workflow-item.delete.notification.error.title": "Das hat leider nicht funktioniert.", @@ -5926,4 +5926,36 @@ // "workflow-item.send-back.button.confirm": "Send back" "workflow-item.send-back.button.confirm": "Zurücksenden", + // "workflow-item.view.breadcrumbs": "Workflow View", + "workflow-item.view.breadcrumbs": "Workflow-Ansicht", + + // "workspace-item.view.breadcrumbs": "Workspace View", + "workspace-item.view.breadcrumbs": "Workspace-Ansicht", + + // "workspace-item.view.title": "Workspace View", + "workspace-item.view.title": "Workspace-Ansicht", + + // "workspace-item.delete.breadcrumbs": "Workspace Delete", + "workspace-item.delete.breadcrumbs": "Workspace-Item löschen", + + // "workspace-item.delete.header": "Delete workspace item", + "workspace-item.delete.header": "Workspace-Item löschen", + + // "workspace-item.delete.button.confirm": "Delete", + "workspace-item.delete.button.confirm": "Löschen", + + // "workspace-item.delete.button.cancel": "Cancel", + "workspace-item.delete.button.cancel": "Abbrechen", + + // "workspace-item.delete.notification.success.title": "Deleted", + "workspace-item.delete.notification.success.title": "Gelöscht", + + // "workspace-item.delete.title": "This workspace item was successfully deleted", + "workspace-item.delete.title": "Dieses Workspace-Item wurde erfolgreich gelöscht.", + + // "workspace-item.delete.notification.error.title": "Something went wrong", + "workspace-item.delete.notification.error.title": "Das hat leider nicht funktioniert.", + + // "workspace-item.delete.notification.error.content": "The workspace item could not be deleted", + "workspace-item.delete.notification.error.content": "Das Workspace-Item konnte nicht gelöscht werden.", } diff --git a/src/assets/i18n/fr.json5 b/src/assets/i18n/fr.json5 index 8dc8c95a39..399363f017 100644 --- a/src/assets/i18n/fr.json5 +++ b/src/assets/i18n/fr.json5 @@ -6386,10 +6386,10 @@ "submission.workflow.tasks.pool.show-detail": "Afficher le détail", // "submission.workspace.generic.view": "View", - "submission.workspace.generic.view": "Voir", + "submission.workspace.generic.view": "Afficher", // "submission.workspace.generic.view-help": "Select this option to view the item's metadata.", - "submission.workspace.generic.view-help": "Sélectionner cette option pour voir les métadonnées de l'item.", + "submission.workspace.generic.view-help": "Sélectionner cette option pour afficher les métadonnées de l'item.", // "thumbnail.default.alt": "Thumbnail Image", "thumbnail.default.alt": "Vignette d'image",