diff --git a/Dockerfile.karma b/Dockerfile.karma deleted file mode 100644 index 64fef20d69b..00000000000 --- a/Dockerfile.karma +++ /dev/null @@ -1,12 +0,0 @@ -# Karma/headless-Chrome test runner image for reproducing the dropdown bugs. -# Node 20 (matches the project) + Chromium for ChromeHeadlessCI. -FROM node:20-bookworm - -RUN apt-get update \ - && apt-get install -y --no-install-recommends chromium \ - && rm -rf /var/lib/apt/lists/* - -ENV CHROME_BIN=/usr/bin/chromium -ENV CHROMIUM_FLAGS="--no-sandbox --disable-gpu" - -WORKDIR /app diff --git a/demo/.gitignore b/demo/.gitignore deleted file mode 100644 index c2658d7d1b3..00000000000 --- a/demo/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/demo/gif.mjs b/demo/gif.mjs deleted file mode 100644 index c3c608d5031..00000000000 --- a/demo/gif.mjs +++ /dev/null @@ -1,69 +0,0 @@ -// Builds animated GIFs (before.gif / after.gif) of the dropdown behaviour, -// so they can be embedded inline in a GitHub PR comment. No ffmpeg required. -import { chromium } from 'playwright'; -import { pathToFileURL, fileURLToPath } from 'node:url'; -import { dirname, join } from 'node:path'; -import { writeFileSync } from 'node:fs'; -import { PNG } from 'pngjs'; -import gifenc from 'gifenc'; - -const { GIFEncoder, quantize, applyPalette } = gifenc; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const pageUrl = (mode) => pathToFileURL(join(__dirname, 'index.html')).href + '?mode=' + mode; -const size = { width: 820, height: 600 }; -const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); - -async function frames(mode) { - const browser = await chromium.launch(); - const page = await browser.newPage({ viewport: size }); - await page.goto(pageUrl(mode)); - const out = []; - - const cap = async (tag, text, delay, action) => { - if (action) { await action(); } - await page.evaluate(([t, x]) => window.setCaption(t, x), [tag, text]); - await sleep(350); - const png = PNG.sync.read(await page.screenshot()); - out.push({ rgba: new Uint8Array(png.data), w: png.width, h: png.height, delay }); - }; - - const label = mode === 'after' ? 'AFTER (fixed)' : 'BEFORE (PR #26)'; - await cap(label, 'Repeatable \u201CFunder\u201D field \u2014 authority-controlled.', 1700); - await cap('Step 1', 'Row 1: open the funder dropdown.', 1200, () => page.click('[data-testid="input-0"]')); - await cap('Step 1', 'Choose \u201CNational Science Foundation\u201D.', 1300, () => page.click('[data-testid="opt-0-nsf"]')); - await cap('Step 2', 'Add a second funder row.', 1100, () => page.click('[data-testid="add"]')); - await cap('Step 2', 'Row 2: open the dropdown.', 1300, () => page.click('[data-testid="input-1"]')); - - if (mode === 'before') { - await cap('Bug', 'BEFORE: the already-used funder is NOT greyed out.', 2100); - await cap('Bug', 'Select the SAME funder again\u2026', 1300, () => page.click('[data-testid="opt-1-nsf"]')); - await cap('Result', '\u26A0 Duplicate persisted \u2014 same authority twice.', 2700); - } else { - await cap('Fix', 'AFTER: the used funder is greyed out (disabled).', 2200); - await cap('Fix', 'Clicking it does nothing \u2014 blocked.', 1500, () => page.click('[data-testid="opt-1-nsf"]')); - await cap('Fix', 'Pick a different funder instead.', 1300, () => page.click('[data-testid="opt-1-erc"]')); - await cap('Result', '\u2713 No duplicates \u2014 distinct funders.', 2700); - } - - await browser.close(); - return out; -} - -async function build(mode) { - const fs = await frames(mode); - const gif = GIFEncoder(); - for (const f of fs) { - const palette = quantize(f.rgba, 256); - const index = applyPalette(f.rgba, palette); - gif.writeFrame(index, f.w, f.h, { palette, delay: f.delay }); - } - gif.finish(); - const target = join(__dirname, 'videos', mode + '.gif'); - writeFileSync(target, gif.bytes()); - console.log('Saved', target, (gif.bytes().length / 1024).toFixed(0) + ' KB'); -} - -await build('before'); -await build('after'); -console.log('Done.'); diff --git a/demo/index.html b/demo/index.html deleted file mode 100644 index 2fe70e82868..00000000000 --- a/demo/index.html +++ /dev/null @@ -1,257 +0,0 @@ - - - - - - - Repeatable dropdown – duplicate prevention - - - -
-

Submission form — repeatable “Funder” dropdown

- -
Funder (repeatable, authority-controlled)
-
- - -
-
- - - - diff --git a/demo/package.json b/demo/package.json deleted file mode 100644 index e07981156af..00000000000 --- a/demo/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "dropdown-dup-demo", - "version": "1.0.0", - "private": true, - "type": "module", - "description": "Records before/after videos of the repeatable-dropdown duplicate fix (PR #26).", - "scripts": { - "record": "node record.mjs" - }, - "devDependencies": { - "playwright": "1.48.2" - }, - "dependencies": { - "gifenc": "^1.0.3", - "pngjs": "^7.0.0" - } -} diff --git a/demo/record.mjs b/demo/record.mjs deleted file mode 100644 index 79845efc1e7..00000000000 --- a/demo/record.mjs +++ /dev/null @@ -1,82 +0,0 @@ -// Records BEFORE and AFTER videos of the repeatable-dropdown duplicate fix. -// Usage: npm install && npx playwright install chromium && node record.mjs -import { chromium } from 'playwright'; -import { pathToFileURL } from 'node:url'; -import { fileURLToPath } from 'node:url'; -import { dirname, join } from 'node:path'; -import { renameSync, mkdirSync, existsSync } from 'node:fs'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const pageUrl = (mode) => pathToFileURL(join(__dirname, 'index.html')).href + '?mode=' + mode; -const outDir = join(__dirname, 'videos'); -const size = { width: 940, height: 720 }; - -if (!existsSync(outDir)) { - mkdirSync(outDir, { recursive: true }); -} - -const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); - -async function step(page, tag, text, ms = 1500) { - await page.evaluate(([t, x]) => window.setCaption(t, x), [tag, text]); - await sleep(ms); -} - -async function record(mode) { - const browser = await chromium.launch(); - const context = await browser.newContext({ - viewport: size, - recordVideo: { dir: outDir, size }, - }); - const page = await context.newPage(); - await page.goto(pageUrl(mode)); - await sleep(1200); - - const label = mode === 'after' ? 'AFTER (fixed)' : 'BEFORE (merged PR #26)'; - await step(page, label, 'Repeatable “Funder” field. Let\u2019s add two rows.', 2000); - - // Row 1 -> select National Science Foundation - await step(page, 'Step 1', 'Row 1: open the funder dropdown.', 1200); - await page.click('[data-testid="input-0"]'); - await sleep(700); - await step(page, 'Step 1', 'Choose “National Science Foundation”.', 1400); - await page.click('[data-testid="opt-0-nsf"]'); - await sleep(900); - - // Add row 2 - await step(page, 'Step 2', 'Add a second funder row.', 1300); - await page.click('[data-testid="add"]'); - await sleep(700); - await step(page, 'Step 2', 'Row 2: open the dropdown.', 1200); - await page.click('[data-testid="input-1"]'); - await sleep(900); - - if (mode === 'before') { - await step(page, 'Bug', 'BEFORE: the already-used funder is NOT greyed out\u2026', 2200); - await step(page, 'Bug', 'Select the SAME funder again.', 1400); - await page.click('[data-testid="opt-1-nsf"]'); - await sleep(900); - await step(page, 'Result', '\u26A0 Duplicate persisted \u2014 same authority in two rows.', 3200); - } else { - await step(page, 'Fix', 'AFTER: the already-used funder is greyed out (disabled).', 2400); - await step(page, 'Fix', 'Clicking it does nothing \u2014 duplicate is blocked.', 1600); - await page.click('[data-testid="opt-1-nsf"]'); - await sleep(900); - await step(page, 'Fix', 'Pick a different funder instead: “European Research Council”.', 1600); - await page.click('[data-testid="opt-1-erc"]'); - await sleep(900); - await step(page, 'Result', '\u2713 No duplicates \u2014 every row references a distinct funder.', 3200); - } - - const videoPath = await page.video().path(); - await context.close(); - await browser.close(); - - const target = join(outDir, mode + '.webm'); - renameSync(videoPath, target); - console.log('Saved', target); -} - -await record('before'); -await record('after'); -console.log('Done. Videos in', outDir); diff --git a/demo/screenshot.mjs b/demo/screenshot.mjs deleted file mode 100644 index 14a1fd80929..00000000000 --- a/demo/screenshot.mjs +++ /dev/null @@ -1,43 +0,0 @@ -// Captures still screenshots of the decisive BEFORE/AFTER states. -import { chromium } from 'playwright'; -import { pathToFileURL, fileURLToPath } from 'node:url'; -import { dirname, join } from 'node:path'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const pageUrl = (mode) => pathToFileURL(join(__dirname, 'index.html')).href + '?mode=' + mode; -const size = { width: 940, height: 720 }; -const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); - -const browser = await chromium.launch(); - -// BEFORE: duplicate persisted -{ - const page = await browser.newPage({ viewport: size }); - await page.goto(pageUrl('before')); - await page.click('[data-testid="input-0"]'); - await page.click('[data-testid="opt-0-nsf"]'); - await page.click('[data-testid="add"]'); - await page.click('[data-testid="input-1"]'); - await page.click('[data-testid="opt-1-nsf"]'); - await page.evaluate(() => window.setCaption('Before', 'Duplicate funder persisted (same authority in two rows).')); - await sleep(400); - await page.screenshot({ path: join(__dirname, 'videos', 'before.png') }); - await page.close(); -} - -// AFTER: option greyed out / blocked -{ - const page = await browser.newPage({ viewport: size }); - await page.goto(pageUrl('after')); - await page.click('[data-testid="input-0"]'); - await page.click('[data-testid="opt-0-nsf"]'); - await page.click('[data-testid="add"]'); - await page.click('[data-testid="input-1"]'); - await page.evaluate(() => window.setCaption('After', 'The already-used funder is greyed out and cannot be selected.')); - await sleep(400); - await page.screenshot({ path: join(__dirname, 'videos', 'after.png') }); - await page.close(); -} - -await browser.close(); -console.log('Screenshots saved to videos/before.png and videos/after.png'); diff --git a/demo/videos/after.gif b/demo/videos/after.gif deleted file mode 100644 index af91a2874fe..00000000000 Binary files a/demo/videos/after.gif and /dev/null differ diff --git a/demo/videos/after.png b/demo/videos/after.png deleted file mode 100644 index 6d16330b489..00000000000 Binary files a/demo/videos/after.png and /dev/null differ diff --git a/demo/videos/after.webm b/demo/videos/after.webm deleted file mode 100644 index 888d40e271f..00000000000 Binary files a/demo/videos/after.webm and /dev/null differ diff --git a/demo/videos/before.gif b/demo/videos/before.gif deleted file mode 100644 index a8f819fe4cf..00000000000 Binary files a/demo/videos/before.gif and /dev/null differ diff --git a/demo/videos/before.png b/demo/videos/before.png deleted file mode 100644 index 92e14244467..00000000000 Binary files a/demo/videos/before.png and /dev/null differ diff --git a/demo/videos/before.webm b/demo/videos/before.webm deleted file mode 100644 index bdbfbac048b..00000000000 Binary files a/demo/videos/before.webm and /dev/null differ diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index e050069efde..0816518b76b 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2000,6 +2000,8 @@ "form.first-name": "First name", + "form.vocabulary.load-error": "Something went wrong while loading the options. Please try again.", + "form.group-collapse": "Collapse", "form.group-collapse-help": "Click here to collapse", @@ -2812,6 +2814,8 @@ "item.page.files": "Files", + "item.page.filesection.checksum": "Checksum:", + "item.page.filesection.description": "Description:", "item.page.filesection.download": "Download", @@ -7183,6 +7187,8 @@ "item.page.doi": "Persistent Identifier", + "item.page.doi.pending": "DOI registration in progress", + "item.page.date.available": "Date Available", "item.page.replaced": "Relation (Is Replaced By)",