diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6ef83030dc4..002b4c06268 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,6 +14,7 @@ jobs: tests: runs-on: ubuntu-latest env: + CI: true # Set at job level # The ci step will test the dspace-angular code against DSpace REST. # Direct that step to utilize a DSpace REST service that has been started in docker. # NOTE: These settings should be kept in sync with those in [src]/docker/docker-compose-ci.yml @@ -59,6 +60,7 @@ jobs: with: node-version: ${{ matrix.node-version }} + # If CHROME_VERSION env variable specified above, then pin to that version. # Otherwise, just install latest version of Chrome. - name: Install Chrome (for e2e tests) @@ -108,6 +110,8 @@ jobs: - name: Run specs (unit tests) run: yarn run test:headless + env: + CI: true # Using "docker compose" start backend using CI configuration @@ -118,6 +122,65 @@ jobs: docker compose -f ./docker/cli.yml -f ./docker/cli.assetstore.yml run --rm dspace-cli docker container ls + - name: Create Test Data + id: testdata + run: | + chmod +x scripts/create-test-data.sh + ./scripts/create-test-data.sh + env: + DSPACE_REST_URL: http://localhost:8080/server + DSPACE_ADMIN_EMAIL: dspacedemo+admin@gmail.com + DSPACE_ADMIN_PASS: dspace + + - name: Update Field Config with Collection Handles + run: | + echo "Updating item-field-config.ts with actual handles..." + + # Get all collection handles from test data output + JONES_HANDLE="${{ steps.testdata.outputs.jones_collection_handle }}" + RELICS_HANDLE="${{ steps.testdata.outputs.relics_collection_handle }}" + LAEFER_HANDLE="${{ steps.testdata.outputs.laefer_collection_handle }}" + TANDON_HANDLE="${{ steps.testdata.outputs.tandon_collection_handle }}" + TANDONCAPSTONE_HANDLE="${{ steps.testdata.outputs.tandoncapstone_collection_handle }}" + DNP_HANDLE="${{ steps.testdata.outputs.dnp_collection_handle }}" + CALABASH_HANDLE="${{ steps.testdata.outputs.calabash_collection_handle }}" + OPENSCHOLARSHIP_HANDLE="${{ steps.testdata.outputs.openscholarship_collection_handle }}" + SYLLABI_HANDLE="${{ steps.testdata.outputs.syllabi_collection_handle }}" + + echo "Jones Handle: $JONES_HANDLE" + echo "Relics Handle: $RELICS_HANDLE" + + # Update the config file with actual handles + sed -i "s|handles: \['JONES_HANDLE'\]|handles: ['$JONES_HANDLE']|g" \ + src/themes/fda/app/item-page/field-config/item-field-config.ts + + sed -i "s|handles: \['RELICS_HANDLE'\]|handles: ['$RELICS_HANDLE']|g" \ + src/themes/fda/app/item-page/field-config/item-field-config.ts + + sed -i "s|handles: \['LAEFER_HANDLE'\]|handles: ['$LAEFER_HANDLE']|g" \ + src/themes/fda/app/item-page/field-config/item-field-config.ts + + sed -i "s|handles: \['TANDON_HANDLE'\]|handles: ['$TANDON_HANDLE']|g" \ + src/themes/fda/app/item-page/field-config/item-field-config.ts + + sed -i "s|handles: \['TANDONCAPSTONE_HANDLE'\]|handles: ['$TANDONCAPSTONE_HANDLE']|g" \ + src/themes/fda/app/item-page/field-config/item-field-config.ts + + sed -i "s|handles: \['DNP_HANDLE'\]|handles: ['$DNP_HANDLE']|g" \ + src/themes/fda/app/item-page/field-config/item-field-config.ts + + sed -i "s|handles: \['CALABASH_HANDLE'\]|handles: ['$CALABASH_HANDLE']|g" \ + src/themes/fda/app/item-page/field-config/item-field-config.ts + + sed -i "s|handles: \['OPENSCHOLARSHIP_HANDLE'\]|handles: ['$OPENSCHOLARSHIP_HANDLE']|g" \ + src/themes/fda/app/item-page/field-config/item-field-config.ts + + sed -i "s|handles: \['SYLLABI_HANDLE'\]|handles: ['$SYLLABI_HANDLE']|g" \ + src/themes/fda/app/item-page/field-config/item-field-config.ts + + echo "Updated config file:" + grep -A2 "handles:" src/themes/fda/app/item-page/field-config/item-field-config.ts || true + # Run integration tests via Cypress.io # https://github.com/cypress-io/github-action # (NOTE: to run these e2e tests locally, just use 'ng e2e') @@ -133,15 +196,20 @@ jobs: wait-on: http://127.0.0.1:8080/server/api/core/sites, http://127.0.0.1:4000 # Wait for 2 mins max for everything to respond wait-on-timeout: 120 - - # 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@v4 - if: always() - with: - name: e2e-test-videos-${{ matrix.node-version }} - path: cypress/videos + # Don't reinstall dependencies + install: false + env: + # Pass test data UUIDs to Cypress + CYPRESS_DSPACE_TEST_FDA_DEFAULT_ITEM: ${{ steps.testdata.outputs.default_item_uuid }} + CYPRESS_DSPACE_TEST_JONES_ITEM: ${{ steps.testdata.outputs.jones_item_uuid }} + CYPRESS_DSPACE_TEST_RELICS_ITEM: ${{ steps.testdata.outputs.relics_item_uuid }} + CYPRESS_DSPACE_TEST_LAEFER_ITEM: ${{ steps.testdata.outputs.laefer_item_uuid }} + CYPRESS_DSPACE_TEST_TANDON_ITEM: ${{ steps.testdata.outputs.tandon_item_uuid }} + CYPRESS_DSPACE_TEST_TANDONCAPSTONE_ITEM: ${{ steps.testdata.outputs.tandoncapstone_item_uuid }} + CYPRESS_DSPACE_TEST_DNP_ITEM: ${{ steps.testdata.outputs.dnp_item_uuid }} + CYPRESS_DSPACE_TEST_CALABASH_ITEM: ${{ steps.testdata.outputs.calabash_item_uuid }} + CYPRESS_DSPACE_TEST_OPENSCHOLARSHIP_ITEM: ${{ steps.testdata.outputs.openscholarship_item_uuid }} + CYPRESS_DSPACE_TEST_SYLLABI_ITEM: ${{ steps.testdata.outputs.syllabi_item_uuid }} # If e2e tests fail, Cypress creates a screenshot of what happened # Save those in an Artifact diff --git a/.github/workflows/codescan.yml b/.github/workflows/codescan.yml index d96e786cc37..1e16f8fcf86 100644 --- a/.github/workflows/codescan.yml +++ b/.github/workflows/codescan.yml @@ -50,4 +50,4 @@ jobs: # Perform GitHub Code Scanning. - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 \ No newline at end of file + uses: github/codeql-action/analyze@v2 diff --git a/angular.json b/angular.json index f7711638b07..e417c29e533 100644 --- a/angular.json +++ b/angular.json @@ -63,11 +63,6 @@ "input": "src/themes/fda/styles/theme.scss", "inject": false, "bundleName": "fda-theme" - }, - { - "input": "src/themes/calabash/styles/theme.scss", - "inject": false, - "bundleName": "calabash-theme" } ], "scripts": [], diff --git a/config/config.dev.yml b/config/config.dev.yml index 0272227083c..67f73715bc1 100644 --- a/config/config.dev.yml +++ b/config/config.dev.yml @@ -439,9 +439,6 @@ themes: extends: fda handle: 2451/34841 - - name: calabash - extends: fda - handle: 2451/62242 - name: fda headTags: diff --git a/config/config.yml b/config/config.yml index 109db60ca92..a2e5bf8f1aa 100644 --- a/config/config.yml +++ b/config/config.yml @@ -3,3 +3,17 @@ rest: host: sandbox.dspace.org port: 443 nameSpace: /server + +themes: + - name: gallatin-syllabi + extends: fda + handle: 2451/34841 + + + - name: fda + headTags: + - tagName: link + attributes: + rel: icon + href: https://cdn.library.nyu.edu/images/favicon.ico # Path to your favicon file + sizes: any diff --git a/cypress.config.ts b/cypress.config.ts index 36d8120342a..83ac50c5a57 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -41,6 +41,22 @@ export default defineConfig({ e2e: { // Setup our plugins for e2e tests setupNodeEvents(on, config) { + // Configure Chrome launch args FIRST + on('before:browser:launch', (browser, launchOptions) => { + if (browser.name === 'chrome' || browser.family === 'chromium') { + launchOptions.args.push('--no-sandbox'); + launchOptions.args.push('--disable-setuid-sandbox'); + launchOptions.args.push('--disable-dev-shm-usage'); + launchOptions.args.push('--disable-gpu'); + + if (process.env.CI) { + launchOptions.args.push('--disable-software-rasterizer'); + launchOptions.args.push('--disable-extensions'); + } + } + + return launchOptions; + }); return require('./cypress/plugins/index.ts')(on, config); }, // This is the base URL that Cypress will run all tests against diff --git a/cypress/e2e/header.cy.ts b/cypress/e2e/header.cy.ts index aa65aee570e..0b69f069388 100644 --- a/cypress/e2e/header.cy.ts +++ b/cypress/e2e/header.cy.ts @@ -11,7 +11,7 @@ describe('Header', () => { testA11y('ds-header'); }); - it('should allow for changing language to German (for example)', () => { + it.skip('skip as we do not allow for changing language to German (for example)', () => { cy.visit('/'); // Click the language switcher (globe) in header diff --git a/cypress/e2e/item-page-collection-config.cy.ts b/cypress/e2e/item-page-collection-config.cy.ts new file mode 100644 index 00000000000..5e745c6a2a7 --- /dev/null +++ b/cypress/e2e/item-page-collection-config.cy.ts @@ -0,0 +1,408 @@ +// cypress/e2e/item-page-collection-config.cy.ts + +import { testA11y } from 'cypress/support/utils'; + +describe('Item Page Collection Configuration', () => { + + // Test item UUIDs from environment + const DEFAULT_ITEM = '/items/'.concat(Cypress.env('DSPACE_TEST_FDA_DEFAULT_ITEM')); + const JONES_ITEM = '/items/'.concat(Cypress.env('DSPACE_TEST_FDA_JONES_ITEM') || 'skip'); + const RELICS_ITEM = '/items/'.concat(Cypress.env('DSPACE_TEST_FDA_RELICS_ITEM') || 'skip'); + const LAEFER_ITEM = '/items/'.concat(Cypress.env('DSPACE_TEST_FDA_LAEFER_ITEM') || 'skip'); + const TANDON_ITEM = '/items/'.concat(Cypress.env('DSPACE_TEST_FDA_TANDON_ITEM') || 'skip'); + const TANDONCAPSTONE_ITEM = '/items/'.concat(Cypress.env('DSPACE_TEST_FDA_TANDONCAPSTONE_ITEM') || 'skip'); + const DNP_ITEM = '/items/'.concat(Cypress.env('DSPACE_TEST_FDA_DNP_ITEM') || 'skip'); + const CALABASH_ITEM = '/items/'.concat(Cypress.env('DSPACE_TEST_FDA_CALABASH_ITEM') || 'skip'); + const OPENSCHOLARSHIP_ITEM = '/items/'.concat(Cypress.env('DSPACE_TEST_FDA_OPENSCHOLARSHIP_ITEM') || 'skip'); + const SYLLABI_ITEM = '/items/'.concat(Cypress.env('DSPACE_TEST_FDA_SYLLABI_ITEM') || 'skip'); + + describe('Default Collection Item', () => { + beforeEach(() => { + cy.visit(DEFAULT_ITEM); + }); + + it('should load the item page', () => { + cy.get('.item-page-content', { timeout: 10000 }).should('be.visible'); + }); + + it('should display default fields', () => { + cy.get('.itemDisplayTable').should('be.visible'); + cy.get('th.metadataFieldLabel, td.metadataFieldLabel, .itemDisplayTable th').should('have.length.greaterThan', 0); + }); + + it('should display title as h1', () => { + cy.get('h1.page-title, h1').should('be.visible'); + }); + + it('should have authors as links separated by semicolons', () => { + cy.get('.itemDisplayTable').then($table => { + const html = $table.html(); + // Check for various author field labels + if (html.includes('Author') || html.includes('Creator') || html.includes('Contributor')) { + cy.get('.itemDisplayTable').contains(/Author|Creator|Contributor/i).parents('tr').within(() => { + cy.get('a').should('exist'); // Authors should be links + }); + } else { + cy.log('No author field found - skipping test'); + this.skip(); + } + }); + }); + + it('should display files section', () => { + // Check for multiple possible file section selectors + cy.get('body').then($body => { + const hasFiles = + $body.find('.panel-info').length > 0 || + $body.find('.file-section').length > 0 || + $body.find('ds-item-page-file-section').length > 0 || + $body.find('[class*="file"]').length > 0; + + if (hasFiles) { + cy.get('.panel-info, .file-section, ds-item-page-file-section, [class*="file"]').should('exist'); + } else { + cy.log('No files section found - item may not have files'); + } + }); + }); + + it('should have working full item link', () => { + cy.get('body').then($body => { + if ($body.find('a[href*="/full"]').length > 0) { + cy.get('a[href*="/full"]').first().click(); + cy.url().should('include', '/full'); + } else { + cy.log('No full item link found - skipping'); + } + }); + }); + + it('should pass accessibility tests', () => { + cy.get('.item-page-content', { timeout: 10000 }).should('be.visible'); + testA11y('.item-page-content'); + }); + }); + + // Jones Collection Tests + describe('Jones Collection Item', () => { + beforeEach(function() { + if (!Cypress.env('DSPACE_TEST_FDA_JONES_ITEM')) { + this.skip(); + } + cy.visit(JONES_ITEM); + }); + + it('should load with Jones configuration', () => { + cy.get('.item-page-content', { timeout: 10000 }).should('be.visible'); + cy.get('.itemDisplayTable').should('be.visible'); + }); + + it('should display Jones-specific labels', () => { + cy.get('.itemDisplayTable').then($table => { + const text = $table.text(); + const hasJonesLabels = + text.includes('Date of digital object') || + text.includes('Date of object depicted') || + text.includes('Technical designation') || + text.includes('Technical specifications'); + + if (hasJonesLabels) { + cy.log('Jones-specific labels found'); + const result = expect(hasJonesLabels).to.be.true; + cy.wrap(result); + } else { + cy.log('No Jones-specific labels found'); + } + }); + }); + + it('should pass accessibility tests', () => { + cy.get('.item-page-content', { timeout: 10000 }).should('be.visible'); + testA11y('.item-page-content'); + }); + }); + + // Relics Collection Tests + describe('Relics Collection Item', () => { + beforeEach(function() { + if (!Cypress.env('DSPACE_TEST_FDA_RELICS_ITEM')) { + this.skip(); + } + cy.visit(RELICS_ITEM); + }); + + it('should load with Relics configuration', () => { + cy.get('.item-page-content', { timeout: 10000 }).should('be.visible'); + cy.get('.itemDisplayTable').should('be.visible'); + }); + + it('should display Relics-specific fields', () => { + cy.get('.itemDisplayTable').then($table => { + const text = $table.text(); + const hasRelicsFields = + text.includes('Country') || + text.includes('Source'); + + if (hasRelicsFields) { + cy.log('Relics-specific fields found'); + const result = expect(hasRelicsFields).to.be.true; + cy.wrap(result); + } + }); + }); + + it('should pass accessibility tests', () => { + cy.get('.item-page-content', { timeout: 10000 }).should('be.visible'); + testA11y('.item-page-content'); + }); + }); + + // Laefer Collection Tests + describe('Laefer Collection Item', () => { + beforeEach(function() { + if (!Cypress.env('DSPACE_TEST_FDA_LAEFER_ITEM')) { + this.skip(); + } + cy.visit(LAEFER_ITEM); + }); + + it('should load with Laefer configuration', () => { + cy.get('.item-page-content', { timeout: 10000 }).should('be.visible'); + cy.get('.itemDisplayTable').should('be.visible'); + }); + + it('should pass accessibility tests', () => { + cy.get('.item-page-content', { timeout: 10000 }).should('be.visible'); + testA11y('.item-page-content'); + }); + }); + + // Tandon Collection Tests + describe('Tandon Collection Item', () => { + beforeEach(function() { + if (!Cypress.env('DSPACE_TEST_FDA_TANDON_ITEM')) { + this.skip(); + } + cy.visit(TANDON_ITEM); + }); + + it('should load with Tandon configuration', () => { + cy.get('.item-page-content', { timeout: 10000 }).should('be.visible'); + cy.get('.itemDisplayTable').should('be.visible'); + }); + + it('should display page number fields', () => { + cy.get('.itemDisplayTable').then($table => { + const text = $table.text(); + const hasPageFields = + text.includes('First Page') || + text.includes('Last Page'); + + if (hasPageFields) { + cy.log('Page number fields found'); + } + }); + }); + + it('should pass accessibility tests', () => { + cy.get('.item-page-content', { timeout: 10000 }).should('be.visible'); + testA11y('.item-page-content'); + }); + }); + + // Tandon Capstone Collection Tests + describe('Tandon Capstone Collection Item', () => { + beforeEach(function() { + if (!Cypress.env('DSPACE_TEST_FDA_TANDONCAPSTONE_ITEM')) { + this.skip(); + } + cy.visit(TANDONCAPSTONE_ITEM); + }); + + it('should load with Tandon Capstone configuration', () => { + cy.get('.item-page-content', { timeout: 10000 }).should('be.visible'); + cy.get('.itemDisplayTable').should('be.visible'); + }); + + it('should display advisor field', () => { + cy.get('.itemDisplayTable').then($table => { + const text = $table.text(); + if (text.includes('Capstone Professor') || text.includes('Advisor')) { + cy.log('Advisor field found'); + } + }); + }); + + it('should pass accessibility tests', () => { + cy.get('.item-page-content', { timeout: 10000 }).should('be.visible'); + testA11y('.item-page-content'); + }); + }); + + // DNP Collection Tests + describe('DNP Collection Item', () => { + beforeEach(function() { + if (!Cypress.env('DSPACE_TEST_FDA_DNP_ITEM')) { + this.skip(); + } + cy.visit(DNP_ITEM); + }); + + it('should load with DNP configuration', () => { + cy.get('.item-page-content', { timeout: 10000 }).should('be.visible'); + cy.get('.itemDisplayTable').should('be.visible'); + }); + + it('should display thesis-specific fields', () => { + cy.get('.itemDisplayTable').then($table => { + const text = $table.text(); + const hasThesisFields = + text.includes('Degree') || + text.includes('Grantor') || + text.includes('MeSH term') || + text.includes('DNP Project Team Member'); + + if (hasThesisFields) { + cy.log('Thesis-specific fields found'); + } + }); + }); + + it('should pass accessibility tests', () => { + cy.get('.item-page-content', { timeout: 10000 }).should('be.visible'); + testA11y('.item-page-content'); + }); + }); + + // Calabash Collection Tests + describe('Calabash Collection Item', () => { + beforeEach(function() { + if (!Cypress.env('DSPACE_TEST_FDA_CALABASH_ITEM')) { + this.skip(); + } + cy.visit(CALABASH_ITEM); + }); + + it('should load with Calabash configuration', () => { + cy.get('.item-page-content', { timeout: 10000 }).should('be.visible'); + cy.get('.itemDisplayTable').should('be.visible'); + }); + + it('should display journal-specific fields', () => { + cy.get('.itemDisplayTable').then($table => { + const text = $table.text(); + const hasJournalFields = + text.includes('Journal Title') || + text.includes('Volume') || + text.includes('Issue') || + text.includes('Translator'); + + if (hasJournalFields) { + cy.log('Journal-specific fields found'); + } + }); + }); + + it('should pass accessibility tests', () => { + cy.get('.item-page-content', { timeout: 10000 }).should('be.visible'); + testA11y('.item-page-content'); + }); + }); + + // OpenScholarship Collection Tests + describe('OpenScholarship Collection Item', () => { + beforeEach(function() { + if (!Cypress.env('DSPACE_TEST_FDA_OPENSCHOLARSHIP_ITEM')) { + this.skip(); + } + cy.visit(OPENSCHOLARSHIP_ITEM); + }); + + it('should load with OpenScholarship configuration', () => { + cy.get('.item-page-content', { timeout: 10000 }).should('be.visible'); + cy.get('.itemDisplayTable').should('be.visible'); + }); + + it('should display sponsorship field', () => { + cy.get('.itemDisplayTable').then($table => { + const text = $table.text(); + if (text.includes('Sponsorship')) { + cy.log('Sponsorship field found'); + } + }); + }); + + it('should pass accessibility tests', () => { + cy.get('.item-page-content', { timeout: 10000 }).should('be.visible'); + testA11y('.item-page-content'); + }); + }); + + // Syllabi Collection Tests + describe('Syllabi Collection Item', () => { + beforeEach(function() { + if (!Cypress.env('DSPACE_TEST_FDA_SYLLABI_ITEM')) { + this.skip(); + } + cy.visit(SYLLABI_ITEM); + }); + + it('should load with Syllabi configuration', () => { + cy.get('.item-page-content', { timeout: 10000 }).should('be.visible'); + cy.get('.itemDisplayTable').should('be.visible'); + }); + + it('should display syllabi-specific fields', () => { + cy.get('.itemDisplayTable').then($table => { + const text = $table.text(); + const hasSyllabiFields = + text.includes('Instructor') || + text.includes('Course Number') || + text.includes('Term'); + + if (hasSyllabiFields) { + cy.log('Syllabi-specific fields found'); + } + }); + }); + + it('should pass accessibility tests', () => { + cy.get('.item-page-content', { timeout: 10000 }).should('be.visible'); + testA11y('.item-page-content'); + }); + }); + + // Common tests + describe('Common Item Page Features', () => { + + it('should have correct table structure', () => { + cy.visit(DEFAULT_ITEM); + cy.get('.itemDisplayTable tbody tr, .itemDisplayTable tr').should('have.length.greaterThan', 0); + }); + + it('should display collections link', () => { + cy.visit(DEFAULT_ITEM); + cy.get('.itemDisplayTable').then($table => { + const text = $table.text(); + if (text.includes('Collection') || text.includes('Part of')) { + cy.log('Collections field found'); + } + }); + }); + + it('should display copyright notice', () => { + cy.visit(DEFAULT_ITEM); + cy.get('footer').should('be.visible'); + }); + + it('should have accessible links', () => { + cy.visit(DEFAULT_ITEM); + cy.get('a').each(($link) => { + const text = $link.text().trim(); + const ariaLabel = $link.attr('aria-label'); + const hasAccessibleText = text.length > 0 || !!ariaLabel; + cy.wrap(hasAccessibleText, 'Link should have text or aria-label').should('be.true'); + }); + }); + }); +}); diff --git a/karma.conf.js b/karma.conf.js index f96558bfaff..27490d33e2b 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,6 +1,8 @@ // Karma configuration file, see link for more information // https://karma-runner.github.io/1.0/config/configuration-file.html +// karma.conf.js + module.exports = function (config) { config.set({ basePath: '', @@ -35,7 +37,29 @@ module.exports = function (config) { logLevel: config.LOG_INFO, autoWatch: true, browsers: ['Chrome'], + + // Override ChromeHeadless to always include --no-sandbox + customLaunchers: { + ChromeHeadlessNoSandBox: { + base: 'ChromeHeadless', + flags: [ + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-gpu', + '--disable-dev-shm-usage', + '--disable-software-rasterizer', + '--disable-extensions' + ] + } + }, + singleRun: false, - restartOnFileChange: true + restartOnFileChange: true, + + // Increase timeouts + browserNoActivityTimeout: 60000, + browserDisconnectTimeout: 10000, + browserDisconnectTolerance: 3, + captureTimeout: 210000 }); }; diff --git a/package.json b/package.json index 997a8bb85df..085c7a9ad84 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "build:lint": "rimraf 'lint/dist/**/*.js' 'lint/dist/**/*.js.map' && tsc -b lint/tsconfig.json", "test": "ng test --source-map=true --watch=false --configuration test", "test:watch": "nodemon --exec \"ng test --source-map=true --watch=true --configuration test\"", - "test:headless": "ng test --source-map=true --watch=false --configuration test --browsers=ChromeHeadless --code-coverage", + "test:headless": "ng test --source-map=true --watch=false --configuration test --browsers=ChromeHeadlessNoSandBox --code-coverage", "test:lint": "yarn build:lint && yarn test:lint:nobuild", "test:lint:nobuild": "jasmine --config=lint/jasmine.json", "lint": "yarn build:lint && yarn lint:nobuild", diff --git a/scripts/create-test-data.sh b/scripts/create-test-data.sh new file mode 100755 index 00000000000..f9a23ccafb8 --- /dev/null +++ b/scripts/create-test-data.sh @@ -0,0 +1,312 @@ +#!/bin/bash +# scripts/create-test-data.sh + +set -e + +DSPACE_REST_URL=${DSPACE_REST_URL:-http://localhost:8080/server} +DSPACE_ADMIN_EMAIL=${DSPACE_ADMIN_EMAIL:-dspacedemo+admin@gmail.com} +DSPACE_ADMIN_PASS=${DSPACE_ADMIN_PASS:-dspace} + +echo "DSpace REST API: $DSPACE_REST_URL" + +# Wait for DSpace to be ready +echo "Waiting for DSpace API..." +max_attempts=30 +attempt=0 +while [ $attempt -lt $max_attempts ]; do + if curl -s "${DSPACE_REST_URL}/api" > /dev/null 2>&1; then + echo "DSpace is ready!" + break + fi + attempt=$((attempt + 1)) + echo "Attempt $attempt/$max_attempts..." + sleep 10 +done + +# Clean up old cookies +rm -f /tmp/dspace-cookies.txt + +# Login +echo "Authenticating..." + +curl -s "${DSPACE_REST_URL}/api/authn/status" \ + -c /tmp/dspace-cookies.txt \ + > /dev/null + +CSRF_TOKEN=$(grep "DSPACE-XSRF-COOKIE" /tmp/dspace-cookies.txt | awk '{print $NF}') + +if [ -z "$CSRF_TOKEN" ]; then + echo "❌ Failed to get CSRF token" + exit 1 +fi + +echo "Logging in as $DSPACE_ADMIN_EMAIL..." + +LOGIN_RESPONSE=$(curl -s -i -X POST "${DSPACE_REST_URL}/api/authn/login" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -H "X-XSRF-TOKEN: ${CSRF_TOKEN}" \ + -b /tmp/dspace-cookies.txt \ + -c /tmp/dspace-cookies.txt \ + --data-urlencode "user=${DSPACE_ADMIN_EMAIL}" \ + --data-urlencode "password=${DSPACE_ADMIN_PASS}") + +if ! echo "$LOGIN_RESPONSE" | grep -q "HTTP/1.1 200"; then + echo "❌ Login failed" + exit 1 +fi + +AUTH_TOKEN=$(echo "$LOGIN_RESPONSE" | grep -i "^Authorization:" | sed 's/Authorization: //' | tr -d '\r\n') + +if [ -z "$AUTH_TOKEN" ]; then + echo "❌ Failed to get authorization token" + exit 1 +fi + +echo "✅ Authenticated" + +# Function to get current CSRF token +get_current_csrf() { + grep "DSPACE-XSRF-COOKIE" /tmp/dspace-cookies.txt | awk '{print $NF}' +} + +# Function to make authenticated API requests +api_request() { + local method=$1 + local endpoint=$2 + local data=$3 + + local csrf=$(get_current_csrf) + + if [ -z "$csrf" ]; then + echo "❌ No CSRF token" >&2 + return 1 + fi + + local temp_file=$(mktemp) + local http_code + + if [ -n "$data" ]; then + http_code=$(curl -s -w "%{http_code}" -o "$temp_file" \ + -X "$method" "${DSPACE_REST_URL}${endpoint}" \ + -H "Content-Type: application/json" \ + -H "Authorization: ${AUTH_TOKEN}" \ + -H "X-XSRF-TOKEN: ${csrf}" \ + -b /tmp/dspace-cookies.txt \ + -c /tmp/dspace-cookies.txt \ + -d "$data") + else + http_code=$(curl -s -w "%{http_code}" -o "$temp_file" \ + -X "$method" "${DSPACE_REST_URL}${endpoint}" \ + -H "Authorization: ${AUTH_TOKEN}" \ + -H "X-XSRF-TOKEN: ${csrf}" \ + -b /tmp/dspace-cookies.txt \ + -c /tmp/dspace-cookies.txt) + fi + + local response=$(cat "$temp_file") + rm "$temp_file" + + if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then + echo "$response" + return 0 + else + echo "❌ HTTP $http_code: $response" >&2 + return 1 + fi +} + +# Create test community +echo "" +echo "=== Creating Test Community ===" + +COMMUNITY_RESPONSE=$(api_request POST "/api/core/communities" '{ + "name": "Test Community", + "metadata": { + "dc.title": [{"value": "Test Community for FDA Theme"}] + } +}') + +if [ $? -ne 0 ]; then + exit 1 +fi + +COMMUNITY_UUID=$(echo "$COMMUNITY_RESPONSE" | jq -r '.uuid') +COMMUNITY_HANDLE=$(echo "$COMMUNITY_RESPONSE" | jq -r '.handle') + +if [ -z "$COMMUNITY_UUID" ] || [ "$COMMUNITY_UUID" = "null" ]; then + echo "❌ Failed to extract community UUID" + exit 1 +fi + +echo "✅ Community created (UUID: $COMMUNITY_UUID, Handle: $COMMUNITY_HANDLE)" + +# Function to create a collection - FIXED to send messages to stderr +create_collection() { + local name=$1 + local community_uuid=$2 + + echo "Creating $name..." >&2 # Send to stderr + + local response=$(api_request POST "/api/core/collections?parent=${community_uuid}" '{ + "name": "'"$name"'", + "metadata": { + "dc.title": [{"value": "'"$name"'"}] + } + }') + + if [ $? -ne 0 ]; then + return 1 + fi + + local uuid=$(echo "$response" | jq -r '.uuid') + local handle=$(echo "$response" | jq -r '.handle') + + if [ -z "$uuid" ] || [ "$uuid" = "null" ]; then + echo "❌ Failed to extract UUID" >&2 + return 1 + fi + + echo "✅ $name created (UUID: $uuid, Handle: $handle)" >&2 # Send to stderr + + # Only output the data to stdout (no extra text) + echo "$uuid|$handle" +} + +# Create collections +echo "" +echo "=== Creating Collections ===" + +DEFAULT_RESULT=$(create_collection "Default Test Collection" "$COMMUNITY_UUID") +DEFAULT_COLLECTION_UUID=$(echo "$DEFAULT_RESULT" | cut -d'|' -f1) +DEFAULT_COLLECTION_HANDLE=$(echo "$DEFAULT_RESULT" | cut -d'|' -f2) + +JONES_RESULT=$(create_collection "Jones Test Collection" "$COMMUNITY_UUID") +JONES_COLLECTION_UUID=$(echo "$JONES_RESULT" | cut -d'|' -f1) +JONES_COLLECTION_HANDLE=$(echo "$JONES_RESULT" | cut -d'|' -f2) + +RELICS_RESULT=$(create_collection "Relics Test Collection" "$COMMUNITY_UUID") +RELICS_COLLECTION_UUID=$(echo "$RELICS_RESULT" | cut -d'|' -f1) +RELICS_COLLECTION_HANDLE=$(echo "$RELICS_RESULT" | cut -d'|' -f2) + +LAEFER_RESULT=$(create_collection "Laefer Test Collection" "$COMMUNITY_UUID") +LAEFER_COLLECTION_UUID=$(echo "$LAEFER_RESULT" | cut -d'|' -f1) +LAEFER_COLLECTION_HANDLE=$(echo "$LAEFER_RESULT" | cut -d'|' -f2) + +TANDON_RESULT=$(create_collection "Tandon Test Collection" "$COMMUNITY_UUID") +TANDON_COLLECTION_UUID=$(echo "$TANDON_RESULT" | cut -d'|' -f1) +TANDON_COLLECTION_HANDLE=$(echo "$TANDON_RESULT" | cut -d'|' -f2) + +TANDONCAPSTONE_RESULT=$(create_collection "Tandon Capstone Test Collection" "$COMMUNITY_UUID") +TANDONCAPSTONE_COLLECTION_UUID=$(echo "$TANDONCAPSTONE_RESULT" | cut -d'|' -f1) +TANDONCAPSTONE_COLLECTION_HANDLE=$(echo "$TANDONCAPSTONE_RESULT" | cut -d'|' -f2) + +DNP_RESULT=$(create_collection "DNP Test Collection" "$COMMUNITY_UUID") +DNP_COLLECTION_UUID=$(echo "$DNP_RESULT" | cut -d'|' -f1) +DNP_COLLECTION_HANDLE=$(echo "$DNP_RESULT" | cut -d'|' -f2) + +CALABASH_RESULT=$(create_collection "Calabash Test Collection" "$COMMUNITY_UUID") +CALABASH_COLLECTION_UUID=$(echo "$CALABASH_RESULT" | cut -d'|' -f1) +CALABASH_COLLECTION_HANDLE=$(echo "$CALABASH_RESULT" | cut -d'|' -f2) + +OPENSCHOLARSHIP_RESULT=$(create_collection "OpenScholarship Test Collection" "$COMMUNITY_UUID") +OPENSCHOLARSHIP_COLLECTION_UUID=$(echo "$OPENSCHOLARSHIP_RESULT" | cut -d'|' -f1) +OPENSCHOLARSHIP_COLLECTION_HANDLE=$(echo "$OPENSCHOLARSHIP_RESULT" | cut -d'|' -f2) + +SYLLABI_RESULT=$(create_collection "Syllabi Test Collection" "$COMMUNITY_UUID") +SYLLABI_COLLECTION_UUID=$(echo "$SYLLABI_RESULT" | cut -d'|' -f1) +SYLLABI_COLLECTION_HANDLE=$(echo "$SYLLABI_RESULT" | cut -d'|' -f2) + +# Function to create an item - FIXED to send messages to stderr +create_item() { + local collection_uuid=$1 + local title=$2 + local author=$3 + + echo "Creating $title..." >&2 # Send to stderr + + local response=$(api_request POST "/api/core/items?owningCollection=${collection_uuid}" '{ + "name": "'"$title"'", + "metadata": { + "dc.title": [{"value": "'"$title"'"}], + "dc.contributor.author": [{"value": "'"$author"'"}], + "dc.description.abstract": [{"value": "Test abstract"}] + }, + "inArchive": true, + "discoverable": true + }') + + if [ $? -ne 0 ]; then + return 1 + fi + + local uuid=$(echo "$response" | jq -r '.uuid') + + if [ -z "$uuid" ] || [ "$uuid" = "null" ]; then + echo "❌ Failed to extract UUID" >&2 + return 1 + fi + + echo "✅ $title created (UUID: $uuid)" >&2 # Send to stderr + + # Only output UUID to stdout (no extra text) + echo "$uuid" +} + +# Create items +echo "" +echo "=== Creating Test Items ===" + +DEFAULT_ITEM_UUID=$(create_item "$DEFAULT_COLLECTION_UUID" "Default Test Publication" "Test Author") +JONES_ITEM_UUID=$(create_item "$JONES_COLLECTION_UUID" "Jones Test Item" "Jones Author") +RELICS_ITEM_UUID=$(create_item "$RELICS_COLLECTION_UUID" "Relics Test Item" "Relics Author") +LAEFER_ITEM_UUID=$(create_item "$LAEFER_COLLECTION_UUID" "Laefer Test Publication" "Laefer Author") +TANDON_ITEM_UUID=$(create_item "$TANDON_COLLECTION_UUID" "Tandon Test Article" "Tandon Author") +TANDONCAPSTONE_ITEM_UUID=$(create_item "$TANDONCAPSTONE_COLLECTION_UUID" "Tandon Capstone Project" "Capstone Student") +DNP_ITEM_UUID=$(create_item "$DNP_COLLECTION_UUID" "DNP Test Thesis" "DNP Student") +CALABASH_ITEM_UUID=$(create_item "$CALABASH_COLLECTION_UUID" "Calabash Test Article" "Calabash Author") +OPENSCHOLARSHIP_ITEM_UUID=$(create_item "$OPENSCHOLARSHIP_COLLECTION_UUID" "OpenScholarship Test Paper" "Scholar") +SYLLABI_ITEM_UUID=$(create_item "$SYLLABI_COLLECTION_UUID" "Test Syllabus" "Test Instructor") + +# Create cypress.env.json - USE DIFFERENT VARIABLE NAMES +cat > cypress.env.json << EOF +{ + "DSPACE_TEST_FDA_DEFAULT_ITEM": "$DEFAULT_ITEM_UUID", + "DSPACE_TEST_FDA_JONES_ITEM": "$JONES_ITEM_UUID", + "DSPACE_TEST_FDA_RELICS_ITEM": "$RELICS_ITEM_UUID", + "DSPACE_TEST_FDA_LAEFER_ITEM": "$LAEFER_ITEM_UUID", + "DSPACE_TEST_FDA_TANDON_ITEM": "$TANDON_ITEM_UUID", + "DSPACE_TEST_FDA_TANDONCAPSTONE_ITEM": "$TANDONCAPSTONE_ITEM_UUID", + "DSPACE_TEST_FDA_DNP_ITEM": "$DNP_ITEM_UUID", + "DSPACE_TEST_FDA_CALABASH_ITEM": "$CALABASH_ITEM_UUID", + "DSPACE_TEST_FDA_OPENSCHOLARSHIP_ITEM": "$OPENSCHOLARSHIP_ITEM_UUID", + "DSPACE_TEST_FDA_SYLLABI_ITEM": "$SYLLABI_ITEM_UUID" +} +EOF + +echo "" +echo "✅ Test data created successfully!" + +# Export for GitHub Actions - CLEAN FORMAT +if [ -n "$GITHUB_OUTPUT" ]; then + { + echo "default_item_uuid=$DEFAULT_ITEM_UUID" + echo "jones_item_uuid=$JONES_ITEM_UUID" + echo "relics_item_uuid=$RELICS_ITEM_UUID" + echo "laefer_item_uuid=$LAEFER_ITEM_UUID" + echo "tandon_item_uuid=$TANDON_ITEM_UUID" + echo "tandoncapstone_item_uuid=$TANDONCAPSTONE_ITEM_UUID" + echo "dnp_item_uuid=$DNP_ITEM_UUID" + echo "calabash_item_uuid=$CALABASH_ITEM_UUID" + echo "openscholarship_item_uuid=$OPENSCHOLARSHIP_ITEM_UUID" + echo "syllabi_item_uuid=$SYLLABI_ITEM_UUID" + echo "jones_collection_handle=$JONES_COLLECTION_HANDLE" + echo "relics_collection_handle=$RELICS_COLLECTION_HANDLE" + echo "laefer_collection_handle=$LAEFER_COLLECTION_HANDLE" + echo "tandon_collection_handle=$TANDON_COLLECTION_HANDLE" + echo "tandoncapstone_collection_handle=$TANDONCAPSTONE_COLLECTION_HANDLE" + echo "dnp_collection_handle=$DNP_COLLECTION_HANDLE" + echo "calabash_collection_handle=$CALABASH_COLLECTION_HANDLE" + echo "openscholarship_collection_handle=$OPENSCHOLARSHIP_COLLECTION_HANDLE" + echo "syllabi_collection_handle=$SYLLABI_COLLECTION_HANDLE" + } >> "$GITHUB_OUTPUT" +fi diff --git a/scripts/cypress.env.json b/scripts/cypress.env.json new file mode 100644 index 00000000000..4d8c08ac5b4 --- /dev/null +++ b/scripts/cypress.env.json @@ -0,0 +1,12 @@ +{ + "DSPACE_TEST_FDA_DEFAULT_ITEM": "450ce8b2-46b4-4490-9a34-2d29d6027b9e", + "DSPACE_TEST_JONES_ITEM": "5fce9399-152f-4f32-a8f5-0cd77c307a50", + "DSPACE_TEST_RELICS_ITEM": "cbf6502f-382b-4a62-a9fd-a50ee6bc461c", + "DSPACE_TEST_LAEFER_ITEM": "c00bc7bd-ad28-4fee-bbe9-2ac90451297a", + "DSPACE_TEST_TANDON_ITEM": "ffbe5a48-23c2-432d-b0b1-d12e8fe370a6", + "DSPACE_TEST_TANDONCAPSTONE_ITEM": "ef71c4de-68af-4e5d-88c7-e4304988dc4c", + "DSPACE_TEST_DNP_ITEM": "25fc7653-0d94-4330-a6d6-d52ba45a0826", + "DSPACE_TEST_CALABASH_ITEM": "291e765c-356c-4861-99f8-dc6556fcd01b", + "DSPACE_TEST_OPENSCHOLARSHIP_ITEM": "c7641ccb-9a20-4ba3-b521-3ddd036de76f", + "DSPACE_TEST_SYLLABI_ITEM": "58c853b0-c428-43e1-bf30-c6669205093b" +} diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index dd5f0db6db6..acd26323d92 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1,4 +1,5 @@ { + "401.help": "You're not authorized to access this page. You can use the button below to get back to the home page.", "401.link.home-page": "Take me to the home page", @@ -2803,7 +2804,7 @@ "item.page.citation": "Citation", - "item.page.collections": "Collections", + "item.page.collections": "Appears in Collections", "item.page.collections.loading": "Loading...", @@ -2813,7 +2814,7 @@ "item.page.edit": "Edit this item", - "item.page.files": "Files", + "item.page.files": "Files in This Item", "item.page.filesection.description": "Description:", @@ -2827,7 +2828,7 @@ "item.page.journal.search.title": "Articles in this journal", - "item.page.link.full": "Full item page", + "item.page.link.full": "Show full item record", "item.page.link.simple": "Simple item page", @@ -2887,7 +2888,7 @@ "item.preview.dc.description.abstract": "Abstract:", - "item.preview.dc.identifier.other": "Other identifier:", + "item.preview.dc.identifier.other": "Other Identifier", "item.preview.dc.language.iso": "Language:", @@ -2907,8 +2908,6 @@ "item.preview.dc.rights": "Rights", - "item.preview.dc.identifier.other": "Other Identifier", - "item.preview.dc.relation.issn": "ISSN", "item.preview.dc.identifier.isbn": "ISBN", @@ -4093,7 +4092,6 @@ "syllabus.search.title": "Syllabus Search", - "media-viewer.next": "Next", "media-viewer.previous": "Previous", @@ -5720,7 +5718,7 @@ "thumbnail.person.placeholder": "No Profile Picture Available", - "title": "FDA title", + title: "FDA title", "vocabulary-treeview.header": "Hierarchical tree view", @@ -6255,152 +6253,263 @@ "service.overview.delete.header": "Delete Service", "ldn-registered-services.title": "Registered Services", + "ldn-registered-services.table.name": "Name", + "ldn-registered-services.table.description": "Description", + "ldn-registered-services.table.status": "Status", + "ldn-registered-services.table.action": "Action", + "ldn-registered-services.new": "NEW", + "ldn-registered-services.new.breadcrumbs": "Registered Services", "ldn-service.overview.table.enabled": "Enabled", + "ldn-service.overview.table.disabled": "Disabled", + "ldn-service.overview.table.clickToEnable": "Click to enable", + "ldn-service.overview.table.clickToDisable": "Click to disable", "ldn-edit-registered-service.title": "Edit Service", + "ldn-create-service.title": "Create service", + "service.overview.create.modal": "Create Service", + "service.overview.create.body": "Please confirm the creation of this service.", + "ldn-service-status": "Status", + "service.confirm.create": "Create", + "service.refuse.create": "Cancel", + "ldn-register-new-service.title": "Register a new service", + "ldn-new-service.form.label.submit": "Save", + "ldn-new-service.form.label.name": "Name", + "ldn-new-service.form.label.description": "Description", + "ldn-new-service.form.label.url": "Service URL", + "ldn-new-service.form.label.ip-range": "Service IP range", + "ldn-new-service.form.label.score": "Level of trust", + "ldn-new-service.form.label.ldnUrl": "LDN Inbox URL", + "ldn-new-service.form.placeholder.name": "Please provide service name", + "ldn-new-service.form.placeholder.description": "Please provide a description regarding your service", + "ldn-new-service.form.placeholder.url": "Please input the URL for users to check out more information about the service", + "ldn-new-service.form.placeholder.lowerIp": "IPv4 range lower bound", + "ldn-new-service.form.placeholder.upperIp": "IPv4 range upper bound", + "ldn-new-service.form.placeholder.ldnUrl": "Please specify the URL of the LDN Inbox", + "ldn-new-service.form.placeholder.score": "Please enter a value between 0 and 1. Use the “.” as decimal separator", + "ldn-service.form.label.placeholder.default-select": "Select a pattern", "ldn-service.form.pattern.ack-accept.label": "Acknowledge and Accept", + "ldn-service.form.pattern.ack-accept.description": "This pattern is used to acknowledge and accept a request (offer). It implies an intention to act on the request.", + "ldn-service.form.pattern.ack-accept.category": "Acknowledgements", "ldn-service.form.pattern.ack-reject.label": "Acknowledge and Reject", + "ldn-service.form.pattern.ack-reject.description": "This pattern is used to acknowledge and reject a request (offer). It signifies no further action regarding the request.", + "ldn-service.form.pattern.ack-reject.category": "Acknowledgements", "ldn-service.form.pattern.ack-tentative-accept.label": "Acknowledge and Tentatively Accept", + "ldn-service.form.pattern.ack-tentative-accept.description": "This pattern is used to acknowledge and tentatively accept a request (offer). It implies an intention to act, which may change.", + "ldn-service.form.pattern.ack-tentative-accept.category": "Acknowledgements", "ldn-service.form.pattern.ack-tentative-reject.label": "Acknowledge and Tentatively Reject", + "ldn-service.form.pattern.ack-tentative-reject.description": "This pattern is used to acknowledge and tentatively reject a request (offer). It signifies no further action, subject to change.", + "ldn-service.form.pattern.ack-tentative-reject.category": "Acknowledgements", "ldn-service.form.pattern.announce-endorsement.label": "Announce Endorsement", + "ldn-service.form.pattern.announce-endorsement.description": "This pattern is used to announce the existence of an endorsement, referencing the endorsed resource.", + "ldn-service.form.pattern.announce-endorsement.category": "Announcements", "ldn-service.form.pattern.announce-ingest.label": "Announce Ingest", + "ldn-service.form.pattern.announce-ingest.description": "This pattern is used to announce that a resource has been ingested.", + "ldn-service.form.pattern.announce-ingest.category": "Announcements", "ldn-service.form.pattern.announce-relationship.label": "Announce Relationship", + "ldn-service.form.pattern.announce-relationship.description": "This pattern is used to announce a relationship between two resources.", + "ldn-service.form.pattern.announce-relationship.category": "Announcements", "ldn-service.form.pattern.announce-review.label": "Announce Review", + "ldn-service.form.pattern.announce-review.description": "This pattern is used to announce the existence of a review, referencing the reviewed resource.", + "ldn-service.form.pattern.announce-review.category": "Announcements", "ldn-service.form.pattern.announce-service-result.label": "Announce Service Result", + "ldn-service.form.pattern.announce-service-result.description": "This pattern is used to announce the existence of a 'service result', referencing the relevant resource.", + "ldn-service.form.pattern.announce-service-result.category": "Announcements", "ldn-service.form.pattern.request-endorsement.label": "Request Endorsement", + "ldn-service.form.pattern.request-endorsement.description": "This pattern is used to request endorsement of a resource owned by the origin system.", + "ldn-service.form.pattern.request-endorsement.category": "Requests", "ldn-service.form.pattern.request-ingest.label": "Request Ingest", + "ldn-service.form.pattern.request-ingest.description": "This pattern is used to request that the target system ingest a resource.", + "ldn-service.form.pattern.request-ingest.category": "Requests", "ldn-service.form.pattern.request-review.label": "Request Review", + "ldn-service.form.pattern.request-review.description": "This pattern is used to request a review of a resource owned by the origin system.", + "ldn-service.form.pattern.request-review.category": "Requests", "ldn-service.form.pattern.undo-offer.label": "Undo Offer", + "ldn-service.form.pattern.undo-offer.description": "This pattern is used to undo (retract) an offer previously made.", + "ldn-service.form.pattern.undo-offer.category": "Undo", "ldn-new-service.form.label.placeholder.selectedItemFilter": "No Item Filter Selected", + "ldn-new-service.form.label.ItemFilter": "Item Filter", + "ldn-new-service.form.label.automatic": "Automatic", + "ldn-new-service.form.error.name": "Name is required", + "ldn-new-service.form.error.url": "URL is required", + "ldn-new-service.form.error.ipRange": "Please enter a valid IP range", + "ldn-new-service.form.hint.ipRange": "Please enter a valid IpV4 in both range bounds (note: for single IP, please enter the same value in both fields)", + "ldn-new-service.form.error.ldnurl": "LDN URL is required", + "ldn-new-service.form.error.patterns": "At least a pattern is required", + "ldn-new-service.form.error.score": "Please enter a valid score (between 0 and 1). Use the “.” as decimal separator", "ldn-new-service.form.label.inboundPattern": "Supported Pattern", + "ldn-new-service.form.label.addPattern": "+ Add more", + "ldn-new-service.form.label.removeItemFilter": "Remove", + "ldn-register-new-service.breadcrumbs": "New Service", + "service.overview.delete.body": "Are you sure you want to delete this service?", + "service.overview.edit.body": "Do you confirm the changes?", + "service.overview.edit.modal": "Edit Service", + "service.detail.update": "Confirm", + "service.detail.return": "Cancel", + "service.overview.reset-form.body": "Are you sure you want to discard the changes and leave?", + "service.overview.reset-form.modal": "Discard Changes", + "service.overview.reset-form.reset-confirm": "Discard", + "admin.registries.services-formats.modify.success.head": "Successful Edit", + "admin.registries.services-formats.modify.success.content": "The service has been edited", + "admin.registries.services-formats.modify.failure.head": "Failed Edit", + "admin.registries.services-formats.modify.failure.content": "The service has not been edited", + "ldn-service-notification.created.success.title": "Successful Create", + "ldn-service-notification.created.success.body": "The service has been created", + "ldn-service-notification.created.failure.title": "Failed Create", + "ldn-service-notification.created.failure.body": "The service has not been created", + "ldn-service-notification.created.warning.title": "Please select at least one Inbound Pattern", + "ldn-enable-service.notification.success.title": "Successful status updated", + "ldn-enable-service.notification.success.content": "The service status has been updated", + "ldn-service-delete.notification.success.title": "Successful Deletion", + "ldn-service-delete.notification.success.content": "The service has been deleted", + "ldn-service-delete.notification.error.title": "Failed Deletion", + "ldn-service-delete.notification.error.content": "The service has not been deleted", + "service.overview.reset-form.reset-return": "Cancel", + "service.overview.delete": "Delete service", + "ldn-edit-service.title": "Edit service", + "ldn-edit-service.form.label.name": "Name", + "ldn-edit-service.form.label.description": "Description", + "ldn-edit-service.form.label.url": "Service URL", + "ldn-edit-service.form.label.ldnUrl": "LDN Inbox URL", + "ldn-edit-service.form.label.inboundPattern": "Inbound Pattern", + "ldn-edit-service.form.label.noInboundPatternSelected": "No Inbound Pattern", + "ldn-edit-service.form.label.selectedItemFilter": "Selected Item Filter", + "ldn-edit-service.form.label.selectItemFilter": "No Item Filter", + "ldn-edit-service.form.label.automatic": "Automatic", + "ldn-edit-service.form.label.addInboundPattern": "+ Add more", + "ldn-edit-service.form.label.submit": "Save", + "ldn-edit-service.breadcrumbs": "Edit Service", + "ldn-service.control-constaint-select-none": "Select none", "ldn-register-new-service.notification.error.title": "Error", + "ldn-register-new-service.notification.error.content": "An error occurred while creating this process", + "ldn-register-new-service.notification.success.title": "Success", + "ldn-register-new-service.notification.success.content": "The process was successfully created", "submission.sections.notify.info": "The selected service is compatible with the item according to its current status. {{ service.name }}: {{ service.description }}", @@ -6420,6 +6529,7 @@ "menu.section.services_new": "LDN Service", "quality-assurance.topics.description-with-target": "Below you can see all the topics received from the subscriptions to {{source}} in regards to the", + "quality-assurance.events.description": "Below the list of all the suggestions for the selected topic {{topic}}, related to {{source}}.", "quality-assurance.events.description-with-topic-and-target": "Below the list of all the suggestions for the selected topic {{topic}}, related to {{source}} and ", @@ -6929,4 +7039,304 @@ "item.page.relation.ispartofseries": "Series/Report no.", "item.page.copyright": "Items in FDA are protected by copyright, with all rights reserved, unless otherwise indicated.", -} + + "jones.item.page.title": "Title", + + "jones.item.page.date.issued": "Date of digital object", + + "jones.item.page.date.created": "Date of object depicted", + + "jones.item.page.contributor.*": "Authors", + + "jones.item.page.description": "Description of particular view", + + "jones.item.page.identifier.other": "Technical designation of object", + + "jones.item.page.description.equipment": "Technical specifications", + + "jones.item.page.description.additional": "Additional description", + + "relics.item.page.title": "Title", + + "relics.item.page.contributor.*": "Authors", + + "relics.item.page.coverage.spatial": "Country", + + "relics.item.page.date.issued": "Date", + + "jones.item.page.language.iso": "Language", + + "jones.item.page.format.mimetype": "File format", + + "jones.item.page.relation.isreferencedby": "Bibliographic citation", + + "jones.item.page.relation.uri": "Citation link", + + "jones.item.page.rights": "Rights", + + "jones.item.page.subject": "Subject Keywords", + + "relics.item.page.publisher": "Publisher", + + "relics.item.page.type": "Type", + + "relics.item.page.source": "Source", + + "relics.item.page.description": "Description", + + "relics.item.page.rights": "Rights", + + "tandon.item.page.title": "Title", + + "tandon.item.page.contributor.*": "Authors", + + "tandon.item.page.date.issued": "Date Issued", + + "tandon.item.page.citation": "Citation", + + "tandon.item.page.abstract": "Abstract", + + "tandon.item.page.description.firstPage": "First Page", + + "tandon.item.page.description.lastPage": "Last Page", + + "tandon.item.page.doi": "DOI", + + "tandon.item.page.type": "Type", + + "tandoncapstone.item.page.title": "Title", + + "tandoncapstone.item.page.contributor.author": "Authors", + + "tandoncapstone.item.page.contributor.advisor": "Capstone Professor", + + "tandoncapstone.item.page.date.issued": "Date Issued", + + "tandoncapstone.item.page.language.iso": "Language", + + "tandoncapstone.item.page.abstract": "Abstract", + + "tandoncapstone.item.page.type": "Type", + + "tandoncapstone.item.page.format.medium": "Medium", + + "tandoncapstone.item.page.format.Extent": "Extent", + + "tandoncapstone.item.page.uri": "URI", + + "dnp.item.page.title": "Title", + + "dnp.item.page.title.alternative": "Alternate Title", + + "dnp.item.page.contributor.author": "Author", + + "dnp.item.page.contributor.advisor": "DNP Project Team Member", + + "dnp.item.page.date.issued": "Degree Year", + + "dnp.item.page.description": "Description", + + "dnp.item.page.abstract": "Abstract", + + "dnp.item.page.description.sponsorship": "Sponsorship", + + "dnp.item.page.uri": "FDA Handle", + + "dnp.item.page.subject": "Keyword", + + "dnp.item.page.subject.mesh": "MeSH term", + + "dnp.item.page.subject.cinahl": "CINAHL term", + + "dnp.item.page.subject.apa": "APA Thesaurus term", + + "dnp.item.page.rights": "Rights", + + "dnp.item.page.type": "Type", + + "dnp.item.page.format.medium": "Medium", + + "dnp.item.page.language.iso": "Language", + + "dnp.item.page.thesis.degree.name": "Degree", + + "dnp.item.page.thesis.degree.level": "Degree Level", + + "dnp.item.page.thesis.degree.discipline": "Discipline", + + "dnp.item.page.thesis.degree.grantor": "Grantor", + + "openscholarship.item.page.title": "Title", + + "openscholarship.item.page.contributor.author": "Authors", + + "openscholarship.item.page.date.issued": "Date Issued", + + "openscholarship.item.page.abstract": "Abstract", + + "openscholarship.item.page.description.sponsorship": "Sponsorship", + + "openscholarship.item.page.doi": "DOI", + + "openscholarship.item.page.rights": "Rights", + + "openscholarship.item.page.subject": "Subject", + + "laefer.item.page.title": "Title", + + "laefer.item.page.contributor.*": "Authors", + + "laefer.item.page.contributor.author": "Authors", + + "laefer.item.page.contributor.editor": "Editors", + + "laefer.item.page.date.issued": "Issue Date", + + "laefer.item.page.description": "Description", + + "laefer.item.page.abstract": "Abstract", + + "laefer.item.page.identifier": "Other Identifiers", + + "laefer.item.page.citation": "Citation", + + "laefer.item.page.identifier.govdoc": "Gov't Doc #", + + "laefer.item.page.isbn": "ISBN", + + "laefer.item.page.ismn": "ISMN", + + "laefer.item.page.issn": "ISSN", + + "laefer.item.page.uri": "URI", + + "laefer.item.page.publisher": "Publisher", + + "laefer.item.page.relation.ispartofseries": "Series/Report no.", + + "laefer.item.page.subject": "Keywords", + + "laefer.item.page.title.alternative": "Other Titles", + + "laefer.item.page.rights": "Rights", + + "calabash.item.page.title": "Title", + + "calabash.item.page.contributor.*": "Authors", + + "calabash.item.page.contributor.author": "Authors", + + "calabash.item.page.contributor.translator": "Translator", + + "calabash.item.page.date.issued": "Issue Date", + + "calabash.item.page.description": "Description", + + "calabash.item.page.abstract": "Abstract", + + "calabash.item.page.identifier": "Other Identifiers", + + "calabash.item.page.citation": "Citation", + + "calabash.item.page.issn": "ISSN", + + "calabash.item.page.uri": "URI", + + "calabash.item.page.doi": "DOI", + + "calabash.item.page.publisher": "Publisher", + + "calabash.item.page.subject": "Subject Keywords", + + "calabash.item.page.title.alternative": "Other Titles", + + "calabash.item.page.rights": "Rights", + + "calabash.item.page.type": "Type", + + "calabash.item.page.prism.endingPage": "Last Page", + + "calabash.item.page.prism.issueIdentifier": "Issue", + + "calabash.item.page.prism.publicationName": "Journal Title", + + "calabash.item.page.prism.startingPage": "First Page", + + "calabash.item.page.prism.volume": "Volume", + + "syllabi.item.page.title": "Title", + + "syllabi.item.page.contributor.instructor": "Instructor(s)", + + "syllabi.item.page.identifier.coursenumber": "Course Number", + + "syllabi.item.page.description.semester": "Term", + + "syllabi.item.page.subject": "Keywords", + + "syllabi.item.page.uri": "URI", + + "syllabi.item.page.date.issued": "Date", + + "syllabi.item.page.rights": "Rights", + + "item.page.type": "Type", + + "item.page.source": "Source", + + "item.page.coverage.spatial": "Location", + + "item.page.identifier.other": "Other Identifier", + + "item.page.language.iso": "Language", + + "item.page.format.mimetype": "Format", + + "item.page.description.equipment": "Equipment", + + "item.page.description.additional": "Additional Information", + + "item.page.relation.isreferencedby": "Referenced By", + + "item.page.relation.uri": "Related Link", + + "item.page.description.firstPage": "First Page", + + "item.page.description.lastPage": "Last Page", + + "item.page.contributor.advisor": "Advisor", + + "item.page.contributor.instructor": "Instructor", + + "item.page.thesis.degree.name": "Degree", + + "item.page.thesis.degree.level": "Degree Level", + + "item.page.thesis.degree.discipline": "Discipline", + + "item.page.thesis.degree.grantor": "Grantor", + + "item.page.subject.mesh": "MeSH Terms", + + "item.page.subject.cinahl": "CINAHL Terms", + + "item.page.subject.apa": "APA Thesaurus Terms", + + "item.page.description.sponsorship": "Sponsorship", + + "item.page.format.medium": "Medium", + + "item.page.prism.publicationName": "Publication Name", + + "item.page.prism.issueIdentifier": "Issue", + + "item.page.prism.volume": "Volume", + + "item.page.prism.startingPage": "Starting Page", + + "item.page.prism.endingPage": "Ending Page", + + "item.page.identifier.coursenumber": "Course Number", + + "item.page.description.semester": "Semester", +} \ No newline at end of file diff --git a/src/themes/calabash/app/item-page/simple/item-types/untyped-item/untyped-item.component.html b/src/themes/calabash/app/item-page/simple/item-types/untyped-item/untyped-item.component.html deleted file mode 100644 index 2359e3324be..00000000000 --- a/src/themes/calabash/app/item-page/simple/item-types/untyped-item/untyped-item.component.html +++ /dev/null @@ -1,155 +0,0 @@ - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/src/themes/calabash/app/item-page/simple/item-types/untyped-item/untyped-item.component.scss b/src/themes/calabash/app/item-page/simple/item-types/untyped-item/untyped-item.component.scss deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/themes/calabash/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts b/src/themes/calabash/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts deleted file mode 100644 index 78fc3c2f783..00000000000 --- a/src/themes/calabash/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { - AsyncPipe, - NgIf, -} from '@angular/common'; -import { - ChangeDetectionStrategy, - Component, -} from '@angular/core'; -import { RouterLink } from '@angular/router'; -import { TranslateModule } from '@ngx-translate/core'; - -import { Context } from '../../../../../../../app/core/shared/context.model'; -import { Item } from '../../../../../../../app/core/shared/item.model'; -import { ViewMode } from '../../../../../../../app/core/shared/view-mode.model'; -import { CollectionsComponent } from '../../../../../../../app/item-page/field-components/collections/collections.component'; -import { ThemedMediaViewerComponent } from '../../../../../../../app/item-page/media-viewer/themed-media-viewer.component'; -import { MiradorViewerComponent } from '../../../../../../../app/item-page/mirador-viewer/mirador-viewer.component'; -import { ThemedFileSectionComponent } from '../../../../../../../app/item-page/simple/field-components/file-section/themed-file-section.component'; -import { ItemPageAbstractFieldComponent } from '../../../../../../../app/item-page/simple/field-components/specific-field/abstract/item-page-abstract-field.component'; -import { ItemPageCcLicenseFieldComponent } from '../../../../../../../app/item-page/simple/field-components/specific-field/cc-license/item-page-cc-license-field.component'; -import { ItemPageDateFieldComponent } from '../../../../../../../app/item-page/simple/field-components/specific-field/date/item-page-date-field.component'; -import { GenericItemPageFieldComponent } from '../../../../../../../app/item-page/simple/field-components/specific-field/generic/generic-item-page-field.component'; -import { ThemedItemPageTitleFieldComponent } from '../../../../../../../app/item-page/simple/field-components/specific-field/title/themed-item-page-field.component'; -import { ItemPageUriFieldComponent } from '../../../../../../../app/item-page/simple/field-components/specific-field/uri/item-page-uri-field.component'; -import { UntypedItemComponent as BaseComponent } from '../../../../../../../app/item-page/simple/item-types/untyped-item/untyped-item.component'; -import { ThemedMetadataRepresentationListComponent } from '../../../../../../../app/item-page/simple/metadata-representation-list/themed-metadata-representation-list.component'; -import { DsoEditMenuComponent } from '../../../../../../../app/shared/dso-page/dso-edit-menu/dso-edit-menu.component'; -import { MetadataFieldWrapperComponent } from '../../../../../../../app/shared/metadata-field-wrapper/metadata-field-wrapper.component'; -import { listableObjectComponent } from '../../../../../../../app/shared/object-collection/shared/listable-object/listable-object.decorator'; -import { ThemedResultsBackButtonComponent } from '../../../../../../../app/shared/results-back-button/themed-results-back-button.component'; -import { ThemedThumbnailComponent } from '../../../../../../../app/thumbnail/themed-thumbnail.component'; - -/** - * Component that represents an untyped Item page - */ -@listableObjectComponent(Item, ViewMode.StandalonePage, Context.Any,'calabash') -@Component({ - selector: 'ds-untyped-item', - // styleUrls: ['./untyped-item.component.scss'], - styleUrls: [ - './untyped-item.component.scss', - ], - // templateUrl: './untyped-item.component.html', - templateUrl: - './untyped-item.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - standalone: true, - imports: [ - NgIf, - ThemedResultsBackButtonComponent, - MiradorViewerComponent, - ThemedItemPageTitleFieldComponent, - DsoEditMenuComponent, - MetadataFieldWrapperComponent, - ThemedThumbnailComponent, - ThemedMediaViewerComponent, - ThemedFileSectionComponent, - ItemPageDateFieldComponent, - ThemedMetadataRepresentationListComponent, - GenericItemPageFieldComponent, - ItemPageAbstractFieldComponent, - ItemPageUriFieldComponent, - CollectionsComponent, - RouterLink, - AsyncPipe, - TranslateModule, - ItemPageCcLicenseFieldComponent, - ], -}) -export class UntypedItemComponent extends BaseComponent {} diff --git a/src/themes/calabash/assets/i18n/en.json5 b/src/themes/calabash/assets/i18n/en.json5 deleted file mode 100644 index 2ed4bf41a84..00000000000 --- a/src/themes/calabash/assets/i18n/en.json5 +++ /dev/null @@ -1,13 +0,0 @@ -{ - "item.page.contributor.*": "Authors", - "item.page.contributor.author": "Authors", - "item.page.contributor.translator": "Translator", - "item.page.doi": "DOI", - "item.page.subject": "Subject Keywords", - "item.page.title": "Title", - "item.page.rights": "Rights", - "item.page.issue": "Issue", - "item.page.journal": "Journal Title", - "item.page.volume": "Volume", - "item.page.issn": "ISSN", -} diff --git a/src/themes/calabash/eager-theme.module.ts b/src/themes/calabash/eager-theme.module.ts deleted file mode 100644 index 5f3dc6088d6..00000000000 --- a/src/themes/calabash/eager-theme.module.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; - -import { UntypedItemComponent } from './app/item-page/simple/item-types/untyped-item/untyped-item.component'; - - -/** - * Add components that use a custom decorator to ENTRY_COMPONENTS as well as DECLARATIONS. - * This will ensure that decorator gets picked up when the app loads - */ -const ENTRY_COMPONENTS = [ - UntypedItemComponent, -]; - -const DECLARATIONS = [ - ...ENTRY_COMPONENTS, -]; - -@NgModule({ - imports: [ - CommonModule, - ...DECLARATIONS, - ], - providers: [ - ...ENTRY_COMPONENTS.map((component) => ({ provide: component })), - ], -}) -/** - * This module is included in the main bundle that gets downloaded at first page load. So it should - * contain only the themed components that have to be available immediately for the first page load, - * and the minimal set of imports required to make them work. Anything you can cut from it will make - * the initial page load faster, but may cause the page to flicker as components that were already - * rendered server side need to be lazy-loaded again client side - * - * Themed EntryComponents should also be added here - */ -export class EagerThemeModule { -} diff --git a/src/themes/calabash/styles/_global-styles.scss b/src/themes/calabash/styles/_global-styles.scss deleted file mode 100644 index c07db76527e..00000000000 --- a/src/themes/calabash/styles/_global-styles.scss +++ /dev/null @@ -1,69 +0,0 @@ -// Add any global css for the FDA theme here - -// imports the base global style -@import '../../../styles/_global-styles.scss'; - -.facet-filter, -.setting-option, -.advanced-search { - background-color: var(--bs-light); - border-radius: var(--bs-border-radius); - - &.p-3 { - // Needs !important because the original bootstrap class uses it - padding-top: 0.5rem !important; - padding-bottom: 0.5rem !important; - } - - .badge-secondary { - background-color: var(--bs-primary); - } - - h4, - .h4 { - font-size: 1.1rem - } -} - -#desktop-navbar.navbar-expand { - justify-content: space-around !important; -} - -h1 { - margin-top: 1rem; - margin-bottom: 1.5rem; -} - -.comcol-browse-label { - display: none; -} - -.community-page, -.collection-page { - .border-bottom { - border-bottom: none !important; - } - - .content-with-optional-title h2 { - font-size: 1.25rem; - } -} - -.comcol-page-browse-section h3 { - margin-top: 3rem; -} - -.fda-syllabus-header { - display: none; -} - -li:first-of-type .fda-syllabus-header { - display: flex; - padding-bottom: 1.5rem; - text-transform: uppercase; - font-size: .9rem; - font-weight: bold; -} -.pagination { - margin-top:2rem; -} diff --git a/src/themes/calabash/styles/_theme_css_variable_overrides.scss b/src/themes/calabash/styles/_theme_css_variable_overrides.scss deleted file mode 100644 index db9797a586a..00000000000 --- a/src/themes/calabash/styles/_theme_css_variable_overrides.scss +++ /dev/null @@ -1,38 +0,0 @@ -// Override or add CSS variables for your theme here - -:root { - - @include media-breakpoint-up(md) { - --ds-header-logo-height: 40px; - --ds-header-height: 80px; - } - @include media-breakpoint-down(sm) { - --ds-header-logo-height: 50px; - --ds-header-height: 90px; - } - - --ds-banner-text-background: #{$nyu-deep-violet}; - --ds-banner-background-gradient-width: 300px; - - --ds-header-navbar-border-bottom-height: 5px; - - --ds-header-bg: #{$nyu-violet}; - - --ds-navbar-link-color: #{$white}; - --ds-navbar-link-color-hover: #{$white}; - --ds-header-icon-color: #{$white}; - --ds-header-icon-color-hover: #{$white}; - /* set the next two properties as `--ds-header-navbar-border-bottom-*` - in order to keep the bottom border of the header when navbar is expanded */ - - --ds-expandable-navbar-border-top-color: #{$white}; - --ds-expandable-navbar-border-top-height: 0; - --ds-expandable-navbar-padding-top: 0; - - --ds-item-page-img-field-default-inline-height: 24px; - --ds-item-page-img-field-ror-inline-height: var(--ds-item-page-img-field-default-inline-height); - - --ds-footer-bg: #{$nyu-violet}; - -} - diff --git a/src/themes/calabash/styles/_theme_sass_variable_overrides.scss b/src/themes/calabash/styles/_theme_sass_variable_overrides.scss deleted file mode 100644 index dbbab76d3ab..00000000000 --- a/src/themes/calabash/styles/_theme_sass_variable_overrides.scss +++ /dev/null @@ -1,92 +0,0 @@ -// DSpace works with CSS variables for its own components, and has a mapping of all bootstrap Sass -// variables to CSS equivalents (see src/styles/_bootstrap_variables_mapping.scss). However Bootstrap -// still uses Sass variables internally. So if you want to override Bootstrap (or other sass -// variables) you can do so here. Their CSS counterparts will include the changes you make here. - -// When this file is going to be compiled, internal Bootstrap variables won't have been declared yet, -// therefore if you want to use any Bootstrap variable you also need to declare it here. - -// All SASS variables from the base theme are also included here. Do not use the '!default' flag -// here if you want to override them. - - -/*** FONT FAMILIES ***/ - -@import url('https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,200;0,300;0,400;0,600;0,700;0,800;1,200;1,300;1,400;1,600;1,700;1,800&display=swap'); - -$font-family-sans-serif: 'Lato', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; - -$h1-font-size: 1.8rem; -/*** SEMANTIC COLOR SCHEME ***/ - -// Gray scale (uncomment the variables that you want to override or that you need to use in this file) -//$white: #fff; -//$gray-100: #f8f9fa; -//$gray-200: #e9ecef; -//$gray-300: #dee2e6; -//$gray-400: #ced4da; -//$gray-500: #adb5bd; -//$gray-600: #6c757d; -//$gray-700: #495057; -//$gray-800: #343a40; -//$gray-900: #212529; -//$black: #000; - -// Other colors (uncomment the variables that you want to override or that you need to use in this file) -//$blue: #007bff !default; -//$indigo: #6610f2 !default; -//$purple: #6f42c1 !default; -//$pink: #e83e8c !default; -//$red: #dc3545 !default; -//$orange: #fd7e14 !default; -//$yellow: #ffc107 !default; -//$green: #28a745 !default; -//$teal: #20c997 !default; -//$cyan: #17a2b8 !default; - -// Define or override other colors here -// ... - -// Override semantic colors here -$primary: #43515f; // Gray -$secondary: #495057; // As Bootstrap $gray-700 -$success: #92c642; // Lime -$info: #1e6f90; // Light blue -$warning: #ec9433; // Orange -$danger: #cf4444; // Red -$light: #f8f9fa; // As Bootstrap $gray-100 -$dark: #43515f; // Gray - -// Add new semantic colors here (you don't need to add existing semantic colors) -$theme-colors: ( - // ... -); - -$nyu-violet: #57068c; -$nyu-deep-violet: #330662; -/*** OTHER BOOTSTRAP VARIABLES ***/ - -// The yiq lightness value that determines when the lightness of color changes from "dark" to "light". Acceptable values are between 0 and 255. -$yiq-contrasted-threshold: 170; - -$body-color: #343a40; // As Bootstrap $gray-800 - -$link-color: $nyu-violet; // NYU Violet -$link-decoration: none; -$link-hover-color: darken($link-color, 15%); -$link-hover-decoration: underline; - -$table-accent-bg: #f8f9fa; // As Bootstrap $gray-100 -$table-hover-bg: #ced4da; // As Bootstrap $gray-400 - -$navbar-dark-color: #fff; - - - -/*** CUSTOM DSPACE VARIABLES ***/ -$ds-home-news-link-color: $nyu-violet; -$ds-header-navbar-border-bottom-color: $nyu-violet; -$ds-navbar-dropdown-bg: $nyu-violet; -$ds-navbar-dropdown-bg: $nyu-violet; -$ds-breadcrumb-link-color: #154E66 !default; -$ds-breadcrumb-link-active-color: #040D11 !default; diff --git a/src/themes/calabash/styles/theme.scss b/src/themes/calabash/styles/theme.scss deleted file mode 100644 index 05c96f33728..00000000000 --- a/src/themes/calabash/styles/theme.scss +++ /dev/null @@ -1,12 +0,0 @@ -// This file combines the other scss files in to one. You usually shouldn't edit this file directly - -@import './_theme_sass_variable_overrides.scss'; -@import '../../../styles/_variables.scss'; -@import '../../../styles/_mixins.scss'; -@import '../../../styles/helpers/font_awesome_imports.scss'; -@import '../../../styles/_vendor.scss'; -@import '../../../styles/_custom_variables.scss'; -@import './_theme_css_variable_overrides.scss'; -@import '../../../styles/bootstrap_variables_mapping.scss'; -@import '../../../styles/_truncatable-part.component.scss'; -@import './_global-styles.scss'; diff --git a/src/themes/eager-themes.module.ts b/src/themes/eager-themes.module.ts index 9123bb67046..20f779d1d48 100644 --- a/src/themes/eager-themes.module.ts +++ b/src/themes/eager-themes.module.ts @@ -1,7 +1,7 @@ import { NgModule } from '@angular/core'; -import { EagerThemeModule as CalabashEagerThemeModule } from './calabash/eager-theme.module'; -// import { EagerThemeModule as CustomEagerThemeModule } from './custom/eager-theme.module'; +import { EagerThemeModule as CustomEagerThemeModule } from './custom/eager-theme.module'; +import { EagerThemeModule as DspaceEagerThemeModule } from './dspace/eager-theme.module'; import { EagerThemeModule as FDAEagerThemeModule } from './fda/eager-theme.module'; import { EagerThemeModule as GallatinSyllabiEagerThemeModule } from './gallatin-syllabi/eager-theme.module'; @@ -15,8 +15,9 @@ import { EagerThemeModule as GallatinSyllabiEagerThemeModule } from './gallatin- */ @NgModule({ imports: [ + DspaceEagerThemeModule, + CustomEagerThemeModule, FDAEagerThemeModule, - CalabashEagerThemeModule, GallatinSyllabiEagerThemeModule ], }) export class EagerThemesModule { } diff --git a/src/themes/fda/app/item-page/field-config/item-field-config.spec.ts b/src/themes/fda/app/item-page/field-config/item-field-config.spec.ts new file mode 100644 index 00000000000..136e13b0272 --- /dev/null +++ b/src/themes/fda/app/item-page/field-config/item-field-config.spec.ts @@ -0,0 +1,63 @@ +import { + COLLECTION_CONFIGS, + DEFAULT_CONFIG, + getConfigForCollection, +} from './item-field-config'; + +describe('Item Field Configuration', () => { + + describe('getConfigForCollection', () => { + + it('should return default config for unknown handle', () => { + const config = getConfigForCollection('unknown/handle'); + expect(config).toBe(DEFAULT_CONFIG); + }); + + it('should return default config for empty handle', () => { + const config = getConfigForCollection(''); + expect(config).toBe(DEFAULT_CONFIG); + }); + + it('should return default config for null handle', () => { + const config = getConfigForCollection(null as any); + expect(config).toBe(DEFAULT_CONFIG); + }); + + it('should have fields array in returned config', () => { + const config = getConfigForCollection('any/handle'); + expect(config.fields).toBeDefined(); + expect(Array.isArray(config.fields)).toBe(true); + expect(config.fields.length).toBeGreaterThan(0); + }); + }); + + describe('DEFAULT_CONFIG', () => { + + it('should have empty handles array', () => { + expect(DEFAULT_CONFIG.handles).toEqual([]); + }); + + it('should have empty i18nPrefix', () => { + expect(DEFAULT_CONFIG.i18nPrefix).toBe(''); + }); + + it('should have required default fields', () => { + const fieldNames = DEFAULT_CONFIG.fields.map(f => f.field); + expect(fieldNames).toContain('dc.title'); + expect(fieldNames).toContain('dc.identifier.uri'); + }); + }); + + describe('COLLECTION_CONFIGS', () => { + + it('should be an array', () => { + expect(Array.isArray(COLLECTION_CONFIGS)).toBe(true); + }); + + it('should have unique i18nPrefix for each config', () => { + const prefixes = COLLECTION_CONFIGS.map(c => c.i18nPrefix).filter(p => p); + const uniquePrefixes = [...new Set(prefixes)]; + expect(prefixes.length).toBe(uniquePrefixes.length); + }); + }); +}); diff --git a/src/themes/fda/app/item-page/field-config/item-field-config.ts b/src/themes/fda/app/item-page/field-config/item-field-config.ts new file mode 100644 index 00000000000..dd6bb1a3109 --- /dev/null +++ b/src/themes/fda/app/item-page/field-config/item-field-config.ts @@ -0,0 +1,266 @@ +export interface FieldConfig { +field: string; +labelKey: string; +type: 'text' | 'uri' | 'date' | 'authors'; +separator?: string; +} + +export interface CollectionConfig { +handles: string[]; +fields: FieldConfig[]; +i18nPrefix?: string; +} + +// ============================================ +// DEFAULT FIELDS +// ============================================ +const DEFAULT_FIELDS: FieldConfig[] = [ + { field: 'dc.title', labelKey: 'item.page.title', type: 'text' }, + { field: 'dc.title.alternative', labelKey: 'item.page.title.alternative', type: 'text' }, + { field: 'dc.contributor.author,dc.contributor,dc.creator', labelKey: 'item.page.contributor.*', type: 'authors' }, + { field: 'dc.subject', labelKey: 'item.page.subject', type: 'text', separator: '; ' }, + { field: 'dc.date.issued', labelKey: 'item.page.date.issued', type: 'date' }, + { field: 'dc.publisher', labelKey: 'item.page.publisher', type: 'text' }, + { field: 'dc.identifier.citation', labelKey: 'item.page.citation', type: 'uri' }, + { field: 'dc.relation.ispartofseries', labelKey: 'item.page.relation.ispartofseries', type: 'text' }, + { field: 'dc.description.abstract', labelKey: 'item.page.abstract', type: 'text' }, + { field: 'dc.description', labelKey: 'item.page.description', type: 'text' }, + { field: 'dc.identifier.govdoc', labelKey: 'item.page.identifier.govdoc', type: 'text' }, + { field: 'dc.identifier.uri', labelKey: 'item.page.uri', type: 'uri' }, + { field: 'dc.identifier.isbn', labelKey: 'item.page.isbn', type: 'text' }, + { field: 'dc.identifier.issn', labelKey: 'item.page.issn', type: 'text' }, + { field: 'dc.identifier.DOI', labelKey: 'item.page.doi', type: 'uri' }, + { field: 'dc.identifier.ismn', labelKey: 'item.page.ismn', type: 'text' }, + { field: 'dc.identifier', labelKey: 'item.page.identifier', type: 'text' }, + { field: 'dc.rights', labelKey: 'item.page.rights', type: 'text' }, +]; + +// ============================================ +// JONES COLLECTION FIELDS +// ============================================ +const JONES_FIELDS: FieldConfig[] = [ + { field: 'dc.title', labelKey: 'item.page.title', type: 'text' }, + { field: 'dc.contributor.author,dc.contributor', labelKey: 'item.page.contributor.*', type: 'authors' }, + { field: 'dc.subject', labelKey: 'item.page.subject', type: 'text', separator: '; ' }, + { field: 'dc.date.issued', labelKey: 'item.page.date.issued', type: 'date' }, + { field: 'dc.date.created', labelKey: 'item.page.date.created', type: 'date' }, + { field: 'dc.identifier.other', labelKey: 'item.page.identifier.other', type: 'text' }, + { field: 'dc.language.iso', labelKey: 'item.page.language.iso', type: 'text' }, + { field: 'dc.description', labelKey: 'item.page.description', type: 'text' }, + { field: 'dc.format.mimetype', labelKey: 'item.page.format.mimetype', type: 'text' }, + { field: 'dc.description.equipment', labelKey: 'item.page.description.equipment', type: 'text' }, + { field: 'dc.description.additional', labelKey: 'item.page.description.additional', type: 'text' }, + { field: 'dc.relation.isreferencedby', labelKey: 'item.page.relation.isreferencedby', type: 'text' }, + { field: 'dc.relation.uri', labelKey: 'item.page.relation.uri', type: 'uri' }, + { field: 'dc.rights', labelKey: 'item.page.rights', type: 'text' }, +]; + +// ============================================ +// RELICS COLLECTION FIELDS +// ============================================ +const RELICS_FIELDS: FieldConfig[] = [ + { field: 'dc.title', labelKey: 'item.page.title', type: 'text' }, + { field: 'dc.contributor.author,dc.contributor', labelKey: 'item.page.contributor.*', type: 'authors' }, + { field: 'dc.date.issued', labelKey: 'item.page.date.issued', type: 'date' }, + { field: 'dc.publisher', labelKey: 'item.page.publisher', type: 'text' }, + { field: 'dc.identifier.other', labelKey: 'item.page.identifier.other', type: 'text' }, + { field: 'dc.language.iso', labelKey: 'item.page.language.iso', type: 'text' }, + { field: 'dc.type', labelKey: 'item.page.type', type: 'text' }, + { field: 'dc.description', labelKey: 'item.page.description', type: 'text' }, + { field: 'dc.coverage.spatial', labelKey: 'item.page.coverage.spatial', type: 'text' }, + { field: 'dc.source', labelKey: 'item.page.source', type: 'text' }, + { field: 'dc.rights', labelKey: 'item.page.rights', type: 'text' }, +]; + +// ============================================ +// LAEFER COLLECTION FIELDS +// ============================================ +const LAEFER_FIELDS: FieldConfig[] = [ + { field: 'dc.title', labelKey: 'item.page.title', type: 'text' }, + { field: 'dc.title.alternative', labelKey: 'item.page.title.alternative', type: 'text' }, + { field: 'dc.contributor.author,dc.contributor', labelKey: 'item.page.contributor.*', type: 'authors' }, + { field: 'dc.subject', labelKey: 'item.page.subject', type: 'text', separator: '; ' }, + { field: 'dc.date.issued', labelKey: 'item.page.date.issued', type: 'date' }, + { field: 'dc.publisher', labelKey: 'item.page.publisher', type: 'text' }, + { field: 'dc.identifier.citation', labelKey: 'item.page.citation', type: 'text' }, + { field: 'dc.relation.ispartofseries', labelKey: 'item.page.relation.ispartofseries', type: 'text' }, + { field: 'dc.description.abstract', labelKey: 'item.page.abstract', type: 'text' }, + { field: 'dc.description', labelKey: 'item.page.description', type: 'text' }, + { field: 'dc.identifier.govdoc', labelKey: 'item.page.identifier.govdoc', type: 'text' }, + { field: 'dc.identifier.uri', labelKey: 'item.page.uri', type: 'uri' }, + { field: 'dc.identifier.isbn', labelKey: 'item.page.isbn', type: 'text' }, + { field: 'dc.identifier.issn', labelKey: 'item.page.issn', type: 'text' }, + { field: 'dc.identifier.ismn', labelKey: 'item.page.ismn', type: 'text' }, + { field: 'dc.identifier', labelKey: 'item.page.identifier', type: 'text' }, + { field: 'dc.rights', labelKey: 'item.page.rights', type: 'text' }, +]; + +// ============================================ +// TANDON COLLECTION FIELDS +// ============================================ +const TANDON_FIELDS: FieldConfig[] = [ + { field: 'dc.title', labelKey: 'item.page.title', type: 'text' }, + { field: 'dc.title.alternative', labelKey: 'item.page.title.alternative', type: 'text' }, + { field: 'dc.contributor.author,dc.contributor', labelKey: 'item.page.contributor.*', type: 'authors' }, + { field: 'dc.date.issued', labelKey: 'item.page.date.issued', type: 'date' }, + { field: 'dc.identifier.citation', labelKey: 'item.page.citation', type: 'text' }, + { field: 'dc.description.abstract', labelKey: 'item.page.abstract', type: 'text' }, + { field: 'dc.description.firstPage', labelKey: 'item.page.description.firstPage', type: 'text' }, + { field: 'dc.description.lastPage', labelKey: 'item.page.description.lastPage', type: 'text' }, + { field: 'dc.identifier.DOI', labelKey: 'item.page.doi', type: 'uri' }, + { field: 'dc.type', labelKey: 'item.page.type', type: 'text' }, +]; + +// ============================================ +// TANDON CAPSTONE COLLECTION FIELDS +// ============================================ +const TANDONCAPSTONE_FIELDS: FieldConfig[] = [ + { field: 'dc.title', labelKey: 'item.page.title', type: 'text' }, + { field: 'dc.contributor.author', labelKey: 'item.page.contributor.author', type: 'authors' }, + { field: 'dc.contributor.advisor', labelKey: 'item.page.contributor.advisor', type: 'authors' }, + { field: 'dc.date.issued', labelKey: 'item.page.date.issued', type: 'date' }, + { field: 'dc.description.abstract', labelKey: 'item.page.abstract', type: 'text' }, + { field: 'dc.identifier.URI', labelKey: 'item.page.uri', type: 'uri' }, + { field: 'dc.type', labelKey: 'item.page.type', type: 'text' }, +]; + +// ============================================ +// DNP COLLECTION FIELDS +// ============================================ +const DNP_FIELDS: FieldConfig[] = [ + { field: 'dc.title', labelKey: 'item.page.title', type: 'text' }, + { field: 'dc.title.alternative', labelKey: 'item.page.title.alternative', type: 'text' }, + { field: 'dc.description.abstract', labelKey: 'item.page.abstract', type: 'text' }, + { field: 'dc.contributor.author', labelKey: 'item.page.contributor.author', type: 'authors' }, + { field: 'dc.contributor.advisor', labelKey: 'item.page.contributor.advisor', type: 'authors' }, + { field: 'dc.description', labelKey: 'item.page.description', type: 'text' }, + { field: 'thesis.degree.name', labelKey: 'item.page.thesis.degree.name', type: 'text' }, + { field: 'thesis.degree.level', labelKey: 'item.page.thesis.degree.level', type: 'text' }, + { field: 'thesis.degree.discipline', labelKey: 'item.page.thesis.degree.discipline', type: 'text' }, + { field: 'dc.date.issued', labelKey: 'item.page.date.issued', type: 'date' }, + { field: 'thesis.degree.grantor', labelKey: 'item.page.thesis.degree.grantor', type: 'text' }, + { field: 'dc.language.iso', labelKey: 'item.page.language.iso', type: 'text' }, + { field: 'dc.subject.mesh', labelKey: 'item.page.subject.mesh', type: 'text', separator: '; ' }, + { field: 'dc.subject.cinahl', labelKey: 'item.page.subject.cinahl', type: 'text', separator: '; ' }, + { field: 'dc.subject.apa', labelKey: 'item.page.subject.apa', type: 'text', separator: '; ' }, + { field: 'dc.subject', labelKey: 'item.page.subject', type: 'text', separator: '; ' }, + { field: 'dc.type', labelKey: 'item.page.type', type: 'text' }, + { field: 'dc.format.medium', labelKey: 'item.page.format.medium', type: 'text' }, + { field: 'dc.description.sponsorship', labelKey: 'item.page.description.sponsorship', type: 'text' }, + { field: 'dc.identifier.uri', labelKey: 'item.page.uri', type: 'uri' }, + { field: 'dc.rights', labelKey: 'item.page.rights', type: 'text' }, +]; + +// ============================================ +// CALABASH COLLECTION FIELDS +// ============================================ +const CALABASH_FIELDS: FieldConfig[] = [ + { field: 'dc.title', labelKey: 'item.page.title', type: 'text' }, + { field: 'dc.title.alternative', labelKey: 'item.page.title.alternative', type: 'text' }, + { field: 'dc.contributor.author', labelKey: 'item.page.contributor.author', type: 'authors' }, + { field: 'dc.contributor.translator', labelKey: 'item.page.contributor.translator', type: 'authors' }, + { field: 'dc.subject', labelKey: 'item.page.subject', type: 'text', separator: '; ' }, + { field: 'dc.date.issued', labelKey: 'item.page.date.issued', type: 'date' }, + { field: 'dc.publisher', labelKey: 'item.page.publisher', type: 'text' }, + { field: 'dc.identifier.citation', labelKey: 'item.page.citation', type: 'text' }, + { field: 'dc.identifier.DOI', labelKey: 'item.page.doi', type: 'uri' }, + { field: 'dc.description.abstract', labelKey: 'item.page.abstract', type: 'text' }, + { field: 'dc.description', labelKey: 'item.page.description', type: 'text' }, + { field: 'dc.identifier.uri', labelKey: 'item.page.uri', type: 'uri' }, + { field: 'dc.identifier.issn', labelKey: 'item.page.issn', type: 'text' }, + { field: 'dc.rights', labelKey: 'item.page.rights', type: 'text' }, + { field: 'prism.publicationName', labelKey: 'item.page.prism.publicationName', type: 'text' }, + { field: 'prism.issueIdentifier', labelKey: 'item.page.prism.issueIdentifier', type: 'text' }, + { field: 'prism.volume', labelKey: 'item.page.prism.volume', type: 'text' }, + { field: 'prism.startingPage', labelKey: 'item.page.prism.startingPage', type: 'text' }, + { field: 'prism.endingPage', labelKey: 'item.page.prism.endingPage', type: 'text' }, +]; + +// ============================================ +// OPENSCHOLARSHIP COLLECTION FIELDS +// ============================================ +const OPENSCHOLARSHIP_FIELDS: FieldConfig[] = [ + { field: 'dc.title', labelKey: 'item.page.title', type: 'text' }, + { field: 'dc.contributor.author', labelKey: 'item.page.contributor.author', type: 'authors' }, + { field: 'dc.date.issued', labelKey: 'item.page.date.issued', type: 'date' }, + { field: 'dc.description.abstract', labelKey: 'item.page.abstract', type: 'text' }, + { field: 'dc.description.sponsorship', labelKey: 'item.page.description.sponsorship', type: 'text' }, + { field: 'dc.identifier.DOI', labelKey: 'item.page.doi', type: 'text' }, + { field: 'dc.rights', labelKey: 'item.page.rights', type: 'text' }, + { field: 'dc.subject', labelKey: 'item.page.subject', type: 'text', separator: '; ' }, +]; + +// ============================================ +// SYLLABI COLLECTION FIELDS +// ============================================ +const SYLLABI_FIELDS: FieldConfig[] = [ + { field: 'dc.title', labelKey: 'item.page.title', type: 'text' }, + { field: 'dc.contributor.instructor', labelKey: 'item.page.contributor.instructor', type: 'authors' }, + { field: 'dc.date.issued', labelKey: 'item.page.date.issued', type: 'date' }, + { field: 'dc.description.abstract', labelKey: 'item.page.abstract', type: 'text' }, + { field: 'dc.identifier.coursenumber', labelKey: 'item.page.identifier.coursenumber', type: 'text' }, + { field: 'dc.description.semester', labelKey: 'item.page.description.semester', type: 'text' }, + { field: 'dc.rights', labelKey: 'item.page.rights', type: 'text' }, +]; + +// ============================================ +// COLLECTION CONFIGURATIONS +// ============================================ +export const COLLECTION_CONFIGS: CollectionConfig[] = [ + { + handles: ['2451/44191', '2451/44193', '2451/44466'], + fields: JONES_FIELDS, + i18nPrefix: 'jones', + }, + { + handles: ['2451/44428'], + fields: RELICS_FIELDS, + i18nPrefix: 'relics', + }, + { + handles: ['2451/37860', '2451/42280'], + fields: LAEFER_FIELDS, + i18nPrefix: 'laefer', + }, + { + handles: ['2451/60387'], + fields: TANDON_FIELDS, + i18nPrefix: 'tandon', + }, + { + handles: ['2451/62790', '2451/62791'], + fields: TANDONCAPSTONE_FIELDS, + i18nPrefix: 'tandoncapstone', + }, + { + handles: ['2451/62822'], + fields: DNP_FIELDS, + i18nPrefix: 'dnp', + }, + { + handles: ['2451/62242', '2451/62243', '2451/62244', '2451/62245', '2451/62246', '2451/62247', '2451/62248', '2451/62249', '2451/62250'], + fields: CALABASH_FIELDS, + i18nPrefix: 'calabash', + }, + { + handles: ['2451/63332'], + fields: OPENSCHOLARSHIP_FIELDS, + i18nPrefix: 'openscholarship', + }, + { + handles: ['2451/34841'], + fields: SYLLABI_FIELDS, + i18nPrefix: 'syllabi', + }, +]; + +export const DEFAULT_CONFIG: CollectionConfig = { + handles: [], + fields: DEFAULT_FIELDS, + i18nPrefix: '', +}; + +export function getConfigForCollection(handle: string): CollectionConfig { + const config = COLLECTION_CONFIGS.find(c => c.handles.includes(handle)); + return config || DEFAULT_CONFIG; +} diff --git a/src/themes/fda/app/item-page/simple/item-types/untyped-item/untyped-item.component.html b/src/themes/fda/app/item-page/simple/item-types/untyped-item/untyped-item.component.html index 668dc29466b..e466bf16275 100644 --- a/src/themes/fda/app/item-page/simple/item-types/untyped-item/untyped-item.component.html +++ b/src/themes/fda/app/item-page/simple/item-types/untyped-item/untyped-item.component.html @@ -1,8 +1,4 @@ - - -
+
@@ -21,197 +17,66 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -224,7 +89,12 @@

{{'item.page.title' | translate}}:  -

- -

-
{{'item.page.title.alternative' | translate}}:  - - -
{{'item.page.contributor.*' | translate}}:  - - -
{{'item.page.subject' | translate}}:  - {{object.allMetadataValues('dc.subject').join('; ')}} -
{{'item.page.date.issued' | translate}}:  - {{object.allMetadataValues('dc.date.issued')}} -
{{'item.page.publisher' | translate}}:  - - -
{{'item.page.citation' | translate}}:  - - -
{{'item.page.relation.ispartofseries' | translate}}:  - - -
{{'item.page.abstract' | translate}}:  - - -
{{'item.page.description' | translate}}:  - - -
{{'item.page.identifier.govdoc' | translate}}:  - - -
{{'item.page.uri' | translate}}:  - - -
{{'item.page.isbn' | translate}}:  - - -
{{'item.page.issn' | translate}}:  - - -
{{'item.page.doi' | translate}}:  - - -
{{'item.page.ismn' | translate}}:  - - -
{{'item.page.identifier' | translate}}:  - - -
{{'item.page.rights' | translate}}:  - - -
{{getI18nKey(fieldConfig.labelKey) | translate}}:  +

{{getFirstFieldValue(fieldConfig)}}

+
+ {{getI18nKey(fieldConfig.labelKey) | translate}}: + + + + {{author}} + + ; + +
{{getI18nKey(fieldConfig.labelKey) | translate}}:  + + {{getFirstFieldValue(fieldConfig)}} + +
{{getI18nKey(fieldConfig.labelKey) | translate}}:  + + {{getFieldValues(fieldConfig).join(fieldConfig.separator)}} + + + {{getFirstFieldValue(fieldConfig)}} + +
{{getI18nKey(fieldConfig.labelKey) | translate}}:  + {{getFirstFieldValue(fieldConfig)}} +
- + +
@@ -234,13 +104,6 @@

- -
- - {{'item.page.link.full' | translate}} - -
-
diff --git a/src/themes/fda/app/item-page/simple/item-types/untyped-item/untyped-item.component.spec.ts b/src/themes/fda/app/item-page/simple/item-types/untyped-item/untyped-item.component.spec.ts new file mode 100644 index 00000000000..1ed66a33a5d --- /dev/null +++ b/src/themes/fda/app/item-page/simple/item-types/untyped-item/untyped-item.component.spec.ts @@ -0,0 +1,174 @@ +describe('UntypedItemComponent Logic', () => { + + describe('extractHandle function', () => { + // Copy the extractHandle logic from your component + function extractHandle(handleOrUrl: string): string { + if (!handleOrUrl) { + return ''; + } + + if (handleOrUrl.includes('/handle/')) { + const parts = handleOrUrl.split('/handle/'); + return parts[parts.length - 1]; + } + + if (handleOrUrl.includes('://')) { + const parts = handleOrUrl.split('/'); + if (parts.length >= 2) { + return `${parts[parts.length - 2]}/${parts[parts.length - 1]}`; + } + } + + return handleOrUrl; + } + + it('should extract handle from localhost URL', () => { + const result = extractHandle('http://localhost:4000/handle/2451/48010'); + expect(result).toBe('2451/48010'); + }); + + it('should extract handle from production URL', () => { + const result = extractHandle('https://example.com/handle/2451/48010'); + expect(result).toBe('2451/48010'); + }); + + it('should return handle as-is if already in correct format', () => { + const result = extractHandle('2451/48010'); + expect(result).toBe('2451/48010'); + }); + + it('should handle empty string', () => { + const result = extractHandle(''); + expect(result).toBe(''); + }); + + it('should handle null', () => { + const result = extractHandle(null as any); + expect(result).toBe(''); + }); + }); + + describe('getI18nKey function', () => { + function getI18nKey(collectionConfig: any, baseKey: string): string { + if (collectionConfig.i18nPrefix) { + return `${collectionConfig.i18nPrefix}.${baseKey}`; + } + return baseKey; + } + + it('should return base key when no prefix', () => { + const config = { handles: [], fields: [], i18nPrefix: '' }; + const result = getI18nKey(config, 'item.page.title'); + expect(result).toBe('item.page.title'); + }); + + it('should return prefixed key when prefix exists', () => { + const config = { handles: [], fields: [], i18nPrefix: 'jones' }; + const result = getI18nKey(config, 'item.page.title'); + expect(result).toBe('jones.item.page.title'); + }); + + it('should handle relics prefix', () => { + const config = { handles: [], fields: [], i18nPrefix: 'relics' }; + const result = getI18nKey(config, 'item.page.description'); + expect(result).toBe('relics.item.page.description'); + }); + }); + + describe('getFieldValues function', () => { + function getFieldValues(mockObject: any, fieldConfig: any): string[] { + const fields = fieldConfig.field.split(','); + return mockObject.allMetadataValues(fields); + } + + it('should return values from metadata', () => { + const mockObject = { + allMetadataValues: (fields: string[]) => ['Test Title'], + }; + const fieldConfig = { field: 'dc.title', labelKey: 'test', type: 'text' }; + + const result = getFieldValues(mockObject, fieldConfig); + expect(result).toEqual(['Test Title']); + }); + + it('should handle multiple fields', () => { + const mockObject = { + allMetadataValues: (fields: string[]) => { + if (fields.includes('dc.contributor.author')) {return ['Author 1', 'Author 2'];} + return []; + }, + }; + const fieldConfig = { + field: 'dc.contributor.author,dc.creator', + labelKey: 'test', + type: 'authors', + }; + + const result = getFieldValues(mockObject, fieldConfig); + expect(result).toEqual(['Author 1', 'Author 2']); + }); + + it('should return empty array for non-existent fields', () => { + const mockObject = { + allMetadataValues: (fields: string[]) => [], + }; + const fieldConfig = { field: 'dc.nonexistent', labelKey: 'test', type: 'text' }; + + const result = getFieldValues(mockObject, fieldConfig); + expect(result).toEqual([]); + }); + }); + + describe('hasFieldValue function', () => { + function hasFieldValue(mockObject: any, fieldConfig: any): boolean { + const fields = fieldConfig.field.split(','); + const values = mockObject.allMetadataValues(fields); + return values.length > 0; + } + + it('should return true when values exist', () => { + const mockObject = { + allMetadataValues: (fields: string[]) => ['Value 1'], + }; + const fieldConfig = { field: 'dc.title', labelKey: 'test', type: 'text' }; + + expect(hasFieldValue(mockObject, fieldConfig)).toBe(true); + }); + + it('should return false when no values', () => { + const mockObject = { + allMetadataValues: (fields: string[]) => [], + }; + const fieldConfig = { field: 'dc.title', labelKey: 'test', type: 'text' }; + + expect(hasFieldValue(mockObject, fieldConfig)).toBe(false); + }); + }); + + describe('getFirstFieldValue function', () => { + function getFirstFieldValue(mockObject: any, fieldConfig: any): string { + const fields = fieldConfig.field.split(','); + return mockObject.firstMetadataValue(fields); + } + + it('should return first value', () => { + const mockObject = { + firstMetadataValue: (fields: string[]) => 'Test Title', + }; + const fieldConfig = { field: 'dc.title', labelKey: 'test', type: 'text' }; + + const result = getFirstFieldValue(mockObject, fieldConfig); + expect(result).toBe('Test Title'); + }); + + it('should return null for non-existent field', () => { + const mockObject = { + firstMetadataValue: (fields: string[]) => null, + }; + const fieldConfig = { field: 'dc.nonexistent', labelKey: 'test', type: 'text' }; + + const result = getFirstFieldValue(mockObject, fieldConfig); + expect(result).toBe(null); + }); + }); +}); diff --git a/src/themes/fda/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts b/src/themes/fda/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts index 03aa587c9e8..a2d924dbf24 100644 --- a/src/themes/fda/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts +++ b/src/themes/fda/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts @@ -1,16 +1,23 @@ import { AsyncPipe, + CommonModule, + NgFor, NgIf, } from '@angular/common'; import { ChangeDetectionStrategy, Component, + OnInit, } from '@angular/core'; -import { RouterLink } from '@angular/router'; +import { + RouterLink, + RouterModule, +} from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; +import { filter } from 'rxjs/operators'; -import { Context } from '../../../../../../../app/core/shared/context.model'; import { Item } from '../../../../../../../app/core/shared/item.model'; +import { getFirstSucceededRemoteDataPayload } from '../../../../../../../app/core/shared/operators'; import { ViewMode } from '../../../../../../../app/core/shared/view-mode.model'; import { CollectionsComponent } from '../../../../../../../app/item-page/field-components/collections/collections.component'; import { ThemedMediaViewerComponent } from '../../../../../../../app/item-page/media-viewer/themed-media-viewer.component'; @@ -29,24 +36,28 @@ import { MetadataFieldWrapperComponent } from '../../../../../../../app/shared/m import { listableObjectComponent } from '../../../../../../../app/shared/object-collection/shared/listable-object/listable-object.decorator'; import { ThemedResultsBackButtonComponent } from '../../../../../../../app/shared/results-back-button/themed-results-back-button.component'; import { ThemedThumbnailComponent } from '../../../../../../../app/thumbnail/themed-thumbnail.component'; +import { + CollectionConfig, + DEFAULT_CONFIG, + FieldConfig, + getConfigForCollection, +} from '../../../field-config/item-field-config'; /** * Component that represents an untyped Item page */ -@listableObjectComponent(Item, ViewMode.StandalonePage, Context.Any,'fda') +@listableObjectComponent(Item, ViewMode.StandalonePage, undefined,'fda') @Component({ selector: 'ds-untyped-item', - // styleUrls: ['./untyped-item.component.scss'], - styleUrls: [ - './untyped-item.component.scss', - ], - // templateUrl: './untyped-item.component.html', - templateUrl: -'./untyped-item.component.html', + styleUrls: ['./untyped-item.component.scss'], + templateUrl:'./untyped-item.component.html', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [ NgIf, + NgFor, + CommonModule, + RouterModule, ThemedResultsBackButtonComponent, MiradorViewerComponent, ThemedItemPageTitleFieldComponent, @@ -67,4 +78,95 @@ import { ThemedThumbnailComponent } from '../../../../../../../app/thumbnail/the ItemPageCcLicenseFieldComponent, ], }) -export class UntypedItemComponent extends BaseComponent {} +export class UntypedItemComponent extends BaseComponent implements OnInit { + + collectionConfig: CollectionConfig = DEFAULT_CONFIG; + collectionHandle = ''; + + ngOnInit(): void { + super.ngOnInit(); + this.loadCollectionConfig(); + } + + private loadCollectionConfig(): void { + if (this.object?.owningCollection) { + this.object.owningCollection.pipe( + getFirstSucceededRemoteDataPayload(), + filter(collection => !!collection), + ).subscribe(collection => { + if (collection?.handle) { + // Extract handle from URL (e.g., "http://localhost:4000/handle/2451/48010" → "2451/48010") + this.collectionHandle = this.extractHandle(collection.handle); + this.collectionConfig = getConfigForCollection(this.collectionHandle); + + console.log('Extracted handle:', this.collectionHandle); + console.log('Config loaded:', this.collectionConfig.i18nPrefix); + + //this.cdr.markForCheck(); + } + }); + } + } + + /** + * Get metadata values for a field config + */ + getFieldValues(fieldConfig: FieldConfig): string[] { + const fields = fieldConfig.field.split(','); + return this.object.allMetadataValues(fields); + } + + /** + * Get first metadata value for a field config + */ + getFirstFieldValue(fieldConfig: FieldConfig): string { + const fields = fieldConfig.field.split(','); + return this.object.firstMetadataValue(fields); + } + + /** + * Check if field has values + */ + hasFieldValue(fieldConfig: FieldConfig): boolean { + return this.getFieldValues(fieldConfig).length > 0; + } + + /** + * Get i18n key with collection prefix fallback + * First tries collection-specific key, then falls back to default + */ + getI18nKey(baseKey: string): string { + if (this.collectionConfig.i18nPrefix) { + // Return prefixed key - Angular translate pipe will handle fallback + return `${this.collectionConfig.i18nPrefix}.${baseKey}`; + } + return baseKey; + } + + /** + * Extract handle from URL or return as-is if already in correct format + * "http://localhost:4000/handle/2451/48010" → "2451/48010" + */ + private extractHandle(handleOrUrl: string): string { + if (!handleOrUrl) { + return ''; + } + + // If it's a URL containing "/handle/", extract the last two segments + if (handleOrUrl.includes('/handle/')) { + const parts = handleOrUrl.split('/handle/'); + return parts[parts.length - 1]; // Returns "2451/48010" + } + + // If it contains "://", it's a URL - get last two path segments + if (handleOrUrl.includes('://')) { + const parts = handleOrUrl.split('/'); + if (parts.length >= 2) { + return `${parts[parts.length - 2]}/${parts[parts.length - 1]}`; + } + } + + // Already in correct format + return handleOrUrl; + } +} diff --git a/src/themes/fda/assets/i18n/en.json5 b/src/themes/fda/assets/i18n/en.json5 new file mode 100644 index 00000000000..e85ef2bc5de --- /dev/null +++ b/src/themes/fda/assets/i18n/en.json5 @@ -0,0 +1,178 @@ +{ + // Default labels + "item.page.title": "Title", + "item.page.date.issued": "Issue Date", + "item.page.contributor.*": "Authors", + "item.page.description": "Description", + + + // Jones Collection + "jones.item.page.title": "Title", + "jones.item.page.contributor.*": "Authors", + "jones.item.page.date.issued": "Date of digital object", + "jones.item.page.date.created": "Date of object depicted", + "jones.item.page.identifier.other": "Technical designation of object", + "jones.item.page.language.iso": "Language", + "jones.item.page.description": "Description of particular view", + "jones.item.page.format.mimetype": "File format", + "jones.item.page.description.equipment": "Technical specifications", + "jones.item.page.description.additional": "Additional description", + "jones.item.page.relation.isreferencedby": "Bibliographic citation", + "jones.item.page.relation.uri": "Citation link", + "jones.item.page.rights": "Rights", + "jones.item.page.subject": "Subject Keywords", + + // Relics Collection + "relics.item.page.title": "Title", + "relics.item.page.contributor.*": "Authors", + "relics.item.page.date.issued": "Date", + "relics.item.page.publisher": "Publisher", + "relics.item.page.type": "Type", + "relics.item.page.coverage.spatial": "Country", + "relics.item.page.source": "Source", + "relics.item.page.description": "Description", + "relics.item.page.rights": "Rights", + + // Tandon Collection + "tandon.item.page.title": "Title", + "tandon.item.page.contributor.*": "Authors", + "tandon.item.page.date.issued": "Date Issued", + "tandon.item.page.citation": "Citation", + "tandon.item.page.abstract": "Abstract", + "tandon.item.page.description.firstPage": "First Page", + "tandon.item.page.description.lastPage": "Last Page", + "tandon.item.page.doi": "DOI", + "tandon.item.page.type": "Type", + + // Tandon Capstone Collection + "tandoncapstone.item.page.title": "Title", + "tandoncapstone.item.page.contributor.author": "Authors", + "tandoncapstone.item.page.contributor.advisor": "Capstone Professor", + "tandoncapstone.item.page.date.issued": "Date Issued", + "tandoncapstone.item.page.language.iso": "Language", + "tandoncapstone.item.page.abstract": "Abstract", + "tandoncapstone.item.page.type": "Type", + "tandoncapstone.item.page.format.medium": "Medium", + "tandoncapstone.item.page.format.Extent": "Extent", + "tandoncapstone.item.page.uri": "URI", + + // DNP Collection + "dnp.item.page.title": "Title", + "dnp.item.page.title.alternative": "Alternate Title", + "dnp.item.page.contributor.author": "Author", + "dnp.item.page.contributor.advisor": "DNP Project Team Member", + "dnp.item.page.date.issued": "Degree Year", + "dnp.item.page.description": "Description", + "dnp.item.page.abstract": "Abstract", + "dnp.item.page.description.sponsorship": "Sponsorship", + "dnp.item.page.uri": "FDA Handle", + "dnp.item.page.subject": "Keyword", + "dnp.item.page.subject.mesh": "MeSH term", + "dnp.item.page.subject.cinahl": "CINAHL term", + "dnp.item.page.subject.apa": "APA Thesaurus term", + "dnp.item.page.rights": "Rights", + "dnp.item.page.type": "Type", + "dnp.item.page.format.medium": "Medium", + "dnp.item.page.language.iso": "Language", + "dnp.item.page.thesis.degree.name": "Degree", + "dnp.item.page.thesis.degree.level": "Degree Level", + "dnp.item.page.thesis.degree.discipline": "Discipline", + "dnp.item.page.thesis.degree.grantor": "Grantor", + + // OpenScholarship Collection + "openscholarship.item.page.title": "Title", + "openscholarship.item.page.contributor.author": "Authors", + "openscholarship.item.page.date.issued": "Date Issued", + "openscholarship.item.page.abstract": "Abstract", + "openscholarship.item.page.description.sponsorship": "Sponsorship", + "openscholarship.item.page.doi": "DOI", + "openscholarship.item.page.rights": "Rights", + "openscholarship.item.page.subject": "Subject", + + // Laefer Collection (uses default labels mostly) + "laefer.item.page.title": "Title", + "laefer.item.page.contributor.*": "Authors", + "laefer.item.page.contributor.author": "Authors", + "laefer.item.page.contributor.editor": "Editors", + "laefer.item.page.date.issued": "Issue Date", + "laefer.item.page.description": "Description", + "laefer.item.page.abstract": "Abstract", + "laefer.item.page.identifier": "Other Identifiers", + "laefer.item.page.citation": "Citation", + "laefer.item.page.identifier.govdoc": "Gov't Doc #", + "laefer.item.page.isbn": "ISBN", + "laefer.item.page.ismn": "ISMN", + "laefer.item.page.issn": "ISSN", + "laefer.item.page.uri": "URI", + "laefer.item.page.publisher": "Publisher", + "laefer.item.page.relation.ispartofseries": "Series/Report no.", + "laefer.item.page.subject": "Keywords", + "laefer.item.page.title.alternative": "Other Titles", + "laefer.item.page.rights": "Rights", + + // Calabash Collection + "calabash.item.page.title": "Title", + "calabash.item.page.contributor.*": "Authors", + "calabash.item.page.contributor.author": "Authors", + "calabash.item.page.contributor.translator": "Translator", + "calabash.item.page.date.issued": "Issue Date", + "calabash.item.page.description": "Description", + "calabash.item.page.abstract": "Abstract", + "calabash.item.page.identifier": "Other Identifiers", + "calabash.item.page.citation": "Citation", + "calabash.item.page.issn": "ISSN", + "calabash.item.page.uri": "URI", + "calabash.item.page.doi": "DOI", + "calabash.item.page.publisher": "Publisher", + "calabash.item.page.subject": "Subject Keywords", + "calabash.item.page.title.alternative": "Other Titles", + "calabash.item.page.rights": "Rights", + "calabash.item.page.type": "Type", + "calabash.item.page.prism.endingPage": "Last Page", + "calabash.item.page.prism.issueIdentifier": "Issue", + "calabash.item.page.prism.publicationName": "Journal Title", + "calabash.item.page.prism.startingPage": "First Page", + "calabash.item.page.prism.volume": "Volume", + + // Syllabi Collection + "syllabi.item.page.title": "Title", + "syllabi.item.page.contributor.instructor": "Instructor(s)", + "syllabi.item.page.identifier.coursenumber": "Course Number", + "syllabi.item.page.description.semester": "Term", + "syllabi.item.page.subject": "Keywords", + "syllabi.item.page.uri": "URI", + "syllabi.item.page.date.issued": "Date", + "syllabi.item.page.rights": "Rights", + + // Default labels (add missing ones) + "item.page.type": "Type", + "item.page.source": "Source", + "item.page.coverage.spatial": "Location", + "item.page.identifier.other": "Other Identifier", + "item.page.language.iso": "Language", + "item.page.format.mimetype": "Format", + "item.page.description.equipment": "Equipment", + "item.page.description.additional": "Additional Information", + "item.page.relation.isreferencedby": "Referenced By", + "item.page.relation.uri": "Related Link", + "item.page.description.firstPage": "First Page", + "item.page.description.lastPage": "Last Page", + "item.page.contributor.advisor": "Advisor", + "item.page.contributor.instructor": "Instructor", + "item.page.thesis.degree.name": "Degree", + "item.page.thesis.degree.level": "Degree Level", + "item.page.thesis.degree.discipline": "Discipline", + "item.page.thesis.degree.grantor": "Grantor", + "item.page.subject.mesh": "MeSH Terms", + "item.page.subject.cinahl": "CINAHL Terms", + "item.page.subject.apa": "APA Thesaurus Terms", + "item.page.description.sponsorship": "Sponsorship", + "item.page.format.medium": "Medium", + "item.page.prism.publicationName": "Publication Name", + "item.page.prism.issueIdentifier": "Issue", + "item.page.prism.volume": "Volume", + "item.page.prism.startingPage": "Starting Page", + "item.page.prism.endingPage": "Ending Page", + "item.page.identifier.coursenumber": "Course Number", + "item.page.description.semester": "Semester", +} diff --git a/src/themes/fda/eager-theme.module.ts b/src/themes/fda/eager-theme.module.ts index 3c50f96a70d..b212541cbc4 100644 --- a/src/themes/fda/eager-theme.module.ts +++ b/src/themes/fda/eager-theme.module.ts @@ -39,6 +39,7 @@ const DECLARATIONS = [ RootModule, ...DECLARATIONS, ], + declarations: [], providers: [ ...ENTRY_COMPONENTS.map((component) => ({ provide: component })), ], diff --git a/src/themes/gallatin-syllabi/assets/i18n/en.json5 b/src/themes/gallatin-syllabi/assets/i18n/en.json5 new file mode 100644 index 00000000000..5f90050e97f --- /dev/null +++ b/src/themes/gallatin-syllabi/assets/i18n/en.json5 @@ -0,0 +1,10 @@ +{ + "item.page.title": "Title", + "item.page.contributor.instructor": "Instructor(s)", + "item.page.identifier.coursenumber": "Course Number", + "item.page.description.semester": "Term", + "item.page.subject": "Keywords", + "item.page.identifier.uri": "URI", + "item.page.date.issued": "Date", + "item.page.rights": "Rights", +}