diff --git a/.eslintrc.js b/.eslintrc.js index 94c22cf68..3c52cf91f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -11,7 +11,8 @@ module.exports = { $: 'readonly', alert: 'readonly', document: 'readonly', - window: 'readonly' + window: 'readonly', + defineModel: 'readonly' }, rules: { 'arrow-parens': 'off', diff --git a/e2e-tests/run-tests.sh b/e2e-tests/run-tests.sh index f2efa0be9..10e7667f2 100755 --- a/e2e-tests/run-tests.sh +++ b/e2e-tests/run-tests.sh @@ -67,7 +67,7 @@ if [[ ${CI-} = true ]]; then fi log "Installing npm packages..." -npm install +npm ci cd e2e-tests log "Playwright: $(npx playwright --version)" diff --git a/e2e-tests/tests/enketo.spec.js b/e2e-tests/tests/enketo.spec.js index 342415493..e12a18cd8 100644 --- a/e2e-tests/tests/enketo.spec.js +++ b/e2e-tests/tests/enketo.spec.js @@ -251,7 +251,7 @@ test.describe('Enketo', () => { await expect(frame.getByRole('heading', { name: 'Successful' })).toBeVisible(); }); - test('default value is consistent between rendering enketo directly or via iframe', async ({ page }) => { + test('default value is consistent between rendering enketo directly and via iframe', async ({ page }) => { await login(page); const queryWithSpaces = '?d[/data/first_name]=hello earth + hello mars %20 hello jupiter %2B hello saturn'; @@ -259,12 +259,13 @@ test.describe('Enketo', () => { await page.goto(`${appUrl}/enketo-passthrough/${publishedForm.enketoId}${queryWithSpaces}`); await expect(page.getByRole('heading', { name: publishedForm.name })).toBeVisible(); - const defaultValueInEnketo = await page.getByLabel('First Name').inputValue(); + await expect(page.getByLabel('First Name')).toHaveValue('hello earth + hello mars hello jupiter + hello saturn'); await page.goto(`${appUrl}/projects/${projectId}/forms/${publishedForm.xmlFormId}/submissions/new${queryWithSpaces}`); const iframe = await page.frameLocator('iframe'); await expect(iframe.getByRole('heading', { name: publishedForm.name })).toBeVisible(); - await expect(iframe.getByLabel('First Name')).toHaveValue(defaultValueInEnketo); + // except we transform + into space as well in iframe + await expect(iframe.getByLabel('First Name')).toHaveValue('hello earth hello mars hello jupiter + hello saturn'); }); }); diff --git a/main.nginx.conf b/main.nginx.conf index 7cfd463f6..69ce7e9a9 100644 --- a/main.nginx.conf +++ b/main.nginx.conf @@ -46,6 +46,21 @@ http { ~^__Host-(session=.+)$ $1; } + map $args $qp_deliminator { + ~.+ "&"; + default "?"; + } + + map $arg_st $redirect_non_single_prefix { + ~.+ "${is_args}${args}${qp_deliminator}single=false"; + default "/new${is_args}${args}"; + } + + map $arg_st $redirect_single_prefix { + ~.+ "${is_args}${args}"; + default "/new${is_args}${args}${qp_deliminator}single=true"; + } + server { listen 8686; server_name localhost; @@ -70,7 +85,9 @@ http { # Following are the locations that serve a Form and these are redirected to the frontend: location ~ "^/-/single/(?[a-zA-Z0-9]+)$" { # Form fill link, public - return 301 "/f/$enketoId$is_args$args"; + # If 'st' query parameter is not present, redirect to protected route with single only + # end-of-form behavior (/new?single=true) + return 301 "/f/$enketoId$redirect_single_prefix"; } location ~ "^/-/preview/(?[a-zA-Z0-9]+)$" { # preview link @@ -81,7 +98,8 @@ http { # we don't want them to be redirected to central-frontend location ~ "^/-/(?!thanks$|connection$|login$|logout$|api$|preview$)(?[a-zA-Z0-9]+)$" { # Form fill link (non-public), or Draft - return 301 "/f/$enketoId/new$is_args$args"; + # If 'st' query parameter is present, add ?single=false for public access + return 301 "/f/$enketoId$redirect_non_single_prefix"; } location = /-/single/check-submitted { alias ./dist/blank.html; diff --git a/package-lock.json b/package-lock.json index 75ea9d985..ebbb69562 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "dependencies": { "@floating-ui/dom": "^1.1.0", - "@getodk/web-forms": "^0.13.1", + "@getodk/web-forms": "^0.14.0", "axios": "^1.6.2", "bootstrap": "~3", "dompurify": "^3.2.5", @@ -336,12 +336,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", - "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.1" + "@babel/types": "^7.28.2" }, "bin": { "parser": "bin/babel-parser.js" @@ -422,9 +422,9 @@ } }, "node_modules/@babel/types": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", - "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -949,20 +949,20 @@ } }, "node_modules/@getodk/web-forms": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/@getodk/web-forms/-/web-forms-0.13.1.tgz", - "integrity": "sha512-BCcanQTxF3zfk7JVGcFZ5Wa0bp+C4gIJQnKefByELTYzylSUpDowzBiyf4T+L/uJvjEX8aMxO+SU6rvsGPPKlg==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@getodk/web-forms/-/web-forms-0.14.0.tgz", + "integrity": "sha512-0pr2LkFl9eR/ihEOT3SxmZvZqfk79ab12pvCPmdm7ACtnwVDd1vUFCtlCaTxHprfT0HmPcEh322ZIUY5HLkTqg==", "license": "Apache-2.0", "dependencies": { "@mdi/js": "^7.4.47", "vue-draggable-plus": "^0.6.0" }, "engines": { - "node": "^18.20.5 || ^20.18.1 || ^22.12.0", + "node": "^20.19.3 || ^22.12.0 || ^24.3.0", "yarn": "1.22.22" }, "peerDependencies": { - "vue": "^3.5.13" + "vue": "^3.5.18" } }, "node_modules/@hapi/hoek": { @@ -2499,16 +2499,16 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", - "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.20.tgz", + "integrity": "sha512-8TWXUyiqFd3GmP4JTX9hbiTFRwYHgVL/vr3cqhr4YQ258+9FADwvj7golk2sWNGHR67QgmCZ8gz80nQcMokhwg==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.25.3", - "@vue/shared": "3.5.13", + "@babel/parser": "^7.28.3", + "@vue/shared": "3.5.20", "entities": "^4.5.0", "estree-walker": "^2.0.2", - "source-map-js": "^1.2.0" + "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-core/node_modules/entities": { @@ -2524,40 +2524,40 @@ } }, "node_modules/@vue/compiler-dom": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", - "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.20.tgz", + "integrity": "sha512-whB44M59XKjqUEYOMPYU0ijUV0G+4fdrHVKDe32abNdX/kJe1NUEMqsi4cwzXa9kyM9w5S8WqFsrfo1ogtBZGQ==", "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.13", - "@vue/shared": "3.5.13" + "@vue/compiler-core": "3.5.20", + "@vue/shared": "3.5.20" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", - "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.20.tgz", + "integrity": "sha512-SFcxapQc0/feWiSBfkGsa1v4DOrnMAQSYuvDMpEaxbpH5dKbnEM5KobSNSgU+1MbHCl+9ftm7oQWxvwDB6iBfw==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.25.3", - "@vue/compiler-core": "3.5.13", - "@vue/compiler-dom": "3.5.13", - "@vue/compiler-ssr": "3.5.13", - "@vue/shared": "3.5.13", + "@babel/parser": "^7.28.3", + "@vue/compiler-core": "3.5.20", + "@vue/compiler-dom": "3.5.20", + "@vue/compiler-ssr": "3.5.20", + "@vue/shared": "3.5.20", "estree-walker": "^2.0.2", - "magic-string": "^0.30.11", - "postcss": "^8.4.48", - "source-map-js": "^1.2.0" + "magic-string": "^0.30.17", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", - "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.20.tgz", + "integrity": "sha512-RSl5XAMc5YFUXpDQi+UQDdVjH9FnEpLDHIALg5J0ITHxkEzJ8uQLlo7CIbjPYqmZtt6w0TsIPbo1izYXwDG7JA==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.13", - "@vue/shared": "3.5.13" + "@vue/compiler-dom": "3.5.20", + "@vue/shared": "3.5.20" } }, "node_modules/@vue/component-compiler-utils": { @@ -2651,53 +2651,53 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", - "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.20.tgz", + "integrity": "sha512-hS8l8x4cl1fmZpSQX/NXlqWKARqEsNmfkwOIYqtR2F616NGfsLUm0G6FQBK6uDKUCVyi1YOL8Xmt/RkZcd/jYQ==", "license": "MIT", "dependencies": { - "@vue/shared": "3.5.13" + "@vue/shared": "3.5.20" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz", - "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.20.tgz", + "integrity": "sha512-vyQRiH5uSZlOa+4I/t4Qw/SsD/gbth0SW2J7oMeVlMFMAmsG1rwDD6ok0VMmjXY3eI0iHNSSOBilEDW98PLRKw==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.13", - "@vue/shared": "3.5.13" + "@vue/reactivity": "3.5.20", + "@vue/shared": "3.5.20" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", - "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.20.tgz", + "integrity": "sha512-KBHzPld/Djw3im0CQ7tGCpgRedryIn4CcAl047EhFTCCPT2xFf4e8j6WeKLgEEoqPSl9TYqShc3Q6tpWpz/Xgw==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.13", - "@vue/runtime-core": "3.5.13", - "@vue/shared": "3.5.13", + "@vue/reactivity": "3.5.20", + "@vue/runtime-core": "3.5.20", + "@vue/shared": "3.5.20", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz", - "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.20.tgz", + "integrity": "sha512-HthAS0lZJDH21HFJBVNTtx+ULcIbJQRpjSVomVjfyPkFSpCwvsPTA+jIzOaUm3Hrqx36ozBHePztQFg6pj5aKg==", "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.13", - "@vue/shared": "3.5.13" + "@vue/compiler-ssr": "3.5.20", + "@vue/shared": "3.5.20" }, "peerDependencies": { - "vue": "3.5.13" + "vue": "3.5.20" } }, "node_modules/@vue/shared": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", - "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.20.tgz", + "integrity": "sha512-SoRGP596KU/ig6TfgkCMbXkr4YJ91n/QSdMuqeP5r3hVIYA3CPHUBCc7Skak0EAKV+5lL4KyIh61VA/pK1CIAA==", "license": "MIT" }, "node_modules/@vue/test-utils": { @@ -10268,9 +10268,9 @@ } }, "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "funding": [ { "type": "opencollective", @@ -10287,7 +10287,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.8", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -12958,16 +12958,16 @@ } }, "node_modules/vue": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", - "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.20.tgz", + "integrity": "sha512-2sBz0x/wis5TkF1XZ2vH25zWq3G1bFEPOfkBcx2ikowmphoQsPH6X0V3mmPCXA2K1N/XGTnifVyDQP4GfDDeQw==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.13", - "@vue/compiler-sfc": "3.5.13", - "@vue/runtime-dom": "3.5.13", - "@vue/server-renderer": "3.5.13", - "@vue/shared": "3.5.13" + "@vue/compiler-dom": "3.5.20", + "@vue/compiler-sfc": "3.5.20", + "@vue/runtime-dom": "3.5.20", + "@vue/server-renderer": "3.5.20", + "@vue/shared": "3.5.20" }, "peerDependencies": { "typescript": "*" @@ -14453,11 +14453,11 @@ } }, "@babel/parser": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", - "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", "requires": { - "@babel/types": "^7.27.1" + "@babel/types": "^7.28.2" } }, "@babel/runtime": { @@ -14516,9 +14516,9 @@ } }, "@babel/types": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", - "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "requires": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" @@ -14759,9 +14759,9 @@ } }, "@getodk/web-forms": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/@getodk/web-forms/-/web-forms-0.13.1.tgz", - "integrity": "sha512-BCcanQTxF3zfk7JVGcFZ5Wa0bp+C4gIJQnKefByELTYzylSUpDowzBiyf4T+L/uJvjEX8aMxO+SU6rvsGPPKlg==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@getodk/web-forms/-/web-forms-0.14.0.tgz", + "integrity": "sha512-0pr2LkFl9eR/ihEOT3SxmZvZqfk79ab12pvCPmdm7ACtnwVDd1vUFCtlCaTxHprfT0HmPcEh322ZIUY5HLkTqg==", "requires": { "@mdi/js": "^7.4.47", "vue-draggable-plus": "^0.6.0" @@ -15835,15 +15835,15 @@ } }, "@vue/compiler-core": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", - "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.20.tgz", + "integrity": "sha512-8TWXUyiqFd3GmP4JTX9hbiTFRwYHgVL/vr3cqhr4YQ258+9FADwvj7golk2sWNGHR67QgmCZ8gz80nQcMokhwg==", "requires": { - "@babel/parser": "^7.25.3", - "@vue/shared": "3.5.13", + "@babel/parser": "^7.28.3", + "@vue/shared": "3.5.20", "entities": "^4.5.0", "estree-walker": "^2.0.2", - "source-map-js": "^1.2.0" + "source-map-js": "^1.2.1" }, "dependencies": { "entities": { @@ -15854,37 +15854,37 @@ } }, "@vue/compiler-dom": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", - "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.20.tgz", + "integrity": "sha512-whB44M59XKjqUEYOMPYU0ijUV0G+4fdrHVKDe32abNdX/kJe1NUEMqsi4cwzXa9kyM9w5S8WqFsrfo1ogtBZGQ==", "requires": { - "@vue/compiler-core": "3.5.13", - "@vue/shared": "3.5.13" + "@vue/compiler-core": "3.5.20", + "@vue/shared": "3.5.20" } }, "@vue/compiler-sfc": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", - "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", - "requires": { - "@babel/parser": "^7.25.3", - "@vue/compiler-core": "3.5.13", - "@vue/compiler-dom": "3.5.13", - "@vue/compiler-ssr": "3.5.13", - "@vue/shared": "3.5.13", + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.20.tgz", + "integrity": "sha512-SFcxapQc0/feWiSBfkGsa1v4DOrnMAQSYuvDMpEaxbpH5dKbnEM5KobSNSgU+1MbHCl+9ftm7oQWxvwDB6iBfw==", + "requires": { + "@babel/parser": "^7.28.3", + "@vue/compiler-core": "3.5.20", + "@vue/compiler-dom": "3.5.20", + "@vue/compiler-ssr": "3.5.20", + "@vue/shared": "3.5.20", "estree-walker": "^2.0.2", - "magic-string": "^0.30.11", - "postcss": "^8.4.48", - "source-map-js": "^1.2.0" + "magic-string": "^0.30.17", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" } }, "@vue/compiler-ssr": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", - "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.20.tgz", + "integrity": "sha512-RSl5XAMc5YFUXpDQi+UQDdVjH9FnEpLDHIALg5J0ITHxkEzJ8uQLlo7CIbjPYqmZtt6w0TsIPbo1izYXwDG7JA==", "requires": { - "@vue/compiler-dom": "3.5.13", - "@vue/shared": "3.5.13" + "@vue/compiler-dom": "3.5.20", + "@vue/shared": "3.5.20" } }, "@vue/component-compiler-utils": { @@ -15966,46 +15966,46 @@ } }, "@vue/reactivity": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", - "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.20.tgz", + "integrity": "sha512-hS8l8x4cl1fmZpSQX/NXlqWKARqEsNmfkwOIYqtR2F616NGfsLUm0G6FQBK6uDKUCVyi1YOL8Xmt/RkZcd/jYQ==", "requires": { - "@vue/shared": "3.5.13" + "@vue/shared": "3.5.20" } }, "@vue/runtime-core": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz", - "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.20.tgz", + "integrity": "sha512-vyQRiH5uSZlOa+4I/t4Qw/SsD/gbth0SW2J7oMeVlMFMAmsG1rwDD6ok0VMmjXY3eI0iHNSSOBilEDW98PLRKw==", "requires": { - "@vue/reactivity": "3.5.13", - "@vue/shared": "3.5.13" + "@vue/reactivity": "3.5.20", + "@vue/shared": "3.5.20" } }, "@vue/runtime-dom": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", - "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.20.tgz", + "integrity": "sha512-KBHzPld/Djw3im0CQ7tGCpgRedryIn4CcAl047EhFTCCPT2xFf4e8j6WeKLgEEoqPSl9TYqShc3Q6tpWpz/Xgw==", "requires": { - "@vue/reactivity": "3.5.13", - "@vue/runtime-core": "3.5.13", - "@vue/shared": "3.5.13", + "@vue/reactivity": "3.5.20", + "@vue/runtime-core": "3.5.20", + "@vue/shared": "3.5.20", "csstype": "^3.1.3" } }, "@vue/server-renderer": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz", - "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.20.tgz", + "integrity": "sha512-HthAS0lZJDH21HFJBVNTtx+ULcIbJQRpjSVomVjfyPkFSpCwvsPTA+jIzOaUm3Hrqx36ozBHePztQFg6pj5aKg==", "requires": { - "@vue/compiler-ssr": "3.5.13", - "@vue/shared": "3.5.13" + "@vue/compiler-ssr": "3.5.20", + "@vue/shared": "3.5.20" } }, "@vue/shared": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", - "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==" + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.20.tgz", + "integrity": "sha512-SoRGP596KU/ig6TfgkCMbXkr4YJ91n/QSdMuqeP5r3hVIYA3CPHUBCc7Skak0EAKV+5lL4KyIh61VA/pK1CIAA==" }, "@vue/test-utils": { "version": "2.4.6", @@ -21657,11 +21657,11 @@ } }, "postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "requires": { - "nanoid": "^3.3.8", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } @@ -23491,15 +23491,15 @@ "dev": true }, "vue": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", - "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", - "requires": { - "@vue/compiler-dom": "3.5.13", - "@vue/compiler-sfc": "3.5.13", - "@vue/runtime-dom": "3.5.13", - "@vue/server-renderer": "3.5.13", - "@vue/shared": "3.5.13" + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.20.tgz", + "integrity": "sha512-2sBz0x/wis5TkF1XZ2vH25zWq3G1bFEPOfkBcx2ikowmphoQsPH6X0V3mmPCXA2K1N/XGTnifVyDQP4GfDDeQw==", + "requires": { + "@vue/compiler-dom": "3.5.20", + "@vue/compiler-sfc": "3.5.20", + "@vue/runtime-dom": "3.5.20", + "@vue/server-renderer": "3.5.20", + "@vue/shared": "3.5.20" } }, "vue-component-type-helpers": { diff --git a/package.json b/package.json index bc6e39512..d0b8876e0 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ }, "dependencies": { "@floating-ui/dom": "^1.1.0", - "@getodk/web-forms": "^0.13.1", + "@getodk/web-forms": "^0.14.0", "axios": "^1.6.2", "bootstrap": "~3", "dompurify": "^3.2.5", diff --git a/src/assets/scss/_mixins.scss b/src/assets/scss/_mixins.scss index 3c275ad98..4f20d86d0 100644 --- a/src/assets/scss/_mixins.scss +++ b/src/assets/scss/_mixins.scss @@ -114,3 +114,17 @@ &::after { clear: both; } } + +@mixin floating-container { + display: flex; + flex-direction: column; + align-items: center; + position: fixed; + left: 10vw; + width: 80vw; + bottom: 34px; + z-index: $z-index-toast; + pointer-events: none; + + & > * { pointer-events: auto; } +} diff --git a/src/assets/scss/_variables.scss b/src/assets/scss/_variables.scss index fc3d05cdd..5d6918d67 100644 --- a/src/assets/scss/_variables.scss +++ b/src/assets/scss/_variables.scss @@ -85,6 +85,8 @@ $padding-bottom-table-data: 8px; $padding-left-table-data: 8px; $padding-right-table-data: 8px; $padding-top-table-data: 8px; +// Row +$color-selected-row: #E4EDF1; // Panels $box-shadow-panel-main: 0 0 24px rgba(0, 0, 0, 0.25), 0 35px 115px rgba(0, 0, 0, 0.28); @@ -131,3 +133,6 @@ $max-width-page-body: 1280px; // Popover $box-shadow-popover: 0 5px 10px rgba(0, 0, 0, 0.2); + +// Figma Variables +$m3-elevation-light-3: 0px 4px 8px 3px rgba(0, 0, 0, 0.15), 0px 1px 3px 0px rgba(0, 0, 0, 0.30); diff --git a/src/assets/scss/app.scss b/src/assets/scss/app.scss index 59bc11a7d..9ad75fe6b 100644 --- a/src/assets/scss/app.scss +++ b/src/assets/scss/app.scss @@ -186,7 +186,7 @@ class. */ //////////////////////////////////////////////////////////////////////////////// // LINKS AND BUTTONS -.btn, a { +.btn, a, button.close { &[aria-disabled="true"], &.disabled, fieldset[disabled] & { cursor: not-allowed; opacity: 0.65; diff --git a/src/components/action-bar.vue b/src/components/action-bar.vue new file mode 100644 index 000000000..45e384506 --- /dev/null +++ b/src/components/action-bar.vue @@ -0,0 +1,90 @@ + + + + + + diff --git a/src/components/alerts.vue b/src/components/alerts.vue index 15750893a..a2d01de5c 100644 --- a/src/components/alerts.vue +++ b/src/components/alerts.vue @@ -48,22 +48,13 @@ watch(() => redAlert.messageId, () => { diff --git a/src/components/enketo-iframe.vue b/src/components/enketo-iframe.vue index 4e06e969c..0fbbed427 100644 --- a/src/components/enketo-iframe.vue +++ b/src/components/enketo-iframe.vue @@ -70,6 +70,12 @@ const lastSubmitted = (enketoOnceId) => { const enketoSrc = ref(); +const single = computed(() => { + const { query } = route; + return (props.actionType === 'public-link' && query.single !== 'false') || + (props.actionType === 'new' && query.single === 'true'); +}); + const setEnketoSrc = () => { let basePath = '/enketo-passthrough'; // this is to avoid 404 warning @@ -77,25 +83,29 @@ const setEnketoSrc = () => { basePath = `/#${basePath}`; } let prefix = basePath; - const { search } = new URL(route.fullPath, location.origin); + const { return_url: _, returnUrl: __, ...query } = route.query; - // pass URL query parameters as it is to the Enketo iframe after stripping return URLs - let qs = `?parentWindowOrigin=${encodeURIComponent(location.origin)}`; - qs += (!search ? '' - : `&${search.substring(1) - .split('&') - .filter(queryParameter => !queryParameter.startsWith('return_url=') && !queryParameter.startsWith('returnUrl=')) - .join('&')}`); + query.parentWindowOrigin = location.origin; + + // We need to use encodeURIComponent here instead of URLSearchParams because enketo expects space + // to pass as either ' ' (literal space character) or '%20'. Whereas URLSearchParams converts + // space into '+' sign. + const qs = `?${Object.entries(query) + .filter(([, value]) => typeof value === 'string') + .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) + .join('&')}`; if (props.actionType === 'offline') { return; // we don't render offline Enketo through central-frontend } - if (props.actionType === 'public-link') { + // for actionType 'new', we add '/single' only if 'single' query parameter is true + // for actionType 'public-link', we add '/single' only if 'single' query parameter is not false + if (single.value) { prefix += '/single'; } else if (props.actionType === 'preview') { prefix += `/${props.actionType}`; } - // for actionType 'new', we don't need to add anything to the prefix. + // we no longer render Enketo for Edit Submission from central-frontend. if (props.enketoId === form.enketoOnceId) { @@ -131,7 +141,7 @@ const handleIframeMessage = (event) => { try { eventData = JSON.parse(event.data); } catch {} if (eventData?.enketoEvent === 'submissionsuccess') { - if (props.actionType === 'public-link' && redirectUrl.value) { + if (redirectUrl.value && single.value) { // for public link, we read return value from query parameter. The value could be 3rd party // site as well, typically a thank you page try { diff --git a/src/components/entity/data-row.vue b/src/components/entity/data-row.vue index 28afac7f3..ea4272b7c 100644 --- a/src/components/entity/data-row.vue +++ b/src/components/entity/data-row.vue @@ -10,7 +10,7 @@ including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the LICENSE file. -->