diff --git a/.eslintrc.json b/.eslintrc.json
index af1b97849b..6920cc4712 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -231,10 +231,13 @@
"*.json5"
],
"extends": [
- "plugin:jsonc/recommended-with-jsonc"
+ "plugin:jsonc/recommended-with-json5"
],
"rules": {
- "no-irregular-whitespace": "error",
+ // The ESLint core no-irregular-whitespace rule doesn't work well in JSON
+ // See: https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-irregular-whitespace.html
+ "no-irregular-whitespace": "off",
+ "jsonc/no-irregular-whitespace": "error",
"no-trailing-spaces": "error",
"jsonc/comma-dangle": [
"error",
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index bb641bea1e..a72d0d6c18 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -184,12 +184,115 @@ jobs:
# Get homepage and verify that the tag includes "DSpace".
# If it does, then SSR is working, as this tag is created by our MetadataService.
# This step also prints entire HTML of homepage for easier debugging if grep fails.
- - name: Verify SSR (server-side rendering)
+ - name: Verify SSR (server-side rendering) on Homepage
run: |
result=$(wget -O- -q http://127.0.0.1:4000/home)
echo "$result"
echo "$result" | grep -oE "]*>" | grep DSpace
+ # Get a specific community in our test data and verify that the "
" tag includes "Publications" (the community name).
+ # If it does, then SSR is working.
+ - name: Verify SSR on a Community page
+ run: |
+ result=$(wget -O- -q http://127.0.0.1:4000/communities/0958c910-2037-42a9-81c7-dca80e3892b4)
+ echo "$result"
+ echo "$result" | grep -oE "]*>[^><]*
" | grep Publications
+
+ # Get a specific collection in our test data and verify that the "" tag includes "Articles" (the collection name).
+ # If it does, then SSR is working.
+ - name: Verify SSR on a Collection page
+ run: |
+ result=$(wget -O- -q http://127.0.0.1:4000/collections/282164f5-d325-4740-8dd1-fa4d6d3e7200)
+ echo "$result"
+ echo "$result" | grep -oE "]*>[^><]*
" | grep Articles
+
+ # Get a specific publication in our test data and verify that the tag includes
+ # the title of this publication. If it does, then SSR is working.
+ - name: Verify SSR on a Publication page
+ run: |
+ result=$(wget -O- -q http://127.0.0.1:4000/entities/publication/6160810f-1e53-40db-81ef-f6621a727398)
+ echo "$result"
+ echo "$result" | grep -oE "]*>" | grep "An Economic Model of Mortality Salience"
+
+ # Get a specific person in our test data and verify that the tag includes
+ # the name of the person. If it does, then SSR is working.
+ - name: Verify SSR on a Person page
+ run: |
+ result=$(wget -O- -q http://127.0.0.1:4000/entities/person/b1b2c768-bda1-448a-a073-fc541e8b24d9)
+ echo "$result"
+ echo "$result" | grep -oE "]*>" | grep "Simmons, Cameron"
+
+ # Get a specific project in our test data and verify that the tag includes
+ # the name of the project. If it does, then SSR is working.
+ - name: Verify SSR on a Project page
+ run: |
+ result=$(wget -O- -q http://127.0.0.1:4000/entities/project/46ccb608-a74c-4bf6-bc7a-e29cc7defea9)
+ echo "$result"
+ echo "$result" | grep -oE "]*>" | grep "University Research Fellowship"
+
+ # Get a specific orgunit in our test data and verify that the tag includes
+ # the name of the orgunit. If it does, then SSR is working.
+ - name: Verify SSR on an OrgUnit page
+ run: |
+ result=$(wget -O- -q http://127.0.0.1:4000/entities/orgunit/9851674d-bd9a-467b-8d84-068deb568ccf)
+ echo "$result"
+ echo "$result" | grep -oE "]*>" | grep "Law and Development"
+
+ # Get a specific journal in our test data and verify that the tag includes
+ # the name of the journal. If it does, then SSR is working.
+ - name: Verify SSR on a Journal page
+ run: |
+ result=$(wget -O- -q http://127.0.0.1:4000/entities/journal/d4af6c3e-53d0-4757-81eb-566f3b45d63a)
+ echo "$result"
+ echo "$result" | grep -oE "]*>" | grep "Environmental & Architectural Phenomenology"
+
+ # Get a specific journal volume in our test data and verify that the tag includes
+ # the name of the volume. If it does, then SSR is working.
+ - name: Verify SSR on a Journal Volume page
+ run: |
+ result=$(wget -O- -q http://127.0.0.1:4000/entities/journalvolume/07c6249f-4bf7-494d-9ce3-6ffdb2aed538)
+ echo "$result"
+ echo "$result" | grep -oE "]*>" | grep "Environmental & Architectural Phenomenology Volume 28 (2017)"
+
+ # Get a specific journal issue in our test data and verify that the tag includes
+ # the name of the issue. If it does, then SSR is working.
+ - name: Verify SSR on a Journal Issue page
+ run: |
+ result=$(wget -O- -q http://127.0.0.1:4000/entities/journalissue/44c29473-5de2-48fa-b005-e5029aa1a50b)
+ echo "$result"
+ echo "$result" | grep -oE "]*>" | grep "Environmental & Architectural Phenomenology Vol. 28, No. 1"
+
+ # Verify 301 Handle redirect behavior
+ # Note: /handle/123456789/260 is the same test Publication used by our e2e tests
+ - name: Verify 301 redirect from '/handle' URLs
+ run: |
+ result=$(wget --server-response --quiet http://127.0.0.1:4000/handle/123456789/260 2>&1 | head -1 | awk '{print $2}')
+ echo "$result"
+ [[ "$result" -eq "301" ]]
+
+ # Verify 403 error code behavior
+ - name: Verify 403 error code from '/403'
+ run: |
+ result=$(wget --server-response --quiet http://127.0.0.1:4000/403 2>&1 | head -1 | awk '{print $2}')
+ echo "$result"
+ [[ "$result" -eq "403" ]]
+
+ # Verify 404 error code behavior
+ - name: Verify 404 error code from '/404' and on invalid pages
+ run: |
+ result=$(wget --server-response --quiet http://127.0.0.1:4000/404 2>&1 | head -1 | awk '{print $2}')
+ echo "$result"
+ result2=$(wget --server-response --quiet http://127.0.0.1:4000/invalidurl 2>&1 | head -1 | awk '{print $2}')
+ echo "$result2"
+ [[ "$result" -eq "404" && "$result2" -eq "404" ]]
+
+ # Verify 500 error code behavior
+ - name: Verify 500 error code from '/500'
+ run: |
+ result=$(wget --server-response --quiet http://127.0.0.1:4000/500 2>&1 | head -1 | awk '{print $2}')
+ echo "$result"
+ [[ "$result" -eq "500" ]]
+
- name: Stop running app
run: kill -9 $(lsof -t -i:4000)
diff --git a/config/config.example.yml b/config/config.example.yml
index a1b2f3f579..c82df9e3b2 100644
--- a/config/config.example.yml
+++ b/config/config.example.yml
@@ -23,10 +23,24 @@ universal:
# Determining which styles are critical is a relatively expensive operation; this option is
# disabled (false) by default to boost server performance at the expense of loading smoothness.
inlineCriticalCss: false
- # Path prefixes to enable SSR for. By default these are limited to paths of primary DSpace objects.
- # NOTE: The "/handle/" path ensures Handle redirects work via SSR. The "/reload/" path ensures
- # hard refreshes (e.g. after login) trigger SSR while fully reloading the page.
- paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/', '/reload/' ]
+ # Patterns to be run as regexes against the path of the page to check if SSR is allowed.
+ # If the path match any of the regexes it will be served directly in CSR.
+ # By default, excludes community and collection browse, global browse, global search, community list, statistics and various administrative tools.
+ excludePathPatterns:
+ - pattern: "^/communities/[a-f0-9-]{36}/browse(/.*)?$"
+ flag: "i"
+ - pattern: "^/collections/[a-f0-9-]{36}/browse(/.*)?$"
+ flag: "i"
+ - pattern: "^/browse/"
+ - pattern: "^/search$"
+ - pattern: "^/community-list$"
+ - pattern: "^/admin/"
+ - pattern: "^/processes/?"
+ - pattern: "^/notifications/"
+ - pattern: "^/statistics/?"
+ - pattern: "^/access-control/"
+ - pattern: "^/health$"
+
# Whether to enable rendering of Search component on SSR.
# If set to true the component will be included in the HTML returned from the server side rendering.
# If set to false the component will not be included in the HTML returned from the server side rendering.
diff --git a/package.json b/package.json
index 1ab4e54262..1a625edf21 100644
--- a/package.json
+++ b/package.json
@@ -60,7 +60,7 @@
"@angular/platform-browser-dynamic": "^15.2.10",
"@angular/platform-server": "^15.2.10",
"@angular/router": "^15.2.10",
- "@babel/runtime": "7.26.7",
+ "@babel/runtime": "7.27.6",
"@kolkov/ngx-gallery": "^2.0.1",
"@ng-bootstrap/ng-bootstrap": "^11.0.0",
"@ng-dynamic-forms/core": "^15.0.0",
@@ -73,14 +73,14 @@
"@nicky-lenaers/ngx-scroll-to": "^14.0.0",
"angular-idle-preload": "3.0.0",
"angulartics2": "^12.2.1",
- "axios": "^1.7.9",
+ "axios": "^1.10.0",
"bootstrap": "^4.6.1",
"cerialize": "0.1.18",
"cli-progress": "^3.12.0",
"colors": "^1.4.0",
- "compression": "^1.7.5",
+ "compression": "^1.8.0",
"cookie-parser": "1.4.7",
- "core-js": "^3.40.0",
+ "core-js": "^3.42.0",
"date-fns": "^2.30.0",
"date-fns-tz": "^1.3.7",
"deepmerge": "^4.3.1",
@@ -89,9 +89,9 @@
"express-rate-limit": "^5.1.3",
"fast-json-patch": "^3.1.1",
"filesize": "^6.1.0",
- "http-proxy-middleware": "^2.0.7",
+ "http-proxy-middleware": "^2.0.9",
"http-terminator": "^3.2.0",
- "isbot": "^5.1.22",
+ "isbot": "^5.1.28",
"js-cookie": "2.2.1",
"js-yaml": "^4.1.0",
"json5": "^2.2.3",
@@ -116,8 +116,8 @@
"nouislider": "^15.8.1",
"pem": "1.14.8",
"reflect-metadata": "^0.2.2",
- "rxjs": "^7.8.0",
- "sanitize-html": "^2.14.0",
+ "rxjs": "^7.8.2",
+ "sanitize-html": "^2.17.0",
"sortablejs": "1.15.6",
"uuid": "^8.3.2",
"zone.js": "~0.13.3"
@@ -146,12 +146,12 @@
"@types/grecaptcha": "^3.0.9",
"@types/jasmine": "~3.6.0",
"@types/js-cookie": "2.2.6",
- "@types/lodash": "^4.17.15",
+ "@types/lodash": "^4.17.17",
"@types/node": "^14.18.63",
- "@types/sanitize-html": "^2.13.0",
+ "@types/sanitize-html": "^2.16.0",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
- "axe-core": "^4.10.2",
+ "axe-core": "^4.10.3",
"compression-webpack-plugin": "^9.2.0",
"copy-webpack-plugin": "^6.4.1",
"cross-env": "^7.0.3",
@@ -163,7 +163,7 @@
"eslint-plugin-deprecation": "^1.5.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsdoc": "^45.0.0",
- "eslint-plugin-jsonc": "^2.19.1",
+ "eslint-plugin-jsonc": "^2.20.1",
"eslint-plugin-lodash": "^7.4.0",
"eslint-plugin-unused-imports": "^2.0.0",
"express-static-gzip": "^2.2.0",
@@ -175,7 +175,7 @@
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"karma-mocha-reporter": "2.2.5",
- "ng-mocks": "^14.13.2",
+ "ng-mocks": "^14.13.5",
"ngx-mask": "^13.1.7",
"nodemon": "^2.0.22",
"postcss": "^8.5",
@@ -187,7 +187,7 @@
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^16.14.0",
"rimraf": "^3.0.2",
- "sass": "~1.84.0",
+ "sass": "~1.89.1",
"sass-loader": "^12.6.0",
"sass-resources-loader": "^2.2.5",
"ts-node": "^8.10.2",
diff --git a/server.ts b/server.ts
index cfab230ef5..1aee5dc657 100644
--- a/server.ts
+++ b/server.ts
@@ -55,6 +55,7 @@ import { APP_CONFIG, AppConfig } from './src/config/app-config.interface';
import { extendEnvironmentWithAppConfig } from './src/config/config.util';
import { logStartupMessage } from './startup-message';
import { TOKENITEM } from './src/app/core/auth/models/auth-token-info.model';
+import { SsrExcludePatterns } from './src/config/universal-config.interface';
/*
@@ -241,7 +242,7 @@ export function app() {
* The callback function to serve server side angular
*/
function ngApp(req, res) {
- if (environment.universal.preboot && req.method === 'GET' && (req.path === '/' || environment.universal.paths.some(pathPrefix => req.path.startsWith(pathPrefix)))) {
+ if (environment.universal.preboot && req.method === 'GET' && (req.path === '/' || !isExcludedFromSsr(req.path, environment.universal.excludePathPatterns))) {
// Render the page to user via SSR (server side rendering)
serverSideRender(req, res);
} else {
@@ -625,6 +626,21 @@ function start() {
}
}
+/**
+ * Check if SSR should be skipped for path
+ *
+ * @param path
+ * @param excludePathPattern
+ */
+function isExcludedFromSsr(path: string, excludePathPattern: SsrExcludePatterns[]): boolean {
+ const patterns = excludePathPattern.map(p =>
+ new RegExp(p.pattern, p.flag || '')
+ );
+ return patterns.some((regex) => {
+ return regex.test(path)
+ });
+}
+
/*
* The callback function to serve health check requests
*/
diff --git a/src/app/access-control/bulk-access/bulk-access.component.spec.ts b/src/app/access-control/bulk-access/bulk-access.component.spec.ts
index e9b253147d..4a545dc3dd 100644
--- a/src/app/access-control/bulk-access/bulk-access.component.spec.ts
+++ b/src/app/access-control/bulk-access/bulk-access.component.spec.ts
@@ -1,5 +1,5 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { NO_ERRORS_SCHEMA } from '@angular/core';
+import { NO_ERRORS_SCHEMA, Component } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { of } from 'rxjs';
@@ -57,10 +57,15 @@ describe('BulkAccessComponent', () => {
'file': { }
};
- const mockSettings: any = jasmine.createSpyObj('AccessControlFormContainerComponent', {
- getValue: jasmine.createSpy('getValue'),
- reset: jasmine.createSpy('reset')
- });
+ @Component({
+ selector: 'ds-bulk-access-settings',
+ template: ''
+ })
+ class MockBulkAccessSettingsComponent {
+ isFormValid = jasmine.createSpy('isFormValid').and.returnValue(false);
+ getValue = jasmine.createSpy('getValue');
+ reset = jasmine.createSpy('reset');
+ }
const selection: any[] = [{ indexableObject: { uuid: '1234' } }, { indexableObject: { uuid: '5678' } }];
const selectableListState: SelectableListState = { id: 'test', selection };
const expectedIdList = ['1234', '5678'];
@@ -73,7 +78,10 @@ describe('BulkAccessComponent', () => {
RouterTestingModule,
TranslateModule.forRoot()
],
- declarations: [ BulkAccessComponent ],
+ declarations: [
+ BulkAccessComponent,
+ MockBulkAccessSettingsComponent,
+ ],
providers: [
{ provide: BulkAccessControlService, useValue: bulkAccessControlServiceMock },
{ provide: NotificationsService, useValue: NotificationsServiceStub },
@@ -102,7 +110,6 @@ describe('BulkAccessComponent', () => {
(component as any).selectableListService.getSelectableList.and.returnValue(of(selectableListStateEmpty));
fixture.detectChanges();
- component.settings = mockSettings;
});
it('should create', () => {
@@ -119,13 +126,12 @@ describe('BulkAccessComponent', () => {
});
- describe('when there are elements selected', () => {
+ describe('when there are elements selected and step two form is invalid', () => {
beforeEach(() => {
(component as any).selectableListService.getSelectableList.and.returnValue(of(selectableListState));
fixture.detectChanges();
- component.settings = mockSettings;
});
it('should create', () => {
@@ -136,9 +142,9 @@ describe('BulkAccessComponent', () => {
expect(component.objectsSelected$.value).toEqual(expectedIdList);
});
- it('should enable the execute button when there are objects selected', () => {
+ it('should not enable the execute button when there are objects selected and step two form is invalid', () => {
component.objectsSelected$.next(['1234']);
- expect(component.canExport()).toBe(true);
+ expect(component.canExport()).toBe(false);
});
it('should call the settings reset method when reset is called', () => {
@@ -146,6 +152,23 @@ describe('BulkAccessComponent', () => {
expect(component.settings.reset).toHaveBeenCalled();
});
+
+ });
+
+ describe('when there are elements selectedted and the step two form is valid', () => {
+
+ beforeEach(() => {
+
+ (component as any).selectableListService.getSelectableList.and.returnValue(of(selectableListState));
+ fixture.detectChanges();
+ (component as any).settings.isFormValid.and.returnValue(true);
+ });
+
+ it('should enable the execute button when there are objects selected and step two form is valid', () => {
+ component.objectsSelected$.next(['1234']);
+ expect(component.canExport()).toBe(true);
+ });
+
it('should call the bulkAccessControlService executeScript method when submit is called', () => {
(component.settings as any).getValue.and.returnValue(mockFormState);
bulkAccessControlService.createPayloadFile.and.returnValue(mockFile);
diff --git a/src/app/access-control/bulk-access/bulk-access.component.ts b/src/app/access-control/bulk-access/bulk-access.component.ts
index 04724614cb..bdea3d5cbe 100644
--- a/src/app/access-control/bulk-access/bulk-access.component.ts
+++ b/src/app/access-control/bulk-access/bulk-access.component.ts
@@ -37,7 +37,7 @@ export class BulkAccessComponent implements OnInit {
constructor(
private bulkAccessControlService: BulkAccessControlService,
- private selectableListService: SelectableListService
+ private selectableListService: SelectableListService,
) {
}
@@ -51,7 +51,7 @@ export class BulkAccessComponent implements OnInit {
}
canExport(): boolean {
- return this.objectsSelected$.value?.length > 0;
+ return this.objectsSelected$.value?.length > 0 && this.settings?.isFormValid();
}
/**
diff --git a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts
index eecc016245..5d1070893c 100644
--- a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts
+++ b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts
@@ -31,4 +31,8 @@ export class BulkAccessSettingsComponent {
this.controlForm.reset();
}
+ isFormValid() {
+ return this.controlForm.isValid();
+ }
+
}
diff --git a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.html b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.html
index 44b6bfb697..a9fb368ef4 100644
--- a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.html
+++ b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.html
@@ -1,4 +1,4 @@
-
+
{{messagePrefix + '.create' | translate}}
diff --git a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts
index f04324bdc5..c072d183aa 100644
--- a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts
+++ b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts
@@ -11,7 +11,7 @@ import { RegistryService } from '../../../../core/registry/registry.service';
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
import { take } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
-import { combineLatest } from 'rxjs';
+import { Observable } from 'rxjs';
import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model';
import { MetadataField } from '../../../../core/metadata/metadata-field.model';
@@ -90,6 +90,8 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy {
*/
@Output() submitForm: EventEmitter = new EventEmitter();
+ activeMetadataField$: Observable;
+
constructor(public registryService: RegistryService,
private formBuilderService: FormBuilderService,
private translateService: TranslateService) {
@@ -99,70 +101,64 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy {
* Initialize the component, setting up the necessary Models for the dynamic form
*/
ngOnInit() {
- combineLatest([
- this.translateService.get(`${this.messagePrefix}.element`),
- this.translateService.get(`${this.messagePrefix}.qualifier`),
- this.translateService.get(`${this.messagePrefix}.scopenote`)
- ]).subscribe(([element, qualifier, scopenote]) => {
- this.element = new DynamicInputModel({
- id: 'element',
- label: element,
- name: 'element',
- validators: {
- required: null,
- pattern: '^[^. ,]*$',
- maxLength: 64,
- },
- required: true,
- errorMessages: {
- pattern: 'error.validation.metadata.element.invalid-pattern',
- maxLength: 'error.validation.metadata.element.max-length',
- },
- });
- this.qualifier = new DynamicInputModel({
- id: 'qualifier',
- label: qualifier,
- name: 'qualifier',
- validators: {
- pattern: '^[^. ,]*$',
- maxLength: 64,
- },
- required: false,
- errorMessages: {
- pattern: 'error.validation.metadata.qualifier.invalid-pattern',
- maxLength: 'error.validation.metadata.qualifier.max-length',
- },
- });
- this.scopeNote = new DynamicTextAreaModel({
- id: 'scopeNote',
- label: scopenote,
- name: 'scopeNote',
- required: false,
- rows: 5,
- });
- this.formModel = [
- new DynamicFormGroupModel(
- {
- id: 'metadatadatafieldgroup',
- group:[this.element, this.qualifier, this.scopeNote]
- })
- ];
- this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
- this.registryService.getActiveMetadataField().subscribe((field: MetadataField): void => {
- if (field == null) {
- this.clearFields();
- } else {
- this.formGroup.patchValue({
- metadatadatafieldgroup: {
- element: field.element,
- qualifier: field.qualifier,
- scopeNote: field.scopeNote,
- },
- });
- this.element.disabled = true;
- this.qualifier.disabled = true;
- }
- });
+ this.element = new DynamicInputModel({
+ id: 'element',
+ label: this.translateService.instant(`${this.messagePrefix}.element`),
+ name: 'element',
+ validators: {
+ required: null,
+ pattern: '^[^. ,]*$',
+ maxLength: 64,
+ },
+ required: true,
+ errorMessages: {
+ pattern: 'error.validation.metadata.element.invalid-pattern',
+ maxLength: 'error.validation.metadata.element.max-length',
+ },
+ });
+ this.qualifier = new DynamicInputModel({
+ id: 'qualifier',
+ label: this.translateService.instant(`${this.messagePrefix}.qualifier`),
+ name: 'qualifier',
+ validators: {
+ pattern: '^[^. ,]*$',
+ maxLength: 64,
+ },
+ required: false,
+ errorMessages: {
+ pattern: 'error.validation.metadata.qualifier.invalid-pattern',
+ maxLength: 'error.validation.metadata.qualifier.max-length',
+ },
+ });
+ this.scopeNote = new DynamicTextAreaModel({
+ id: 'scopeNote',
+ label: this.translateService.instant(`${this.messagePrefix}.scopenote`),
+ name: 'scopeNote',
+ required: false,
+ rows: 5,
+ });
+ this.formModel = [
+ new DynamicFormGroupModel(
+ {
+ id: 'metadatadatafieldgroup',
+ group:[this.element, this.qualifier, this.scopeNote]
+ })
+ ];
+ this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
+ this.registryService.getActiveMetadataField().subscribe((field: MetadataField): void => {
+ if (field == null) {
+ this.clearFields();
+ } else {
+ this.formGroup.patchValue({
+ metadatadatafieldgroup: {
+ element: field.element,
+ qualifier: field.qualifier,
+ scopeNote: field.scopeNote,
+ },
+ });
+ this.element.disabled = true;
+ this.qualifier.disabled = true;
+ }
});
}
diff --git a/src/app/admin/admin-routing.module.ts b/src/app/admin/admin-routing.module.ts
index 8e4f13b164..df938fb6c9 100644
--- a/src/app/admin/admin-routing.module.ts
+++ b/src/app/admin/admin-routing.module.ts
@@ -1,13 +1,13 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MetadataImportPageComponent } from './admin-import-metadata-page/metadata-import-page.component';
-import { AdminSearchPageComponent } from './admin-search-page/admin-search-page.component';
+import { ThemedAdminSearchPageComponent } from './admin-search-page/themed-admin-search-page.component';
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
-import { AdminWorkflowPageComponent } from './admin-workflow-page/admin-workflow-page.component';
import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.service';
import { AdminCurationTasksComponent } from './admin-curation-tasks/admin-curation-tasks.component';
import { REGISTRIES_MODULE_PATH } from './admin-routing-paths';
import { BatchImportPageComponent } from './admin-import-batch-page/batch-import-page.component';
+import { ThemedAdminWorkflowPageComponent } from './admin-workflow-page/themed-admin-workflow-page.component';
@NgModule({
imports: [
@@ -20,13 +20,13 @@ import { BatchImportPageComponent } from './admin-import-batch-page/batch-import
{
path: 'search',
resolve: { breadcrumb: I18nBreadcrumbResolver },
- component: AdminSearchPageComponent,
+ component: ThemedAdminSearchPageComponent,
data: { title: 'admin.search.title', breadcrumbKey: 'admin.search' }
},
{
path: 'workflow',
resolve: { breadcrumb: I18nBreadcrumbResolver },
- component: AdminWorkflowPageComponent,
+ component: ThemedAdminWorkflowPageComponent,
data: { title: 'admin.workflow.title', breadcrumbKey: 'admin.workflow' }
},
{
diff --git a/src/app/admin/admin-search-page/admin-search.module.ts b/src/app/admin/admin-search-page/admin-search.module.ts
index 353d6dd498..b45eca15c4 100644
--- a/src/app/admin/admin-search-page/admin-search.module.ts
+++ b/src/app/admin/admin-search-page/admin-search.module.ts
@@ -1,5 +1,6 @@
import { NgModule } from '@angular/core';
import { SharedModule } from '../../shared/shared.module';
+import { ThemedAdminSearchPageComponent } from './themed-admin-search-page.component';
import { AdminSearchPageComponent } from './admin-search-page.component';
import { ItemAdminSearchResultListElementComponent } from './admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component';
import { CommunityAdminSearchResultListElementComponent } from './admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component';
@@ -31,6 +32,7 @@ const ENTRY_COMPONENTS = [
ResearchEntitiesModule.withEntryComponents()
],
declarations: [
+ ThemedAdminSearchPageComponent,
AdminSearchPageComponent,
...ENTRY_COMPONENTS
]
diff --git a/src/app/admin/admin-search-page/themed-admin-search-page.component.ts b/src/app/admin/admin-search-page/themed-admin-search-page.component.ts
new file mode 100644
index 0000000000..741a3b04f9
--- /dev/null
+++ b/src/app/admin/admin-search-page/themed-admin-search-page.component.ts
@@ -0,0 +1,26 @@
+import { Component } from '@angular/core';
+import { ThemedComponent } from '../../shared/theme-support/themed.component';
+import { AdminSearchPageComponent } from './admin-search-page.component';
+
+/**
+ * Themed wrapper for {@link AdminSearchPageComponent}
+ */
+@Component({
+ selector: 'ds-themed-admin-search-page',
+ templateUrl: '../../shared/theme-support/themed.component.html',
+})
+export class ThemedAdminSearchPageComponent extends ThemedComponent {
+
+ protected getComponentName(): string {
+ return 'AdminSearchPageComponent';
+ }
+
+ protected importThemedComponent(themeName: string): Promise {
+ return import(`../../../themes/${themeName}/app/admin/admin-search-page/admin-search-page.component`);
+ }
+
+ protected importUnthemedComponent(): Promise {
+ return import('./admin-search-page.component');
+ }
+
+}
diff --git a/src/app/admin/admin-workflow-page/admin-workflow.module.ts b/src/app/admin/admin-workflow-page/admin-workflow.module.ts
index 21990c1ea9..1de73dee53 100644
--- a/src/app/admin/admin-workflow-page/admin-workflow.module.ts
+++ b/src/app/admin/admin-workflow-page/admin-workflow.module.ts
@@ -27,6 +27,7 @@ import {
import {
SupervisionOrderStatusComponent
} from './admin-workflow-search-results/actions/workspace-item/supervision-order-status/supervision-order-status.component';
+import { ThemedAdminWorkflowPageComponent } from './themed-admin-workflow-page.component';
const ENTRY_COMPONENTS = [
// put only entry components that use custom decorator
@@ -42,6 +43,7 @@ const ENTRY_COMPONENTS = [
SharedModule.withEntryComponents()
],
declarations: [
+ ThemedAdminWorkflowPageComponent,
AdminWorkflowPageComponent,
SupervisionOrderGroupSelectorComponent,
SupervisionOrderStatusComponent,
diff --git a/src/app/admin/admin-workflow-page/themed-admin-workflow-page.component.ts b/src/app/admin/admin-workflow-page/themed-admin-workflow-page.component.ts
new file mode 100644
index 0000000000..fe84c44d0e
--- /dev/null
+++ b/src/app/admin/admin-workflow-page/themed-admin-workflow-page.component.ts
@@ -0,0 +1,26 @@
+import { Component } from '@angular/core';
+import { ThemedComponent } from '../../shared/theme-support/themed.component';
+import { AdminWorkflowPageComponent } from './admin-workflow-page.component';
+
+/**
+ * Themed wrapper for {@link AdminWorkflowPageComponent}
+ */
+@Component({
+ selector: 'ds-themed-admin-workflow-page',
+ templateUrl: '../../shared/theme-support/themed.component.html',
+})
+export class ThemedAdminWorkflowPageComponent extends ThemedComponent {
+
+ protected getComponentName(): string {
+ return 'AdminWorkflowPageComponent';
+ }
+
+ protected importThemedComponent(themeName: string): Promise {
+ return import(`../../../themes/${themeName}/app/admin/admin-workflow-page/admin-workflow-page.component`);
+ }
+
+ protected importUnthemedComponent(): Promise {
+ return import('./admin-workflow-page.component');
+ }
+
+}
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index deb68f1ea9..b81201dd01 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -40,6 +40,8 @@ import {
import { ServerCheckGuard } from './core/server-check/server-check.guard';
import { MenuResolver } from './menu.resolver';
import { ThemedPageErrorComponent } from './page-error/themed-page-error.component';
+import { HomePageResolver } from './home-page/home-page.resolver';
+import { ViewTrackerResolverService } from './statistics/angulartics/dspace/view-tracker-resolver.service';
@NgModule({
imports: [
@@ -63,7 +65,15 @@ import { ThemedPageErrorComponent } from './page-error/themed-page-error.compone
path: 'home',
loadChildren: () => import('./home-page/home-page.module')
.then((m) => m.HomePageModule),
- data: { showBreadcrumbs: false },
+ data: {
+ showBreadcrumbs: false,
+ dsoPath: 'site'
+ },
+ resolve: {
+ site: HomePageResolver,
+ tracking: ViewTrackerResolverService,
+ },
+
canActivate: [EndUserAgreementCurrentUserGuard]
},
{
@@ -251,6 +261,7 @@ import { ThemedPageErrorComponent } from './page-error/themed-page-error.compone
})
],
exports: [RouterModule],
+ providers: [HomePageResolver, ViewTrackerResolverService],
})
export class AppRoutingModule {
diff --git a/src/app/breadcrumbs/breadcrumbs.component.html b/src/app/breadcrumbs/breadcrumbs.component.html
index 3bba89622f..1d95c39cc0 100644
--- a/src/app/breadcrumbs/breadcrumbs.component.html
+++ b/src/app/breadcrumbs/breadcrumbs.component.html
@@ -10,7 +10,7 @@
-
+
diff --git a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html
index c24ca93403..6a46de108c 100644
--- a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html
+++ b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html
@@ -10,6 +10,8 @@
+ [queryParamsHandling]="'merge'"
+ role="link"
+ tabindex="0">
{{ 'browse.taxonomy.button' | translate }}
diff --git a/src/app/collection-page/collection-page-routing.module.ts b/src/app/collection-page/collection-page-routing.module.ts
index 56305d86c0..7d657e52db 100644
--- a/src/app/collection-page/collection-page-routing.module.ts
+++ b/src/app/collection-page/collection-page-routing.module.ts
@@ -23,6 +23,7 @@ import { ThemedCollectionPageComponent } from './themed-collection-page.componen
import { MenuItemType } from '../shared/menu/menu-item-type.model';
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
import { CommunityBreadcrumbResolver } from '../core/breadcrumbs/community-breadcrumb.resolver';
+import { ViewTrackerResolverService } from '../statistics/angulartics/dspace/view-tracker-resolver.service';
@NgModule({
imports: [
@@ -86,6 +87,7 @@ import { CommunityBreadcrumbResolver } from '../core/breadcrumbs/community-bread
pathMatch: 'full',
resolve: {
menu: DSOEditMenuResolver,
+ tracking: ViewTrackerResolverService,
},
}
],
@@ -116,6 +118,7 @@ import { CommunityBreadcrumbResolver } from '../core/breadcrumbs/community-bread
CreateCollectionPageGuard,
CollectionPageAdministratorGuard,
CommunityBreadcrumbResolver,
+ ViewTrackerResolverService,
]
})
export class CollectionPageRoutingModule {
diff --git a/src/app/collection-page/collection-page.component.html b/src/app/collection-page/collection-page.component.html
index 9a5414952f..ec8da3c7e7 100644
--- a/src/app/collection-page/collection-page.component.html
+++ b/src/app/collection-page/collection-page.component.html
@@ -3,7 +3,6 @@
*ngVar="(collectionRD$ | async) as collectionRD">