mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge branch 'main' into CST-6171
This commit is contained in:
@@ -6,7 +6,8 @@
|
||||
"eslint-plugin-import",
|
||||
"eslint-plugin-jsdoc",
|
||||
"eslint-plugin-deprecation",
|
||||
"eslint-plugin-unused-imports"
|
||||
"unused-imports",
|
||||
"eslint-plugin-lodash"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
@@ -202,7 +203,13 @@
|
||||
"deprecation/deprecation": "warn",
|
||||
|
||||
"import/order": "off",
|
||||
"import/no-deprecated": "warn"
|
||||
"import/no-deprecated": "warn",
|
||||
"import/no-namespace": "error",
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
"lodash/import-scope": [
|
||||
"error",
|
||||
"method"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
17
.github/workflows/build.yml
vendored
17
.github/workflows/build.yml
vendored
@@ -6,6 +6,9 @@ name: Build
|
||||
# Run this Build for all pushes / PRs to current branch
|
||||
on: [push, pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -29,11 +32,11 @@ jobs:
|
||||
steps:
|
||||
# https://github.com/actions/checkout
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# https://github.com/actions/setup-node
|
||||
- name: Install Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
@@ -58,7 +61,7 @@ jobs:
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Cache Yarn dependencies
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
# Cache entire Yarn cache directory (see previous step)
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
@@ -85,7 +88,7 @@ jobs:
|
||||
# Upload coverage reports to Codecov (for one version of Node only)
|
||||
# https://github.com/codecov/codecov-action
|
||||
- name: Upload coverage to Codecov.io
|
||||
uses: codecov/codecov-action@v2
|
||||
uses: codecov/codecov-action@v3
|
||||
if: matrix.node-version == '16.x'
|
||||
|
||||
# Using docker-compose start backend using CI configuration
|
||||
@@ -100,7 +103,7 @@ jobs:
|
||||
# https://github.com/cypress-io/github-action
|
||||
# (NOTE: to run these e2e tests locally, just use 'ng e2e')
|
||||
- name: Run e2e tests (integration tests)
|
||||
uses: cypress-io/github-action@v2
|
||||
uses: cypress-io/github-action@v4
|
||||
with:
|
||||
# Run tests in Chrome, headless mode
|
||||
browser: chrome
|
||||
@@ -116,7 +119,7 @@ jobs:
|
||||
# Cypress always creates a video of all e2e tests (whether they succeeded or failed)
|
||||
# Save those in an Artifact
|
||||
- name: Upload e2e test videos to Artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: e2e-test-videos
|
||||
@@ -125,7 +128,7 @@ jobs:
|
||||
# If e2e tests fail, Cypress creates a screenshot of what happened
|
||||
# Save those in an Artifact
|
||||
- name: Upload e2e test failure screenshots to Artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
if: failure()
|
||||
with:
|
||||
name: e2e-test-screenshots
|
||||
|
13
.github/workflows/docker.yml
vendored
13
.github/workflows/docker.yml
vendored
@@ -12,6 +12,9 @@ on:
|
||||
- 'dspace-**'
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace-angular'
|
||||
@@ -39,11 +42,11 @@ jobs:
|
||||
steps:
|
||||
# https://github.com/actions/checkout
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# https://github.com/docker/setup-buildx-action
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
# https://github.com/docker/setup-qemu-action
|
||||
- name: Set up QEMU emulation to build for multiple architectures
|
||||
@@ -53,7 +56,7 @@ jobs:
|
||||
- name: Login to DockerHub
|
||||
# Only login if not a PR, as PRs only trigger a Docker build and not a push
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
@@ -65,7 +68,7 @@ jobs:
|
||||
# Get Metadata for docker_build step below
|
||||
- name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-angular' image
|
||||
id: meta_build
|
||||
uses: docker/metadata-action@v3
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: dspace/dspace-angular
|
||||
tags: ${{ env.IMAGE_TAGS }}
|
||||
@@ -74,7 +77,7 @@ jobs:
|
||||
# https://github.com/docker/build-push-action
|
||||
- name: Build and push 'dspace-angular' image
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
|
17
.github/workflows/issue_opened.yml
vendored
17
.github/workflows/issue_opened.yml
vendored
@@ -5,25 +5,22 @@ on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
permissions: {}
|
||||
jobs:
|
||||
automation:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Add the new issue to a project board, if it needs triage
|
||||
# See https://github.com/marketplace/actions/create-project-card-action
|
||||
- name: Add issue to project board
|
||||
# See https://github.com/actions/add-to-project
|
||||
- name: Add issue to triage board
|
||||
# Only add to project board if issue is flagged as "needs triage" or has no labels
|
||||
# NOTE: By default we flag new issues as "needs triage" in our issue template
|
||||
if: (contains(github.event.issue.labels.*.name, 'needs triage') || join(github.event.issue.labels.*.name) == '')
|
||||
uses: technote-space/create-project-card-action@v1
|
||||
uses: actions/add-to-project@v0.3.0
|
||||
# Note, the authentication token below is an ORG level Secret.
|
||||
# It must be created/recreated manually via a personal access token with "public_repo" and "admin:org" permissions
|
||||
# It must be created/recreated manually via a personal access token with admin:org, project, public_repo permissions
|
||||
# See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#permissions-for-the-github_token
|
||||
# This is necessary because the "DSpace Backlog" project is an org level project (i.e. not repo specific)
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.ORG_PROJECT_TOKEN }}
|
||||
PROJECT: DSpace Backlog
|
||||
COLUMN: Triage
|
||||
CHECK_ORG_PROJECT: true
|
||||
# Ignore errors
|
||||
continue-on-error: true
|
||||
github-token: ${{ secrets.TRIAGE_PROJECT_TOKEN }}
|
||||
project-url: https://github.com/orgs/DSpace/projects/24
|
||||
|
27
.github/workflows/label_merge_conflicts.yml
vendored
27
.github/workflows/label_merge_conflicts.yml
vendored
@@ -5,21 +5,32 @@ name: Check for merge conflicts
|
||||
# NOTE: This means merge conflicts are only checked for when a PR is merged to main.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
branches: [ main ]
|
||||
# So that the `conflict_label_name` is removed if conflicts are resolved,
|
||||
# we allow this to run for `pull_request_target` so that github secrets are available.
|
||||
pull_request_target:
|
||||
types: [ synchronize ]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace-angular'
|
||||
if: github.repository == 'dspace/dspace-angular'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
# See: https://github.com/mschilde/auto-label-merge-conflicts/
|
||||
# See: https://github.com/prince-chrismc/label-merge-conflicts-action
|
||||
- name: Auto-label PRs with merge conflicts
|
||||
uses: mschilde/auto-label-merge-conflicts@v2.0
|
||||
uses: prince-chrismc/label-merge-conflicts-action@v2
|
||||
# Add "merge conflict" label if a merge conflict is detected. Remove it when resolved.
|
||||
# Note, the authentication token is created automatically
|
||||
# See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token
|
||||
with:
|
||||
CONFLICT_LABEL_NAME: 'merge conflict'
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Ignore errors
|
||||
continue-on-error: true
|
||||
conflict_label_name: 'merge conflict'
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
conflict_comment: |
|
||||
Hi @${author},
|
||||
Conflicts have been detected against the base branch.
|
||||
Please [resolve these conflicts](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/about-merge-conflicts) as soon as you can. Thanks!
|
@@ -4,10 +4,11 @@ import { testA11y } from 'cypress/support/utils';
|
||||
|
||||
describe('My DSpace page', () => {
|
||||
it('should display recent submissions and pass accessibility tests', () => {
|
||||
cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||
|
||||
cy.visit('/mydspace');
|
||||
|
||||
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||
|
||||
cy.get('ds-my-dspace-page').should('exist');
|
||||
|
||||
// At least one recent submission should be displayed
|
||||
@@ -36,10 +37,11 @@ describe('My DSpace page', () => {
|
||||
});
|
||||
|
||||
it('should have a working detailed view that passes accessibility tests', () => {
|
||||
cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||
|
||||
cy.visit('/mydspace');
|
||||
|
||||
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||
|
||||
cy.get('ds-my-dspace-page').should('exist');
|
||||
|
||||
// Click button in sidebar to display detailed view
|
||||
@@ -61,9 +63,11 @@ describe('My DSpace page', () => {
|
||||
|
||||
// NOTE: Deleting existing submissions is exercised by submission.spec.ts
|
||||
it('should let you start a new submission & edit in-progress submissions', () => {
|
||||
cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||
cy.visit('/mydspace');
|
||||
|
||||
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||
|
||||
// Open the New Submission dropdown
|
||||
cy.get('button[data-test="submission-dropdown"]').click();
|
||||
// Click on the "Item" type in that dropdown
|
||||
@@ -131,9 +135,11 @@ describe('My DSpace page', () => {
|
||||
});
|
||||
|
||||
it('should let you import from external sources', () => {
|
||||
cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||
cy.visit('/mydspace');
|
||||
|
||||
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||
|
||||
// Open the New Import dropdown
|
||||
cy.get('button[data-test="import-dropdown"]').click();
|
||||
// Click on the "Item" type in that dropdown
|
||||
|
@@ -6,11 +6,12 @@ describe('New Submission page', () => {
|
||||
// NOTE: We already test that new submissions can be started from MyDSpace in my-dspace.spec.ts
|
||||
|
||||
it('should create a new submission when using /submit path & pass accessibility', () => {
|
||||
cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||
|
||||
// Test that calling /submit with collection & entityType will create a new submission
|
||||
cy.visit('/submit?collection=' + TEST_SUBMIT_COLLECTION_UUID + '&entityType=none');
|
||||
|
||||
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||
|
||||
// Should redirect to /workspaceitems, as we've started a new submission
|
||||
cy.url().should('include', '/workspaceitems');
|
||||
|
||||
@@ -33,11 +34,12 @@ describe('New Submission page', () => {
|
||||
});
|
||||
|
||||
it('should block submission & show errors if required fields are missing', () => {
|
||||
cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||
|
||||
// Create a new submission
|
||||
cy.visit('/submit?collection=' + TEST_SUBMIT_COLLECTION_UUID + '&entityType=none');
|
||||
|
||||
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||
|
||||
// Attempt an immediate deposit without filling out any fields
|
||||
cy.get('button#deposit').click();
|
||||
|
||||
@@ -92,11 +94,12 @@ describe('New Submission page', () => {
|
||||
});
|
||||
|
||||
it('should allow for deposit if all required fields completed & file uploaded', () => {
|
||||
cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||
|
||||
// Create a new submission
|
||||
cy.visit('/submit?collection=' + TEST_SUBMIT_COLLECTION_UUID + '&entityType=none');
|
||||
|
||||
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||
|
||||
// Fill out all required fields (Title, Date)
|
||||
cy.get('input#dc_title').type('DSpace logo uploaded via e2e tests');
|
||||
cy.get('input#dc_date_issued_year').type('2022');
|
||||
|
@@ -19,6 +19,14 @@ declare global {
|
||||
* @param password password to login as
|
||||
*/
|
||||
login(email: string, password: string): typeof login;
|
||||
|
||||
/**
|
||||
* Login via form before accessing the next page. Useful to fill out login
|
||||
* form when a cy.visit() call is to an a page which requires authentication.
|
||||
* @param email email to login as
|
||||
* @param password password to login as
|
||||
*/
|
||||
loginViaForm(email: string, password: string): typeof loginViaForm;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +34,8 @@ declare global {
|
||||
/**
|
||||
* Login user via REST API directly, and pass authentication token to UI via
|
||||
* the UI's dsAuthInfo cookie.
|
||||
* WARNING: WHILE THIS METHOD WORKS, OCCASIONALLY RANDOM AUTHENTICATION ERRORS OCCUR.
|
||||
* At this time "loginViaForm()" seems more consistent/stable.
|
||||
* @param email email to login as
|
||||
* @param password password to login as
|
||||
*/
|
||||
@@ -81,3 +91,20 @@ function login(email: string, password: string): void {
|
||||
}
|
||||
// Add as a Cypress command (i.e. assign to 'cy.login')
|
||||
Cypress.Commands.add('login', login);
|
||||
|
||||
|
||||
/**
|
||||
* Login user via displayed login form
|
||||
* @param email email to login as
|
||||
* @param password password to login as
|
||||
*/
|
||||
function loginViaForm(email: string, password: string): void {
|
||||
// Enter email
|
||||
cy.get('ds-log-in [data-test="email"]').type(email);
|
||||
// Enter password
|
||||
cy.get('ds-log-in [data-test="password"]').type(password);
|
||||
// Click login button
|
||||
cy.get('ds-log-in [data-test="login-button"]').click();
|
||||
}
|
||||
// Add as a Cypress command (i.e. assign to 'cy.loginViaForm')
|
||||
Cypress.Commands.add('loginViaForm', loginViaForm);
|
11
package.json
11
package.json
@@ -89,6 +89,8 @@
|
||||
"compression": "^1.7.4",
|
||||
"cookie-parser": "1.4.5",
|
||||
"core-js": "^3.7.0",
|
||||
"date-fns": "^2.29.3",
|
||||
"date-fns-tz": "^1.3.7",
|
||||
"deepmerge": "^4.2.2",
|
||||
"express": "^4.17.1",
|
||||
"express-rate-limit": "^5.1.3",
|
||||
@@ -103,20 +105,18 @@
|
||||
"json5": "^2.1.3",
|
||||
"jsonschema": "1.4.0",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"klaro": "^0.7.10",
|
||||
"klaro": "^0.7.18",
|
||||
"lodash": "^4.17.21",
|
||||
"markdown-it": "^13.0.1",
|
||||
"markdown-it-mathjax3": "^4.3.1",
|
||||
"mirador": "^3.3.0",
|
||||
"mirador-dl-plugin": "^0.13.0",
|
||||
"mirador-share-plugin": "^0.11.0",
|
||||
"moment": "^2.29.4",
|
||||
"morgan": "^1.10.0",
|
||||
"ng-mocks": "^13.1.1",
|
||||
"ng2-file-upload": "1.4.0",
|
||||
"ng2-nouislider": "^1.8.3",
|
||||
"ngx-infinite-scroll": "^10.0.1",
|
||||
"ngx-moment": "^5.0.0",
|
||||
"ngx-pagination": "5.0.0",
|
||||
"ngx-sortablejs": "^11.1.0",
|
||||
"ngx-ui-switch": "^11.0.1",
|
||||
@@ -162,14 +162,14 @@
|
||||
"@types/sanitize-html": "^2.6.2",
|
||||
"@typescript-eslint/eslint-plugin": "5.11.0",
|
||||
"@typescript-eslint/parser": "5.11.0",
|
||||
"axe-core": "^4.3.3",
|
||||
"axe-core": "^4.4.3",
|
||||
"compression-webpack-plugin": "^9.2.0",
|
||||
"copy-webpack-plugin": "^6.4.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^6.2.0",
|
||||
"css-minimizer-webpack-plugin": "^3.4.1",
|
||||
"cssnano": "^5.0.6",
|
||||
"cypress": "9.5.1",
|
||||
"cypress": "9.7.0",
|
||||
"cypress-axe": "^0.14.0",
|
||||
"debug-loader": "^0.0.1",
|
||||
"deep-freeze": "0.0.1",
|
||||
@@ -178,6 +178,7 @@
|
||||
"eslint-plugin-deprecation": "^1.3.2",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-jsdoc": "^38.0.6",
|
||||
"eslint-plugin-lodash": "^7.4.0",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"express-static-gzip": "^2.1.5",
|
||||
"fork-ts-checker-webpack-plugin": "^6.0.3",
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import * as fs from 'fs';
|
||||
import { existsSync, writeFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { AppConfig } from '../src/config/app-config.interface';
|
||||
@@ -16,7 +16,7 @@ const appConfig: AppConfig = buildAppConfig();
|
||||
|
||||
const angularJsonPath = join(process.cwd(), 'angular.json');
|
||||
|
||||
if (!fs.existsSync(angularJsonPath)) {
|
||||
if (!existsSync(angularJsonPath)) {
|
||||
console.error(`Error:\n${angularJsonPath} does not exist\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -30,7 +30,7 @@ try {
|
||||
|
||||
angularJson.projects['dspace-angular'].architect.build.options.baseHref = baseHref;
|
||||
|
||||
fs.writeFileSync(angularJsonPath, JSON.stringify(angularJson, null, 2) + '\n');
|
||||
writeFileSync(angularJsonPath, JSON.stringify(angularJson, null, 2) + '\n');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import * as fs from 'fs';
|
||||
import * as yaml from 'js-yaml';
|
||||
import { existsSync, writeFileSync } from 'fs';
|
||||
import { dump } from 'js-yaml';
|
||||
import { join } from 'path';
|
||||
|
||||
/**
|
||||
@@ -18,7 +18,7 @@ if (args[0] === undefined) {
|
||||
|
||||
const envFullPath = join(process.cwd(), args[0]);
|
||||
|
||||
if (!fs.existsSync(envFullPath)) {
|
||||
if (!existsSync(envFullPath)) {
|
||||
console.error(`Error:\n${envFullPath} does not exist\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -26,10 +26,10 @@ if (!fs.existsSync(envFullPath)) {
|
||||
try {
|
||||
const env = require(envFullPath).environment;
|
||||
|
||||
const config = yaml.dump(env);
|
||||
const config = dump(env);
|
||||
if (args[1]) {
|
||||
const ymlFullPath = join(process.cwd(), args[1]);
|
||||
fs.writeFileSync(ymlFullPath, config);
|
||||
writeFileSync(ymlFullPath, config);
|
||||
} else {
|
||||
console.log(config);
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import * as child from 'child_process';
|
||||
import { spawn } from 'child_process';
|
||||
|
||||
import { AppConfig } from '../src/config/app-config.interface';
|
||||
import { buildAppConfig } from '../src/config/config.server';
|
||||
@@ -9,7 +9,7 @@ const appConfig: AppConfig = buildAppConfig();
|
||||
* Calls `ng serve` with the following arguments configured for the UI in the app config: host, port, nameSpace, ssl
|
||||
* Any CLI arguments given to this script are patched through to `ng serve` as well.
|
||||
*/
|
||||
child.spawn(
|
||||
spawn(
|
||||
`ng serve --host ${appConfig.ui.host} --port ${appConfig.ui.port} --serve-path ${appConfig.ui.nameSpace} --ssl ${appConfig.ui.ssl} ${process.argv.slice(2).join(' ')} --configuration development`,
|
||||
{ stdio: 'inherit', shell: true }
|
||||
);
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
import { request } from 'http';
|
||||
import { request as https_request } from 'https';
|
||||
|
||||
import { AppConfig } from '../src/config/app-config.interface';
|
||||
import { buildAppConfig } from '../src/config/config.server';
|
||||
@@ -20,7 +20,7 @@ console.log(`...Testing connection to REST API at ${restUrl}...\n`);
|
||||
|
||||
// If SSL enabled, test via HTTPS, else via HTTP
|
||||
if (appConfig.rest.ssl) {
|
||||
const req = https.request(restUrl, (res) => {
|
||||
const req = https_request(restUrl, (res) => {
|
||||
console.log(`RESPONSE: ${res.statusCode} ${res.statusMessage} \n`);
|
||||
// We will keep reading data until the 'end' event fires.
|
||||
// This ensures we don't just read the first chunk.
|
||||
@@ -39,7 +39,7 @@ if (appConfig.rest.ssl) {
|
||||
|
||||
req.end();
|
||||
} else {
|
||||
const req = http.request(restUrl, (res) => {
|
||||
const req = request(restUrl, (res) => {
|
||||
console.log(`RESPONSE: ${res.statusCode} ${res.statusMessage} \n`);
|
||||
// We will keep reading data until the 'end' event fires.
|
||||
// This ensures we don't just read the first chunk.
|
||||
|
17
server.ts
17
server.ts
@@ -19,14 +19,17 @@ import 'zone.js/node';
|
||||
import 'reflect-metadata';
|
||||
import 'rxjs';
|
||||
|
||||
import axios from 'axios';
|
||||
import * as pem from 'pem';
|
||||
import * as https from 'https';
|
||||
/* eslint-disable import/no-namespace */
|
||||
import * as morgan from 'morgan';
|
||||
import * as express from 'express';
|
||||
import * as bodyParser from 'body-parser';
|
||||
import * as compression from 'compression';
|
||||
import * as expressStaticGzip from 'express-static-gzip';
|
||||
/* eslint-enable import/no-namespace */
|
||||
|
||||
import axios from 'axios';
|
||||
import { createCertificate } from 'pem';
|
||||
import { createServer } from 'https';
|
||||
import { json } from 'body-parser';
|
||||
|
||||
import { existsSync, readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
@@ -110,7 +113,7 @@ export function app() {
|
||||
* Add parser for request bodies
|
||||
* See [morgan](https://github.com/expressjs/body-parser)
|
||||
*/
|
||||
server.use(bodyParser.json());
|
||||
server.use(json());
|
||||
|
||||
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
|
||||
server.engine('html', (_, options, callback) =>
|
||||
@@ -266,7 +269,7 @@ function serverStarted() {
|
||||
* @param keys SSL credentials
|
||||
*/
|
||||
function createHttpsServer(keys) {
|
||||
https.createServer({
|
||||
createServer({
|
||||
key: keys.serviceKey,
|
||||
cert: keys.certificate
|
||||
}, app).listen(environment.ui.port, environment.ui.host, () => {
|
||||
@@ -320,7 +323,7 @@ function start() {
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; // lgtm[js/disabling-certificate-validation]
|
||||
|
||||
pem.createCertificate({
|
||||
createCertificate({
|
||||
days: 1,
|
||||
selfSigned: true
|
||||
}, (error, keys) => {
|
||||
|
@@ -1,12 +1,8 @@
|
||||
import { Location } from '@angular/common';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
import { AuthService } from '../../core/auth/auth.service';
|
||||
import { METADATA_IMPORT_SCRIPT_NAME, ScriptDataService } from '../../core/data/processes/script-data.service';
|
||||
import { EPerson } from '../../core/eperson/models/eperson.model';
|
||||
import { ProcessParameter } from '../../process-page/processes/process-parameter.model';
|
||||
import { isNotEmpty } from '../../shared/empty.util';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
|
@@ -13,32 +13,34 @@
|
||||
[paginationOptions]="pageConfig"
|
||||
[pageInfoState]="(bitstreamFormats | async)?.payload"
|
||||
[collectionSize]="(bitstreamFormats | async)?.payload?.totalElements"
|
||||
[hideGear]="true"
|
||||
[hideGear]="false"
|
||||
[hidePagerWhenSinglePage]="true">
|
||||
<div class="table-responsive">
|
||||
<table id="formats" class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"></th>
|
||||
<th scope="col">{{'admin.registries.bitstream-formats.table.name' | translate}}</th>
|
||||
<th scope="col">{{'admin.registries.bitstream-formats.table.mimetype' | translate}}</th>
|
||||
<th scope="col">{{'admin.registries.bitstream-formats.table.supportLevel.head' | translate}}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="col"></th>
|
||||
<th scope="col">{{'admin.registries.bitstream-formats.table.id' | translate}}</th>
|
||||
<th scope="col">{{'admin.registries.bitstream-formats.table.name' | translate}}</th>
|
||||
<th scope="col">{{'admin.registries.bitstream-formats.table.mimetype' | translate}}</th>
|
||||
<th scope="col">{{'admin.registries.bitstream-formats.table.supportLevel.head' | translate}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let bitstreamFormat of (bitstreamFormats | async)?.payload?.page">
|
||||
<td>
|
||||
<label>
|
||||
<input type="checkbox"
|
||||
[checked]="isSelected(bitstreamFormat) | async"
|
||||
(change)="selectBitStreamFormat(bitstreamFormat, $event)"
|
||||
>
|
||||
</label>
|
||||
</td>
|
||||
<td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{bitstreamFormat.shortDescription}}</a></td>
|
||||
<td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{bitstreamFormat.mimetype}} <span *ngIf="bitstreamFormat.internal">({{'admin.registries.bitstream-formats.table.internal' | translate}})</span></a></td>
|
||||
<td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{'admin.registries.bitstream-formats.table.supportLevel.'+bitstreamFormat.supportLevel | translate}}</a></td>
|
||||
</tr>
|
||||
<tr *ngFor="let bitstreamFormat of (bitstreamFormats | async)?.payload?.page">
|
||||
<td>
|
||||
<label>
|
||||
<input type="checkbox"
|
||||
[checked]="isSelected(bitstreamFormat) | async"
|
||||
(change)="selectBitStreamFormat(bitstreamFormat, $event)"
|
||||
>
|
||||
</label>
|
||||
</td>
|
||||
<td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{bitstreamFormat.id}}</a></td>
|
||||
<td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{bitstreamFormat.shortDescription}}</a></td>
|
||||
<td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{bitstreamFormat.mimetype}} <span *ngIf="bitstreamFormat.internal">({{'admin.registries.bitstream-formats.table.internal' | translate}})</span></a></td>
|
||||
<td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{'admin.registries.bitstream-formats.table.supportLevel.'+bitstreamFormat.supportLevel | translate}}</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@@ -129,16 +129,19 @@ describe('BitstreamFormatsComponent', () => {
|
||||
});
|
||||
|
||||
it('should contain the correct formats', () => {
|
||||
const unknownName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(1) td:nth-child(2)')).nativeElement;
|
||||
const unknownName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(1) td:nth-child(3)')).nativeElement;
|
||||
expect(unknownName.textContent).toBe('Unknown');
|
||||
|
||||
const licenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(2) td:nth-child(2)')).nativeElement;
|
||||
const UUID: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(1) td:nth-child(2)')).nativeElement;
|
||||
expect(UUID.textContent).toBe('test-uuid-1');
|
||||
|
||||
const licenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(2) td:nth-child(3)')).nativeElement;
|
||||
expect(licenseName.textContent).toBe('License');
|
||||
|
||||
const ccLicenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(3) td:nth-child(2)')).nativeElement;
|
||||
const ccLicenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(3) td:nth-child(3)')).nativeElement;
|
||||
expect(ccLicenseName.textContent).toBe('CC License');
|
||||
|
||||
const adobeName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(4) td:nth-child(2)')).nativeElement;
|
||||
const adobeName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(4) td:nth-child(3)')).nativeElement;
|
||||
expect(adobeName.textContent).toBe('Adobe PDF');
|
||||
});
|
||||
});
|
||||
|
@@ -1,12 +1,11 @@
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { combineLatest as observableCombineLatest, Observable, zip } from 'rxjs';
|
||||
import { combineLatest as observableCombineLatest, Observable} from 'rxjs';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
|
||||
import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
|
||||
import { map, mergeMap, switchMap, take, toArray } from 'rxjs/operators';
|
||||
import { hasValue } from '../../../shared/empty.util';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
@@ -29,21 +28,14 @@ export class BitstreamFormatsComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
bitstreamFormats: Observable<RemoteData<PaginatedList<BitstreamFormat>>>;
|
||||
|
||||
/**
|
||||
* The current pagination configuration for the page used by the FindAll method
|
||||
* Currently simply renders all bitstream formats
|
||||
*/
|
||||
config: FindListOptions = Object.assign(new FindListOptions(), {
|
||||
elementsPerPage: 20
|
||||
});
|
||||
|
||||
/**
|
||||
* The current pagination configuration for the page
|
||||
* Currently simply renders all bitstream formats
|
||||
*/
|
||||
pageConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
|
||||
id: 'rbp',
|
||||
pageSize: 20
|
||||
pageSize: 20,
|
||||
pageSizeOptions: [20, 40, 60, 80, 100]
|
||||
});
|
||||
|
||||
constructor(private notificationsService: NotificationsService,
|
||||
@@ -51,7 +43,7 @@ export class BitstreamFormatsComponent implements OnInit, OnDestroy {
|
||||
private translateService: TranslateService,
|
||||
private bitstreamFormatService: BitstreamFormatDataService,
|
||||
private paginationService: PaginationService,
|
||||
) {
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -149,7 +141,7 @@ export class BitstreamFormatsComponent implements OnInit, OnDestroy {
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
this.bitstreamFormats = this.paginationService.getFindListOptions(this.pageConfig.id, this.config).pipe(
|
||||
this.bitstreamFormats = this.paginationService.getFindListOptions(this.pageConfig.id, this.pageConfig).pipe(
|
||||
switchMap((findListOptions: FindListOptions) => {
|
||||
return this.bitstreamFormatService.findAll(findListOptions);
|
||||
})
|
||||
|
@@ -19,10 +19,7 @@ import { RestResponse } from '../../../core/cache/response.models';
|
||||
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
|
||||
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
||||
import { FindListOptions } from '../../../core/data/find-list-options.model';
|
||||
|
||||
describe('MetadataRegistryComponent', () => {
|
||||
let comp: MetadataRegistryComponent;
|
||||
|
@@ -25,6 +25,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th scope="col">{{'admin.registries.schema.fields.table.id' | translate}}</th>
|
||||
<th scope="col">{{'admin.registries.schema.fields.table.field' | translate}}</th>
|
||||
<th scope="col">{{'admin.registries.schema.fields.table.scopenote' | translate}}</th>
|
||||
</tr>
|
||||
@@ -39,6 +40,7 @@
|
||||
(change)="selectMetadataField(field, $event)">
|
||||
</label>
|
||||
</td>
|
||||
<td class="selectable-row" (click)="editField(field)">{{field.id}}</td>
|
||||
<td class="selectable-row" (click)="editField(field)">{{schema?.prefix}}.{{field.element}}<label *ngIf="field.qualifier">.</label>{{field.qualifier}}</td>
|
||||
<td class="selectable-row" (click)="editField(field)">{{field.scopeNote}}</td>
|
||||
</tr>
|
||||
|
@@ -23,11 +23,8 @@ import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
|
||||
import { MetadataField } from '../../../core/metadata/metadata-field.model';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||
import { VarDirective } from '../../../shared/utils/var.directive';
|
||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
|
||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
||||
import { FindListOptions } from '../../../core/data/find-list-options.model';
|
||||
|
||||
describe('MetadataSchemaComponent', () => {
|
||||
let comp: MetadataSchemaComponent;
|
||||
@@ -169,10 +166,10 @@ describe('MetadataSchemaComponent', () => {
|
||||
});
|
||||
|
||||
it('should contain the correct fields', () => {
|
||||
const editorField: HTMLElement = fixture.debugElement.query(By.css('#metadata-fields tr:nth-child(1) td:nth-child(2)')).nativeElement;
|
||||
const editorField: HTMLElement = fixture.debugElement.query(By.css('#metadata-fields tr:nth-child(1) td:nth-child(3)')).nativeElement;
|
||||
expect(editorField.textContent).toBe('mock.contributor.editor');
|
||||
|
||||
const illustratorField: HTMLElement = fixture.debugElement.query(By.css('#metadata-fields tr:nth-child(2) td:nth-child(2)')).nativeElement;
|
||||
const illustratorField: HTMLElement = fixture.debugElement.query(By.css('#metadata-fields tr:nth-child(2) td:nth-child(3)')).nativeElement;
|
||||
expect(illustratorField.textContent).toBe('mock.contributor.illustrator');
|
||||
});
|
||||
|
||||
|
@@ -16,7 +16,6 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
||||
import createSpy = jasmine.createSpy;
|
||||
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
|
@@ -31,7 +31,6 @@ import { models } from './core/core.module';
|
||||
import { ThemeService } from './shared/theme-support/theme.service';
|
||||
import { IdleModalComponent } from './shared/idle-modal/idle-modal.component';
|
||||
import { distinctNext } from './core/shared/distinct-next';
|
||||
import { ModalBeforeDismiss } from './shared/interfaces/modal-before-dismiss.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-app',
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import * as fromRouter from '@ngrx/router-store';
|
||||
import { routerReducer, RouterReducerState } from '@ngrx/router-store';
|
||||
import { ActionReducerMap, createSelector, MemoizedSelector } from '@ngrx/store';
|
||||
import {
|
||||
ePeopleRegistryReducer,
|
||||
@@ -53,7 +53,7 @@ import { MenusState } from './shared/menu/menus-state.model';
|
||||
import { correlationIdReducer } from './correlation-id/correlation-id.reducer';
|
||||
|
||||
export interface AppState {
|
||||
router: fromRouter.RouterReducerState;
|
||||
router: RouterReducerState;
|
||||
hostWindow: HostWindowState;
|
||||
forms: FormState;
|
||||
metadataRegistry: MetadataRegistryState;
|
||||
@@ -75,7 +75,7 @@ export interface AppState {
|
||||
}
|
||||
|
||||
export const appReducers: ActionReducerMap<AppState> = {
|
||||
router: fromRouter.routerReducer,
|
||||
router: routerReducer,
|
||||
hostWindow: hostWindowReducer,
|
||||
forms: formReducer,
|
||||
metadataRegistry: metadataRegistryReducer,
|
||||
|
@@ -26,7 +26,7 @@ import {
|
||||
import { FormGroup } from '@angular/forms';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { DynamicCustomSwitchModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
|
||||
import {
|
||||
getAllSucceededRemoteDataPayload,
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { LegacyBitstreamUrlResolver } from './legacy-bitstream-url.resolver';
|
||||
import { of as observableOf, EMPTY } from 'rxjs';
|
||||
import { EMPTY } from 'rxjs';
|
||||
import { BitstreamDataService } from '../core/data/bitstream-data.service';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { TestScheduler } from 'rxjs/testing';
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { first } from 'rxjs/operators';
|
||||
import { BrowseByGuard } from './browse-by-guard';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { BrowseDefinitionDataService } from '../core/browse/browse-definition-data.service';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
||||
import { BrowseDefinition } from '../core/shared/browse-definition.model';
|
||||
import { BrowseByDataType } from './browse-by-switcher/browse-by-decorator';
|
||||
|
@@ -16,7 +16,7 @@ import { Collection } from '../../core/shared/collection.model';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||
import { ChangeDetectionStrategy, EventEmitter } from '@angular/core';
|
||||
import { EventEmitter } from '@angular/core';
|
||||
import { HostWindowService } from '../../shared/host-window.service';
|
||||
import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub';
|
||||
import { By } from '@angular/platform-browser';
|
||||
@@ -41,7 +41,7 @@ import {
|
||||
} from '../../shared/remote-data.utils';
|
||||
import { createPaginatedList } from '../../shared/testing/utils.test';
|
||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||
import { MyDSpacePageComponent, SEARCH_CONFIG_SERVICE } from '../../my-dspace-page/my-dspace-page.component';
|
||||
import { SEARCH_CONFIG_SERVICE } from '../../my-dspace-page/my-dspace-page.component';
|
||||
import { SearchConfigurationServiceStub } from '../../shared/testing/search-configuration-service.stub';
|
||||
import { GroupDataService } from '../../core/eperson/group-data.service';
|
||||
import { LinkHeadService } from '../../core/services/link-head.service';
|
||||
|
@@ -11,11 +11,11 @@
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-weight-bold">{{'collection.source.controls.harvest.last' | translate}}</span>
|
||||
<span>{{contentSource?.message ? contentSource?.message : 'collection.source.controls.harvest.no-information'|translate }}</span>
|
||||
<span>{{contentSource?.lastHarvested ? contentSource?.lastHarvested : 'collection.source.controls.harvest.no-information'|translate }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-weight-bold">{{'collection.source.controls.harvest.message' | translate}}</span>
|
||||
<span>{{contentSource?.lastHarvested ? contentSource?.lastHarvested : 'collection.source.controls.harvest.no-information'|translate }}</span>
|
||||
<span>{{contentSource?.message ? contentSource?.message: 'collection.source.controls.harvest.no-information'|translate }}</span>
|
||||
</div>
|
||||
|
||||
<button *ngIf="!(testConfigRunning$ |async)" class="btn btn-secondary"
|
||||
|
@@ -8,8 +8,7 @@ import {
|
||||
DynamicInputModel,
|
||||
DynamicOptionControlModel,
|
||||
DynamicRadioGroupModel,
|
||||
DynamicSelectModel,
|
||||
DynamicTextAreaModel
|
||||
DynamicSelectModel
|
||||
} from '@ng-dynamic-forms/core';
|
||||
import { Location } from '@angular/common';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
@@ -23,7 +22,7 @@ import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { Collection } from '../../../core/shared/collection.model';
|
||||
import { first, map, switchMap, take } from 'rxjs/operators';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
||||
import { getFirstSucceededRemoteData, getFirstCompletedRemoteData } from '../../../core/shared/operators';
|
||||
import { MetadataConfig } from '../../../core/shared/metadata-config.model';
|
||||
|
@@ -17,9 +17,6 @@ import { PageInfo } from '../../core/shared/page-info.model';
|
||||
import { HostWindowService } from '../../shared/host-window.service';
|
||||
import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub';
|
||||
import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service';
|
||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||
import { getMockThemeService } from '../../shared/mocks/theme-service.mock';
|
||||
import { ThemeService } from '../../shared/theme-support/theme.service';
|
||||
@@ -29,7 +26,6 @@ import { GroupDataService } from '../../core/eperson/group-data.service';
|
||||
import { LinkHeadService } from '../../core/services/link-head.service';
|
||||
import { ConfigurationDataService } from '../../core/data/configuration-data.service';
|
||||
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
|
||||
import { SearchServiceStub } from '../../shared/testing/search-service.stub';
|
||||
import { ConfigurationProperty } from '../../core/shared/configuration-property.model';
|
||||
import { createPaginatedList } from '../../shared/testing/utils.test';
|
||||
import { SearchConfigurationServiceStub } from '../../shared/testing/search-configuration-service.stub';
|
||||
|
@@ -17,9 +17,6 @@ import { HostWindowService } from '../../shared/host-window.service';
|
||||
import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub';
|
||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service';
|
||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||
import { getMockThemeService } from '../../shared/mocks/theme-service.mock';
|
||||
import { ThemeService } from '../../shared/theme-support/theme.service';
|
||||
|
@@ -7,7 +7,6 @@ import { createSelector } from '@ngrx/store';
|
||||
* notation packages up all of the exports into a single object.
|
||||
*/
|
||||
import { AuthState } from './auth.reducer';
|
||||
import { AppState } from '../../app.reducer';
|
||||
import { CoreState } from '../core-state.model';
|
||||
import { coreSelector } from '../core.selectors';
|
||||
|
||||
|
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line import/no-namespace
|
||||
import * as deepFreeze from 'deep-freeze';
|
||||
import { Operation } from 'fast-json-patch';
|
||||
import { Item } from '../shared/item.model';
|
||||
|
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line import/no-namespace
|
||||
import * as deepFreeze from 'deep-freeze';
|
||||
import { RemoveFromObjectCacheAction } from './object-cache.actions';
|
||||
import { serverSyncBufferReducer } from './server-sync-buffer.reducer';
|
||||
|
@@ -3,7 +3,7 @@ import { ChangeAnalyzer } from './change-analyzer';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
import { MetadataMap } from '../shared/metadata.models';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
|
||||
/**
|
||||
* A class to determine what differs between two
|
||||
|
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line import/no-namespace
|
||||
import * as deepFreeze from 'deep-freeze';
|
||||
import {
|
||||
AddFieldUpdateAction,
|
||||
|
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line import/no-namespace
|
||||
import * as deepFreeze from 'deep-freeze';
|
||||
import {
|
||||
RequestConfigureAction,
|
||||
|
@@ -4,7 +4,7 @@ import { HttpHeaders } from '@angular/common/http';
|
||||
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs';
|
||||
import { filter, map, take, tap } from 'rxjs/operators';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { hasValue, isEmpty, isNotEmpty, hasNoValue } from '../../shared/empty.util';
|
||||
import { ObjectCacheEntry } from '../cache/object-cache.reducer';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
|
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line import/no-namespace
|
||||
import * as deepFreeze from 'deep-freeze';
|
||||
|
||||
import { indexReducer, MetaIndexState } from './index.reducer';
|
||||
|
@@ -3,7 +3,6 @@ import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||
import { coreSelector } from '../core.selectors';
|
||||
import { URLCombiner } from '../url-combiner/url-combiner';
|
||||
import { IndexState, MetaIndexState } from './index.reducer';
|
||||
import * as parse from 'url-parse';
|
||||
import { IndexName } from './index-name.model';
|
||||
import { CoreState } from '../core-state.model';
|
||||
|
||||
@@ -21,17 +20,21 @@ import { CoreState } from '../core-state.model';
|
||||
*/
|
||||
export const getUrlWithoutEmbedParams = (url: string): string => {
|
||||
if (isNotEmpty(url)) {
|
||||
const parsed = parse(url);
|
||||
if (isNotEmpty(parsed.query)) {
|
||||
const parts = parsed.query.split(/[?|&]/)
|
||||
.filter((part: string) => isNotEmpty(part))
|
||||
.filter((part: string) => !(part.startsWith('embed=') || part.startsWith('embed.size=')));
|
||||
let args = '';
|
||||
if (isNotEmpty(parts)) {
|
||||
args = `?${parts.join('&')}`;
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
if (isNotEmpty(parsed.search)) {
|
||||
const parts = parsed.search.split(/[?|&]/)
|
||||
.filter((part: string) => isNotEmpty(part))
|
||||
.filter((part: string) => !(part.startsWith('embed=') || part.startsWith('embed.size=')));
|
||||
let args = '';
|
||||
if (isNotEmpty(parts)) {
|
||||
args = `?${parts.join('&')}`;
|
||||
}
|
||||
url = new URLCombiner(parsed.origin, parsed.pathname, args).toString();
|
||||
return url;
|
||||
}
|
||||
url = new URLCombiner(parsed.origin, parsed.pathname, args).toString();
|
||||
return url;
|
||||
} catch (e) {
|
||||
// Ignore parsing errors. By default, we return the original string below.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,15 +47,19 @@ export const getUrlWithoutEmbedParams = (url: string): string => {
|
||||
*/
|
||||
export const getEmbedSizeParams = (url: string): { name: string, size: number }[] => {
|
||||
if (isNotEmpty(url)) {
|
||||
const parsed = parse(url);
|
||||
if (isNotEmpty(parsed.query)) {
|
||||
return parsed.query.split(/[?|&]/)
|
||||
.filter((part: string) => isNotEmpty(part))
|
||||
.map((part: string) => part.match(/^embed.size=([^=]+)=(\d+)$/))
|
||||
.filter((matches: RegExpMatchArray) => hasValue(matches) && hasValue(matches[1]) && hasValue(matches[2]))
|
||||
.map((matches: RegExpMatchArray) => {
|
||||
return { name: matches[1], size: Number(matches[2]) };
|
||||
});
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
if (isNotEmpty(parsed.search)) {
|
||||
return parsed.search.split(/[?|&]/)
|
||||
.filter((part: string) => isNotEmpty(part))
|
||||
.map((part: string) => part.match(/^embed.size=([^=]+)=(\d+)$/))
|
||||
.filter((matches: RegExpMatchArray) => hasValue(matches) && hasValue(matches[1]) && hasValue(matches[2]))
|
||||
.map((matches: RegExpMatchArray) => {
|
||||
return { name: matches[1], size: Number(matches[2]) };
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore parsing errors. By default, we return an empty result below.
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line import/no-namespace
|
||||
import * as deepFreeze from 'deep-freeze';
|
||||
|
||||
import {
|
||||
|
@@ -1,8 +1,7 @@
|
||||
import { filter, map, pairwise } from 'rxjs/operators';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||
import * as fromRouter from '@ngrx/router-store';
|
||||
import { RouterNavigationAction } from '@ngrx/router-store';
|
||||
import { RouterNavigationAction, ROUTER_NAVIGATION } from '@ngrx/router-store';
|
||||
import { Router } from '@angular/router';
|
||||
import { RouteUpdateAction } from './router.actions';
|
||||
|
||||
@@ -14,7 +13,7 @@ export class RouterEffects {
|
||||
*/
|
||||
routeChange$ = createEffect(() => this.actions$
|
||||
.pipe(
|
||||
ofType(fromRouter.ROUTER_NAVIGATION),
|
||||
ofType(ROUTER_NAVIGATION),
|
||||
pairwise(),
|
||||
map((actions: RouterNavigationAction[]) =>
|
||||
actions.map((navigateAction) => {
|
||||
|
@@ -4,7 +4,7 @@ import { ActivatedRoute, NavigationEnd, Params, Router, RouterStateSnapshot, } f
|
||||
|
||||
import { combineLatest, Observable } from 'rxjs';
|
||||
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
|
||||
import { isEqual } from 'lodash';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
||||
import { AddParameterAction, SetParameterAction, SetParametersAction, SetQueryParameterAction, SetQueryParametersAction } from './route.actions';
|
||||
import { coreSelector } from '../core.selectors';
|
||||
|
@@ -3,7 +3,7 @@ import { getMockRequestService } from '../../shared/mocks/request.service.mock';
|
||||
import { RequestService } from '../data/request.service';
|
||||
import { HALEndpointService } from './hal-endpoint.service';
|
||||
import { EndpointMapRequest } from '../data/request.models';
|
||||
import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
|
||||
import { combineLatest as observableCombineLatest, of as observableOf } from 'rxjs';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||
|
@@ -5,7 +5,8 @@ import {
|
||||
MetadataValueFilter,
|
||||
MetadatumViewModel
|
||||
} from './metadata.models';
|
||||
import { groupBy, sortBy } from 'lodash';
|
||||
import groupBy from 'lodash/groupBy';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
|
||||
/**
|
||||
* Utility class for working with DSpace object metadata.
|
||||
|
@@ -26,6 +26,8 @@ import { SearchConfigurationService } from './search-configuration.service';
|
||||
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
||||
import { RequestEntry } from '../../data/request-entry.model';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
import { SearchFilterConfig } from '../../../shared/search/models/search-filter-config.model';
|
||||
import anything = jasmine.anything;
|
||||
|
||||
@Component({ template: '' })
|
||||
class DummyComponent {
|
||||
@@ -36,7 +38,7 @@ describe('SearchService', () => {
|
||||
let searchService: SearchService;
|
||||
const router = new RouterStub();
|
||||
const route = new ActivatedRouteStub();
|
||||
const searchConfigService = {paginationID: 'page-id'};
|
||||
const searchConfigService = { paginationID: 'page-id' };
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
@@ -103,7 +105,8 @@ describe('SearchService', () => {
|
||||
};
|
||||
|
||||
const paginationService = new PaginationServiceStub();
|
||||
const searchConfigService = {paginationID: 'page-id'};
|
||||
const searchConfigService = { paginationID: 'page-id' };
|
||||
const requestService = getMockRequestService();
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@@ -119,7 +122,7 @@ describe('SearchService', () => {
|
||||
providers: [
|
||||
{ provide: Router, useValue: router },
|
||||
{ provide: RouteService, useValue: routeServiceStub },
|
||||
{ provide: RequestService, useValue: getMockRequestService() },
|
||||
{ provide: RequestService, useValue: requestService },
|
||||
{ provide: RemoteDataBuildService, useValue: remoteDataBuildService },
|
||||
{ provide: HALEndpointService, useValue: halService },
|
||||
{ provide: CommunityDataService, useValue: {} },
|
||||
@@ -138,13 +141,13 @@ describe('SearchService', () => {
|
||||
|
||||
it('should call the navigate method on the Router with view mode list parameter as a parameter when setViewMode is called', () => {
|
||||
searchService.setViewMode(ViewMode.ListElement);
|
||||
expect(paginationService.updateRouteWithUrl).toHaveBeenCalledWith('page-id', ['/search'], {page: 1}, { view: ViewMode.ListElement }
|
||||
expect(paginationService.updateRouteWithUrl).toHaveBeenCalledWith('page-id', ['/search'], { page: 1 }, { view: ViewMode.ListElement }
|
||||
);
|
||||
});
|
||||
|
||||
it('should call the navigate method on the Router with view mode grid parameter as a parameter when setViewMode is called', () => {
|
||||
searchService.setViewMode(ViewMode.GridElement);
|
||||
expect(paginationService.updateRouteWithUrl).toHaveBeenCalledWith('page-id', ['/search'], {page: 1}, { view: ViewMode.GridElement }
|
||||
expect(paginationService.updateRouteWithUrl).toHaveBeenCalledWith('page-id', ['/search'], { page: 1 }, { view: ViewMode.GridElement }
|
||||
);
|
||||
});
|
||||
|
||||
@@ -191,5 +194,23 @@ describe('SearchService', () => {
|
||||
expect((searchService as any).rdb.buildFromHref).toHaveBeenCalledWith(endPoint);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when getFacetValuesFor is called with a filterQuery', () => {
|
||||
it('should add the encoded filterQuery to the args list', () => {
|
||||
jasmine.getEnv().allowRespy(true);
|
||||
const spyRequest = spyOn((searchService as any), 'request').and.stub();
|
||||
spyOn(requestService, 'send').and.returnValue(true);
|
||||
const searchFilterConfig = new SearchFilterConfig();
|
||||
searchFilterConfig._links = {
|
||||
self: {
|
||||
href: 'https://demo.dspace.org/',
|
||||
},
|
||||
};
|
||||
|
||||
searchService.getFacetValuesFor(searchFilterConfig, 1, undefined, 'filter&Query');
|
||||
|
||||
expect(spyRequest).toHaveBeenCalledWith(anything(), 'https://demo.dspace.org?page=0&size=5&prefix=filter%26Query');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -3,7 +3,6 @@ import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
||||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
import { map, switchMap, take } from 'rxjs/operators';
|
||||
import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model';
|
||||
import { PaginatedList } from '../../data/paginated-list.model';
|
||||
import { ResponseParsingService } from '../../data/parsing.service';
|
||||
import { RemoteData } from '../../data/remote-data';
|
||||
import { GetRequest } from '../../data/request.models';
|
||||
@@ -271,7 +270,7 @@ export class SearchService implements OnDestroy {
|
||||
let href;
|
||||
let args: string[] = [];
|
||||
if (hasValue(filterQuery)) {
|
||||
args.push(`prefix=${filterQuery}`);
|
||||
args.push(`prefix=${encodeURIComponent(filterQuery)}`);
|
||||
}
|
||||
if (hasValue(searchOptions)) {
|
||||
searchOptions = Object.assign(new PaginatedSearchOptions({}), searchOptions, {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable max-classes-per-file */
|
||||
import { EquatableObject, excludeFromEquals, fieldsForEquals } from './equals.decorators';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
|
||||
class Dog extends EquatableObject<Dog> {
|
||||
public name: string;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { CorrelationIdService } from './correlation-id.service';
|
||||
import { CookieServiceMock } from '../shared/mocks/cookie.service.mock';
|
||||
import { UUIDService } from '../core/shared/uuid.service';
|
||||
import { MockStore, provideMockStore } from '@ngrx/store/testing';
|
||||
import { MockStore } from '@ngrx/store/testing';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { Store, StoreModule } from '@ngrx/store';
|
||||
import { appReducers, AppState, storeModuleConfig } from '../app.reducer';
|
||||
|
@@ -5,7 +5,7 @@ import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||
import { find, map } from 'rxjs/operators';
|
||||
import { NotificationsService } from '../shared/notifications/notifications.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { hasValue, isEmpty, isNotEmpty, hasNoValue } from '../shared/empty.util';
|
||||
import { hasValue, isEmpty, isNotEmpty } from '../shared/empty.util';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { Router } from '@angular/router';
|
||||
import { ProcessDataService } from '../core/data/processes/process-data.service';
|
||||
|
@@ -17,9 +17,6 @@ import { HostWindowService } from '../../shared/host-window.service';
|
||||
import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub';
|
||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||
import { getMockThemeService } from '../../shared/mocks/theme-service.mock';
|
||||
import { ThemeService } from '../../shared/theme-support/theme.service';
|
||||
|
@@ -13,7 +13,7 @@ import { makeStateKey, TransferState } from '@angular/platform-browser';
|
||||
import { APP_CONFIG, AppConfig } from '../config/app-config.interface';
|
||||
import { environment } from '../environments/environment';
|
||||
import { AppState } from './app.reducer';
|
||||
import { isEqual } from 'lodash';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { LocaleService } from './core/locale/locale.service';
|
||||
import { Angulartics2DSpace } from './statistics/angulartics/dspace-provider';
|
||||
|
@@ -1,12 +1,11 @@
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { waitForAsync, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { of as observableOf, of } from 'rxjs';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { cold } from 'jasmine-marbles';
|
||||
import { ItemAuthorizationsComponent, BitstreamMapValue } from './item-authorizations.component';
|
||||
import { ItemAuthorizationsComponent } from './item-authorizations.component';
|
||||
import { Bitstream } from '../../../core/shared/bitstream.model';
|
||||
import { Bundle } from '../../../core/shared/bundle.model';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
@@ -14,8 +13,6 @@ import { LinkService } from '../../../core/cache/builders/link.service';
|
||||
import { getMockLinkService } from '../../../shared/mocks/link-service.mock';
|
||||
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||
import { createPaginatedList, createTestComponent } from '../../../shared/testing/utils.test';
|
||||
import { PaginatedList, buildPaginatedList } from '../../../core/data/paginated-list.model';
|
||||
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||
|
||||
describe('ItemAuthorizationsComponent test suite', () => {
|
||||
let comp: ItemAuthorizationsComponent;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { isEqual } from 'lodash';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
@@ -17,10 +17,7 @@ import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-
|
||||
import { createPaginatedList } from '../../../../../shared/testing/utils.test';
|
||||
import { RequestService } from '../../../../../core/data/request.service';
|
||||
import { PaginationService } from '../../../../../core/pagination/pagination.service';
|
||||
import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model';
|
||||
import { SortDirection, SortOptions } from '../../../../../core/cache/models/sort-options.model';
|
||||
import { PaginationServiceStub } from '../../../../../shared/testing/pagination-service.stub';
|
||||
import { FindListOptions } from '../../../../../core/data/find-list-options.model';
|
||||
|
||||
describe('PaginatedDragAndDropBitstreamListComponent', () => {
|
||||
let comp: PaginatedDragAndDropBitstreamListComponent;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Component, Input, OnChanges, OnInit, SimpleChanges, ViewChild, ViewContainerRef } from '@angular/core';
|
||||
import { Bitstream } from '../../../../core/shared/bitstream.model';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
|
||||
|
@@ -5,7 +5,7 @@ import {
|
||||
} from '../../../../core/shared/operators';
|
||||
import { hasValue, isNotEmpty } from '../../../../shared/empty.util';
|
||||
import { RegistryService } from '../../../../core/registry/registry.service';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { BehaviorSubject, Observable, of as observableOf } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
|
||||
|
@@ -3,7 +3,7 @@ import { Item } from '../../../core/shared/item.model';
|
||||
import { ItemDataService } from '../../../core/data/item-data.service';
|
||||
import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { first, switchMap } from 'rxjs/operators';
|
||||
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
|
@@ -1,18 +1,16 @@
|
||||
<ds-metadata-field-wrapper [label]="label | translate">
|
||||
<ng-container *ngFor="let mdValue of mdValues; let last=last;">
|
||||
<ng-container *ngTemplateOutlet="(renderMarkdown ? markdown : simple); context: {value: mdValue.value, classes: 'dont-break-out preserve-line-breaks'}">
|
||||
<ng-container *ngTemplateOutlet="(renderMarkdown ? markdown : simple); context: {value: mdValue.value}">
|
||||
</ng-container>
|
||||
<span class="separator" *ngIf="!last" [innerHTML]="separator"></span>
|
||||
</ng-container>
|
||||
</ds-metadata-field-wrapper>
|
||||
|
||||
<ng-template #markdown let-value="value" let-classes="classes">
|
||||
<span class="{{classes}}" [innerHTML]="value | dsMarkdown | async">
|
||||
<ng-template #markdown let-value="value">
|
||||
<span class="dont-break-out" [innerHTML]="value | dsMarkdown | async">
|
||||
</span>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #simple let-value="value" let-classes="classes">
|
||||
<span class="{{classes}}">
|
||||
{{value}}
|
||||
</span>
|
||||
<ng-template #simple let-value="value">
|
||||
<span class="dont-break-out preserve-line-breaks">{{value}}</span>
|
||||
</ng-template>
|
||||
|
@@ -16,11 +16,8 @@ import { MockBitstreamFormat1 } from '../../../../shared/mocks/item.mock';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||
import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
|
||||
import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model';
|
||||
import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model';
|
||||
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
||||
import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub';
|
||||
import { FindListOptions } from '../../../../core/data/find-list-options.model';
|
||||
|
||||
describe('FullFileSectionComponent', () => {
|
||||
let comp: FullFileSectionComponent;
|
||||
|
@@ -35,4 +35,10 @@ export class GenericItemPageFieldComponent extends ItemPageFieldComponent {
|
||||
*/
|
||||
@Input() label: string;
|
||||
|
||||
/**
|
||||
* Whether the {@link MarkdownPipe} should be used to render this metadata.
|
||||
*/
|
||||
@Input() enableMarkdown = false;
|
||||
|
||||
|
||||
}
|
||||
|
@@ -76,7 +76,7 @@
|
||||
|
||||
<ds-generic-item-page-field [item]="object"
|
||||
[fields]="['dc.subject']"
|
||||
[separator]="','"
|
||||
[separator]="', '"
|
||||
[label]="'item.page.subject'">
|
||||
</ds-generic-item-page-field>
|
||||
<ds-generic-item-page-field [item]="object"
|
||||
|
@@ -28,7 +28,6 @@ import { TruncatableService } from '../../../../shared/truncatable/truncatable.s
|
||||
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
|
||||
import { GenericItemPageFieldComponent } from '../../field-components/specific-field/generic/generic-item-page-field.component';
|
||||
import { compareArraysUsing, compareArraysUsingIds } from './item-relationships-utils';
|
||||
import { ItemComponent } from './item.component';
|
||||
import { createPaginatedList } from '../../../../shared/testing/utils.test';
|
||||
import { RouteService } from '../../../../core/services/route.service';
|
||||
import { MetadataValue } from '../../../../core/shared/metadata.models';
|
||||
|
@@ -61,7 +61,7 @@
|
||||
|
||||
<ds-generic-item-page-field [item]="object"
|
||||
[fields]="['dc.subject']"
|
||||
[separator]="','"
|
||||
[separator]="', '"
|
||||
[label]="'item.page.subject'">
|
||||
</ds-generic-item-page-field>
|
||||
<ds-generic-item-page-field [item]="object"
|
||||
|
@@ -11,10 +11,6 @@ import { cold, hot } from 'jasmine-marbles';
|
||||
import { MyDSpaceConfigurationValueType } from './my-dspace-configuration-value-type';
|
||||
import { PaginationServiceStub } from '../shared/testing/pagination-service.stub';
|
||||
import { Context } from '../core/shared/context.model';
|
||||
import { LinkService } from '../core/cache/builders/link.service';
|
||||
import { HALEndpointService } from '../core/shared/hal-endpoint.service';
|
||||
import { RequestService } from '../core/data/request.service';
|
||||
import { RemoteDataBuildService } from '../core/cache/builders/remote-data-build.service';
|
||||
import { HALEndpointServiceStub } from '../shared/testing/hal-endpoint-service.stub';
|
||||
import { getMockRemoteDataBuildService } from '../shared/mocks/remote-data-build.service.mock';
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<div class="nav-item dropdown expandable-navbar-section"
|
||||
<div class="nav-item dropdown expandable-navbar-section text-md-center"
|
||||
*ngVar="(active | async) as isActive"
|
||||
(keyup.enter)="isActive ? deactivateSection($event) : activateSection($event)"
|
||||
(keyup.space)="isActive ? deactivateSection($event) : activateSection($event)"
|
||||
|
@@ -1,3 +1,10 @@
|
||||
.expandable-navbar-section {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
overflow: hidden;
|
||||
min-width: 100%;
|
||||
|
@@ -1,3 +1,3 @@
|
||||
<div class="nav-item navbar-section">
|
||||
<div class="nav-item navbar-section text-md-center">
|
||||
<ng-container *ngComponentOutlet="(sectionMap$ | async).get(section.id).component; injector: (sectionMap$ | async).get(section.id).injector;"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -0,0 +1,5 @@
|
||||
.navbar-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
@@ -1,10 +1,13 @@
|
||||
<nav [ngClass]="{'open': !(menuCollapsed | async)}" [@slideMobileNav]="!(windowService.isXsOrSm() | async) ? 'default' : ((menuCollapsed | async) ? 'collapsed' : 'expanded')"
|
||||
class="navbar navbar-light navbar-expand-md p-md-0 navbar-container" role="navigation" [attr.aria-label]="'nav.main.description' | translate">
|
||||
<!-- TODO remove navbar-container class when https://github.com/twbs/bootstrap/issues/24726 is fixed -->
|
||||
<div class="container">
|
||||
<div class="navbar-inner-container w-100" [class.container]="!(isXsOrSm$ | async)">
|
||||
<div class="reset-padding-md w-100">
|
||||
<div id="collapsingNav">
|
||||
<ul class="navbar-nav navbar-navigation mr-auto shadow-none">
|
||||
<li *ngIf="(isXsOrSm$ | async) && (isAuthenticated$ | async)">
|
||||
<ds-user-menu [inExpandableNavbar]="true"></ds-user-menu>
|
||||
</li>
|
||||
<ng-container *ngFor="let section of (sections | async)">
|
||||
<ng-container *ngComponentOutlet="(sectionMap$ | async).get(section.id)?.component; injector: (sectionMap$ | async).get(section.id)?.injector;"></ng-container>
|
||||
</ng-container>
|
||||
|
@@ -6,13 +6,14 @@ nav.navbar {
|
||||
/** Mobile menu styling **/
|
||||
@media screen and (max-width: map-get($grid-breakpoints, md)-0.02) {
|
||||
.navbar {
|
||||
width: 100vw;
|
||||
width: 100%;
|
||||
background-color: var(--bs-white);
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
height: 0;
|
||||
&.open {
|
||||
height: 100vh; //doesn't matter because wrapper is sticky
|
||||
height: auto;
|
||||
min-height: 100vh; //doesn't matter because wrapper is sticky
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +28,7 @@ nav.navbar {
|
||||
/* TODO remove when https://github.com/twbs/bootstrap/issues/24726 is fixed */
|
||||
.navbar-expand-md.navbar-container {
|
||||
@media screen and (max-width: map-get($grid-breakpoints, md)-0.02) {
|
||||
> .container {
|
||||
> .navbar-inner-container {
|
||||
padding: 0 var(--bs-spacer);
|
||||
}
|
||||
padding: 0;
|
||||
|
@@ -22,9 +22,17 @@ import { Item } from '../core/shared/item.model';
|
||||
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
|
||||
import { ThemeService } from '../shared/theme-support/theme.service';
|
||||
import { getMockThemeService } from '../shared/mocks/theme-service.mock';
|
||||
import { Store, StoreModule } from '@ngrx/store';
|
||||
import { AppState, storeModuleConfig } from '../app.reducer';
|
||||
import { authReducer } from '../core/auth/auth.reducer';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { AuthTokenInfo } from '../core/auth/models/auth-token-info.model';
|
||||
import { EPersonMock } from '../shared/testing/eperson.mock';
|
||||
|
||||
let comp: NavbarComponent;
|
||||
let fixture: ComponentFixture<NavbarComponent>;
|
||||
let store: Store<AppState>;
|
||||
let initialState: any;
|
||||
|
||||
const authorizationService = jasmine.createSpyObj('authorizationService', {
|
||||
isAuthorized: observableOf(true)
|
||||
@@ -83,10 +91,24 @@ describe('NavbarComponent', () => {
|
||||
}
|
||||
),
|
||||
];
|
||||
initialState = {
|
||||
core: {
|
||||
auth: {
|
||||
authenticated: true,
|
||||
loaded: true,
|
||||
blocking: false,
|
||||
loading: false,
|
||||
authToken: new AuthTokenInfo('test_token'),
|
||||
userId: EPersonMock.id,
|
||||
authMethods: []
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
StoreModule.forRoot({ auth: authReducer }, storeModuleConfig),
|
||||
NoopAnimationsModule,
|
||||
ReactiveFormsModule,
|
||||
RouterTestingModule],
|
||||
@@ -99,6 +121,7 @@ describe('NavbarComponent', () => {
|
||||
{ provide: ActivatedRoute, useValue: routeStub },
|
||||
{ provide: BrowseService, useValue: { getBrowseDefinitions: createSuccessfulRemoteDataObject$(buildPaginatedList(undefined, browseDefinitions)) } },
|
||||
{ provide: AuthorizationDataService, useValue: authorizationService },
|
||||
provideMockStore({ initialState }),
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
})
|
||||
@@ -107,7 +130,7 @@ describe('NavbarComponent', () => {
|
||||
|
||||
// synchronous beforeEach
|
||||
beforeEach(() => {
|
||||
|
||||
store = TestBed.inject(Store);
|
||||
fixture = TestBed.createComponent(NavbarComponent);
|
||||
|
||||
comp = fixture.componentInstance;
|
||||
|
@@ -8,6 +8,10 @@ import { ActivatedRoute } from '@angular/router';
|
||||
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
|
||||
import { MenuID } from '../shared/menu/menu-id.model';
|
||||
import { ThemeService } from '../shared/theme-support/theme.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { select, Store } from '@ngrx/store';
|
||||
import { AppState } from '../app.reducer';
|
||||
import { isAuthenticated } from '../core/auth/selectors';
|
||||
|
||||
/**
|
||||
* Component representing the public navbar
|
||||
@@ -25,18 +29,29 @@ export class NavbarComponent extends MenuComponent {
|
||||
*/
|
||||
menuID = MenuID.PUBLIC;
|
||||
|
||||
/**
|
||||
* Whether user is authenticated.
|
||||
* @type {Observable<string>}
|
||||
*/
|
||||
public isAuthenticated$: Observable<boolean>;
|
||||
|
||||
public isXsOrSm$: Observable<boolean>;
|
||||
|
||||
constructor(protected menuService: MenuService,
|
||||
protected injector: Injector,
|
||||
public windowService: HostWindowService,
|
||||
public browseService: BrowseService,
|
||||
public authorizationService: AuthorizationDataService,
|
||||
public route: ActivatedRoute,
|
||||
protected themeService: ThemeService
|
||||
protected themeService: ThemeService,
|
||||
private store: Store<AppState>,
|
||||
) {
|
||||
super(menuService, injector, authorizationService, route, themeService);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
super.ngOnInit();
|
||||
this.isXsOrSm$ = this.windowService.isXsOrSm();
|
||||
this.isAuthenticated$ = this.store.pipe(select(isAuthenticated));
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ import { HostWindowResizeAction } from '../shared/host-window.actions';
|
||||
import { Observable } from 'rxjs';
|
||||
import { provideMockActions } from '@ngrx/effects/testing';
|
||||
import { cold, hot } from 'jasmine-marbles';
|
||||
import * as fromRouter from '@ngrx/router-store';
|
||||
import { ROUTER_NAVIGATION } from '@ngrx/router-store';
|
||||
import { CollapseMenuAction } from '../shared/menu/menu.actions';
|
||||
import { MenuService } from '../shared/menu/menu.service';
|
||||
import { MenuServiceStub } from '../shared/testing/menu-service.stub';
|
||||
@@ -43,7 +43,7 @@ describe('NavbarEffects', () => {
|
||||
describe('routeChange$', () => {
|
||||
|
||||
it('should return a COLLAPSE action in response to an UPDATE_LOCATION action', () => {
|
||||
actions = hot('--a-', { a: { type: fromRouter.ROUTER_NAVIGATION } });
|
||||
actions = hot('--a-', { a: { type: ROUTER_NAVIGATION } });
|
||||
|
||||
const expected = cold('--b-', { b: new CollapseMenuAction(MenuID.PUBLIC) });
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { first, map, switchMap } from 'rxjs/operators';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||
import * as fromRouter from '@ngrx/router-store';
|
||||
import { ROUTER_NAVIGATION } from '@ngrx/router-store';
|
||||
|
||||
import { HostWindowActionTypes } from '../shared/host-window.actions';
|
||||
import {
|
||||
@@ -33,7 +33,7 @@ export class NavbarEffects {
|
||||
*/
|
||||
routeChange$ = createEffect(() => this.actions$
|
||||
.pipe(
|
||||
ofType(fromRouter.ROUTER_NAVIGATION),
|
||||
ofType(ROUTER_NAVIGATION),
|
||||
map(() => new CollapseMenuAction(this.menuID))
|
||||
));
|
||||
/**
|
||||
|
@@ -15,7 +15,6 @@ import {
|
||||
} from '@angular/core/testing';
|
||||
import { VarDirective } from '../../shared/utils/var.directive';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { ProcessDetailFieldComponent } from './process-detail-field/process-detail-field.component';
|
||||
import { Process } from '../processes/process.model';
|
||||
|
@@ -8,7 +8,7 @@ import { EPerson } from '../../core/eperson/models/eperson.model';
|
||||
import { FormBuilderService } from '../../shared/form/builder/form-builder.service';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { EPersonDataService } from '../../core/eperson/eperson-data.service';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||
|
||||
describe('ProfilePageMetadataFormComponent', () => {
|
||||
|
@@ -11,7 +11,7 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||
import { LangConfig } from '../../../config/lang-config.interface';
|
||||
import { EPersonDataService } from '../../core/eperson/eperson-data.service';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { getRemoteDataPayload, getFirstSucceededRemoteData } from '../../core/shared/operators';
|
||||
import { FormBuilderService } from '../../shared/form/builder/form-builder.service';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
|
@@ -2,7 +2,6 @@ import { RegistrationGuard } from './registration.guard';
|
||||
import { EpersonRegistrationService } from '../core/data/eperson-registration.service';
|
||||
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { AuthService } from '../core/auth/auth.service';
|
||||
import { Location } from '@angular/common';
|
||||
import {
|
||||
createFailedRemoteDataObject$,
|
||||
createSuccessfulRemoteDataObject,
|
||||
|
@@ -1,9 +1,9 @@
|
||||
<div class="outer-wrapper" [class.d-none]="shouldShowFullscreenLoader">
|
||||
<ds-themed-admin-sidebar></ds-themed-admin-sidebar>
|
||||
<div class="inner-wrapper" [@slideSidebarPadding]="{
|
||||
<div class="outer-wrapper" [class.d-none]="shouldShowFullscreenLoader" [@slideSidebarPadding]="{
|
||||
value: (!(sidebarVisible | async) ? 'hidden' : (slideSidebarOver | async) ? 'shown' : 'expanded'),
|
||||
params: {collapsedSidebarWidth: (collapsedSidebarWidth | async), totalSidebarWidth: (totalSidebarWidth | async)}
|
||||
}">
|
||||
<ds-themed-admin-sidebar></ds-themed-admin-sidebar>
|
||||
<div class="inner-wrapper">
|
||||
<ds-themed-header-navbar-wrapper></ds-themed-header-navbar-wrapper>
|
||||
<main class="main-content">
|
||||
<ds-themed-breadcrumbs></ds-themed-breadcrumbs>
|
||||
|
@@ -12,7 +12,7 @@ export const slide = trigger('slide', [
|
||||
|
||||
export const slideMobileNav = trigger('slideMobileNav', [
|
||||
|
||||
state('expanded', style({ height: '100vh' })),
|
||||
state('expanded', style({ height: 'auto', 'min-height': '100vh' })),
|
||||
|
||||
state('collapsed', style({ height: 0 })),
|
||||
|
||||
|
@@ -2,11 +2,11 @@
|
||||
<li *ngIf="!(isAuthenticated | async) && !(isXsOrSm$ | async) && (showAuth | async)" class="nav-item"
|
||||
(click)="$event.stopPropagation();">
|
||||
<div ngbDropdown #loginDrop display="dynamic" placement="bottom-right" class="d-inline-block" @fadeInOut>
|
||||
<a href="javascript:void(0);" class="dropdownLogin px-1 " [attr.aria-label]="'nav.login' |translate" (click)="$event.preventDefault()" [attr.data-test]="'login-menu' | dsBrowserOnly" ngbDropdownToggle>
|
||||
{{ 'nav.login' | translate }}
|
||||
</a>
|
||||
<a href="javascript:void(0);" class="dropdownLogin px-1" [attr.aria-label]="'nav.login' |translate"
|
||||
(click)="$event.preventDefault()" [attr.data-test]="'login-menu' | dsBrowserOnly"
|
||||
ngbDropdownToggle>{{ 'nav.login' | translate }}</a>
|
||||
<div class="loginDropdownMenu" [ngClass]="{'pl-3 pr-3': (loading | async)}" ngbDropdownMenu
|
||||
[attr.aria-label]="'nav.login' |translate">
|
||||
[attr.aria-label]="'nav.login' | translate">
|
||||
<ds-log-in
|
||||
[isStandalonePage]="false"></ds-log-in>
|
||||
</div>
|
||||
@@ -19,16 +19,16 @@
|
||||
</li>
|
||||
<li *ngIf="(isAuthenticated | async) && !(isXsOrSm$ | async) && (showAuth | async)" class="nav-item">
|
||||
<div ngbDropdown display="dynamic" placement="bottom-right" class="d-inline-block" @fadeInOut>
|
||||
<a href="javascript:void(0);" role="button" [attr.aria-label]="'nav.logout' |translate" (click)="$event.preventDefault()" [title]="'nav.logout' | translate" class="px-1" [attr.data-test]="'user-menu' | dsBrowserOnly" ngbDropdownToggle>
|
||||
<a href="javascript:void(0);" role="button" [attr.aria-label]="'nav.user-profile-menu-and-logout' |translate" (click)="$event.preventDefault()" [title]="'nav.user-profile-menu-and-logout' | translate" class="px-1" [attr.data-test]="'user-menu' | dsBrowserOnly" ngbDropdownToggle>
|
||||
<i class="fas fa-user-circle fa-lg fa-fw"></i></a>
|
||||
<div class="logoutDropdownMenu" ngbDropdownMenu [attr.aria-label]="'nav.logout' |translate">
|
||||
<div class="logoutDropdownMenu" ngbDropdownMenu [attr.aria-label]="'nav.user-profile-menu-and-logout' |translate">
|
||||
<ds-user-menu></ds-user-menu>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li *ngIf="(isAuthenticated | async) && (isXsOrSm$ | async)" class="nav-item">
|
||||
<a id="logoutLink" role="button" [attr.aria-label]="'nav.logout' |translate" [title]="'nav.logout' | translate" routerLink="/logout" routerLinkActive="active" class="px-1">
|
||||
<i class="fas fa-user-circle fa-lg fa-fw"></i>
|
||||
<i class="fas fa-sign-out-alt fa-lg fa-fw"></i>
|
||||
<span class="sr-only">(current)</span>
|
||||
</a>
|
||||
</li>
|
||||
|
@@ -1,10 +1,13 @@
|
||||
<ds-themed-loading *ngIf="(loading$ | async)"></ds-themed-loading>
|
||||
<div *ngIf="!(loading$ | async)">
|
||||
<span class="dropdown-item-text">{{(user$ | async)?.name}} ({{(user$ | async)?.email}})</span>
|
||||
<a class="dropdown-item" [routerLink]="[profileRoute]" routerLinkActive="active">{{'nav.profile' | translate}}</a>
|
||||
<a class="dropdown-item" [routerLink]="[mydspaceRoute]" routerLinkActive="active">{{'nav.mydspace' | translate}}</a>
|
||||
<span class="dropdown-item-text" [class.pl-0]="inExpandableNavbar">
|
||||
{{(user$ | async)?.name}}<br>
|
||||
<span class="text-muted">{{(user$ | async)?.email}}</span>
|
||||
</span>
|
||||
<a [ngClass]="inExpandableNavbar ? 'nav-item nav-link' : 'dropdown-item'" [routerLink]="[profileRoute]" routerLinkActive="active">{{'nav.profile' | translate}}</a>
|
||||
<a [ngClass]="inExpandableNavbar ? 'nav-item nav-link' : 'dropdown-item'" [routerLink]="[mydspaceRoute]" routerLinkActive="active">{{'nav.mydspace' | translate}}</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<ds-log-out></ds-log-out>
|
||||
<ds-log-out *ngIf="!inExpandableNavbar" data-test="log-out-component"></ds-log-out>
|
||||
</div>
|
||||
|
||||
|
||||
|
@@ -162,10 +162,24 @@ describe('UserMenuComponent', () => {
|
||||
});
|
||||
|
||||
it('should display user name and email', () => {
|
||||
const user = 'User Test (test@test.com)';
|
||||
const username = 'User Test';
|
||||
const email = 'test@test.com';
|
||||
const span = deUserMenu.query(By.css('.dropdown-item-text'));
|
||||
expect(span).toBeDefined();
|
||||
expect(span.nativeElement.innerHTML).toBe(user);
|
||||
expect(span.nativeElement.innerHTML).toContain(username);
|
||||
expect(span.nativeElement.innerHTML).toContain(email);
|
||||
});
|
||||
|
||||
it('should create logout component', () => {
|
||||
const components = fixture.debugElement.query(By.css('[data-test="log-out-component"]'));
|
||||
expect(components).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not create logout component', () => {
|
||||
component.inExpandableNavbar = true;
|
||||
fixture.detectChanges();
|
||||
const components = fixture.debugElement.query(By.css('[data-test="log-out-component"]'));
|
||||
expect(components).toBeFalsy();
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
import { select, Store } from '@ngrx/store';
|
||||
@@ -20,6 +20,11 @@ import { getProfileModuleRoute } from '../../../app-routing-paths';
|
||||
})
|
||||
export class UserMenuComponent implements OnInit {
|
||||
|
||||
/**
|
||||
* The input flag to show user details in navbar expandable menu
|
||||
*/
|
||||
@Input() inExpandableNavbar = false;
|
||||
|
||||
/**
|
||||
* True if the authentication is loading.
|
||||
* @type {Observable<boolean>}
|
||||
|
@@ -11,7 +11,7 @@ import {
|
||||
SimpleChanges
|
||||
} from '@angular/core';
|
||||
|
||||
import { findIndex } from 'lodash';
|
||||
import findIndex from 'lodash/findIndex';
|
||||
|
||||
import { VocabularyEntry } from '../../core/submission/vocabularies/models/vocabulary-entry.model';
|
||||
import { FormFieldMetadataValueObject } from '../form/builder/models/form-field-metadata-value.model';
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, } from '@angular/core';
|
||||
|
||||
import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { isObject } from 'lodash';
|
||||
import isObject from 'lodash/isObject';
|
||||
|
||||
import { Chips } from './models/chips.model';
|
||||
import { ChipsItem } from './models/chips-item.model';
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { isObject, uniqueId } from 'lodash';
|
||||
import isObject from 'lodash/isObject';
|
||||
import uniqueId from 'lodash/uniqueId';
|
||||
import { hasValue, isNotEmpty } from '../../empty.util';
|
||||
import { FormFieldMetadataValueObject } from '../../form/builder/models/form-field-metadata-value.model';
|
||||
import { ConfidenceType } from '../../../core/shared/confidence-type';
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import { findIndex, isEqual, isObject } from 'lodash';
|
||||
import findIndex from 'lodash/findIndex';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import isObject from 'lodash/isObject';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { ChipsItem, ChipsItemIcon } from './chips-item.model';
|
||||
import { hasValue, isNotEmpty } from '../../empty.util';
|
||||
|
@@ -11,7 +11,6 @@ import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { DeleteComColPageComponent } from './delete-comcol-page.component';
|
||||
import { NotificationsService } from '../../../notifications/notifications.service';
|
||||
import { NotificationsServiceStub } from '../../../testing/notifications-service.stub';
|
||||
import { RequestService } from '../../../../core/data/request.service';
|
||||
import { getTestScheduler } from 'jasmine-marbles';
|
||||
import { ComColDataService } from '../../../../core/data/comcol-data.service';
|
||||
import { createFailedRemoteDataObject$, createNoContentRemoteDataObject$ } from '../../../remote-data.utils';
|
||||
|
@@ -10,7 +10,8 @@ import { AuthService } from '../../core/auth/auth.service';
|
||||
import { CookieService } from '../../core/services/cookie.service';
|
||||
import { getTestScheduler } from 'jasmine-marbles';
|
||||
import { MetadataValue } from '../../core/shared/metadata.models';
|
||||
import { clone, cloneDeep } from 'lodash';
|
||||
import clone from 'lodash/clone';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { ConfigurationDataService } from '../../core/data/configuration-data.service';
|
||||
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../remote-data.utils';
|
||||
import { ConfigurationProperty } from '../../core/shared/configuration-property.model';
|
||||
@@ -100,7 +101,7 @@ describe('BrowserKlaroService', () => {
|
||||
|
||||
mockConfig = {
|
||||
translations: {
|
||||
en: {
|
||||
zz: {
|
||||
purposes: {},
|
||||
test: {
|
||||
testeritis: testKey
|
||||
@@ -158,8 +159,8 @@ describe('BrowserKlaroService', () => {
|
||||
|
||||
it('addAppMessages', () => {
|
||||
service.addAppMessages();
|
||||
expect(mockConfig.translations.en[appName]).toBeDefined();
|
||||
expect(mockConfig.translations.en.purposes[purpose]).toBeDefined();
|
||||
expect(mockConfig.translations.zz[appName]).toBeDefined();
|
||||
expect(mockConfig.translations.zz.purposes[purpose]).toBeDefined();
|
||||
});
|
||||
|
||||
it('translateConfiguration', () => {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import * as Klaro from 'klaro';
|
||||
import { setup, show } from 'klaro/dist/klaro-no-translations';
|
||||
import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
|
||||
import { AuthService } from '../../core/auth/auth.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
@@ -10,7 +10,8 @@ import { KlaroService } from './klaro.service';
|
||||
import { hasValue, isEmpty, isNotEmpty } from '../empty.util';
|
||||
import { CookieService } from '../../core/services/cookie.service';
|
||||
import { EPersonDataService } from '../../core/eperson/eperson-data.service';
|
||||
import { cloneDeep, debounce } from 'lodash';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { ANONYMOUS_STORAGE_NAME_KLARO, klaroConfiguration } from './klaro-configuration';
|
||||
import { Operation } from 'fast-json-patch';
|
||||
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
|
||||
@@ -78,7 +79,7 @@ export class BrowserKlaroService extends KlaroService {
|
||||
initialize() {
|
||||
if (!environment.info.enablePrivacyStatement) {
|
||||
delete this.klaroConfig.privacyPolicy;
|
||||
this.klaroConfig.translations.en.consentNotice.description = 'cookies.consent.content-notice.description.no-privacy';
|
||||
this.klaroConfig.translations.zz.consentNotice.description = 'cookies.consent.content-notice.description.no-privacy';
|
||||
}
|
||||
|
||||
const hideGoogleAnalytics$ = this.configService.findByPropertyName(this.GOOGLE_ANALYTICS_KEY).pipe(
|
||||
@@ -135,7 +136,7 @@ export class BrowserKlaroService extends KlaroService {
|
||||
|
||||
this.klaroConfig.services = this.filterConfigServices(servicesToHide);
|
||||
|
||||
Klaro.setup(this.klaroConfig);
|
||||
setup(this.klaroConfig);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -219,7 +220,7 @@ export class BrowserKlaroService extends KlaroService {
|
||||
* Show the cookie consent form
|
||||
*/
|
||||
showSettings() {
|
||||
Klaro.show(this.klaroConfig);
|
||||
show(this.klaroConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -227,12 +228,12 @@ export class BrowserKlaroService extends KlaroService {
|
||||
*/
|
||||
addAppMessages() {
|
||||
this.klaroConfig.services.forEach((app) => {
|
||||
this.klaroConfig.translations.en[app.name] = {
|
||||
this.klaroConfig.translations.zz[app.name] = {
|
||||
title: this.getTitleTranslation(app.name),
|
||||
description: this.getDescriptionTranslation(app.name)
|
||||
};
|
||||
app.purposes.forEach((purpose) => {
|
||||
this.klaroConfig.translations.en.purposes[purpose] = this.getPurposeTranslation(purpose);
|
||||
this.klaroConfig.translations.zz.purposes[purpose] = this.getPurposeTranslation(purpose);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -246,7 +247,7 @@ export class BrowserKlaroService extends KlaroService {
|
||||
*/
|
||||
this.translateService.setDefaultLang(environment.defaultLanguage);
|
||||
|
||||
this.translate(this.klaroConfig.translations.en);
|
||||
this.translate(this.klaroConfig.translations.zz);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -54,10 +54,46 @@ export const klaroConfiguration: any = {
|
||||
https://github.com/KIProtect/klaro/tree/master/src/translations
|
||||
*/
|
||||
translations: {
|
||||
en: {
|
||||
/*
|
||||
The `zz` key contains default translations that will be used as fallback values.
|
||||
This can e.g. be useful for defining a fallback privacy policy URL.
|
||||
FOR DSPACE: We use 'zz' to map to our own i18n translations for klaro, see
|
||||
translateConfiguration() in browser-klaro.service.ts. All the below i18n keys are specified
|
||||
in your /src/assets/i18n/*.json5 translation pack.
|
||||
*/
|
||||
zz: {
|
||||
acceptAll: 'cookies.consent.accept-all',
|
||||
acceptSelected: 'cookies.consent.accept-selected',
|
||||
app: {
|
||||
close: 'cookies.consent.close',
|
||||
consentModal: {
|
||||
title: 'cookies.consent.content-modal.title',
|
||||
description: 'cookies.consent.content-modal.description'
|
||||
},
|
||||
consentNotice: {
|
||||
changeDescription: 'cookies.consent.update',
|
||||
title: 'cookies.consent.content-notice.title',
|
||||
description: 'cookies.consent.content-notice.description',
|
||||
learnMore: 'cookies.consent.content-notice.learnMore',
|
||||
},
|
||||
decline: 'cookies.consent.decline',
|
||||
ok: 'cookies.consent.ok',
|
||||
poweredBy: 'Powered by Klaro!',
|
||||
privacyPolicy: {
|
||||
name: 'cookies.consent.content-modal.privacy-policy.name',
|
||||
text: 'cookies.consent.content-modal.privacy-policy.text'
|
||||
},
|
||||
purposeItem: {
|
||||
service: 'cookies.consent.content-modal.service',
|
||||
services: 'cookies.consent.content-modal.services'
|
||||
},
|
||||
purposes: {
|
||||
},
|
||||
save: 'cookies.consent.save',
|
||||
service: {
|
||||
disableAll: {
|
||||
description: 'cookies.consent.app.disable-all.description',
|
||||
title: 'cookies.consent.app.disable-all.title'
|
||||
},
|
||||
optOut: {
|
||||
description: 'cookies.consent.app.opt-out.description',
|
||||
title: 'cookies.consent.app.opt-out.title'
|
||||
@@ -65,26 +101,10 @@ export const klaroConfiguration: any = {
|
||||
purpose: 'cookies.consent.app.purpose',
|
||||
purposes: 'cookies.consent.app.purposes',
|
||||
required: {
|
||||
description: 'cookies.consent.app.required.description',
|
||||
title: 'cookies.consent.app.required.title'
|
||||
title: 'cookies.consent.app.required.title',
|
||||
description: 'cookies.consent.app.required.description'
|
||||
}
|
||||
},
|
||||
close: 'cookies.consent.close',
|
||||
decline: 'cookies.consent.decline',
|
||||
changeDescription: 'cookies.consent.update',
|
||||
consentNotice: {
|
||||
description: 'cookies.consent.content-notice.description',
|
||||
learnMore: 'cookies.consent.content-notice.learnMore'
|
||||
},
|
||||
consentModal: {
|
||||
description: 'cookies.consent.content-modal.description',
|
||||
privacyPolicy: {
|
||||
name: 'cookies.consent.content-modal.privacy-policy.name',
|
||||
text: 'cookies.consent.content-modal.privacy-policy.text'
|
||||
},
|
||||
title: 'cookies.consent.content-modal.title'
|
||||
},
|
||||
purposes: {}
|
||||
}
|
||||
}
|
||||
},
|
||||
services: [
|
||||
|
107
src/app/shared/date.util.spec.ts
Normal file
107
src/app/shared/date.util.spec.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { dateToString, dateToNgbDateStruct, dateToISOFormat, isValidDate, yearFromString } from './date.util';
|
||||
|
||||
describe('Date Utils', () => {
|
||||
|
||||
describe('dateToISOFormat', () => {
|
||||
it('should convert Date to YYYY-MM-DDThh:mm:ssZ string', () => {
|
||||
// NOTE: month is zero indexed which is why it increases by one
|
||||
expect(dateToISOFormat(new Date(Date.UTC(2022, 5, 3)))).toEqual('2022-06-03T00:00:00Z');
|
||||
});
|
||||
it('should convert Date string to YYYY-MM-DDThh:mm:ssZ string', () => {
|
||||
expect(dateToISOFormat('2022-06-03')).toEqual('2022-06-03T00:00:00Z');
|
||||
});
|
||||
it('should convert Month string to YYYY-MM-DDThh:mm:ssZ string', () => {
|
||||
expect(dateToISOFormat('2022-06')).toEqual('2022-06-01T00:00:00Z');
|
||||
});
|
||||
it('should convert Year string to YYYY-MM-DDThh:mm:ssZ string', () => {
|
||||
expect(dateToISOFormat('2022')).toEqual('2022-01-01T00:00:00Z');
|
||||
});
|
||||
it('should convert ISO Date string to YYYY-MM-DDThh:mm:ssZ string', () => {
|
||||
// NOTE: Time is always zeroed out as proven by this test.
|
||||
expect(dateToISOFormat('2022-06-03T03:24:04Z')).toEqual('2022-06-03T00:00:00Z');
|
||||
});
|
||||
it('should convert NgbDateStruct to YYYY-MM-DDThh:mm:ssZ string', () => {
|
||||
// NOTE: month is zero indexed which is why it increases by one
|
||||
const date = new Date(Date.UTC(2022, 5, 3));
|
||||
expect(dateToISOFormat(dateToNgbDateStruct(date))).toEqual('2022-06-03T00:00:00Z');
|
||||
});
|
||||
});
|
||||
|
||||
describe('dateToString', () => {
|
||||
it('should convert Date to YYYY-MM-DD string', () => {
|
||||
// NOTE: month is zero indexed which is why it increases by one
|
||||
expect(dateToString(new Date(Date.UTC(2022, 5, 3)))).toEqual('2022-06-03');
|
||||
});
|
||||
it('should convert Date with time to YYYY-MM-DD string', () => {
|
||||
// NOTE: month is zero indexed which is why it increases by one
|
||||
expect(dateToString(new Date(Date.UTC(2022, 5, 3, 3, 24, 0)))).toEqual('2022-06-03');
|
||||
});
|
||||
it('should convert Month only to YYYY-MM-DD string', () => {
|
||||
// NOTE: month is zero indexed which is why it increases by one
|
||||
expect(dateToString(new Date(Date.UTC(2022, 5)))).toEqual('2022-06-01');
|
||||
});
|
||||
it('should convert ISO Date to YYYY-MM-DD string', () => {
|
||||
expect(dateToString(new Date('2022-06-03T03:24:00Z'))).toEqual('2022-06-03');
|
||||
});
|
||||
it('should convert NgbDateStruct to YYYY-MM-DD string', () => {
|
||||
// NOTE: month is zero indexed which is why it increases by one
|
||||
const date = new Date(Date.UTC(2022, 5, 3));
|
||||
expect(dateToString(dateToNgbDateStruct(date))).toEqual('2022-06-03');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('isValidDate', () => {
|
||||
it('should return false for null', () => {
|
||||
expect(isValidDate(null)).toBe(false);
|
||||
});
|
||||
it('should return false for empty string', () => {
|
||||
expect(isValidDate('')).toBe(false);
|
||||
});
|
||||
it('should return false for text', () => {
|
||||
expect(isValidDate('test')).toBe(false);
|
||||
});
|
||||
it('should return true for YYYY', () => {
|
||||
expect(isValidDate('2022')).toBe(true);
|
||||
});
|
||||
it('should return true for YYYY-MM', () => {
|
||||
expect(isValidDate('2022-12')).toBe(true);
|
||||
});
|
||||
it('should return true for YYYY-MM-DD', () => {
|
||||
expect(isValidDate('2022-06-03')).toBe(true);
|
||||
});
|
||||
it('should return true for YYYY-MM-DDTHH:MM:SS', () => {
|
||||
expect(isValidDate('2022-06-03T10:20:30')).toBe(true);
|
||||
});
|
||||
it('should return true for YYYY-MM-DDTHH:MM:SSZ', () => {
|
||||
expect(isValidDate('2022-06-03T10:20:30Z')).toBe(true);
|
||||
});
|
||||
it('should return false for a month that does not exist', () => {
|
||||
expect(isValidDate('2022-13')).toBe(false);
|
||||
});
|
||||
it('should return false for a day that does not exist', () => {
|
||||
expect(isValidDate('2022-02-60')).toBe(false);
|
||||
});
|
||||
it('should return false for a time that does not exist', () => {
|
||||
expect(isValidDate('2022-02-60T10:60:20')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('yearFromString', () => {
|
||||
it('should return year from YYYY string', () => {
|
||||
expect(yearFromString('2022')).toEqual(2022);
|
||||
});
|
||||
it('should return year from YYYY-MM string', () => {
|
||||
expect(yearFromString('1970-06')).toEqual(1970);
|
||||
});
|
||||
it('should return year from YYYY-MM-DD string', () => {
|
||||
expect(yearFromString('1914-10-23')).toEqual(1914);
|
||||
});
|
||||
it('should return year from YYYY-MM-DDTHH:MM:SSZ string', () => {
|
||||
expect(yearFromString('1914-10-23T10:20:30Z')).toEqual(1914);
|
||||
});
|
||||
it('should return null if invalid date', () => {
|
||||
expect(yearFromString('test')).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,9 +1,8 @@
|
||||
import { NgbDateStruct } from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
import { isObject } from 'lodash';
|
||||
import * as moment from 'moment';
|
||||
|
||||
import { isNull, isUndefined } from './empty.util';
|
||||
import { formatInTimeZone } from 'date-fns-tz';
|
||||
import { isValid } from 'date-fns';
|
||||
import isObject from 'lodash/isObject';
|
||||
import { hasNoValue } from './empty.util';
|
||||
|
||||
/**
|
||||
* Returns true if the passed value is a NgbDateStruct.
|
||||
@@ -31,21 +30,7 @@ export function dateToISOFormat(date: Date | NgbDateStruct | string): string {
|
||||
const dateObj: Date = (date instanceof Date) ? date :
|
||||
((typeof date === 'string') ? ngbDateStructToDate(stringToNgbDateStruct(date)) : ngbDateStructToDate(date));
|
||||
|
||||
let year = dateObj.getUTCFullYear().toString();
|
||||
let month = (dateObj.getUTCMonth() + 1).toString();
|
||||
let day = dateObj.getUTCDate().toString();
|
||||
let hour = dateObj.getHours().toString();
|
||||
let min = dateObj.getMinutes().toString();
|
||||
let sec = dateObj.getSeconds().toString();
|
||||
|
||||
year = (year.length === 1) ? '0' + year : year;
|
||||
month = (month.length === 1) ? '0' + month : month;
|
||||
day = (day.length === 1) ? '0' + day : day;
|
||||
hour = (hour.length === 1) ? '0' + hour : hour;
|
||||
min = (min.length === 1) ? '0' + min : min;
|
||||
sec = (sec.length === 1) ? '0' + sec : sec;
|
||||
const dateStr = `${year}${month}${day}${hour}${min}${sec}`;
|
||||
return moment.utc(dateStr, 'YYYYMMDDhhmmss').format();
|
||||
return formatInTimeZone(dateObj, 'UTC', "yyyy-MM-dd'T'HH:mm:ss'Z'");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,7 +66,7 @@ export function stringToNgbDateStruct(date: string): NgbDateStruct {
|
||||
* the NgbDateStruct object
|
||||
*/
|
||||
export function dateToNgbDateStruct(date?: Date): NgbDateStruct {
|
||||
if (isNull(date) || isUndefined(date)) {
|
||||
if (hasNoValue(date)) {
|
||||
date = new Date();
|
||||
}
|
||||
|
||||
@@ -102,16 +87,7 @@ export function dateToNgbDateStruct(date?: Date): NgbDateStruct {
|
||||
*/
|
||||
export function dateToString(date: Date | NgbDateStruct): string {
|
||||
const dateObj: Date = (date instanceof Date) ? date : ngbDateStructToDate(date);
|
||||
|
||||
let year = dateObj.getUTCFullYear().toString();
|
||||
let month = (dateObj.getUTCMonth() + 1).toString();
|
||||
let day = dateObj.getUTCDate().toString();
|
||||
|
||||
year = (year.length === 1) ? '0' + year : year;
|
||||
month = (month.length === 1) ? '0' + month : month;
|
||||
day = (day.length === 1) ? '0' + day : day;
|
||||
const dateStr = `${year}-${month}-${day}`;
|
||||
return moment.utc(dateStr, 'YYYYMMDD').format('YYYY-MM-DD');
|
||||
return formatInTimeZone(dateObj, 'UTC', 'yyyy-MM-dd');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,5 +95,15 @@ export function dateToString(date: Date | NgbDateStruct): string {
|
||||
* @param date the string to be checked
|
||||
*/
|
||||
export function isValidDate(date: string) {
|
||||
return moment(date).isValid();
|
||||
return (hasNoValue(date)) ? false : isValid(new Date(date));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse given date string to a year number based on expected formats
|
||||
* @param date the string to be parsed
|
||||
* @param formats possible formats the string may align with. MUST be valid date-fns formats
|
||||
*/
|
||||
export function yearFromString(date: string) {
|
||||
return isValidDate(date) ? new Date(date).getUTCFullYear() : null;
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Component, EventEmitter, HostListener, Input, OnInit, Output } from '@angular/core';
|
||||
import { uniqueId } from 'lodash';
|
||||
import uniqueId from 'lodash/uniqueId';
|
||||
import { FileUploader } from 'ng2-file-upload';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import { UploaderOptions } from '../uploader/uploader-options.model';
|
||||
|
@@ -10,7 +10,7 @@ import {
|
||||
} from '@ng-dynamic-forms/core';
|
||||
|
||||
import {
|
||||
mockInputWithTypeBindModel, MockRelationModel, mockDcTypeInputModel
|
||||
mockInputWithTypeBindModel, MockRelationModel
|
||||
} from '../../../mocks/form-models.mock';
|
||||
import {DsDynamicTypeBindRelationService} from './ds-dynamic-type-bind-relation.service';
|
||||
import {FormFieldMetadataValueObject} from '../models/form-field-metadata-value.model';
|
||||
|
@@ -1,4 +1,10 @@
|
||||
import { DynamicFormControlLayout, DynamicFormGroupModel, DynamicFormGroupModelConfig, serializable } from '@ng-dynamic-forms/core';
|
||||
import {
|
||||
DynamicFormControlLayout,
|
||||
DynamicFormControlRelation,
|
||||
DynamicFormGroupModel,
|
||||
DynamicFormGroupModelConfig,
|
||||
serializable
|
||||
} from '@ng-dynamic-forms/core';
|
||||
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
@@ -16,6 +22,7 @@ export interface DynamicConcatModelConfig extends DynamicFormGroupModelConfig {
|
||||
separator: string;
|
||||
value?: any;
|
||||
hint?: string;
|
||||
typeBindRelations?: DynamicFormControlRelation[];
|
||||
relationship?: RelationshipOptions;
|
||||
repeatable: boolean;
|
||||
required: boolean;
|
||||
@@ -29,6 +36,8 @@ export class DynamicConcatModel extends DynamicFormGroupModel {
|
||||
|
||||
@serializable() separator: string;
|
||||
@serializable() hasLanguages = false;
|
||||
@serializable() typeBindRelations: DynamicFormControlRelation[];
|
||||
@serializable() typeBindHidden = false;
|
||||
@serializable() relationship?: RelationshipOptions;
|
||||
@serializable() repeatable?: boolean;
|
||||
@serializable() required?: boolean;
|
||||
@@ -55,6 +64,7 @@ export class DynamicConcatModel extends DynamicFormGroupModel {
|
||||
this.metadataValue = config.metadataValue;
|
||||
this.valueUpdates = new Subject<string>();
|
||||
this.valueUpdates.subscribe((value: string) => this.value = value);
|
||||
this.typeBindRelations = config.typeBindRelations ? config.typeBindRelations : [];
|
||||
}
|
||||
|
||||
get value() {
|
||||
|
@@ -2,6 +2,7 @@ import { Subject } from 'rxjs';
|
||||
import {
|
||||
DynamicCheckboxGroupModel,
|
||||
DynamicFormControlLayout,
|
||||
DynamicFormControlRelation,
|
||||
DynamicFormGroupModelConfig,
|
||||
serializable
|
||||
} from '@ng-dynamic-forms/core';
|
||||
@@ -15,6 +16,7 @@ export interface DynamicListCheckboxGroupModelConfig extends DynamicFormGroupMod
|
||||
groupLength?: number;
|
||||
repeatable: boolean;
|
||||
value?: any;
|
||||
typeBindRelations?: DynamicFormControlRelation[];
|
||||
}
|
||||
|
||||
export class DynamicListCheckboxGroupModel extends DynamicCheckboxGroupModel {
|
||||
@@ -23,6 +25,7 @@ export class DynamicListCheckboxGroupModel extends DynamicCheckboxGroupModel {
|
||||
@serializable() repeatable: boolean;
|
||||
@serializable() groupLength: number;
|
||||
@serializable() _value: VocabularyEntry[];
|
||||
@serializable() typeBindRelations: DynamicFormControlRelation[];
|
||||
isListGroup = true;
|
||||
valueUpdates: Subject<any>;
|
||||
|
||||
@@ -37,6 +40,7 @@ export class DynamicListCheckboxGroupModel extends DynamicCheckboxGroupModel {
|
||||
this.valueUpdates = new Subject<any>();
|
||||
this.valueUpdates.subscribe((value: VocabularyEntry | VocabularyEntry[]) => this.value = value);
|
||||
this.valueUpdates.next(config.value);
|
||||
this.typeBindRelations = config.typeBindRelations ? config.typeBindRelations : [];
|
||||
}
|
||||
|
||||
get hasAuthority(): boolean {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user