diff --git a/Dockerfile b/Dockerfile index e7420983a0f..26b07b099ba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,7 +24,7 @@ ENV NODE_OPTIONS="--max_old_space_size=4096" # Listen / accept connections from all IP addresses. # NOTE: At this time it is only possible to run Docker container in Production mode # if you have a public URL. See https://github.com/DSpace/dspace-angular/issues/1485 -ENV NODE_ENV development +ENV NODE_ENV=development RUN apk add tzdata RUN yarn build:prod RUN npm install pm2 -g diff --git a/Dockerfile.dist b/Dockerfile.dist index 2a6a66fc063..de5b41ff64e 100644 --- a/Dockerfile.dist +++ b/Dockerfile.dist @@ -4,7 +4,7 @@ # Test build: # docker build -f Dockerfile.dist -t dspace/dspace-angular:dspace-7_x-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 @@ -26,6 +26,6 @@ COPY --chown=node:node docker/dspace-ui.json /app/dspace-ui.json WORKDIR /app USER node -ENV NODE_ENV production +ENV NODE_ENV=production EXPOSE 4000 -CMD pm2-runtime start dspace-ui.json --json +CMD ["pm2-runtime", "start", "dspace-ui.json", "--json"] diff --git a/docker/cli.assetstore.yml b/docker/cli.assetstore.yml index a1d6377bfee..ef5cf95caf6 100644 --- a/docker/cli.assetstore.yml +++ b/docker/cli.assetstore.yml @@ -12,7 +12,6 @@ # https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/cli.assetstore.yml # # Therefore, it should be kept in sync with that file -version: "3.7" networks: dspacenet: diff --git a/docker/cli.ingest.yml b/docker/cli.ingest.yml index 1db241af3bf..2f812cde038 100644 --- a/docker/cli.ingest.yml +++ b/docker/cli.ingest.yml @@ -12,7 +12,6 @@ # https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/cli.ingest.yml # # Therefore, it should be kept in sync with that file -version: "3.7" services: dspace-cli: diff --git a/docker/cli.yml b/docker/cli.yml index efe9034b6f2..9b2175491a9 100644 --- a/docker/cli.yml +++ b/docker/cli.yml @@ -6,8 +6,6 @@ # http://www.dspace.org/license/ # -version: "3.7" - services: dspace-cli: image: "${DOCKER_OWNER:-dataquest}/dspace-cli:${DSPACE_VER:-dspace-7_x}" diff --git a/docker/db.entities.yml b/docker/db.entities.yml index 6473bf2e385..d927af04df7 100644 --- a/docker/db.entities.yml +++ b/docker/db.entities.yml @@ -12,8 +12,6 @@ # https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/db.entities.yml # # # Therefore, it should be kept in sync with that file -version: "3.7" - services: dspacedb: image: dspace/dspace-postgres-pgcrypto:loadsql @@ -48,4 +46,4 @@ services: \ \ ' /dspace/config/item-submission.xml - catalina.sh run \ No newline at end of file + catalina.sh run diff --git a/docker/docker-compose-ci.yml b/docker/docker-compose-ci.yml index 9a2d674a761..5e930b7ba51 100644 --- a/docker/docker-compose-ci.yml +++ b/docker/docker-compose-ci.yml @@ -10,7 +10,6 @@ # This is used by our GitHub CI at .github/workflows/build.yml # It is based heavily on the Backend's Docker Compose: # https://github.com/DSpace/DSpace/blob/main/docker-compose.yml -version: '3.7' networks: dspacenet: services: diff --git a/docker/docker-compose-dist.yml b/docker/docker-compose-dist.yml index 00225e8052a..1f4d2d7f5e6 100644 --- a/docker/docker-compose-dist.yml +++ b/docker/docker-compose-dist.yml @@ -8,7 +8,6 @@ # Docker Compose for running the DSpace Angular UI dist build # for previewing with the DSpace Demo site backend -version: '3.7' networks: dspacenet: services: diff --git a/docker/docker-compose-rest.yml b/docker/docker-compose-rest.yml index 1c787973581..3bfd5a332b8 100644 --- a/docker/docker-compose-rest.yml +++ b/docker/docker-compose-rest.yml @@ -10,7 +10,6 @@ # This is based heavily on the docker-compose.yml that is available in the DSpace/DSpace # (Backend) at: # https://github.com/DSpace/DSpace/blob/main/docker-compose.yml -version: '3.7' networks: dspacenet: # Due to the following specification, THIS FILE (docker-compose-rest.yml) must be last (if using several YMLs), @@ -105,8 +104,9 @@ services: while (! /dev/null 2>&1; do sleep 1; done; pushd ../webapps && (unlink server || true) && (ln -s /dspace/webapps/server/ 'repository#server' || true) && popd /dspace/bin/dspace database migrate force - ./custom_run.sh + custom_run.sh /dspace/bin/start-handle-server + ./custom_run.sh # DSpace database container dspacedb: restart: unless-stopped diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index c6cc475a30c..6b5d32efff4 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -6,7 +6,6 @@ # http://www.dspace.org/license/ # -version: '3.7' networks: dspacenet: services: diff --git a/docker/matomo-w-db.yml b/docker/matomo-w-db.yml index ddacc7dbde4..1e7c23f6c94 100644 --- a/docker/matomo-w-db.yml +++ b/docker/matomo-w-db.yml @@ -1,5 +1,3 @@ -version: "3.5" - services: db: image: mariadb diff --git a/src/app/core/handle/handle.model.ts b/src/app/core/handle/handle.model.ts index 063d2189589..1160197d541 100644 --- a/src/app/core/handle/handle.model.ts +++ b/src/app/core/handle/handle.model.ts @@ -41,6 +41,9 @@ export class Handle extends ListableObject implements HALResource { @autoserialize url: string; + @autoserialize + resourceId: string; + /** * The element of this metadata field */ diff --git a/src/app/handle-page/edit-handle-page/edit-handle-page.component.html b/src/app/handle-page/edit-handle-page/edit-handle-page.component.html index fdca780899b..215e7d59f76 100644 --- a/src/app/handle-page/edit-handle-page/edit-handle-page.component.html +++ b/src/app/handle-page/edit-handle-page/edit-handle-page.component.html @@ -6,6 +6,13 @@ [(ngModel)]="handle" [placeholder]="'handle-table.edit-handle.form-handle-input-placeholder' | translate"> +
+ + +
- + {{ 'handle-table.title' | translate }}
- + [(ngModel)]="searchQuery"> + @@ -31,7 +30,7 @@
{{ 'handle-table.title' | translate }}
-
+
{{ 'handle-table.title' | translate }} - {{handle?.url}} + + {{handle?.url}} + {{handle?.resourceTypeID}} - - {{handle?.id}} + + + {{handle?.resourceId}} + + + {{handle?.resourceId}} + diff --git a/src/app/handle-page/handle-table/handle-table.component.spec.ts b/src/app/handle-page/handle-table/handle-table.component.spec.ts index a7600298315..b13dd509ab0 100644 --- a/src/app/handle-page/handle-table/handle-table.component.spec.ts +++ b/src/app/handle-page/handle-table/handle-table.component.spec.ts @@ -128,7 +128,7 @@ describe('HandleTableComponent', () => { url: mockHandle.url, currentPage: (component as any).options.currentPage, resourceType: mockHandle.resourceTypeID, - resourceId: mockHandle.id + resourceId: mockHandle.resourceId } }; // should unselect diff --git a/src/app/handle-page/handle-table/handle-table.component.ts b/src/app/handle-page/handle-table/handle-table.component.ts index a08b93770fa..4aed5177cd5 100644 --- a/src/app/handle-page/handle-table/handle-table.component.ts +++ b/src/app/handle-page/handle-table/handle-table.component.ts @@ -1,10 +1,10 @@ -import { ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { BehaviorSubject, combineLatest as observableCombineLatest, fromEvent } from 'rxjs'; +import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; +import { BehaviorSubject, combineLatest } from 'rxjs'; import { RemoteData } from '../../core/data/remote-data'; import { PaginatedList } from '../../core/data/paginated-list.model'; import { HandleDataService } from '../../core/data/handle-data.service'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; -import { debounceTime, distinctUntilChanged, switchMap, take } from 'rxjs/operators'; +import { scan, switchMap, take } from 'rxjs/operators'; import { getFirstSucceededRemoteData, getRemoteDataPayload } from '../../core/shared/operators'; import { PaginationService } from '../../core/pagination/pagination.service'; import { @@ -28,6 +28,9 @@ import { SITE, SUCCESSFUL_RESPONSE_START_CHAR } from '../../core/handle/handle.resource-type'; +import { getCommunityPageRoute } from '../../community-page/community-page-routing-paths'; +import { getCollectionPageRoute } from '../../collection-page/collection-page-routing-paths'; +import { getEntityPageRoute } from '../../item-page/item-page-routing-paths'; /** * Constants for converting the searchQuery for the server @@ -55,11 +58,6 @@ export class HandleTableComponent implements OnInit { private notificationsService: NotificationsService,) { } - /** - * The reference for the input html element - */ - @ViewChild('searchInput', {static: true}) searchInput: ElementRef; - /** * The list of Handle object as BehaviorSubject object */ @@ -150,13 +148,20 @@ export class HandleTableComponent implements OnInit { this.isLoading = true; // load the current pagination and sorting options - const currentPagination$ = this.paginationService.getCurrentPagination(this.options.id, this.options); - const currentSort$ = this.paginationService.getCurrentSort(this.options.id, this.sortConfiguration); - - observableCombineLatest([currentPagination$, currentSort$]).pipe( - switchMap(([currentPagination, currentSort]) => { + const currentPagination$ = this.getCurrentPagination(); + const currentSort$ = this.getCurrentSort(); + const searchTerm$ = new BehaviorSubject(this.searchQuery); + + combineLatest([currentPagination$, currentSort$, searchTerm$]).pipe( + scan((prevState, [currentPagination, currentSort, searchTerm]) => { + // If search term has changed, reset to page 1; otherwise, keep current page + const currentPage = prevState.searchTerm !== searchTerm ? 1 : currentPagination.currentPage; + return { currentPage, currentPagination, currentSort, searchTerm }; + }, { searchTerm: '', currentPage: 1, currentPagination: this.getCurrentPagination(), + currentSort: this.getCurrentSort() }), + switchMap(({ currentPage, currentPagination, currentSort, searchTerm }) => { return this.handleDataService.findAll({ - currentPage: currentPagination.currentPage, + currentPage: currentPage, elementsPerPage: currentPagination.pageSize, sort: {field: currentSort.field, direction: currentSort.direction} }, false @@ -169,6 +174,29 @@ export class HandleTableComponent implements OnInit { }); } + getItemPageRoute(id: string): string { + return getEntityPageRoute(null, id); + } + + type2route(type: string): (id: string) => string { + switch (type) { + case COMMUNITY: + return getCommunityPageRoute; + case COLLECTION: + return getCollectionPageRoute; + case ITEM: + return this.getItemPageRoute; + } + } + + getHandleTargetPageRoute(handle: Handle): string { + return this.type2route(handle.resourceTypeID)(handle.resourceId); + } + + shouldLink(handle: Handle): boolean { + return handle.resourceTypeID !== SITE; + } + /** * Updates the page */ @@ -216,7 +244,7 @@ export class HandleTableComponent implements OnInit { this.switchSelectedHandle(this.selectedHandle); this.router.navigate([this.handleRoute, this.editHandlePath], { queryParams: { id: handle.id, _selflink: handle._links.self.href, handle: handle.handle, - url: handle.url, resourceType: handle.resourceTypeID, resourceId: handle.id, + url: handle.url, resourceType: handle.resourceTypeID, resourceId: handle.resourceId, currentPage: this.options.currentPage } }, ); } @@ -325,29 +353,6 @@ export class HandleTableComponent implements OnInit { }, 250 ); } - /** - * If the user is typing the searchQuery is changing. - */ - setSearchQuery() { - if (isEmpty(this.searchOption)) { - return; - } - - fromEvent(this.searchInput.nativeElement,'keyup') - .pipe( - debounceTime(300), - distinctUntilChanged() - ) - .subscribe( cc => { - this.searchHandles(this.searchInput.nativeElement.value); - setTimeout(() => { - // click to refresh table data because without click it still shows wrong data - document.getElementById('clarin-dc-search-box').click(); - }, 25); - }); - - } - /** * The search option is selected from the dropdown menu. * @param event with the selected value @@ -360,7 +365,7 @@ export class HandleTableComponent implements OnInit { * Update the sortConfiguration based on the `searchOption` and the `searchQuery` but parse that attributes at first. * @param searchQuery */ - searchHandles(searchQuery = '') { + searchHandles() { if (isEmpty(this.searchOption)) { return; } @@ -368,7 +373,7 @@ export class HandleTableComponent implements OnInit { // parse searchQuery for the server request // the new sorting query is in the format e.g. `handle:123456`, `resourceTypeId:2`, `url:internal` let parsedSearchOption = ''; - let parsedSearchQuery = searchQuery; + let parsedSearchQuery = this.searchQuery; switch (this.searchOption) { case this.handleOption: parsedSearchOption = HANDLE_SEARCH_OPTION; @@ -376,16 +381,16 @@ export class HandleTableComponent implements OnInit { case this.internalOption: // if the handle doesn't have the URL - is internal, if it does - is external parsedSearchOption = URL_SEARCH_OPTION; - if (searchQuery === 'Yes' || searchQuery === 'yes') { + if (this.searchQuery.toLowerCase() === 'yes') { parsedSearchQuery = 'internal'; - } else if (searchQuery === 'No' || searchQuery === 'no') { + } else if (this.searchQuery.toLowerCase() === 'no') { parsedSearchQuery = 'external'; } break; case this.resourceTypeOption: parsedSearchOption = RESOURCE_TYPE_SEARCH_OPTION; // parse resourceType from string to the number because the resourceType is integer on the server - switch (searchQuery) { + switch (this.searchQuery) { case ITEM: parsedSearchQuery = '' + 2; break; @@ -415,4 +420,18 @@ export class HandleTableComponent implements OnInit { private initializeSortingOptions() { this.sortConfiguration = defaultSortConfiguration; } + + /** + * Get the current pagination options. + */ + private getCurrentPagination() { + return this.paginationService.getCurrentPagination(this.options.id, defaultPagination); + } + + /** + * Get the current sorting options. + */ + private getCurrentSort() { + return this.paginationService.getCurrentSort(this.options.id, defaultSortConfiguration); + } } diff --git a/src/app/header/header.component.spec.ts b/src/app/header/header.component.spec.ts index ccf8b196219..f5581090d08 100644 --- a/src/app/header/header.component.spec.ts +++ b/src/app/header/header.component.spec.ts @@ -12,6 +12,7 @@ import { MenuService } from '../shared/menu/menu.service'; import { MenuServiceStub } from '../shared/testing/menu-service.stub'; import { HostWindowService } from '../shared/host-window.service'; import { HostWindowServiceStub } from '../shared/testing/host-window-service.stub'; +import { LocaleService } from '../core/locale/locale.service'; let comp: HeaderComponent; let fixture: ComponentFixture; @@ -19,6 +20,11 @@ let fixture: ComponentFixture; describe('HeaderComponent', () => { const menuService = new MenuServiceStub(); + // Mock LocaleService + const localeServiceMock = { + getCurrentLanguageCode: () => 'en' // returns default language code + }; + // waitForAsync beforeEach beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ @@ -29,7 +35,8 @@ describe('HeaderComponent', () => { declarations: [HeaderComponent], providers: [ { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, - { provide: MenuService, useValue: menuService } + { provide: MenuService, useValue: menuService }, + { provide: LocaleService, useValue: localeServiceMock } ], schemas: [NO_ERRORS_SCHEMA] }) diff --git a/src/app/header/header.component.ts b/src/app/header/header.component.ts index 5153a19be47..0a847d1b289 100644 --- a/src/app/header/header.component.ts +++ b/src/app/header/header.component.ts @@ -3,6 +3,7 @@ import { Observable } from 'rxjs'; import { MenuService } from '../shared/menu/menu.service'; import { MenuID } from '../shared/menu/menu-id.model'; import { HostWindowService } from '../shared/host-window.service'; +import { LocaleService } from '../core/locale/locale.service'; /** * Represents the header with the logo and simple navigation @@ -24,6 +25,7 @@ export class HeaderComponent implements OnInit { constructor( protected menuService: MenuService, protected windowService: HostWindowService, + private localeService: LocaleService, ) { } @@ -34,4 +36,41 @@ export class HeaderComponent implements OnInit { public toggleNavbar(): void { this.menuService.toggleMenu(this.menuID); } + + /** + * Returns the current language code from the locale service + * @returns {string} The current language code + */ + getLangCode(): string { + return this.localeService.getCurrentLanguageCode(); + } + + /** + * Returns the current language code only if it's Czech ('cs'), otherwise returns an empty string + * @returns {string} The language code if Czech, empty string otherwise + */ + getLangCodeIfCzech(): string { + return this.localeService.getCurrentLanguageCode() === 'cs' ? this.localeService.getCurrentLanguageCode() : ''; + } + + /** + * Translates English slugs to their Czech equivalents when the current language is Czech + * @param {string} slug - The English slug to translate + * @returns {string} The translated slug if in Czech, the original slug if in English, or empty string if translation not found + */ + translateSlug(slug: string): string { + const currentLang = this.localeService.getCurrentLanguageCode(); + if (currentLang === 'en') { + return slug; + } + + const translations = { + 'partners': 'partneri', + 'integration': 'integrace', + 'partnership': 'partnerstvi', + 'services': 'sluzby' + }; + + return translations[slug] || ''; + } } diff --git a/src/app/item-page/clarin-license-info/clarin-license-info.component.ts b/src/app/item-page/clarin-license-info/clarin-license-info.component.ts index be7be7b3bfc..7b1c3104257 100644 --- a/src/app/item-page/clarin-license-info/clarin-license-info.component.ts +++ b/src/app/item-page/clarin-license-info/clarin-license-info.component.ts @@ -99,7 +99,7 @@ export class ClarinLicenseInfoComponent implements OnInit { } /** - * Check if current english is Czech + * Check if current language is Czech */ isCsLocale() { return this.localeService.getCurrentLanguageCode() === 'cs'; diff --git a/src/app/item-page/edit-item-page/item-license-mapper/item-license-mapper.component.html b/src/app/item-page/edit-item-page/item-license-mapper/item-license-mapper.component.html index 8a79919ca72..294aec7e8b7 100644 --- a/src/app/item-page/edit-item-page/item-license-mapper/item-license-mapper.component.html +++ b/src/app/item-page/edit-item-page/item-license-mapper/item-license-mapper.component.html @@ -26,7 +26,7 @@

{{'item.edit.license.head' | translate}}

diff --git a/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.html b/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.html index 2b2f211a316..4778ab0b795 100644 --- a/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.html +++ b/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.html @@ -60,18 +60,30 @@
-
    - - - - - -
    {{ fileInput.fileInfo[0]?.content }}
    -
    - -
    +
      + +
      +
      {{'item.preview.no-preview' | translate}} + +
      +
      + + + + + + + + + + +
      {{ fileInput.fileInfo[0]?.content }}
      +
      + + + +
      +
diff --git a/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.ts b/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.ts index 6a2b69acdce..f8dc723c5a8 100644 --- a/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.ts +++ b/src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.ts @@ -1,7 +1,8 @@ -import { Component, Input } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { MetadataBitstream } from 'src/app/core/metadata/metadata-bitstream.model'; import { HALEndpointService } from '../../../../../core/shared/hal-endpoint.service'; -import {Router} from '@angular/router'; +import { Router } from '@angular/router'; +import { ConfigurationDataService } from '../../../../../core/data/configuration-data.service'; const allowedPreviewFormats = ['text/plain', 'text/html', 'application/zip', 'application/x-tar']; @Component({ @@ -9,14 +10,24 @@ const allowedPreviewFormats = ['text/plain', 'text/html', 'application/zip', 'ap templateUrl: './file-description.component.html', styleUrls: ['./file-description.component.scss'], }) -export class FileDescriptionComponent { +export class FileDescriptionComponent implements OnInit { MIME_TYPE_IMAGES_PATH = './assets/images/mime/'; MIME_TYPE_DEFAULT_IMAGE_NAME = 'application-octet-stream.png'; @Input() fileInput: MetadataBitstream; - constructor(protected halService: HALEndpointService, private router: Router) { } + emailToContact: string; + + constructor(protected halService: HALEndpointService, + private router: Router, + private configService: ConfigurationDataService) { } + + ngOnInit(): void { + this.configService.findByPropertyName('lr.help.mail').subscribe(remoteData => { + this.emailToContact = remoteData.payload.values[0]; + }); + } public downloadFile() { void this.router.navigateByUrl('bitstreams/' + this.fileInput.id + '/download'); @@ -45,4 +56,9 @@ export class FileDescriptionComponent { const imgElement = event.target as HTMLImageElement; imgElement.src = this.MIME_TYPE_IMAGES_PATH + this.MIME_TYPE_DEFAULT_IMAGE_NAME; } + + isArchive(format: string): boolean { + return format === 'application/zip' || format === 'application/x-tar'; + } + } diff --git a/src/app/item-page/simple/field-components/preview-section/preview-section.component.html b/src/app/item-page/simple/field-components/preview-section/preview-section.component.html index 8404f491b4f..cd937f7b465 100644 --- a/src/app/item-page/simple/field-components/preview-section/preview-section.component.html +++ b/src/app/item-page/simple/field-components/preview-section/preview-section.component.html @@ -1,3 +1,10 @@ + +
+
+
{{'item.preview.loading-files' | translate}} + +
+
diff --git a/src/app/item-page/simple/field-components/preview-section/preview-section.component.spec.ts b/src/app/item-page/simple/field-components/preview-section/preview-section.component.spec.ts index 7b14feefd87..827b53a4cd6 100644 --- a/src/app/item-page/simple/field-components/preview-section/preview-section.component.spec.ts +++ b/src/app/item-page/simple/field-components/preview-section/preview-section.component.spec.ts @@ -7,20 +7,33 @@ import { PreviewSectionComponent } from './preview-section.component'; import { ResourceType } from 'src/app/core/shared/resource-type'; import { HALLink } from 'src/app/core/shared/hal-link.model'; import { Item } from 'src/app/core/shared/item.model'; +import { ConfigurationDataService } from '../../../../core/data/configuration-data.service'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; describe('PreviewSectionComponent', () => { let component: PreviewSectionComponent; let fixture: ComponentFixture; let mockRegistryService: any; + let mockConfigService: any; beforeEach(async () => { + mockConfigService = jasmine.createSpyObj(['findByPropertyName']); mockRegistryService = jasmine.createSpyObj('RegistryService', [ 'getMetadataBitstream', ]); await TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + })], declarations: [PreviewSectionComponent], - providers: [{ provide: RegistryService, useValue: mockRegistryService }], + providers: [ + { provide: RegistryService, useValue: mockRegistryService }, + { provide: ConfigurationDataService, useValue: mockConfigService }], }).compileComponents(); }); diff --git a/src/app/item-page/simple/field-components/preview-section/preview-section.component.ts b/src/app/item-page/simple/field-components/preview-section/preview-section.component.ts index ac8b5df4dd4..efcfcaba609 100644 --- a/src/app/item-page/simple/field-components/preview-section/preview-section.component.ts +++ b/src/app/item-page/simple/field-components/preview-section/preview-section.component.ts @@ -4,6 +4,7 @@ import { MetadataBitstream } from 'src/app/core/metadata/metadata-bitstream.mode import { RegistryService } from 'src/app/core/registry/registry.service'; import { Item } from 'src/app/core/shared/item.model'; import { getAllSucceededRemoteListPayload } from 'src/app/core/shared/operators'; +import { ConfigurationDataService } from '../../../../core/data/configuration-data.service'; @Component({ selector: 'ds-preview-section', @@ -14,8 +15,10 @@ export class PreviewSectionComponent implements OnInit { @Input() item: Item; listOfFiles: BehaviorSubject = new BehaviorSubject([] as any); + emailToContact: string; - constructor(protected registryService: RegistryService) {} // Modified + constructor(protected registryService: RegistryService, + private configService: ConfigurationDataService) {} // Modified ngOnInit(): void { this.registryService @@ -24,5 +27,10 @@ export class PreviewSectionComponent implements OnInit { .subscribe((data: MetadataBitstream[]) => { this.listOfFiles.next(data); }); + this.configService.findByPropertyName('lr.help.mail')?.subscribe(remoteData => { + this.emailToContact = remoteData.payload.values[0]; + }); } + + } diff --git a/src/app/shared/mocks/handle-mock.ts b/src/app/shared/mocks/handle-mock.ts index c5bff91510b..45c161d7285 100644 --- a/src/app/shared/mocks/handle-mock.ts +++ b/src/app/shared/mocks/handle-mock.ts @@ -19,6 +19,7 @@ export const mockHandle = Object.assign(new Handle(), { handle: '123456', resourceTypeID: 0, url: 'handle.url', + resourceId: 'a43666e5-d477-4957-8e63-74baf6955d97', _links: { self: { href: 'url.123456' diff --git a/src/app/statistics/angulartics/dspace/view-tracker.component.ts b/src/app/statistics/angulartics/dspace/view-tracker.component.ts index edb8165967a..9febc08b004 100644 --- a/src/app/statistics/angulartics/dspace/view-tracker.component.ts +++ b/src/app/statistics/angulartics/dspace/view-tracker.component.ts @@ -36,12 +36,14 @@ export class ViewTrackerComponent implements OnInit, OnDestroy { this.sub = this.referrerService.getReferrer() .pipe(take(1)) .subscribe((referrer: string) => { + let dc_identifier = this.object?.firstMetadataValue('dc.identifier.uri'); this.angulartics2.eventTrack.next({ action: 'page_view', properties: { object: this.object, referrer, category: 'page_view', + dc_identifier: dc_identifier }, }); }); diff --git a/src/assets/i18n/cs.json5 b/src/assets/i18n/cs.json5 index cbd5a71cc42..cd0da6dccbe 100644 --- a/src/assets/i18n/cs.json5 +++ b/src/assets/i18n/cs.json5 @@ -2458,6 +2458,10 @@ "handle-table.edit-handle.form-archive-input-check": "Archivovat starý Handle?", // "handle-table.edit-handle.form-button-submit": "Submit", "handle-table.edit-handle.form-button-submit": "Odeslat", + //"handle-table.edit-handle.form-id-input-text": "ID", + "handle-table.edit-handle.form-id-input-text": "ID", + //"handle-table.edit-handle.form-id-input-placeholder": "...", + "handle-table.edit-handle.form-id-input-placeholder": "...", // "handle-page.title": "Handles", "handle-page.title": "Handle", // "handle-table.title": "Handle List", @@ -3376,6 +3380,10 @@ "item.preview.authors.show.everyone": "Zobraz všechny autory", // "item.preview.authors.et.al": " ; et al.", "item.preview.authors.et.al": "; et al.", + // "item.preview.loading-files": "Loading files... This may take a few seconds as file previews are being generated. If the process takes too long, please contact the system administrator", + "item.preview.loading-files": "Načítání souborů... Může to trvat několik sekund, protože se generují náhledy souborů. Pokud proces trvá příliš dlouho, kontaktujte prosím správce systému", + // "item.preview.no-preview": "The file preview wasn't successfully generated, please contact the system administrator", + "item.preview.no-preview": "Náhled souboru nebyl úspěšně vygenerován, kontaktujte prosím správce systému", // "item.refbox.modal.copy.instruction": ["Press", "ctrl + c", "to copy"], "item.refbox.modal.copy.instruction": ["Stiskněte", "ctrl + c", "pro kopírování"], // "item.refbox.modal.submit": "Ok", diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index b282b3bf685..7c8a91cfdbd 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2019,6 +2019,9 @@ "handle-table.edit-handle.form-button-submit": "Submit", + "handle-table.edit-handle.form-id-input-text": "ID", + + "handle-table.edit-handle.form-id-input-placeholder": "...", "handle-page.title": "Handles", @@ -2829,6 +2832,10 @@ "item.preview.authors.et.al": " ; et al.", + "item.preview.loading-files": "Loading files... This may take a few seconds as file previews are being generated. If the process takes too long, please contact the system administrator", + + "item.preview.no-preview": "The file preview wasn't successfully generated, please contact the system administrator", + "item.refbox.modal.copy.instruction": ["Press", "ctrl + c", "to copy"], "item.refbox.modal.submit": "Ok", diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index b396f233c6e..0dd72b617e4 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -433,6 +433,7 @@ export class DefaultAppConfig implements AppConfig { // Matomo configuration matomo: MatomoConfig = { hostUrl: 'http://localhost:8135/', - siteId: '1' + siteId: '1', + dimensionId: 1 }; } diff --git a/src/config/matomo-config.ts b/src/config/matomo-config.ts index dbd61a9f4c9..548f02aee4c 100644 --- a/src/config/matomo-config.ts +++ b/src/config/matomo-config.ts @@ -8,4 +8,6 @@ export class MatomoConfig implements Config { public hostUrl: string; public siteId: string; + + public dimensionId: number; } diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 0178fe83a0e..d780956a0d0 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -320,5 +320,6 @@ export const environment: BuildConfig = { matomo: { hostUrl: 'http://localhost:8135/', siteId: '1', + dimensionId: 1 } }; diff --git a/src/modules/app/browser-init.service.ts b/src/modules/app/browser-init.service.ts index 2a0dc6662fb..1159b1392b4 100644 --- a/src/modules/app/browser-init.service.ts +++ b/src/modules/app/browser-init.service.ts @@ -102,6 +102,21 @@ export class BrowserInitService extends InitService { this.initRouteListeners(); this.themeService.listenForThemeChanges(true); this.trackAuthTokenExpiration(); + // ideally we'd add the custom dimension to the 'trackPageView' action only, but don't have that information + // in pageTrack context. So we add it to page_view events, and remove it after the page view. + // page_view events are fired via view-track.component, and exposes dc.identifier.uri via properties + this.angulartics2Matomo.eventTrack = function (action: string, properties?: any) { + if (action === 'page_view') { + if (properties.dc_identifier) { + (window as any)._paq.push(['setCustomDimension', environment.matomo.dimensionId, properties.dc_identifier]); + } + } + }; + let pageTrack = this.angulartics2Matomo.pageTrack; + this.angulartics2Matomo.pageTrack = function (path: string) { + pageTrack.call(this, path); + (window as any)._paq.push(['deleteCustomDimension', environment.matomo.dimensionId]); + }; this.angulartics2Matomo.startTracking(); this.initKlaro(); diff --git a/src/themes/dspace/app/header/header.component.html b/src/themes/dspace/app/header/header.component.html index db6ab5ce03e..58b9baf7516 100644 --- a/src/themes/dspace/app/header/header.component.html +++ b/src/themes/dspace/app/header/header.component.html @@ -17,34 +17,34 @@