From f6b1896f69506de78c760094fa8f61113a898003 Mon Sep 17 00:00:00 2001 From: Juraj Roka <95219754+jr-rk@users.noreply.github.com> Date: Thu, 18 Sep 2025 19:20:53 +0200 Subject: [PATCH 01/27] Changed method of rendering table with versions to toggle dropdown --- .../versions/item-versions.component.html | 218 ++++++++---------- .../versions/item-versions.component.ts | 14 +- 2 files changed, 104 insertions(+), 128 deletions(-) diff --git a/src/app/item-page/versions/item-versions.component.html b/src/app/item-page/versions/item-versions.component.html index 09e8f21e72d..ca7e34f33bd 100644 --- a/src/app/item-page/versions/item-versions.component.html +++ b/src/app/item-page/versions/item-versions.component.html @@ -1,144 +1,108 @@
-

{{ "item.version.history.head" | translate }}

- - {{ "item.version.history.selected.alert" | translate : { version: itemVersion.version } }} - - - - - - - - - - - - - - + + +
{{ "item.version.history.table.version" | translate }}{{ "item.version.history.table.editor" | translate }}{{ "item.version.history.table.date" | translate }}{{ "item.version.history.table.summary" | translate }}
- - + + +
+

{{ "item.version.history.head" | translate }}

+ +
+ + +
+ + + + + + + + + + - - - - - -
{{ "item.version.history.table.version" | translate }}
+ + *ngVar="((hasDraftVersion$ | async) ? getWorkspaceId(versionDTO.version.item) : undefined) as workspaceId$"> + -
+
- - - {{ versionDTO.version.version }} - - - {{ versionDTO.version.version }} - - * + + + + + {{ versionItem.firstMetadataValue('dc.title') || versionItem.name || 'Untitled' }} + + + + + + {{ versionItem.firstMetadataValue('dc.title') || versionItem.name || 'Untitled' }} + + + * - - {{ "item.version.history.table.workspaceItem" | translate }} - + + {{ "item.version.history.table.workspaceItem" | translate }} + - - {{ "item.version.history.table.workflowItem" | translate }} - + + {{ "item.version.history.table.workflowItem" | translate }} + -
+
-
+
-
- - - - - - - - - - -
- -
+ + + + + + + + +
-
-
-
- {{ versionDTO.version.submitterName }} - - {{ versionDTO.version.created | date : 'yyyy-MM-dd HH:mm:ss' }} - -
- - {{ versionDTO.version.summary }} - - - -
+ -
- - - - - - - - + -
-
-
* {{ "item.version.history.selected" | translate }}
-
+
+
* {{ "item.version.history.selected" | translate }}
+
+
+
diff --git a/src/app/item-page/versions/item-versions.component.ts b/src/app/item-page/versions/item-versions.component.ts index 500cd69ffc4..4de7ac857a7 100644 --- a/src/app/item-page/versions/item-versions.component.ts +++ b/src/app/item-page/versions/item-versions.component.ts @@ -181,6 +181,11 @@ export class ItemVersionsComponent implements OnDestroy, OnInit { canCreateVersion$: Observable; createVersionTitle$: Observable; + /** + * Toggle state for version history table + */ + showVersionHistory = false; + /** * Show `Editor` column in the table. */ @@ -226,6 +231,13 @@ export class ItemVersionsComponent implements OnDestroy, OnInit { return this.versionBeingEditedNumber != null; } + /** + * Toggle the visibility of version history table + */ + toggleVersionHistory(): void { + this.showVersionHistory = !this.showVersionHistory; + } + /** * True if the specified version is being edited * (used to show input field and to change buttons for specified version) @@ -465,7 +477,7 @@ export class ItemVersionsComponent implements OnDestroy, OnInit { getRemoteDataPayload(), map((versions: PaginatedList) => ({ totalElements: versions.totalElements, - versionDTOs: (versions?.page ?? []).map((version: Version) => ({ + versionDTOs: (versions?.page ?? []).reverse().map((version: Version) => ({ version: version, canEditVersion: this.canEditVersion$(version), canDeleteVersion: this.canDeleteVersion$(version), From 640a0765ffe5a31fc11cdb4174f8caa2c6cb590b Mon Sep 17 00:00:00 2001 From: Juraj Roka <95219754+jr-rk@users.noreply.github.com> Date: Mon, 22 Sep 2025 19:04:57 +0200 Subject: [PATCH 02/27] Refactor by Copilot's suggestions --- .../item-page/versions/item-versions.component.html | 10 +++++----- src/app/item-page/versions/item-versions.component.ts | 8 ++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/app/item-page/versions/item-versions.component.html b/src/app/item-page/versions/item-versions.component.html index ca7e34f33bd..91a54a916c1 100644 --- a/src/app/item-page/versions/item-versions.component.html +++ b/src/app/item-page/versions/item-versions.component.html @@ -1,8 +1,8 @@
- + -
+

{{ "item.version.history.head" | translate }}

@@ -39,13 +39,13 @@

{{ "item.version.history.head" | translate }}

- {{ versionItem.firstMetadataValue('dc.title') || versionItem.name || 'Untitled' }} + {{ getVersionItemDisplayName(versionItem) }} - {{ versionItem.firstMetadataValue('dc.title') || versionItem.name || 'Untitled' }} + {{ getVersionItemDisplayName(versionItem) }} * @@ -102,7 +102,7 @@

{{ "item.version.history.head" | translate }}

* {{ "item.version.history.selected" | translate }}
- +
diff --git a/src/app/item-page/versions/item-versions.component.ts b/src/app/item-page/versions/item-versions.component.ts index 4de7ac857a7..5f0919a6520 100644 --- a/src/app/item-page/versions/item-versions.component.ts +++ b/src/app/item-page/versions/item-versions.component.ts @@ -586,6 +586,14 @@ export class ItemVersionsComponent implements OnDestroy, OnInit { map((item: Item) => this.dsoNameService.getName(item))); } + /** + * Get the display name for a version item + * @param versionItem the item to get the name for + */ + getVersionItemDisplayName(versionItem: Item): string { + return versionItem.firstMetadataValue('dc.title') || versionItem.name || 'Untitled'; + } + getItemHandleFromVersion(version: Version) { return version.item .pipe( From fa5214efd71de78d333cca3932f4017ba2a89046 Mon Sep 17 00:00:00 2001 From: Juraj Roka <95219754+jr-rk@users.noreply.github.com> Date: Tue, 23 Sep 2025 18:18:39 +0200 Subject: [PATCH 03/27] Added version history as item-field --- .../item-page/simple/item-page.component.html | 2 +- .../untyped-item/untyped-item.component.html | 6 ++++++ .../versions/item-versions.component.html | 18 ++++++++++-------- .../versions/item-versions.component.scss | 7 +++++++ .../versions/item-versions.component.ts | 5 +++++ src/themes/custom/eager-theme.module.ts | 2 ++ 6 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/app/item-page/simple/item-page.component.html b/src/app/item-page/simple/item-page.component.html index 176270a0275..23c85358c68 100644 --- a/src/app/item-page/simple/item-page.component.html +++ b/src/app/item-page/simple/item-page.component.html @@ -8,7 +8,7 @@ - +
diff --git a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html index 7ce77f678f4..74e42bacabb 100644 --- a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html +++ b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html @@ -114,6 +114,12 @@ [iconName]="'fa-sitemap'" [separator]="'
'"> + +
{{"item.page.link.full" | translate}} diff --git a/src/app/item-page/versions/item-versions.component.html b/src/app/item-page/versions/item-versions.component.html index 91a54a916c1..f9de3a9f5d1 100644 --- a/src/app/item-page/versions/item-versions.component.html +++ b/src/app/item-page/versions/item-versions.component.html @@ -1,14 +1,16 @@ -
-
- - -
-

{{ "item.version.history.head" | translate }}

- +
+
+
+
+ +
+ {{ "item.version.history.head" | translate }} + +
-
+
Date: Wed, 24 Sep 2025 12:56:30 +0200 Subject: [PATCH 04/27] Divided Admin view and Anonymous/User view of version history and changed UI of item-versions-field --- src/app/item-page/item-page.module.ts | 2 + .../clarin-item-versions-field.component.html | 46 ++++ .../clarin-item-versions-field.component.scss | 17 ++ .../clarin-item-versions-field.component.ts | 238 ++++++++++++++++++ .../item-page/simple/item-page.component.html | 2 +- .../untyped-item/untyped-item.component.html | 8 +- .../versions/item-versions.component.html | 161 ++++++------ .../versions/item-versions.component.scss | 19 +- .../versions/item-versions.component.ts | 50 ++-- src/themes/custom/eager-theme.module.ts | 2 - 10 files changed, 419 insertions(+), 126 deletions(-) create mode 100644 src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.html create mode 100644 src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.scss create mode 100644 src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.ts diff --git a/src/app/item-page/item-page.module.ts b/src/app/item-page/item-page.module.ts index 18bbc4fd606..8d3aae57850 100644 --- a/src/app/item-page/item-page.module.ts +++ b/src/app/item-page/item-page.module.ts @@ -77,6 +77,7 @@ import { NgChartsModule } from 'ng2-charts'; import { ClarinGenericItemFieldComponent } from './simple/field-components/clarin-generic-item-field/clarin-generic-item-field.component'; import { ClarinCollectionsItemFieldComponent } from './simple/field-components/clarin-collections-item-field/clarin-collections-item-field.component'; import { ClarinFilesItemFieldComponent } from './simple/field-components/clarin-files-item-field/clarin-files-item-field.component'; +import { ClarinItemVersionsFieldComponent } from './simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import {PreviewSectionComponent} from './simple/field-components/preview-section/preview-section.component'; import { @@ -147,6 +148,7 @@ const DECLARATIONS = [ ClarinGenericItemFieldComponent, ClarinCollectionsItemFieldComponent, ClarinFilesItemFieldComponent, + ClarinItemVersionsFieldComponent, ClarinSponsorItemFieldComponent, PreviewSectionComponent, FileDescriptionComponent, diff --git a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.html b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.html new file mode 100644 index 00000000000..23119d31be7 --- /dev/null +++ b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.html @@ -0,0 +1,46 @@ + diff --git a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.scss b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.scss new file mode 100644 index 00000000000..e83dcb339df --- /dev/null +++ b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.scss @@ -0,0 +1,17 @@ +@import '../../item-page.component.scss'; + +@media (min-width: 992px) { + .col-2-5 { + flex: 0 0 20%; + max-width: 20%; + } +} + +.dropdown-versions { + background-color: #f8f9fa; + border-radius: 4px; + padding: 2%; + + max-height: 200px; + overflow-y: scroll; +} diff --git a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.ts b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.ts new file mode 100644 index 00000000000..e7bbe646563 --- /dev/null +++ b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.ts @@ -0,0 +1,238 @@ +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { Item } from '../../../../core/shared/item.model'; +import { Version } from '../../../../core/shared/version.model'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { combineLatest, Observable, Subscription } from 'rxjs'; +import { VersionHistory } from '../../../../core/shared/version-history.model'; +import { + getAllSucceededRemoteData, + getFirstCompletedRemoteData, + getFirstSucceededRemoteDataPayload, + getRemoteDataPayload +} from '../../../../core/shared/operators'; +import { map, switchMap } from 'rxjs/operators'; +import { PaginatedList } from '../../../../core/data/paginated-list.model'; +import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model'; +import { VersionHistoryDataService } from '../../../../core/data/version-history-data.service'; +import { PaginatedSearchOptions } from '../../../../shared/search/models/paginated-search-options.model'; +import { followLink } from '../../../../shared/utils/follow-link-config.model'; +import { hasValue, hasValueOperator } from '../../../../shared/empty.util'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; +import { getItemVersionRoute } from '../../../item-page-routing-paths'; +import { WorkspaceItem } from '../../../../core/submission/models/workspaceitem.model'; +import { WorkspaceitemDataService } from '../../../../core/submission/workspaceitem-data.service'; +import { WorkflowItemDataService } from '../../../../core/submission/workflowitem-data.service'; + +interface VersionsDTO { + totalElements: number; + versionDTOs: VersionDTO[]; +} + +interface VersionDTO { + version: Version; +} + +@Component({ + selector: 'ds-clarin-item-versions-field', + templateUrl: './clarin-item-versions-field.component.html', + styleUrls: ['./clarin-item-versions-field.component.scss'] +}) + +/** + * Component for displaying item versions as a field in the clarin item page + */ +export class ClarinItemVersionsFieldComponent implements OnDestroy, OnInit { + + /** + * The item to display version history for + */ + @Input() item: Item; + + /** + * Fontawesome v5. icon name with default settings. + */ + @Input() iconName: string; + + /** + * Array of active subscriptions + */ + subs: Subscription[] = []; + + /** + * The item's version + */ + versionRD$: Observable>; + + /** + * The item's full version history (remote data) + */ + versionHistoryRD$: Observable>; + + /** + * The item's full version history + */ + versionHistory$: Observable; + + /** + * The version history information that is used to render the HTML + */ + versionsDTO$: Observable; + + /** + * Verify if there is an inprogress submission in the version history + */ + hasDraftVersion$: Observable; + + /** + * The amount of versions to display per page + */ + pageSize = 10; + + /** + * The page options to use for fetching the versions + * Start at page 1 and always use the set page size + */ + options = Object.assign(new PaginationComponentOptions(), { + id: 'clarin-ivo', + currentPage: 1, + pageSize: this.pageSize + }); + + /** + * Toggle state for version history + */ + showVersionHistory = false; + + /** + * Observable to check if component should be displayed + */ + showMetadataValue: Observable; + + constructor(private versionHistoryService: VersionHistoryDataService, + private paginationService: PaginationService, + private workspaceItemDataService: WorkspaceitemDataService, + private workflowItemDataService: WorkflowItemDataService + ) { + } + + /** + * Toggle the visibility of version history + */ + toggleVersionHistory(): void { + this.showVersionHistory = !this.showVersionHistory; + } + + /** + * Get the route to the specified version + * @param versionId the ID of the version for which the route will be retrieved + */ + getVersionRoute(versionId: string) { + return getItemVersionRoute(versionId); + } + + /** + * Get all versions for the given version history and store them in versionsDTO$ + * @param versionHistory$ + */ + getAllVersions(versionHistory$: Observable): void { + const currentPagination = this.paginationService.getCurrentPagination(this.options.id, this.options); + this.versionsDTO$ = combineLatest([versionHistory$, currentPagination]).pipe( + switchMap(([versionHistory, options]: [VersionHistory, PaginationComponentOptions]) => { + return this.versionHistoryService.getVersions(versionHistory.id, + new PaginatedSearchOptions({pagination: Object.assign({}, options, {currentPage: options.currentPage})}), + false, true, followLink('item')); + }), + getFirstCompletedRemoteData(), + getRemoteDataPayload(), + map((versions: PaginatedList) => ({ + totalElements: versions.totalElements, + versionDTOs: (versions?.page ?? []).reverse().map((version: Version) => ({ + version: version, + })), + })), + ); + } + + /** + * Get the ID of the workspace item, if present, otherwise return undefined + * @param versionItem the item for which retrieve the workspace item id + */ + getWorkspaceId(versionItem): Observable { + return versionItem.pipe( + getFirstSucceededRemoteDataPayload(), + map((item: Item) => item.uuid), + switchMap((itemUuid: string) => this.workspaceItemDataService.findByItem(itemUuid, true)), + getFirstCompletedRemoteData(), + map((res: RemoteData) => res?.payload?.id ), + ); + } + + /** + * Get the ID of the workflow item, if present, otherwise return undefined + * @param versionItem the item for which retrieve the workspace item id + */ + getWorkflowId(versionItem): Observable { + return versionItem.pipe( + getFirstSucceededRemoteDataPayload(), + map((item: Item) => item.uuid), + switchMap((itemUuid: string) => this.workflowItemDataService.findByItem(itemUuid, true)), + getFirstCompletedRemoteData(), + map((res: RemoteData) => res?.payload?.id ), + ); + } + + /** + * Get the display name for a version item + * @param versionItem the item to get the name for + */ + getVersionItemDisplayName(versionItem: Item): string { + return versionItem.firstMetadataValue('dc.title') || versionItem.name || 'Untitled'; + } + + /** + * Initialize all observables + */ + ngOnInit(): void { + if (hasValue(this.item?.version)) { + this.versionRD$ = this.item.version; + this.versionHistoryRD$ = this.versionRD$.pipe( + getAllSucceededRemoteData(), + getRemoteDataPayload(), + hasValueOperator(), + switchMap((version: Version) => version.versionhistory), + ); + this.versionHistory$ = this.versionHistoryRD$.pipe( + getFirstSucceededRemoteDataPayload(), + hasValueOperator(), + ); + + // If there is a draft item in the version history + this.hasDraftVersion$ = this.versionHistoryRD$.pipe( + getFirstSucceededRemoteDataPayload(), + map((res) => Boolean(res?.draftVersion)), + ); + + this.getAllVersions(this.versionHistory$); + + // Determine if component should be displayed (when there are multiple versions) + this.showMetadataValue = this.versionsDTO$.pipe( + map((versionsDTO: VersionsDTO) => versionsDTO && versionsDTO.totalElements > 1) + ); + } else { + // No version history available + this.showMetadataValue = new Observable(observer => observer.next(false)); + } + } + + /** + * Unsub all subscriptions + */ + cleanupSubscribes() { + this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); + } + + ngOnDestroy(): void { + this.cleanupSubscribes(); + this.paginationService.clearPagination(this.options.id); + } +} diff --git a/src/app/item-page/simple/item-page.component.html b/src/app/item-page/simple/item-page.component.html index 23c85358c68..176270a0275 100644 --- a/src/app/item-page/simple/item-page.component.html +++ b/src/app/item-page/simple/item-page.component.html @@ -8,7 +8,7 @@ - +
diff --git a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html index 74e42bacabb..55eb672b9d2 100644 --- a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html +++ b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html @@ -114,12 +114,10 @@ [iconName]="'fa-sitemap'" [separator]="'
'"> - - + [iconName]="'fa-history'"> +
{{"item.page.link.full" | translate}} diff --git a/src/app/item-page/versions/item-versions.component.html b/src/app/item-page/versions/item-versions.component.html index f9de3a9f5d1..377b3ffd13e 100644 --- a/src/app/item-page/versions/item-versions.component.html +++ b/src/app/item-page/versions/item-versions.component.html @@ -1,110 +1,102 @@ -
-
-
-
- -
- {{ "item.version.history.head" | translate }} - -
-
- - -
- - - - - - - - - - + + +
{{ "item.version.history.table.version" | translate }}
- +
+
+
+

{{ "item.version.history.head" | translate }}

+ + {{ "item.version.history.selected.alert" | translate : { version: itemVersion.version } }} + + + + + + + + + + + - - -
{{ "item.version.history.table.version" | translate }}
+ + - + *ngVar=" ((workspaceId$ | async) ? undefined : getWorkflowId(versionDTO.version.item)) as workflowId$"> -
+
- - {{ getVersionItemDisplayName(versionItem) }} + {{ versionItem.firstMetadataValue('dc.title') || versionItem.name || 'Untitled' }} - {{ getVersionItemDisplayName(versionItem) }} + {{ versionItem.firstMetadataValue('dc.title') || versionItem.name || 'Untitled' }} - * + * - - {{ "item.version.history.table.workspaceItem" | translate }} - + + {{ "item.version.history.table.workspaceItem" | translate }} + - - {{ "item.version.history.table.workflowItem" | translate }} - + + {{ "item.version.history.table.workflowItem" | translate }} + -
+
-
+
-
- - + + + - - - - - - - - -
- + + + + +
- - -
-
* {{ "item.version.history.selected" | translate }}
-
-
+
+ + +
+
* {{ "item.version.history.selected" | translate }}
+
@@ -113,3 +105,4 @@ [type]="AlertTypeEnum.Info"> +
diff --git a/src/app/item-page/versions/item-versions.component.scss b/src/app/item-page/versions/item-versions.component.scss index 5ea5b48c613..7b9d40db20a 100644 --- a/src/app/item-page/versions/item-versions.component.scss +++ b/src/app/item-page/versions/item-versions.component.scss @@ -1,12 +1,4 @@ -.left-column { - float: left; - text-align: left; -} - -.right-column { - float: right; - text-align: right; -} +@import '../simple/item-page.component.scss'; @media (min-width: 992px) { .col-2-5 { @@ -14,3 +6,12 @@ max-width: 20%; } } + +.dropdown-versions { + background-color: #f8f9fa; + border-radius: 4px; + padding: 2%; + + max-height: 200px; + overflow-y: scroll; +} diff --git a/src/app/item-page/versions/item-versions.component.ts b/src/app/item-page/versions/item-versions.component.ts index 5fd466d7443..afbff77706f 100644 --- a/src/app/item-page/versions/item-versions.component.ts +++ b/src/app/item-page/versions/item-versions.component.ts @@ -80,11 +80,6 @@ export class ItemVersionsComponent implements OnDestroy, OnInit { */ @Input() item: Item; - /** - * Fontawesome v5. icon name with default settings. - */ - @Input() iconName: string; - /** * An option to display the list of versions, even when there aren't any. * Instead of the table, an alert will be displayed, notifying the user there are no other versions present @@ -144,6 +139,12 @@ export class ItemVersionsComponent implements OnDestroy, OnInit { */ hasDraftVersion$: Observable; + /** + * Check if the current user is an admin + * Used to control component visibility + */ + isAdmin$: Observable; + /** * The amount of versions to display per page */ @@ -186,11 +187,6 @@ export class ItemVersionsComponent implements OnDestroy, OnInit { canCreateVersion$: Observable; createVersionTitle$: Observable; - /** - * Toggle state for version history table - */ - showVersionHistory = false; - /** * Show `Editor` column in the table. */ @@ -236,13 +232,6 @@ export class ItemVersionsComponent implements OnDestroy, OnInit { return this.versionBeingEditedNumber != null; } - /** - * Toggle the visibility of version history table - */ - toggleVersionHistory(): void { - this.showVersionHistory = !this.showVersionHistory; - } - /** * True if the specified version is being edited * (used to show input field and to change buttons for specified version) @@ -458,6 +447,22 @@ export class ItemVersionsComponent implements OnDestroy, OnInit { } + /** + * Check if the current user is an admin (collection admin, community admin, or site admin) + * @returns Observable true if user has admin privileges + */ + isAdmin(): Observable { + return combineLatest([ + this.authorizationService.isAuthorized(FeatureID.IsCollectionAdmin), + this.authorizationService.isAuthorized(FeatureID.IsCommunityAdmin), + this.authorizationService.isAuthorized(FeatureID.AdministratorOf), + ]).pipe( + map(([isCollectionAdmin, isCommunityAdmin, isSiteAdmin]) => { + return isCollectionAdmin || isCommunityAdmin || isSiteAdmin; + }) + ); + } + /** * Check if the current user can delete the version * @param version @@ -555,6 +560,9 @@ export class ItemVersionsComponent implements OnDestroy, OnInit { this.canCreateVersion$ = this.authorizationService.isAuthorized(FeatureID.CanCreateVersion, this.item.self); + // Initialize admin check for component visibility + this.isAdmin$ = this.isAdmin(); + // If there is a draft item in the version history the 'Create version' button is disabled and a different tooltip message is shown this.hasDraftVersion$ = this.versionHistoryRD$.pipe( getFirstSucceededRemoteDataPayload(), @@ -591,14 +599,6 @@ export class ItemVersionsComponent implements OnDestroy, OnInit { map((item: Item) => this.dsoNameService.getName(item))); } - /** - * Get the display name for a version item - * @param versionItem the item to get the name for - */ - getVersionItemDisplayName(versionItem: Item): string { - return versionItem.firstMetadataValue('dc.title') || versionItem.name || 'Untitled'; - } - getItemHandleFromVersion(version: Version) { return version.item .pipe( diff --git a/src/themes/custom/eager-theme.module.ts b/src/themes/custom/eager-theme.module.ts index 22afb8c150a..31047e239ac 100644 --- a/src/themes/custom/eager-theme.module.ts +++ b/src/themes/custom/eager-theme.module.ts @@ -44,7 +44,6 @@ import { CollectionDropdownComponent } from './app/shared/collection-dropdown/co import { SharedBrowseByModule } from '../../app/shared/browse-by/shared-browse-by.module'; import { ResultsBackButtonModule } from '../../app/shared/results-back-button/results-back-button.module'; import { DsoPageModule } from '../../app/shared/dso-page/dso-page.module'; -import { ItemVersionsModule } from '../../app/item-page/versions/item-versions.module'; import { FileDownloadLinkComponent } from './app/shared/file-download-link/file-download-link.component'; import { StartsWithDateComponent } from './app/shared/starts-with/date/starts-with-date.component'; import { StartsWithTextComponent } from './app/shared/starts-with/text/starts-with-text.component'; @@ -112,7 +111,6 @@ const DECLARATIONS = [ ResultsBackButtonModule, ItemPageModule, ItemSharedModule, - ItemVersionsModule, DsoPageModule, ], declarations: DECLARATIONS, From 28b2caf31e30933f4b7a23f6074d47baa5c62fde Mon Sep 17 00:00:00 2001 From: Juraj Roka <95219754+jr-rk@users.noreply.github.com> Date: Wed, 24 Sep 2025 13:03:58 +0200 Subject: [PATCH 05/27] Added clarin-item-versions-field to publication template, added spacing between icon and heading --- .../clarin-item-versions-field.component.html | 2 +- .../simple/item-types/publication/publication.component.html | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.html b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.html index 23119d31be7..0f730ae5ac1 100644 --- a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.html +++ b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.html @@ -3,7 +3,7 @@
- {{ "item.version.history.head" | translate }} + {{ "item.version.history.head" | translate }}
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 ef32bd9c880..aae2dfa2c7f 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 @@ -107,6 +107,10 @@ [iconName]="'fa-sitemap'" [separator]="'
'"> + +
{{"item.page.link.full" | translate}} From f887127e1d6017bf4d8e2295b159ecf79fd8974e Mon Sep 17 00:00:00 2001 From: Juraj Roka <95219754+jr-rk@users.noreply.github.com> Date: Wed, 24 Sep 2025 13:39:16 +0200 Subject: [PATCH 06/27] Refactoring --- .../clarin-item-versions-field.component.html | 7 ++++++- .../clarin-item-versions-field.component.scss | 4 ++-- .../clarin-item-versions-field.component.ts | 4 ++-- .../versions/item-versions.component.scss | 17 ----------------- 4 files changed, 10 insertions(+), 22 deletions(-) diff --git a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.html b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.html index 0f730ae5ac1..d7545d34206 100644 --- a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.html +++ b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.html @@ -2,7 +2,12 @@
-
+
{{ "item.version.history.head" | translate }}
diff --git a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.scss b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.scss index e83dcb339df..774b489f5f7 100644 --- a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.scss +++ b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.scss @@ -12,6 +12,6 @@ border-radius: 4px; padding: 2%; - max-height: 200px; - overflow-y: scroll; + max-height: var(--ds-dso-selector-list-max-height) / 2; + overflow-y: auto; } diff --git a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.ts b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.ts index e7bbe646563..381e55a8173 100644 --- a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.ts +++ b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.ts @@ -157,7 +157,7 @@ export class ClarinItemVersionsFieldComponent implements OnDestroy, OnInit { * Get the ID of the workspace item, if present, otherwise return undefined * @param versionItem the item for which retrieve the workspace item id */ - getWorkspaceId(versionItem): Observable { + getWorkspaceId(versionItem: Observable>): Observable { return versionItem.pipe( getFirstSucceededRemoteDataPayload(), map((item: Item) => item.uuid), @@ -171,7 +171,7 @@ export class ClarinItemVersionsFieldComponent implements OnDestroy, OnInit { * Get the ID of the workflow item, if present, otherwise return undefined * @param versionItem the item for which retrieve the workspace item id */ - getWorkflowId(versionItem): Observable { + getWorkflowId(versionItem: Observable>): Observable { return versionItem.pipe( getFirstSucceededRemoteDataPayload(), map((item: Item) => item.uuid), diff --git a/src/app/item-page/versions/item-versions.component.scss b/src/app/item-page/versions/item-versions.component.scss index 7b9d40db20a..e69de29bb2d 100644 --- a/src/app/item-page/versions/item-versions.component.scss +++ b/src/app/item-page/versions/item-versions.component.scss @@ -1,17 +0,0 @@ -@import '../simple/item-page.component.scss'; - -@media (min-width: 992px) { - .col-2-5 { - flex: 0 0 20%; - max-width: 20%; - } -} - -.dropdown-versions { - background-color: #f8f9fa; - border-radius: 4px; - padding: 2%; - - max-height: 200px; - overflow-y: scroll; -} From 8522e4e732e148ab4f71d9063da9a8528c2ec6c8 Mon Sep 17 00:00:00 2001 From: Juraj Roka <95219754+jr-rk@users.noreply.github.com> Date: Wed, 24 Sep 2025 13:55:42 +0200 Subject: [PATCH 07/27] Refactoring vol.2 --- .../clarin-item-versions-field.component.html | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.html b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.html index d7545d34206..a0cbda9c088 100644 --- a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.html +++ b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.html @@ -7,14 +7,20 @@ (click)="toggleVersionHistory()" role="button" tabindex="0" - [attr.aria-expanded]="showVersionHistory"> + [attr.aria-expanded]="showVersionHistory" + aria-controls="version-history-content" + [attr.aria-label]="(showVersionHistory ? 'Collapse' : 'Expand') + ' version history'"> {{ "item.version.history.head" | translate }}
-
+
+ + + + + From b38331f6d97ea63124b2aecabd86659649b0aa58 Mon Sep 17 00:00:00 2001 From: Juraj Roka <95219754+jr-rk@users.noreply.github.com> Date: Fri, 26 Sep 2025 07:39:04 +0200 Subject: [PATCH 19/27] added calc() to scss --- .../clarin-item-versions-field.component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.scss b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.scss index 774b489f5f7..f60052e1dc9 100644 --- a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.scss +++ b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.scss @@ -12,6 +12,6 @@ border-radius: 4px; padding: 2%; - max-height: var(--ds-dso-selector-list-max-height) / 2; + max-height: calc(var(--ds-dso-selector-list-max-height) / 2); overflow-y: auto; } From 59e8ed8bdf697f64a9a27f7ac811b9e089b2cdd2 Mon Sep 17 00:00:00 2001 From: Juraj Roka <95219754+jr-rk@users.noreply.github.com> Date: Fri, 26 Sep 2025 07:43:14 +0200 Subject: [PATCH 20/27] Fix of uninitialized isAdmin handling --- .../clarin-item-versions-field.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.ts b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.ts index cf4603cbe7b..1ede7c390b8 100644 --- a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.ts +++ b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.ts @@ -56,7 +56,7 @@ export class ClarinItemVersionsFieldComponent extends ItemVersionsComponent impl ); } else { // Fallback: check if isAdmin$ is available, otherwise hide the component - this.showMetadataValue = this.isAdmin$ || of(false); + this.showMetadataValue = this.isAdmin$ ? this.isAdmin$ : of(false); } } From 9797d0aba2121eb686d412a6e0fef27ad233a1d1 Mon Sep 17 00:00:00 2001 From: Juraj Roka <95219754+jr-rk@users.noreply.github.com> Date: Fri, 26 Sep 2025 08:24:47 +0200 Subject: [PATCH 21/27] Optimization of hasDraftVersion logic - exposing it as an observable --- .../clarin-item-versions-field.component.html | 6 ++-- .../clarin-item-versions-field.component.ts | 34 +++++++++++++++++-- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.html b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.html index 9c45c559078..b74e862b3ab 100644 --- a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.html +++ b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.html @@ -27,11 +27,11 @@
diff --git a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.ts b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.ts index c8a4dd6c477..5ed269c5988 100644 --- a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.ts +++ b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.ts @@ -4,6 +4,7 @@ import { map, switchMap } from 'rxjs/operators'; import { ItemVersionsComponent } from '../../../versions/item-versions.component'; import { Item } from '../../../../core/shared/item.model'; import { Version } from '../../../../core/shared/version.model'; +import { RemoteData } from '../../../../core/data/remote-data'; /** * Local type definition matching the parent component's VersionsDTO structure @@ -19,6 +20,16 @@ interface VersionDTO { canDeleteVersion: Observable; } +/** + * Enhanced VersionDTO with pre-computed workspace/workflow IDs for template optimization + */ +interface EnhancedVersionDTO extends VersionDTO { + versionItem$: Observable>; + workspaceId$: Observable; + workflowId$: Observable; + isCurrentVersion: boolean; +} + /** * Clarin-specific field component for User/Anonymous view of item version history that extends ItemVersionsComponent */ @@ -45,6 +56,11 @@ export class ClarinItemVersionsFieldComponent extends ItemVersionsComponent impl */ showMetadataValue: Observable; + /** + * Enhanced versions with pre-computed workspace/workflow IDs + */ + enhancedVersions$: Observable; + ngOnInit(): void { // Call parent's ngOnInit first to set up all the observables super.ngOnInit(); @@ -54,6 +70,36 @@ export class ClarinItemVersionsFieldComponent extends ItemVersionsComponent impl this.showMetadataValue = this.versionsDTO$.pipe( map((versionsDTO: VersionsDTO) => versionsDTO && versionsDTO.totalElements > 1) ); + + // Pre-compute workspace/workflow IDs to optimize template performance + this.enhancedVersions$ = combineLatest([ + this.versionsDTO$, + this.versionRD$ + ]).pipe( + map(([versionsDTO, versionRD]) => { + const currentVersionId = versionRD?.payload?.id; + return versionsDTO.versionDTOs.map(versionDTO => { + const versionItem$ = versionDTO.version.item; + const workspaceId$ = (this.hasDraftVersion$ ?? of(false)).pipe( + switchMap(hasDraftVersion => + hasDraftVersion ? this.getWorkspaceId(versionItem$) : of(undefined) + ) + ); + const workflowId$ = workspaceId$.pipe( + switchMap((workspaceId) => + workspaceId ? of(undefined) : this.getWorkflowId(versionItem$) + ) + ); + return { + ...versionDTO, + versionItem$, + workspaceId$, + workflowId$, + isCurrentVersion: versionDTO.version.id === currentVersionId + } as EnhancedVersionDTO; + }); + }) + ); } else { // Fallback: check if isAdmin$ is available, otherwise hide the component this.showMetadataValue = this.isAdmin$ ? this.isAdmin$ : of(false); @@ -113,4 +159,13 @@ export class ClarinItemVersionsFieldComponent extends ItemVersionsComponent impl ) ); } + + /** + * TrackBy function for version list to optimize *ngFor performance + * @param index the index of the item + * @param versionDTO the version DTO to track + */ + trackByVersionId(index: number, versionDTO: EnhancedVersionDTO): string { + return versionDTO.version.id; + } }