gina 0.6.31 → 0.6.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/README.md +62 -25
- package/ROADMAP.md +3 -1
- package/framework/v0.6.32/VERSION +1 -0
- package/framework/{v0.6.31 → v0.6.32}/core/asset/plugin/dist/vendor/gina/js/gina.js +2389 -202
- package/framework/v0.6.32/core/asset/plugin/dist/vendor/gina/js/gina.min.js +716 -0
- package/framework/v0.6.32/core/asset/plugin/dist/vendor/gina/js/gina.min.js.br +0 -0
- package/framework/v0.6.32/core/asset/plugin/dist/vendor/gina/js/gina.min.js.gz +0 -0
- package/framework/{v0.6.31 → v0.6.32}/core/config.js +45 -7
- package/framework/{v0.6.31 → v0.6.32}/core/connectors/couchbase/index.js +9 -3
- package/framework/{v0.6.31 → v0.6.32}/core/controller/controller.js +164 -17
- package/framework/{v0.6.31 → v0.6.32}/core/controller/controller.render-json.js +35 -1
- package/framework/{v0.6.31 → v0.6.32}/core/controller/controller.render-nunjucks-async.js +20 -0
- package/framework/{v0.6.31 → v0.6.32}/core/controller/controller.render-nunjucks.js +69 -6
- package/framework/{v0.6.31 → v0.6.32}/core/controller/controller.render-swig-async.js +20 -0
- package/framework/{v0.6.31 → v0.6.32}/core/controller/controller.render-swig.js +40 -0
- package/framework/{v0.6.31 → v0.6.32}/core/controller/controller.render-xml.js +20 -0
- package/framework/{v0.6.31 → v0.6.32}/core/gna.js +50 -2
- package/framework/{v0.6.31 → v0.6.32}/core/model/entity.js +79 -28
- package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/validator/src/form-validator.js +32 -1
- package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/validator/src/main.js +1579 -55
- package/framework/{v0.6.31 → v0.6.32}/core/router.js +45 -1
- package/framework/{v0.6.31 → v0.6.32}/core/server.isaac.js +41 -5
- package/framework/{v0.6.31 → v0.6.32}/core/server.js +277 -464
- package/framework/{v0.6.31 → v0.6.32}/helpers/json/src/main.js +57 -4
- package/framework/v0.6.32/lib/duration/package.json +17 -0
- package/framework/v0.6.32/lib/duration/src/main.js +82 -0
- package/framework/{v0.6.31 → v0.6.32}/lib/index.js +18 -4
- package/framework/{v0.6.31 → v0.6.32}/lib/maintenance/src/main.js +387 -7
- package/framework/{v0.6.31 → v0.6.32}/lib/model.js +86 -1
- package/framework/v0.6.32/lib/session-lifetime/package.json +17 -0
- package/framework/v0.6.32/lib/session-lifetime/src/main.js +337 -0
- package/framework/{v0.6.31 → v0.6.32}/lib/storage/src/meta-store.js +45 -37
- package/framework/{v0.6.31 → v0.6.32}/lib/storage/src/util.js +8 -21
- package/framework/{v0.6.31 → v0.6.32}/package.json +1 -1
- package/gna.js +4 -4
- package/llms.txt +14 -14
- package/package.json +3 -3
- package/schema/security.json +35 -0
- package/schema/settings.json +13 -1
- package/script/smoke_ws_h2.js +353 -0
- package/test-results/validator-upload-popin-con-a5268-n-change-fires-RED-pre-fix--chromium/error-context.md +172 -0
- package/test-results/validator-upload-popin-con-cf673--the-page-form-RED-pre-fix--chromium/error-context.md +212 -0
- package/types/gna.d.ts +3 -2
- package/types/index.d.ts +32 -0
- package/framework/v0.6.31/VERSION +0 -1
- package/framework/v0.6.31/core/asset/plugin/dist/vendor/gina/js/gina.min.js +0 -673
- package/framework/v0.6.31/core/asset/plugin/dist/vendor/gina/js/gina.min.js.br +0 -0
- package/framework/v0.6.31/core/asset/plugin/dist/vendor/gina/js/gina.min.js.gz +0 -0
- package/test-results/validator-upload-preview-f-c70b4-t-in-the-DOM-or-on-the-wire-chromium/error-context.md +0 -187
- /package/framework/{v0.6.31 → v0.6.32}/AUTHORS +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/LICENSE +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/html/nolayout.html +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/html/static.html +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/img/android-chrome-192x192.png +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/img/android-chrome-512x512.png +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/img/apple-touch-icon.png +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/img/favicon-16x16.png +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/img/favicon-32x32.png +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/img/favicon.ico +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/plugin/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/plugin/dist/vendor/gina/beemaster/beemaster.css +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/plugin/dist/vendor/gina/beemaster/beemaster.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/plugin/dist/vendor/gina/beemaster/index.html +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/plugin/dist/vendor/gina/css/gina.min.css +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/plugin/dist/vendor/gina/css/gina.min.css.br +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/plugin/dist/vendor/gina/css/gina.min.css.gz +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/plugin/dist/vendor/gina/html/statusbar.html +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/plugin/dist/vendor/gina/html/statusbar.html.br +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/plugin/dist/vendor/gina/html/statusbar.html.gz +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/plugin/dist/vendor/gina/inspector/have_heart_one-webfont.woff2 +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/plugin/dist/vendor/gina/inspector/index.html +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/plugin/dist/vendor/gina/inspector/inspector.css +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/plugin/dist/vendor/gina/inspector/inspector.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/plugin/dist/vendor/gina/inspector/logo.svg +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js.br +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js.gz +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/ai/index.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/ai/lib/connector.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/couchbase/lib/connector.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/couchbase/lib/connector.v3.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/couchbase/lib/connector.v4.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/couchbase/lib/n1ql.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/couchbase/lib/session-store.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/couchbase/lib/session-store.v3.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/couchbase/lib/session-store.v4.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/couchbase/lib/storage-store.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/duckdb/index.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/duckdb/lib/connector.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/mongodb/index.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/mongodb/lib/connector.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/mongodb/lib/job-store.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/mongodb/lib/pipeline-loader.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/mongodb/lib/session-store.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/mysql/index.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/mysql/lib/connector.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/param-redact.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/postgresql/index.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/postgresql/lib/connector.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/redis/index.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/redis/lib/connector.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/redis/lib/job-store.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/redis/lib/kv-store.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/redis/lib/render-cache-store.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/redis/lib/session-store.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/scylladb/index.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/scylladb/lib/connector.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/scylladb/lib/session-store.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/settle-once.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/sql-parser.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/sqlite/index.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/sqlite/lib/connector.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/sqlite/lib/job-store.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/sqlite/lib/kv-store.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/connectors/sqlite/lib/session-store.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/content.encoding +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/controller/controller.framework.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/controller/controller.render-stream.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/controller/controller.render-v1.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/controller/index.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/controller/inline-script.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/controller/inspector-window-emit.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/controller/release-banner.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/dev/index.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/dev/lib/class.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/dev/lib/factory.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/dev/lib/tools.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/locales/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/locales/currency.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/locales/dist/language/en.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/locales/dist/language/fr.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/locales/dist/region/en.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/locales/dist/region/fr.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/locales/index.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/mime.types +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/model/index.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/model/template/entityFactory.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/model/template/index.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/index.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/csrf/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/csrf/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/csrf/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/coep/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/coep/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/coep/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/coop/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/coop/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/coop/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/corp/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/corp/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/corp/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/csp/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/csp/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/csp/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/hide-powered-by/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/hide-powered-by/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/hide-powered-by/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/hsts/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/hsts/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/hsts/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/origin-agent-cluster/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/origin-agent-cluster/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/origin-agent-cluster/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/referrer-policy/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/referrer-policy/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/referrer-policy/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/x-content-type-options/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/x-content-type-options/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/x-content-type-options/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/x-dns-prefetch-control/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/x-dns-prefetch-control/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/x-dns-prefetch-control/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/x-download-options/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/x-download-options/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/x-download-options/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/x-frame-options/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/x-frame-options/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/x-frame-options/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/x-permitted-cross-domain-policies/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/x-permitted-cross-domain-policies/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/x-permitted-cross-domain-policies/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/x-xss-protection/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/x-xss-protection/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/security-headers/x-xss-protection/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/session/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/session/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/session/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/storage/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/storage/build.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/storage/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/storage/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/validator/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/validator/build.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/plugins/lib/validator/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/server.express.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/status.codes +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/_gitignore +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle/config/app.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle/config/connectors.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle/config/routing.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle/config/settings.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle/config/settings.server.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle/config/templates.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle/config/watchers.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle/controllers/controller.content.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle/controllers/controller.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle/controllers/setup.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle/index.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle/locales/en.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle_namespace/controllers/controller.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle_public/css/default.css +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle_public/css/home.css +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle_public/css/vendor/readme.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle_public/favicon.ico +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle_public/js/components/x-checklist.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle_public/js/vendor/readme.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle_public/manifest.webmanifest +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle_public/readme.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle_public/sw.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle_templates/handlers/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle_templates/html/content/homepage.html +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle_templates/html/includes/error-msg-noscript.html +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle_templates/html/includes/error-msg-outdated-browser.html +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle_templates/html/includes/x-checklist.html +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/boilerplate/bundle_templates/html/layouts/main.html +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/command/gina.bat.tpl +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/command/gina.tpl +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/conf/env.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/conf/manifest.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/conf/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/conf/settings.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/conf/statics.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/conf/templates.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/error/client/json/401.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/error/client/json/403.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/error/client/json/404.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/error/server/html/50x.html +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/error/server/json/500.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/error/server/json/503.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/core/template/extensions/logger/config.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/helpers/console.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/helpers/context.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/helpers/data/LICENSE +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/helpers/data/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/helpers/data/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/helpers/data/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/helpers/dateFormat.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/helpers/index.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/helpers/json/LICENSE +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/helpers/json/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/helpers/json/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/helpers/path.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/helpers/plugins/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/helpers/plugins/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/helpers/plugins/src/api-error.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/helpers/plugins/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/helpers/prototypes.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/helpers/task.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/helpers/text.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/admin/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/admin/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/archiver/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/archiver/build.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/archiver/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/archiver/src/dep/jszip.min.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/archiver/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/async/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/async/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/audit/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/audit/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/audit-store.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/authn/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/authn/src/lockout.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/authn/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/authn/src/totp.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/authz-gate/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/authz-gate/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cache/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cache/build.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cache/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cache/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/aliases.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/audit/arguments.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/audit/help.txt +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/audit/verify.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/bundle/add.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/bundle/arguments.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/bundle/build.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/bundle/copy.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/bundle/cp.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/bundle/help.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/bundle/help.txt +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/bundle/inc/name-rewrite.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/bundle/list.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/bundle/man.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/bundle/mcp-start.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/bundle/mcp.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/bundle/oas.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/bundle/openapi.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/bundle/remove.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/bundle/rename.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/bundle/restart.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/bundle/rm.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/bundle/start.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/bundle/status.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/bundle/stop.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/bundle/types.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/cache/arguments.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/cache/clear.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/cache/help.txt +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/cache/stats.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/connector/add.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/connector/arguments.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/connector/help.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/connector/help.txt +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/connector/infer.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/connector/list.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/connector/migrate.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/connector/models.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/connector/remove.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/connector/rm.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/connector/test.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/container/arguments.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/container/help.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/container/help.txt +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/container/man.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/container/ps.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/container/stop.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/controller/add.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/controller/arguments.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/controller/help.txt +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/controller/inc/args.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/controller/inc/namespace.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/controller/inc/reference-rewrite.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/controller/inc/reference-scan.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/controller/inc/scaffold.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/controller/remove.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/controller/rename.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/controller/rm.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/env/add.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/env/get.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/env/help.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/env/help.txt +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/env/link-dev.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/env/list.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/env/remove.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/env/rm.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/env/set.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/env/unset.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/env/use.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/framework/add.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/framework/arguments.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/framework/build.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/framework/dot.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/framework/get.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/framework/help.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/framework/help.txt +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/framework/init.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/framework/link-node-modules.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/framework/link.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/framework/list.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/framework/man.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/framework/msg.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/framework/open.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/framework/remove.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/framework/reset.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/framework/restart.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/framework/set.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/framework/start.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/framework/status.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/framework/stop.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/framework/tail.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/framework/update.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/framework/version.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/gina-dev.1.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/gina-framework.1.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/gina.1.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/helper.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/i18n/add.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/i18n/arguments.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/i18n/export.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/i18n/help.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/i18n/help.txt +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/i18n/import.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/i18n/scan.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/image/_host.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/image/arguments.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/image/build.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/image/help.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/image/help.txt +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/image/list.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/image/man.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/image/rm.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/image/run.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/index.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/inspector/help.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/inspector/help.txt +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/inspector/open.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/man-render.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/minion/arguments.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/minion/help.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/minion/help.txt +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/minion/kill.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/minion/list.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/msg.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/port/help.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/port/help.txt +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/port/inc/scan.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/port/list.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/port/reset.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/port/set.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/project/add.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/project/arguments.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/project/backup.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/project/build.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/project/help.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/project/help.txt +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/project/import.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/project/list.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/project/man.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/project/move.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/project/remove.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/project/rename.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/project/restart.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/project/restore.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/project/rm.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/project/start.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/project/status.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/project/stop.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/protocol/arguments.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/protocol/help.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/protocol/help.txt +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/protocol/list.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/protocol/remove.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/protocol/set.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/scope/add.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/scope/help.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/scope/help.txt +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/scope/link-local.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/scope/link-production.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/scope/list.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/scope/remove.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/scope/rm.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/scope/use.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/secrets/arguments.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/secrets/check.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/secrets/help.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/secrets/help.txt +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/secrets/scan.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/service/help.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/service/help.txt +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/service/list.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/service/man.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/service/start.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/storage/arguments.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/storage/gc.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/storage/help.txt +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/storage/stats.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/storage/verify.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd/view/add.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd-status-format/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cmd-status-format/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/collection/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/collection/build.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/collection/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/collection/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/conf-view/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/conf-view/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/config.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/connector-config/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/connector-config/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/connector-error/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/connector-error/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/connector-registry/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/connector-registry/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cron/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cron/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/cron/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/domain/LICENSE +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/domain/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/domain/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/domain/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/dto/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/dto/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/dto-pipe/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/dto-pipe/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/dto-types/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/dto-types/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/generator/index.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/i18n/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/i18n/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/idempotency/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/idempotency/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/image-build/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/image-build/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/inherits/LICENSE +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/inherits/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/inherits/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/inherits/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/inspector-events/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/inspector-events/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/inspector-redact/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/inspector-redact/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/instrument/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/instrument/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/job/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/job/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/job-store.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/json-config-header/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/json-config-header/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/kv/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/kv/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/kv-store.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/loading-state/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/loading-state/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/logger/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/logger/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/logger/src/containers/default/index.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/logger/src/containers/file/index.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/logger/src/containers/mq/index.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/logger/src/containers/mq/listener.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/logger/src/containers/mq/speaker.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/logger/src/helper.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/logger/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/logger/src/redact.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/maintenance/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/math/index.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/mcp-dispatch/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/mcp-dispatch/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/mcp-http/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/mcp-http/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/mcp-server/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/mcp-server/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/merge/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/merge/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/merge/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/message-validator/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/message-validator/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/metrics/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/metrics/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/money/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/money/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/multipart/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/multipart/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/net-locality/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/net-locality/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/nunjucks-filters/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/nunjucks-filters/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/nunjucks-filters/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/nunjucks-resolver/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/nunjucks-resolver/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/priority/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/priority/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/proc.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/push/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/push/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/rate-limit/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/rate-limit/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/release-watch/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/release-watch/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/render-cache/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/render-cache/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/render-cache-store.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/routing/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/routing/build.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/routing/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/routing/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/routing/src/radix.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/routing-introspect/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/routing-introspect/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/secrets/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/secrets/src/backends/env.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/secrets/src/backends/exec.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/secrets/src/backends/file.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/secrets/src/declaration.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/secrets/src/env-file.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/secrets/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/secrets/src/sources.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/security-headers-emitter/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/security-headers-emitter/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/session-store.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/shell.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/sqlite-driver.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/sri/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/sri/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/state.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/storage/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/storage/src/local-cas.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/storage/src/local-stream.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/storage/src/local.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/storage/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/storage/src/s3.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/storage-store.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/swig-filters/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/swig-filters/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/swig-filters/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/swig-resolver/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/swig-resolver/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/template-loaders/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/template-loaders/src/loaders/http.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/template-loaders/src/loaders/memory.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/template-loaders/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/url/README.md +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/url/index.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/url/routing.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/uuid/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/uuid/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/validator.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/watcher/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/watcher/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/ws-framing/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/ws-framing/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/ws-query/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/ws-query/src/main.js +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/ws-session/package.json +0 -0
- /package/framework/{v0.6.31 → v0.6.32}/lib/ws-session/src/main.js +0 -0
|
@@ -5579,7 +5579,38 @@ function FormValidatorUtil(data, $fields, xhrOptions, fieldsSet, culture) {
|
|
|
5579
5579
|
) {
|
|
5580
5580
|
date = new Date(NaN);
|
|
5581
5581
|
}
|
|
5582
|
-
|
|
5582
|
+
// #B558 (2026-09-18) — the FIELD keeps the Date, the PAYLOAD
|
|
5583
|
+
// takes a date-only string. A Date is the one normalised type
|
|
5584
|
+
// that does not round-trip through JSON: JSON.stringify calls
|
|
5585
|
+
// toISOString(), which yields a UTC INSTANT whose date part is
|
|
5586
|
+
// a day EARLIER for every positive UTC offset, so a client east
|
|
5587
|
+
// of UTC submitted the previous day (driven: Europe/Paris
|
|
5588
|
+
// '2026-09-18' -> '2026-09-17T22:00:00.000Z'). Silent, because
|
|
5589
|
+
// the instant is well-formed — only a reader that slices the
|
|
5590
|
+
// string sees it.
|
|
5591
|
+
//
|
|
5592
|
+
// The Date stays on this.value so `format()` and the #B48
|
|
5593
|
+
// chaining contract still work; only local.data (the submitted
|
|
5594
|
+
// payload) changes. The components are read back with the
|
|
5595
|
+
// Date's LOCAL getters, which the round-trip check above has
|
|
5596
|
+
// already proven equal to the parsed input, so no timezone
|
|
5597
|
+
// enters the written value at all.
|
|
5598
|
+
//
|
|
5599
|
+
// The shape is always `yyyy-mm-dd` regardless of the input
|
|
5600
|
+
// mask: gina's own introspection already publishes an isDate
|
|
5601
|
+
// field as {type:'string', format:'date'} (RFC 3339 full-date,
|
|
5602
|
+
// NOT date-time), and the mask governs INPUT parsing, not the
|
|
5603
|
+
// wire. An invalid date is left exactly as it was, so the
|
|
5604
|
+
// error path below still sees an Invalid Date object.
|
|
5605
|
+
// was: this.value = local.data[this.name] = date;
|
|
5606
|
+
this.value = date;
|
|
5607
|
+
if ( isNaN(date.getTime()) ) {
|
|
5608
|
+
local.data[this.name] = date;
|
|
5609
|
+
} else {
|
|
5610
|
+
local.data[this.name] = date.getFullYear()
|
|
5611
|
+
+ '-' + ('0' + (date.getMonth() + 1)).slice(-2)
|
|
5612
|
+
+ '-' + ('0' + date.getDate()).slice(-2);
|
|
5613
|
+
}
|
|
5583
5614
|
}
|
|
5584
5615
|
|
|
5585
5616
|
if ( /Invalid Date/i.test(date) || date instanceof Date === false ) {
|
|
@@ -8811,28 +8842,35 @@ function handleXhr(xhr, $el, options, require) {
|
|
|
8811
8842
|
// if hasPopinHandler & popinIsBinded
|
|
8812
8843
|
if ( typeof(gina.popin) != 'undefined' && gina.hasPopinHandler ) {
|
|
8813
8844
|
|
|
8814
|
-
//
|
|
8815
|
-
|
|
8845
|
+
// #gh76 — the popin the clicked ANCHOR lives in ($el — never
|
|
8846
|
+
// $target, which is the link plugin's container on this path),
|
|
8847
|
+
// and only while it is open; a page link's HTML answer stays
|
|
8848
|
+
// with its own handler instead of being loaded into whichever
|
|
8849
|
+
// popin happened to be open.
|
|
8850
|
+
var $popin = ( typeof(gina.popin.getPopinContaining) == 'function' )
|
|
8851
|
+
? gina.popin.getPopinContaining($el)
|
|
8852
|
+
: null;
|
|
8853
|
+
if ( $popin && !$popin.isOpen ) {
|
|
8854
|
+
$popin = null;
|
|
8855
|
+
}
|
|
8816
8856
|
|
|
8817
8857
|
if ($popin) {
|
|
8818
8858
|
|
|
8819
|
-
|
|
8859
|
+
// #B575 — ONE tolerant parse: the hidden inputs are a dev-mode
|
|
8860
|
+
// transport, absent outside dev mode; dereferencing them raised a
|
|
8861
|
+
// false 422 and the popin was never loaded
|
|
8862
|
+
var _parsed = parseXhrHtmlAnswer(result.content);
|
|
8863
|
+
XHRData = _parsed.data;
|
|
8864
|
+
XHRView = _parsed.view;
|
|
8820
8865
|
// update toolbar
|
|
8821
|
-
|
|
8822
8866
|
try {
|
|
8823
|
-
XHRData = new DOMParser().parseFromString(result.content, 'text/html').getElementById('gina-without-layout-xhr-data');
|
|
8824
|
-
XHRData = JSON.parse(decodeURIComponent(XHRData.value));
|
|
8825
|
-
|
|
8826
|
-
XHRView = new DOMParser().parseFromString(result.content, 'text/html').getElementById('gina-without-layout-xhr-view');
|
|
8827
|
-
XHRView = JSON.parse(decodeURIComponent(XHRView.value));
|
|
8828
|
-
|
|
8829
8867
|
// update data tab
|
|
8830
|
-
if ( gina && typeof(window.ginaToolbar) != 'undefined' && window.ginaToolbar &&
|
|
8868
|
+
if ( gina && typeof(window.ginaToolbar) != 'undefined' && window.ginaToolbar && XHRData ) {
|
|
8831
8869
|
window.ginaToolbar.update('data-xhr', XHRData);
|
|
8832
8870
|
}
|
|
8833
8871
|
|
|
8834
8872
|
// update view tab
|
|
8835
|
-
if ( gina && typeof(window.ginaToolbar) != 'undefined' && window.ginaToolbar &&
|
|
8873
|
+
if ( gina && typeof(window.ginaToolbar) != 'undefined' && window.ginaToolbar && XHRView ) {
|
|
8836
8874
|
window.ginaToolbar.update('view-xhr', XHRView);
|
|
8837
8875
|
}
|
|
8838
8876
|
|
|
@@ -8842,8 +8880,19 @@ function handleXhr(xhr, $el, options, require) {
|
|
|
8842
8880
|
|
|
8843
8881
|
$popin.loadContent(result.content);
|
|
8844
8882
|
|
|
8845
|
-
|
|
8883
|
+
// the parsed xhr-data in dev mode, delivered VERBATIM, or an
|
|
8884
|
+
// object carrying the status outside dev mode where the transport
|
|
8885
|
+
// inputs are absent (the handler contract is "an object", never
|
|
8886
|
+
// null). The parsed data is never mutated.
|
|
8887
|
+
result = XHRData || { status: xhr.status };
|
|
8846
8888
|
triggerEvent(gina, $target, 'success.' + id, result);
|
|
8889
|
+
// #B571 — this branch returned before the tail that emits the
|
|
8890
|
+
// declared-callback companion, so a link's
|
|
8891
|
+
// `data-gina-link-event-on-success` never ran when its HTML
|
|
8892
|
+
// answer was loaded into a popin. `.hlink` only: the `.hform`
|
|
8893
|
+
// half of this handler is dead (no caller passes `$form`).
|
|
8894
|
+
if (hLinkIsRequired)
|
|
8895
|
+
triggerEvent(gina, $link.target, 'success.' + $link.id + '.hlink', result);
|
|
8847
8896
|
|
|
8848
8897
|
return;
|
|
8849
8898
|
}
|
|
@@ -9231,7 +9280,14 @@ function on(event, cb) {
|
|
|
9231
9280
|
addListener(gina, $target, event, function(e) {
|
|
9232
9281
|
|
|
9233
9282
|
//if ( typeof(e.defaultPrevented) != 'undefined' && e.defaultPrevented)
|
|
9234
|
-
|
|
9283
|
+
// #gh76 slice 2 — `beforeswap.<id>` is CANCELABLE by design: the listener
|
|
9284
|
+
// decides with `preventDefault()` whether the swap happens, so the blanket
|
|
9285
|
+
// cancel below must not pre-empt it (it would read as "every listener
|
|
9286
|
+
// cancels"). Name-scoped, so no other event sees a difference. Slice 3
|
|
9287
|
+
// adds `oobbeforeswap.<id>`, the per-element out-of-band twin.
|
|
9288
|
+
if ( !/^(oob)?beforeswap\./.test(e.type) ) {
|
|
9289
|
+
cancelEvent(e);
|
|
9290
|
+
}
|
|
9235
9291
|
|
|
9236
9292
|
var data = null;
|
|
9237
9293
|
|
|
@@ -9261,6 +9317,273 @@ function on(event, cb) {
|
|
|
9261
9317
|
|
|
9262
9318
|
}
|
|
9263
9319
|
|
|
9320
|
+
/**
|
|
9321
|
+
* parseXhrHtmlAnswer
|
|
9322
|
+
*
|
|
9323
|
+
* Parses a `text/html` XHR answer ONCE and reads the two hidden inputs a
|
|
9324
|
+
* `renderWithoutLayout()` action splices in — `gina-without-layout-xhr-data` (the page data)
|
|
9325
|
+
* and `gina-without-layout-xhr-view` — when they are present. They are a DEV-MODE transport:
|
|
9326
|
+
* the server splices them only under `NODE_ENV_IS_DEV`, so outside dev mode both read `null`
|
|
9327
|
+
* (#B575: the popin branches used to dereference the absent input and raise a false `422`).
|
|
9328
|
+
* The inputs are removed from the parsed document so a swap never carries them into the page
|
|
9329
|
+
* (inside a form they would be submitted; repeated swaps would duplicate their ids); the
|
|
9330
|
+
* caller's raw `content` string is untouched.
|
|
9331
|
+
*
|
|
9332
|
+
* @function parseXhrHtmlAnswer
|
|
9333
|
+
* @param {string} content - the answer body
|
|
9334
|
+
* @returns {{doc: Document, data: (object|null), view: (object|null)}}
|
|
9335
|
+
*
|
|
9336
|
+
* @example
|
|
9337
|
+
* var parsed = parseXhrHtmlAnswer(xhr.responseText);
|
|
9338
|
+
* parsed.data; // the action's data in dev mode, null otherwise
|
|
9339
|
+
* parsed.doc.body.innerHTML; // the fragment, hidden inputs stripped
|
|
9340
|
+
*/
|
|
9341
|
+
function parseXhrHtmlAnswer(content) {
|
|
9342
|
+
var raw = ( typeof(content) == 'string' ) ? content : '';
|
|
9343
|
+
// #B578 — an answer is a FRAGMENT, not a document. Parsed as a document, the HTML
|
|
9344
|
+
// parser foster-parents table-context elements (`<tr>`, `<td>`, `<tbody>`…) out of
|
|
9345
|
+
// existence, so a row answer came out as its cell text and a `select` on `tr` found
|
|
9346
|
+
// nothing. Parse through a `<template>` — whose contents accept any element; htmx's
|
|
9347
|
+
// `makeFragment` idiom — and MOVE the fragment into `doc.body`: a DOM move never
|
|
9348
|
+
// re-parses, so the row survives and every reader of `doc` (`getElementById`,
|
|
9349
|
+
// `querySelectorAll`, `body.innerHTML`) is unchanged. A full-page answer (a layout
|
|
9350
|
+
// render by mistake) drops its `<head>` first, as htmx does, or its `<title>` would
|
|
9351
|
+
// land in the swap. An author-supplied `<template>` inside the answer is content and
|
|
9352
|
+
// is kept as-is.
|
|
9353
|
+
raw = raw.replace(/<head(\s[^>]*)?>[\s\S]*?<\/head>/i, '');
|
|
9354
|
+
var doc = new DOMParser().parseFromString('<body><template>' + raw + '</template></body>', 'text/html');
|
|
9355
|
+
var $tpl = ( doc.body ) ? doc.body.querySelector('template') : null;
|
|
9356
|
+
if ( $tpl ) {
|
|
9357
|
+
var frag = $tpl.content;
|
|
9358
|
+
$tpl.parentNode.removeChild($tpl);
|
|
9359
|
+
doc.body.appendChild(( typeof(doc.adoptNode) == 'function' ) ? doc.adoptNode(frag) : frag);
|
|
9360
|
+
}
|
|
9361
|
+
var read = function(idName) {
|
|
9362
|
+
var $input = doc.getElementById(idName), value = null;
|
|
9363
|
+
if ( !$input ) {
|
|
9364
|
+
return null;
|
|
9365
|
+
}
|
|
9366
|
+
value = $input.value;
|
|
9367
|
+
try { $input.parentNode.removeChild($input); } catch (rmErr) {}
|
|
9368
|
+
if ( typeof(value) != 'string' || value === '' ) {
|
|
9369
|
+
return null;
|
|
9370
|
+
}
|
|
9371
|
+
try {
|
|
9372
|
+
return JSON.parse(decodeURIComponent(value));
|
|
9373
|
+
} catch (parseErr) {
|
|
9374
|
+
return null;
|
|
9375
|
+
}
|
|
9376
|
+
};
|
|
9377
|
+
return {
|
|
9378
|
+
doc : doc,
|
|
9379
|
+
data : read('gina-without-layout-xhr-data'),
|
|
9380
|
+
view : read('gina-without-layout-xhr-view')
|
|
9381
|
+
};
|
|
9382
|
+
}
|
|
9383
|
+
|
|
9384
|
+
/**
|
|
9385
|
+
* applyOobSwaps
|
|
9386
|
+
*
|
|
9387
|
+
* #gh76 slice 3 — out-of-band swaps (the `hx-swap-oob` contract). Every element of the
|
|
9388
|
+
* parsed answer carrying `data-gina-swap-oob` is swapped into the PAGE element with the
|
|
9389
|
+
* same `id`, independently of where the main answer goes, and is REMOVED from the answer
|
|
9390
|
+
* whether or not it swapped — the main fragment is always clean. Runs BEFORE any
|
|
9391
|
+
* `data-gina-form-select` pick and before the main swap (htmx's order, so an oob element
|
|
9392
|
+
* outside the selection still lands; on overlap the main swap wins).
|
|
9393
|
+
*
|
|
9394
|
+
* Value grammar: `true` / empty ⇒ `outerHTML` (the page element is replaced by the oob
|
|
9395
|
+
* element, attribute stripped); a strategy name ⇒ applied with the oob element's CONTENT
|
|
9396
|
+
* (the wrapper is stripped — `innerHTML` would otherwise nest a duplicate id);
|
|
9397
|
+
* `<strategy>:<selector>` is RESERVED; anything else is unknown. A missing `id`, no page
|
|
9398
|
+
* match, a reserved or unknown value: the element is still removed and reported with a
|
|
9399
|
+
* `reason`, never thrown. An oob element inside ANOTHER oob element is not processed on
|
|
9400
|
+
* its own — it travels with its ancestor and its attribute is stripped there. Elements
|
|
9401
|
+
* inside an author-supplied `<template>` are processed too.
|
|
9402
|
+
*
|
|
9403
|
+
* Per element: `oobbeforeswap.<formId>` (cancelable: `preventDefault()` skips it;
|
|
9404
|
+
* `detail.content` may be rewritten) → the write → the region bound through the shared
|
|
9405
|
+
* `bindRegion()` policy → `oobafterswap.<formId>`, whose `.hform` companion fires when the
|
|
9406
|
+
* declared-callback channel is armed — `oobbeforeswap` has none, mirroring `beforeswap`:
|
|
9407
|
+
* a cancel is a decision, and the declared-callback channel carries notifications. An
|
|
9408
|
+
* author-supplied `<template>` emptied of its out-of-band elements is removed with them,
|
|
9409
|
+
* so an out-of-band-only answer leaves NOTHING behind whether or not it was wrapped. A
|
|
9410
|
+
* swap that replaces or removes the SUBMITTING form
|
|
9411
|
+
* defers its re-binding (`deferFormId`) and is reported through `rebindSelf`, so the
|
|
9412
|
+
* caller's shared tail binds the same-id replacement after the `success` events.
|
|
9413
|
+
*
|
|
9414
|
+
* @param {Document} doc - the parsed answer (`parseXhrHtmlAnswer().doc`), MUTATED: every oob element is removed
|
|
9415
|
+
* @param {object} ctx
|
|
9416
|
+
* @param {HTMLFormElement} ctx.$target - the submitting form element
|
|
9417
|
+
* @param {string} ctx.id - the form id (event channel)
|
|
9418
|
+
* @param {boolean} ctx.hFormIsRequired - whether the `.hform` companions are armed
|
|
9419
|
+
* @param {object} ctx.gina - the framework global (event bus)
|
|
9420
|
+
*
|
|
9421
|
+
* @returns {{ list: Array<{ id: (string|null), strategy: string, swapped: boolean, reason: (string|undefined) }>, rebindSelf: boolean }}
|
|
9422
|
+
*
|
|
9423
|
+
* @example
|
|
9424
|
+
* var run = applyOobSwaps(parsed.doc, { $target: $form, id: 'f', hFormIsRequired: true, gina: gina });
|
|
9425
|
+
* // => { list: [{ id: 'totals', strategy: 'outerHTML', swapped: true }], rebindSelf: false }
|
|
9426
|
+
*/
|
|
9427
|
+
function applyOobSwaps(doc, ctx) {
|
|
9428
|
+
var OOB_ATTR = 'data-gina-swap-oob';
|
|
9429
|
+
var STRATEGIES = ['innerHTML', 'outerHTML', 'textContent', 'beforebegin', 'afterbegin', 'beforeend', 'afterend', 'delete', 'none'];
|
|
9430
|
+
var $target = ctx.$target
|
|
9431
|
+
, id = ctx.id
|
|
9432
|
+
, armed = !!ctx.hFormIsRequired
|
|
9433
|
+
, gina = ctx.gina
|
|
9434
|
+
, out = { list: [], rebindSelf: false }
|
|
9435
|
+
, list = []
|
|
9436
|
+
, work = []
|
|
9437
|
+
, i = 0
|
|
9438
|
+
;
|
|
9439
|
+
if ( !doc || typeof(doc.querySelectorAll) != 'function' ) {
|
|
9440
|
+
return out;
|
|
9441
|
+
}
|
|
9442
|
+
var collect = function(root) {
|
|
9443
|
+
var found = root.querySelectorAll('[' + OOB_ATTR + ']');
|
|
9444
|
+
for (var f = 0; f < found.length; ++f) list.push(found[f]);
|
|
9445
|
+
};
|
|
9446
|
+
collect(doc);
|
|
9447
|
+
var tpls = doc.querySelectorAll('template'), harvested = [];
|
|
9448
|
+
for (i = 0; i < tpls.length; ++i) {
|
|
9449
|
+
if ( !tpls[i].content ) continue;
|
|
9450
|
+
var before = list.length;
|
|
9451
|
+
collect(tpls[i].content);
|
|
9452
|
+
if ( list.length > before ) harvested.push(tpls[i]);
|
|
9453
|
+
}
|
|
9454
|
+
// pre-pass while the tree is intact: an oob element under another oob element travels
|
|
9455
|
+
// with its ancestor — strip its attribute now, so it never fires and never re-arms
|
|
9456
|
+
for (i = 0; i < list.length; ++i) {
|
|
9457
|
+
var $anc = list[i].parentNode, nested = false;
|
|
9458
|
+
while ( $anc && $anc.nodeType === 1 ) {
|
|
9459
|
+
if ( $anc.hasAttribute(OOB_ATTR) ) { nested = true; break; }
|
|
9460
|
+
$anc = $anc.parentNode;
|
|
9461
|
+
}
|
|
9462
|
+
if (nested) {
|
|
9463
|
+
list[i].removeAttribute(OOB_ATTR);
|
|
9464
|
+
} else {
|
|
9465
|
+
work.push(list[i]);
|
|
9466
|
+
}
|
|
9467
|
+
}
|
|
9468
|
+
for (i = 0; i < work.length; ++i) {
|
|
9469
|
+
var $oob = work[i]
|
|
9470
|
+
, oobId = $oob.getAttribute('id') || null
|
|
9471
|
+
, value = ( $oob.getAttribute(OOB_ATTR) || '' ).trim()
|
|
9472
|
+
, strategy = null
|
|
9473
|
+
, reason = null
|
|
9474
|
+
, $page = null
|
|
9475
|
+
, content = null
|
|
9476
|
+
, evt = null
|
|
9477
|
+
, $scope = null
|
|
9478
|
+
, detaches = false
|
|
9479
|
+
, entry = null
|
|
9480
|
+
;
|
|
9481
|
+
if ( value === '' || /^true$/i.test(value) ) {
|
|
9482
|
+
strategy = 'outerHTML';
|
|
9483
|
+
} else if ( value.indexOf(':') > -1 ) {
|
|
9484
|
+
strategy = value; reason = 'reserved';
|
|
9485
|
+
} else if ( STRATEGIES.indexOf(value) < 0 ) {
|
|
9486
|
+
strategy = value; reason = 'unknownStrategy';
|
|
9487
|
+
} else {
|
|
9488
|
+
strategy = value;
|
|
9489
|
+
}
|
|
9490
|
+
// consumed transport: never lands in the page, never stays in the answer
|
|
9491
|
+
$oob.removeAttribute(OOB_ATTR);
|
|
9492
|
+
if ( $oob.parentNode ) $oob.parentNode.removeChild($oob);
|
|
9493
|
+
|
|
9494
|
+
if ( !reason && !oobId ) reason = 'noId';
|
|
9495
|
+
if ( !reason ) {
|
|
9496
|
+
$page = document.getElementById(oobId);
|
|
9497
|
+
if ( !$page ) reason = 'noTarget';
|
|
9498
|
+
}
|
|
9499
|
+
entry = { id: oobId, strategy: strategy, swapped: false };
|
|
9500
|
+
if ( reason ) {
|
|
9501
|
+
entry.reason = reason;
|
|
9502
|
+
out.list.push(entry);
|
|
9503
|
+
continue;
|
|
9504
|
+
}
|
|
9505
|
+
|
|
9506
|
+
content = ( strategy === 'outerHTML' )
|
|
9507
|
+
? $oob.outerHTML
|
|
9508
|
+
: ( strategy === 'textContent' ) ? $oob.textContent : $oob.innerHTML;
|
|
9509
|
+
|
|
9510
|
+
if ( strategy !== 'none' ) {
|
|
9511
|
+
// no `.hform` companion, exactly as `beforeswap`: this is a DECISION point and the
|
|
9512
|
+
// declared-callback channel carries notifications — `oobafterswap` has the twin
|
|
9513
|
+
evt = triggerEvent(gina, $target, 'oobbeforeswap.' + id, { target: $page, content: content, strategy: strategy, oob: true, oobId: oobId });
|
|
9514
|
+
if ( evt && evt.defaultPrevented ) {
|
|
9515
|
+
entry.reason = 'cancelled';
|
|
9516
|
+
out.list.push(entry);
|
|
9517
|
+
continue;
|
|
9518
|
+
}
|
|
9519
|
+
if ( evt && evt.detail && typeof(evt.detail.content) == 'string' ) {
|
|
9520
|
+
content = evt.detail.content;
|
|
9521
|
+
}
|
|
9522
|
+
}
|
|
9523
|
+
|
|
9524
|
+
detaches = ( strategy === 'outerHTML' || strategy === 'delete' ) && ( $page === $target || $page.contains($target) );
|
|
9525
|
+
|
|
9526
|
+
switch (strategy) {
|
|
9527
|
+
case 'innerHTML':
|
|
9528
|
+
$page.innerHTML = content;
|
|
9529
|
+
$scope = $page;
|
|
9530
|
+
break;
|
|
9531
|
+
case 'outerHTML':
|
|
9532
|
+
$scope = $page.parentNode;
|
|
9533
|
+
$page.outerHTML = content;
|
|
9534
|
+
break;
|
|
9535
|
+
case 'textContent':
|
|
9536
|
+
$page.textContent = content;
|
|
9537
|
+
break;
|
|
9538
|
+
case 'beforebegin':
|
|
9539
|
+
$scope = $page.parentNode;
|
|
9540
|
+
$page.insertAdjacentHTML('beforebegin', content);
|
|
9541
|
+
break;
|
|
9542
|
+
case 'afterbegin':
|
|
9543
|
+
$page.insertAdjacentHTML('afterbegin', content);
|
|
9544
|
+
$scope = $page;
|
|
9545
|
+
break;
|
|
9546
|
+
case 'beforeend':
|
|
9547
|
+
$page.insertAdjacentHTML('beforeend', content);
|
|
9548
|
+
$scope = $page;
|
|
9549
|
+
break;
|
|
9550
|
+
case 'afterend':
|
|
9551
|
+
$scope = $page.parentNode;
|
|
9552
|
+
$page.insertAdjacentHTML('afterend', content);
|
|
9553
|
+
break;
|
|
9554
|
+
case 'delete':
|
|
9555
|
+
if ( $page.parentNode ) $page.parentNode.removeChild($page);
|
|
9556
|
+
break;
|
|
9557
|
+
case 'none':
|
|
9558
|
+
break;
|
|
9559
|
+
}
|
|
9560
|
+
entry.swapped = ( strategy !== 'none' );
|
|
9561
|
+
if ( detaches ) out.rebindSelf = true;
|
|
9562
|
+
|
|
9563
|
+
if ( $scope && typeof($scope.getElementsByTagName) == 'function' ) {
|
|
9564
|
+
bindRegion($scope, { deferFormId: ( detaches ) ? id : null });
|
|
9565
|
+
}
|
|
9566
|
+
if ( strategy !== 'none' ) {
|
|
9567
|
+
triggerEvent(gina, $target, 'oobafterswap.' + id, { target: $page, strategy: strategy, swapped: entry.swapped, oob: true, oobId: oobId });
|
|
9568
|
+
if ( armed )
|
|
9569
|
+
triggerEvent(gina, $target, 'oobafterswap.' + id + '.hform', { target: $page, strategy: strategy, swapped: entry.swapped, oob: true, oobId: oobId });
|
|
9570
|
+
}
|
|
9571
|
+
out.list.push(entry);
|
|
9572
|
+
}
|
|
9573
|
+
// an author-supplied wrapper that held nothing but out-of-band elements is consumed
|
|
9574
|
+
// transport too: left in place it would defeat the empty-remainder rule downstream
|
|
9575
|
+
// (a popin would be asked to load `<template></template>` and blank itself)
|
|
9576
|
+
for (i = 0; i < harvested.length; ++i) {
|
|
9577
|
+
if ( harvested[i].content.childElementCount === 0
|
|
9578
|
+
&& !/\S/.test(harvested[i].content.textContent || '')
|
|
9579
|
+
&& harvested[i].parentNode
|
|
9580
|
+
) {
|
|
9581
|
+
harvested[i].parentNode.removeChild(harvested[i]);
|
|
9582
|
+
}
|
|
9583
|
+
}
|
|
9584
|
+
return out;
|
|
9585
|
+
}
|
|
9586
|
+
|
|
9264
9587
|
/**
|
|
9265
9588
|
* listenToXhrEvents
|
|
9266
9589
|
*
|
|
@@ -11701,6 +12024,110 @@ function getElementsByAttribute(attribute) {
|
|
|
11701
12024
|
}
|
|
11702
12025
|
};
|
|
11703
12026
|
}(DOMParser));
|
|
12027
|
+
/**
|
|
12028
|
+
* bindRegion
|
|
12029
|
+
*
|
|
12030
|
+
* Binds a freshly injected HTML region — ONE policy, shared by fragment navigation
|
|
12031
|
+
* (`gina/nav`) and by the validator's form-answer swaps (#gh76 slice 2), so a third
|
|
12032
|
+
* copy of "what happens to injected content" never has to be written:
|
|
12033
|
+
* - `<script src>` elements the document does not already have OUTSIDE the region are
|
|
12034
|
+
* re-created in `<head>` (a `<script>` inserted through innerHTML never runs); a src the
|
|
12035
|
+
* document already carries elsewhere — the page's own bundles, a copy an earlier swap or a
|
|
12036
|
+
* popin injected — is not re-created, so a fragment may safely re-declare the page's
|
|
12037
|
+
* scripts. Inline scripts are never executed: innerHTML semantics, the contract popin
|
|
12038
|
+
* and nav content have always had;
|
|
12039
|
+
* - `<link rel="stylesheet">` elements are left alone — inserted through innerHTML they are
|
|
12040
|
+
* already live;
|
|
12041
|
+
* - forms that opt in (#B549: a `data-gina-form-*` attribute, an id naming a registered rule,
|
|
12042
|
+
* or a `gina-upload-*` id) are bound through the live validator's own `bindRegion`; a bare
|
|
12043
|
+
* form keeps its native submit, exactly as on the initial page;
|
|
12044
|
+
* - `<a data-gina-link>` anchors are bound through the live link plugin when one is active;
|
|
12045
|
+
* - declarative `data-gina-dialog` triggers are document-delegated (nothing to bind); legacy
|
|
12046
|
+
* `data-gina-popin-*` triggers are inert in injected content; custom elements upgrade
|
|
12047
|
+
* natively.
|
|
12048
|
+
*
|
|
12049
|
+
* @param {HTMLElement} $root - the injected region: the swapped element, or the parent of
|
|
12050
|
+
* nodes inserted beside it
|
|
12051
|
+
* @param {object} [options]
|
|
12052
|
+
* @param {string} [options.deferFormId] - a form id to SKIP: the submitting form of a swap
|
|
12053
|
+
* that replaced it keeps its listeners until the swap's own events are delivered, so
|
|
12054
|
+
* its replacement is bound by the caller afterwards
|
|
12055
|
+
* @param {boolean} [options.forms=true] - bind opted-in forms
|
|
12056
|
+
* @param {boolean} [options.links=true] - bind `data-gina-link` anchors
|
|
12057
|
+
*
|
|
12058
|
+
* @returns {{scripts: number, forms: number, links: number}} what was bound
|
|
12059
|
+
*
|
|
12060
|
+
* @example
|
|
12061
|
+
* $region.innerHTML = fragment;
|
|
12062
|
+
* bindRegion($region); // => { scripts: 1, forms: 2, links: 0 }
|
|
12063
|
+
*/
|
|
12064
|
+
function bindRegion($root, options) {
|
|
12065
|
+
var out = { scripts: 0, forms: 0, links: 0 };
|
|
12066
|
+
if ( !$root || typeof($root.getElementsByTagName) != 'function' ) {
|
|
12067
|
+
return out;
|
|
12068
|
+
}
|
|
12069
|
+
options = options || {};
|
|
12070
|
+
var _gina = ( typeof(window) != 'undefined' && window.gina ) ? window.gina : null;
|
|
12071
|
+
|
|
12072
|
+
// scripts — dedup against every src the DOCUMENT carries outside the region: the
|
|
12073
|
+
// fragment's own copies are inert (innerHTML) and must not count as "already loaded"
|
|
12074
|
+
var known = []
|
|
12075
|
+
, docScripts = document.getElementsByTagName('script')
|
|
12076
|
+
, scripts = $root.getElementsByTagName('script')
|
|
12077
|
+
, src = null
|
|
12078
|
+
, $s = null
|
|
12079
|
+
, i = 0
|
|
12080
|
+
, len = docScripts.length
|
|
12081
|
+
;
|
|
12082
|
+
for (; i < len; ++i) {
|
|
12083
|
+
if ( docScripts[i].src && !$root.contains(docScripts[i]) ) {
|
|
12084
|
+
known.push(docScripts[i].src);
|
|
12085
|
+
}
|
|
12086
|
+
}
|
|
12087
|
+
for (i = 0, len = scripts.length; i < len; ++i) {
|
|
12088
|
+
src = scripts[i].src; // the resolved absolute URL
|
|
12089
|
+
if ( !src || known.indexOf(src) > -1 ) continue;
|
|
12090
|
+
$s = document.createElement('script');
|
|
12091
|
+
$s.src = src;
|
|
12092
|
+
document.head.appendChild($s);
|
|
12093
|
+
known.push(src);
|
|
12094
|
+
out.scripts++;
|
|
12095
|
+
}
|
|
12096
|
+
|
|
12097
|
+
// forms — the validator owns the opt-in gate (#B549)
|
|
12098
|
+
if (
|
|
12099
|
+
options.forms !== false
|
|
12100
|
+
&& _gina && _gina.hasValidator && _gina.validator
|
|
12101
|
+
&& typeof(_gina.validator.bindRegion) == 'function'
|
|
12102
|
+
) {
|
|
12103
|
+
try {
|
|
12104
|
+
out.forms = _gina.validator.bindRegion($root, { deferFormId: options.deferFormId || null });
|
|
12105
|
+
} catch (formsErr) {
|
|
12106
|
+
if ( typeof(console) != 'undefined' && console.warn ) {
|
|
12107
|
+
console.warn('[gina][bindRegion] form binding failed: ' + (formsErr.message || formsErr));
|
|
12108
|
+
}
|
|
12109
|
+
}
|
|
12110
|
+
}
|
|
12111
|
+
|
|
12112
|
+
// links — the link plugin binds the anchors that opt in
|
|
12113
|
+
if (
|
|
12114
|
+
options.links !== false
|
|
12115
|
+
&& _gina && _gina.hasLinkHandler && _gina.link
|
|
12116
|
+
&& typeof(_gina.link.bindLinks) == 'function'
|
|
12117
|
+
) {
|
|
12118
|
+
try {
|
|
12119
|
+
_gina.link.bindLinks($root);
|
|
12120
|
+
out.links = $root.querySelectorAll('a[data-gina-link]:not([data-gina-link="false"])').length;
|
|
12121
|
+
} catch (linksErr) {
|
|
12122
|
+
if ( typeof(console) != 'undefined' && console.warn ) {
|
|
12123
|
+
console.warn('[gina][bindRegion] link binding failed: ' + (linksErr.message || linksErr));
|
|
12124
|
+
}
|
|
12125
|
+
}
|
|
12126
|
+
}
|
|
12127
|
+
|
|
12128
|
+
return out;
|
|
12129
|
+
}
|
|
12130
|
+
;
|
|
11704
12131
|
define("utils/dom", function(){});
|
|
11705
12132
|
|
|
11706
12133
|
/**
|
|
@@ -12162,8 +12589,13 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
12162
12589
|
'registered',
|
|
12163
12590
|
'success',
|
|
12164
12591
|
'error',
|
|
12592
|
+
'beforeswap', // #gh76 slice 2 — cancelable, before a form answer is swapped into its target
|
|
12593
|
+
'afterswap', // #gh76 slice 2 — after the swap and the region binding
|
|
12594
|
+
'oobbeforeswap', // #gh76 slice 3 — per out-of-band element, cancelable
|
|
12595
|
+
'oobafterswap', // #gh76 slice 3 — per out-of-band element, after its region binding
|
|
12165
12596
|
'progress',
|
|
12166
12597
|
'uploadProgress', // #R8 — upload (client-to-server) wire progress for staged uploads
|
|
12598
|
+
'abort', // #gh76 slice 4 — a request superseded by `data-gina-form-sync`
|
|
12167
12599
|
'submit',
|
|
12168
12600
|
'reset',
|
|
12169
12601
|
'change',
|
|
@@ -12745,15 +13177,1053 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
12745
13177
|
if ( gina.hasPopinHandler && gina.popinIsBinded ) {
|
|
12746
13178
|
$activePopin = gina.popin.getActivePopin();
|
|
12747
13179
|
}
|
|
12748
|
-
|
|
12749
|
-
if ( $activePopin && $activePopin.isOpen ) {
|
|
12750
|
-
isPopinInUse = true;
|
|
13180
|
+
|
|
13181
|
+
if ( $activePopin && $activePopin.isOpen ) {
|
|
13182
|
+
isPopinInUse = true;
|
|
13183
|
+
}
|
|
13184
|
+
|
|
13185
|
+
return isPopinInUse;
|
|
13186
|
+
}
|
|
13187
|
+
|
|
13188
|
+
/**
|
|
13189
|
+
* popinStillContaining
|
|
13190
|
+
*
|
|
13191
|
+
* #gh76 — the settle-time half of the containment rule: the popin captured at
|
|
13192
|
+
* submit, if it is still open and its element still contains the form (a close +
|
|
13193
|
+
* reopen in flight wipes and refetches the content, so the captured form is detached
|
|
13194
|
+
* and the answer must not land in content the user has since navigated to).
|
|
13195
|
+
*
|
|
13196
|
+
* @param {object|null} $captured - the popin captured at submit (`sendCtx.popin`)
|
|
13197
|
+
* @param {HTMLElement} $formEl - the form element
|
|
13198
|
+
*
|
|
13199
|
+
* @returns {object|null} $popin
|
|
13200
|
+
*
|
|
13201
|
+
* @example
|
|
13202
|
+
* var $popin = popinStillContaining(sendCtx.popin, $target); // null => legacy payload
|
|
13203
|
+
*/
|
|
13204
|
+
var popinStillContaining = function($captured, $formEl) {
|
|
13205
|
+
var $dialog = null;
|
|
13206
|
+
if ( !$captured || !$captured.isOpen ) {
|
|
13207
|
+
return null;
|
|
13208
|
+
}
|
|
13209
|
+
$dialog = ( $captured.id ) ? document.getElementById($captured.id) : null;
|
|
13210
|
+
return ( $dialog && $dialog.contains($formEl) ) ? $captured : null;
|
|
13211
|
+
}
|
|
13212
|
+
|
|
13213
|
+
/**
|
|
13214
|
+
* warnIfOldRuleRouted
|
|
13215
|
+
*
|
|
13216
|
+
* #gh76 — dev-only notice when the retired "active popin" rule would have routed a
|
|
13217
|
+
* PAGE form's answer or redirect into a popin the form is not inside. The one
|
|
13218
|
+
* conceivable reliance on that routing is a non-modal popin deliberately updated by a
|
|
13219
|
+
* page form; the notice is how such a page finds out. Two messages: the popin was
|
|
13220
|
+
* open (its content would have been replaced) vs loading, not open (a false 422
|
|
13221
|
+
* would have been raised).
|
|
13222
|
+
*
|
|
13223
|
+
* @param {object|null} $captured - the popin captured at submit
|
|
13224
|
+
* @param {object|null} $routed - the popin the answer is actually routed to
|
|
13225
|
+
* @param {HTMLElement} $formEl - the form element
|
|
13226
|
+
* @param {string} what - `answer` or `redirect`, for the message
|
|
13227
|
+
*
|
|
13228
|
+
* @returns {undefined}
|
|
13229
|
+
*
|
|
13230
|
+
* @example
|
|
13231
|
+
* warnIfOldRuleRouted(sendCtx.popin, $popin, $target, 'answer');
|
|
13232
|
+
*/
|
|
13233
|
+
var warnIfOldRuleRouted = function($captured, $routed, $formEl, what) {
|
|
13234
|
+
var $old = null, $popins = null, activeId = null;
|
|
13235
|
+
if ( !envIsDev || $routed || $captured || typeof(gina.popin) == 'undefined' || !gina.popin ) {
|
|
13236
|
+
return;
|
|
13237
|
+
}
|
|
13238
|
+
// what getActivePopin() resolved before #gh76: the first open popin, else the
|
|
13239
|
+
// popin `activePopinId` names in ANY state
|
|
13240
|
+
$old = gina.popin.getActivePopin();
|
|
13241
|
+
if ( !$old ) {
|
|
13242
|
+
$popins = gina.popin.$popins || {};
|
|
13243
|
+
activeId = gina.popin.activePopinId;
|
|
13244
|
+
$old = ( activeId && $popins[activeId] ) ? $popins[activeId] : null;
|
|
13245
|
+
}
|
|
13246
|
+
if ( !$old ) {
|
|
13247
|
+
return;
|
|
13248
|
+
}
|
|
13249
|
+
try {
|
|
13250
|
+
console.warn('[FormValidator][popin] the HTML '+ what +' of form `#'+ $formEl.id +'` is delivered to its own handler: the form is not inside popin `'+ $old.name +'`, which is '+ ( $old.isOpen ? 'open (the former routing rule would have replaced its content)' : 'loading, not open (the former routing rule would have raised a false 422 error)' ) +'.');
|
|
13251
|
+
} catch (e) {}
|
|
13252
|
+
}
|
|
13253
|
+
|
|
13254
|
+
|
|
13255
|
+
/**
|
|
13256
|
+
* warnIfOldRulePlacedUpload
|
|
13257
|
+
*
|
|
13258
|
+
* #B572 — dev-only notice, the staged-upload twin of `warnIfOldRuleRouted`: the
|
|
13259
|
+
* retired "active popin" rule placed a PAGE form's virtual upload form — and with it
|
|
13260
|
+
* the preview lookups and the staging request's popin headers — inside whichever popin
|
|
13261
|
+
* happened to be open, where the staged metadata never reached the form. Containment
|
|
13262
|
+
* now decides the placement; this is how a page that relied on the old placement
|
|
13263
|
+
* finds out. Only an OPEN popin is named: the retired gate required `isOpen`.
|
|
13264
|
+
*
|
|
13265
|
+
* @param {object|null} $owner - the popin containing the real form (null for a page form)
|
|
13266
|
+
* @param {HTMLElement} $formEl - the real form the file input belongs to
|
|
13267
|
+
* @param {HTMLElement} $inputEl - the file input
|
|
13268
|
+
*
|
|
13269
|
+
* @returns {undefined}
|
|
13270
|
+
*
|
|
13271
|
+
* @example
|
|
13272
|
+
* warnIfOldRulePlacedUpload($ownerPopin, $el.form, $el);
|
|
13273
|
+
*/
|
|
13274
|
+
var warnIfOldRulePlacedUpload = function($owner, $formEl, $inputEl) {
|
|
13275
|
+
var $old = null;
|
|
13276
|
+
if ( !envIsDev || $owner || typeof(gina.popin) == 'undefined' || !gina.popin ) {
|
|
13277
|
+
return;
|
|
13278
|
+
}
|
|
13279
|
+
// what isPopinContext() resolved before #B572: the first OPEN popin
|
|
13280
|
+
$old = ( typeof(gina.popin.getActivePopin) == 'function' ) ? gina.popin.getActivePopin() : null;
|
|
13281
|
+
if ( !$old || !$old.isOpen ) {
|
|
13282
|
+
return;
|
|
13283
|
+
}
|
|
13284
|
+
try {
|
|
13285
|
+
console.warn('[FormValidator][popin] the staged upload of `#'+ ( ( $inputEl && $inputEl.id ) ? $inputEl.id : '' ) +'` (form `#'+ ( ( $formEl && $formEl.id ) ? $formEl.id : '' ) +'`) is placed with its own form: the form is not inside popin `'+ $old.name +'`, which is open (the former rule would have placed the upload inside it and its metadata would never have reached the form).');
|
|
13286
|
+
} catch (e) {}
|
|
13287
|
+
}
|
|
13288
|
+
|
|
13289
|
+
/**
|
|
13290
|
+
* The swap strategies `data-gina-form-swap` accepts (#gh76 slice 2) — htmx's `hx-swap`
|
|
13291
|
+
* values, applied with the matching DOM operation.
|
|
13292
|
+
* @constant {string[]}
|
|
13293
|
+
*/
|
|
13294
|
+
var SWAP_STRATEGIES = ['innerHTML', 'outerHTML', 'textContent', 'beforebegin', 'afterbegin', 'beforeend', 'afterend', 'delete', 'none'];
|
|
13295
|
+
|
|
13296
|
+
/**
|
|
13297
|
+
* resolveSwapTarget
|
|
13298
|
+
*
|
|
13299
|
+
* #gh76 slice 2 — resolves a form's `data-gina-form-target` value to an element, at
|
|
13300
|
+
* SUBMIT time, with htmx's `hx-target` grammar: `this` (the form itself),
|
|
13301
|
+
* `closest <selector>` (the closest ancestor OR the form), `find <selector>` (the first
|
|
13302
|
+
* descendant), or a CSS selector (the first match in the document). `next` and
|
|
13303
|
+
* `previous` are reserved: refused with a message naming them, so a later addition is
|
|
13304
|
+
* purely additive.
|
|
13305
|
+
*
|
|
13306
|
+
* @param {HTMLFormElement} $formEl - the submitting form
|
|
13307
|
+
* @param {string} value - the attribute value
|
|
13308
|
+
*
|
|
13309
|
+
* @returns {{target: (HTMLElement|undefined), error: (string|undefined)}}
|
|
13310
|
+
*
|
|
13311
|
+
* @example
|
|
13312
|
+
* resolveSwapTarget($form, 'closest li'); // => { target: <li> }
|
|
13313
|
+
* resolveSwapTarget($form, '#nowhere'); // => { error: 'no element matches `#nowhere`' }
|
|
13314
|
+
*/
|
|
13315
|
+
var resolveSwapTarget = function($formEl, value) {
|
|
13316
|
+
var v = ( typeof(value) == 'string' ) ? value.trim() : '';
|
|
13317
|
+
var m = null, keyword = null, selector = null, $el = null;
|
|
13318
|
+
if ( v === '' ) {
|
|
13319
|
+
return { error: 'empty value' };
|
|
13320
|
+
}
|
|
13321
|
+
if ( v === 'this' ) {
|
|
13322
|
+
return { target: $formEl };
|
|
13323
|
+
}
|
|
13324
|
+
try {
|
|
13325
|
+
m = v.match(/^(closest|find|next|previous)(?:\s+(.+))?$/i);
|
|
13326
|
+
if (m) {
|
|
13327
|
+
keyword = m[1].toLowerCase();
|
|
13328
|
+
selector = ( m[2] || '' ).trim();
|
|
13329
|
+
if ( keyword === 'next' || keyword === 'previous' ) {
|
|
13330
|
+
return { error: '`'+ keyword +'` targeting is reserved and not supported yet' };
|
|
13331
|
+
}
|
|
13332
|
+
if ( selector === '' ) {
|
|
13333
|
+
return { error: '`'+ keyword +'` needs a selector' };
|
|
13334
|
+
}
|
|
13335
|
+
$el = ( keyword === 'closest' ) ? $formEl.closest(selector) : $formEl.querySelector(selector);
|
|
13336
|
+
} else {
|
|
13337
|
+
$el = document.querySelector(v);
|
|
13338
|
+
}
|
|
13339
|
+
} catch (selectorErr) {
|
|
13340
|
+
return { error: 'invalid selector `'+ v +'`: '+ (selectorErr.message || selectorErr) };
|
|
13341
|
+
}
|
|
13342
|
+
return ( $el ) ? { target: $el } : { error: 'no element matches `'+ v +'`' };
|
|
13343
|
+
}
|
|
13344
|
+
|
|
13345
|
+
/**
|
|
13346
|
+
* refuseSend
|
|
13347
|
+
*
|
|
13348
|
+
* #gh76 slice 2 — refuses a submit BEFORE any request leaves (a declared target or swap
|
|
13349
|
+
* strategy that cannot be honoured): releases what the submit armed and delivers the
|
|
13350
|
+
* refusal to the form's error channels as `{ status: 422, reason: 'targetError', … }`.
|
|
13351
|
+
* Runs before the request is opened, so no `disabled`/`data-gina-form-loading` lock
|
|
13352
|
+
* exists yet; the #B247 trigger arm does, and is released.
|
|
13353
|
+
*
|
|
13354
|
+
* @param {object} $form - the form's registry entry
|
|
13355
|
+
* @param {HTMLFormElement} $target - the form element
|
|
13356
|
+
* @param {string} id - the form id
|
|
13357
|
+
* @param {boolean} hFormIsRequired - whether the declared-callback channel is armed
|
|
13358
|
+
* @param {string} attribute - the offending attribute name
|
|
13359
|
+
* @param {string} value - its value
|
|
13360
|
+
* @param {string} why - the reason
|
|
13361
|
+
* @param {boolean} [ownedByEarlierSend] - a PREVIOUS request was already in flight when
|
|
13362
|
+
* this attempt reached `send()` (read before it claimed `isSending`). When true the
|
|
13363
|
+
* refusal delivers its error but touches neither `isSending`/`sent` nor the loading
|
|
13364
|
+
* state: both belong to the request still running, whose own settle releases them.
|
|
13365
|
+
* `armSubmitLoading` is first-wins, so this refused attempt armed nothing of its own
|
|
13366
|
+
* — the same ownership rule the validation-rejected path applies (#B247).
|
|
13367
|
+
*
|
|
13368
|
+
* @returns {undefined}
|
|
13369
|
+
*
|
|
13370
|
+
* @example
|
|
13371
|
+
* refuseSend($form, $target, id, hFormIsRequired, 'data-gina-form-target', '#nowhere', 'no element matches `#nowhere`', false);
|
|
13372
|
+
*/
|
|
13373
|
+
var refuseSend = function($form, $target, id, hFormIsRequired, attribute, value, why, ownedByEarlierSend) {
|
|
13374
|
+
var result = {
|
|
13375
|
+
status : 422,
|
|
13376
|
+
error : attribute +': '+ why,
|
|
13377
|
+
reason : 'targetError',
|
|
13378
|
+
attribute : attribute,
|
|
13379
|
+
value : value,
|
|
13380
|
+
transportError : false
|
|
13381
|
+
};
|
|
13382
|
+
if ( !ownedByEarlierSend ) {
|
|
13383
|
+
$form.isSending = false;
|
|
13384
|
+
$form.sent = false;
|
|
13385
|
+
if ( instance.$forms[id] ) {
|
|
13386
|
+
instance.$forms[id].isSending = false;
|
|
13387
|
+
instance.$forms[id].sent = false;
|
|
13388
|
+
}
|
|
13389
|
+
disarmSubmitLoading($form);
|
|
13390
|
+
}
|
|
13391
|
+
$form.eventData.error = result;
|
|
13392
|
+
if (envIsDev) {
|
|
13393
|
+
try { console.error('[FormValidator][swap] submit of form `#'+ id +'` refused — '+ result.error); } catch (e) {}
|
|
13394
|
+
}
|
|
13395
|
+
triggerEvent(gina, $target, 'error.' + id, result);
|
|
13396
|
+
if (hFormIsRequired)
|
|
13397
|
+
triggerEvent(gina, $target, 'error.' + id + '.hform', result);
|
|
13398
|
+
}
|
|
13399
|
+
|
|
13400
|
+
/**
|
|
13401
|
+
* #gh76 slice 4 — the three things a submit can do when the region its answer is bound
|
|
13402
|
+
* for is already owned by a request in flight. `data-gina-form-sync` names one of them;
|
|
13403
|
+
* most forms never need to, because the default is DERIVED from the swap strategy they
|
|
13404
|
+
* already declared (see `deriveSync`).
|
|
13405
|
+
* @constant {string[]}
|
|
13406
|
+
*/
|
|
13407
|
+
var SYNC_STRATEGIES = ['drop', 'replace', 'queue'];
|
|
13408
|
+
|
|
13409
|
+
/**
|
|
13410
|
+
* #gh76 slice 4 — the swap strategies that REPLACE what a region holds, as opposed to
|
|
13411
|
+
* adding to it. They are what makes the default derivable: an answer about to overwrite
|
|
13412
|
+
* the region makes the answer still on the wire moot, so the newer submit wins. The five
|
|
13413
|
+
* that remain — the four insertion points and `none` — have no conflict to resolve, so
|
|
13414
|
+
* two such answers both land, exactly as they always have.
|
|
13415
|
+
* @constant {string[]}
|
|
13416
|
+
*/
|
|
13417
|
+
var REPLACING_SWAPS = ['innerHTML', 'outerHTML', 'textContent', 'delete'];
|
|
13418
|
+
|
|
13419
|
+
/**
|
|
13420
|
+
* #gh76 slice 4 — per-KEY coordination state, keyed on the element a form's answer is
|
|
13421
|
+
* bound for: its resolved swap target, or the form itself when it declares none. A
|
|
13422
|
+
* WeakMap because a swapped-away target is a NEW element — the old entry is collected
|
|
13423
|
+
* with it, and a different element is legitimately a different key.
|
|
13424
|
+
*
|
|
13425
|
+
* There is no sequence counter: `#B175` gives every send its own XHR, so the XHR object
|
|
13426
|
+
* IS the identity a late settle is checked against, exactly and without a counter to
|
|
13427
|
+
* keep in step.
|
|
13428
|
+
*
|
|
13429
|
+
* @type {WeakMap<Element, {xhr: (XMLHttpRequest|null), ctx: (object|null), queue: Array<Function>}>}
|
|
13430
|
+
*/
|
|
13431
|
+
var syncRegistry = new WeakMap();
|
|
13432
|
+
|
|
13433
|
+
/**
|
|
13434
|
+
* #gh76 slice 4 — how many in-flight requests currently hold each element disabled
|
|
13435
|
+
* through `data-gina-form-disabled-elt`. Overlapping requests naming the same element
|
|
13436
|
+
* release it once, at the last settle. Only elements WE disabled are ever counted, so an
|
|
13437
|
+
* authored `disabled` can never be cleared by us.
|
|
13438
|
+
*
|
|
13439
|
+
* @type {WeakMap<Element, number>}
|
|
13440
|
+
*/
|
|
13441
|
+
var disabledRefs = new WeakMap();
|
|
13442
|
+
|
|
13443
|
+
/**
|
|
13444
|
+
* syncNotice
|
|
13445
|
+
*
|
|
13446
|
+
* #gh76 slice 4 — dev-mode console notice for a coordination decision that silently
|
|
13447
|
+
* changes what a submit did (dropped, queued, superseded). Production stays quiet: these
|
|
13448
|
+
* are the declared outcomes, not errors.
|
|
13449
|
+
*
|
|
13450
|
+
* @param {string} id - the form id
|
|
13451
|
+
* @param {string} msg - what happened
|
|
13452
|
+
*
|
|
13453
|
+
* @returns {undefined}
|
|
13454
|
+
*
|
|
13455
|
+
* @example
|
|
13456
|
+
* syncNotice('add-row', 'submit dropped — a request already owns this target');
|
|
13457
|
+
*
|
|
13458
|
+
* @inner
|
|
13459
|
+
*/
|
|
13460
|
+
var syncNotice = function(id, msg) {
|
|
13461
|
+
if (envIsDev) {
|
|
13462
|
+
try { console.warn('[FormValidator][sync] form `#'+ id +'`: '+ msg); } catch (e) {}
|
|
13463
|
+
}
|
|
13464
|
+
}
|
|
13465
|
+
|
|
13466
|
+
/**
|
|
13467
|
+
* parseSync
|
|
13468
|
+
*
|
|
13469
|
+
* #gh76 slice 4 — parses an explicit `data-gina-form-sync` value into `{ strategy }`.
|
|
13470
|
+
*
|
|
13471
|
+
* Three htmx spellings are deliberately REFUSED rather than implemented, each with a
|
|
13472
|
+
* message naming the reason and what to write instead — gina refuses what it will not
|
|
13473
|
+
* honour rather than parsing it into silence:
|
|
13474
|
+
* - `abort` means "anything that comes next may cancel me", htmx's idiom for a
|
|
13475
|
+
* disposable GET. A gina submit is a validated POST with side effects, never
|
|
13476
|
+
* disposable; `replace` and `drop` cover both halves of what it was reaching for.
|
|
13477
|
+
* - the `queue` modifiers exist in htmx because its trigger spec has `queue:` modifiers
|
|
13478
|
+
* too. Gina has no trigger spec, and one submit waiting per region is the only
|
|
13479
|
+
* behaviour the region can act on.
|
|
13480
|
+
* - `<selector>:<strategy>` names the element to coordinate on. Gina already knows it:
|
|
13481
|
+
* it is the resolved swap target.
|
|
13482
|
+
*
|
|
13483
|
+
* @param {string} value - the attribute value
|
|
13484
|
+
*
|
|
13485
|
+
* @returns {{strategy: (string|undefined), error: (string|undefined)}}
|
|
13486
|
+
*
|
|
13487
|
+
* @example
|
|
13488
|
+
* parseSync('replace'); // => { strategy: 'replace' }
|
|
13489
|
+
* parseSync('queue'); // => { strategy: 'queue' }
|
|
13490
|
+
* parseSync('abort'); // => { error: '`abort` is not supported: … Use `replace` …' }
|
|
13491
|
+
*
|
|
13492
|
+
* @inner
|
|
13493
|
+
*/
|
|
13494
|
+
var parseSync = function(value) {
|
|
13495
|
+
var v = ( typeof(value) == 'string' ) ? value.trim().toLowerCase() : '';
|
|
13496
|
+
var m = null;
|
|
13497
|
+
if ( v === '' ) {
|
|
13498
|
+
return { error: 'empty value' };
|
|
13499
|
+
}
|
|
13500
|
+
if ( v.indexOf(':') > -1 ) {
|
|
13501
|
+
return { error: '`<selector>:<strategy>` keying is not supported — the key is the resolved `data-gina-form-target`, so there is nothing to name; write the strategy alone' };
|
|
13502
|
+
}
|
|
13503
|
+
if ( /^abort\b/.test(v) ) {
|
|
13504
|
+
return { error: '`abort` is not supported — it means `anything may cancel me`, which fits a disposable GET, not a submit with rules and side effects; use `replace` to let the newer submit take over, or `drop` to yield to the one running' };
|
|
13505
|
+
}
|
|
13506
|
+
m = v.match(/^(drop|replace|queue)(?:\s+(\S+))?$/);
|
|
13507
|
+
if ( !m ) {
|
|
13508
|
+
return { error: 'unknown strategy `'+ v +'` (one of '+ SYNC_STRATEGIES.join(', ') +')' };
|
|
13509
|
+
}
|
|
13510
|
+
if ( m[2] ) {
|
|
13511
|
+
return { error: '`'+ m[1] +'` takes no modifier (found `'+ m[2] +'`) — the key is the resolved swap target, and one submit waits per region, the most recent one' };
|
|
13512
|
+
}
|
|
13513
|
+
return { strategy: m[1] };
|
|
13514
|
+
}
|
|
13515
|
+
|
|
13516
|
+
/**
|
|
13517
|
+
* deriveSync
|
|
13518
|
+
*
|
|
13519
|
+
* #gh76 slice 4 — the DEFAULT, for a form that declares no `data-gina-form-sync`: read
|
|
13520
|
+
* from the swap strategy the form already declared, so the common case coordinates with
|
|
13521
|
+
* nothing written in the markup.
|
|
13522
|
+
*
|
|
13523
|
+
* A form whose answer REPLACES a region coordinates on that region — an answer still on
|
|
13524
|
+
* the wire is about to be overwritten, so it is moot and the newer submit supersedes it.
|
|
13525
|
+
* A form whose answer is INSERTED, or writes nothing, has no conflict to resolve: both
|
|
13526
|
+
* land, exactly as they always have. A form declaring no target coordinates on nothing —
|
|
13527
|
+
* the module-wide one-at-a-time rule is the only thing that governs it, unchanged.
|
|
13528
|
+
*
|
|
13529
|
+
* This is the decision neither htmx nor Turbo can make for you: htmx knows nothing about
|
|
13530
|
+
* the answer until it arrives, and Turbo treats every submit as a navigation, so newest
|
|
13531
|
+
* wins for everything. Gina knows, at submit time, what the answer is going to do.
|
|
13532
|
+
*
|
|
13533
|
+
* The target must RESOLVE here. An unresolvable one is refused by the pre-flight a few
|
|
13534
|
+
* lines below, and a submit already on its way to a refusal must never abort a running
|
|
13535
|
+
* request on the way.
|
|
13536
|
+
*
|
|
13537
|
+
* @param {HTMLFormElement} $formEl - the submitting form
|
|
13538
|
+
*
|
|
13539
|
+
* @returns {?{strategy: string, derived: boolean, key: HTMLElement, swap: string}}
|
|
13540
|
+
*
|
|
13541
|
+
* @example
|
|
13542
|
+
* // <form data-gina-form-target="#rows">
|
|
13543
|
+
* deriveSync($form); // => { strategy: 'replace', derived: true, key: <ul id="rows">, swap: 'innerHTML' }
|
|
13544
|
+
* // <form data-gina-form-target="#rows" data-gina-form-swap="beforeend">
|
|
13545
|
+
* deriveSync($form); // => null — an insertion has nothing to resolve
|
|
13546
|
+
*
|
|
13547
|
+
* @inner
|
|
13548
|
+
*/
|
|
13549
|
+
var deriveSync = function($formEl) {
|
|
13550
|
+
var targetAttr = $formEl.getAttribute('data-gina-form-target');
|
|
13551
|
+
var swapAttr = null, strategy = '', resolved = null;
|
|
13552
|
+
if ( targetAttr === null ) {
|
|
13553
|
+
return null;
|
|
13554
|
+
}
|
|
13555
|
+
swapAttr = $formEl.getAttribute('data-gina-form-swap');
|
|
13556
|
+
// read exactly as the pre-flight reads it, case included: a value it would refuse
|
|
13557
|
+
// must not coordinate here either
|
|
13558
|
+
strategy = ( swapAttr === null ) ? 'innerHTML' : swapAttr.trim();
|
|
13559
|
+
if ( REPLACING_SWAPS.indexOf(strategy) < 0 ) {
|
|
13560
|
+
return null;
|
|
13561
|
+
}
|
|
13562
|
+
resolved = resolveSwapTarget($formEl, targetAttr);
|
|
13563
|
+
if ( resolved.error || !resolved.target ) {
|
|
13564
|
+
return null;
|
|
13565
|
+
}
|
|
13566
|
+
return { strategy: 'replace', derived: true, key: resolved.target, swap: strategy };
|
|
13567
|
+
}
|
|
13568
|
+
|
|
13569
|
+
/**
|
|
13570
|
+
* decideSync
|
|
13571
|
+
*
|
|
13572
|
+
* #gh76 slice 4 — the pure overlap decision: what this submit does when the key it
|
|
13573
|
+
* coordinates on is, or is not, already owned by a request in flight.
|
|
13574
|
+
*
|
|
13575
|
+
* A key nobody owns is never a decision: every strategy proceeds. When one IS owned,
|
|
13576
|
+
* `drop` yields, `replace` takes the slot, and `queue` defers until it is free. There is
|
|
13577
|
+
* no state carried on the entry for this — the strategy of the submit ARRIVING decides,
|
|
13578
|
+
* because it is the one whose answer is about to land.
|
|
13579
|
+
*
|
|
13580
|
+
* @param {object} parsed - a parseSync() or deriveSync() result
|
|
13581
|
+
* @param {object} [entry] - the key's registry entry, when it has one
|
|
13582
|
+
*
|
|
13583
|
+
* @returns {{action: string, abortPrevious: (boolean|undefined)}}
|
|
13584
|
+
*
|
|
13585
|
+
* @example
|
|
13586
|
+
* decideSync({ strategy: 'drop' }, { xhr: {} }); // => { action: 'drop' }
|
|
13587
|
+
* decideSync({ strategy: 'replace' }, { xhr: {} }); // => { action: 'proceed', abortPrevious: true }
|
|
13588
|
+
* decideSync({ strategy: 'queue' }, null); // => { action: 'proceed', abortPrevious: false }
|
|
13589
|
+
*
|
|
13590
|
+
* @inner
|
|
13591
|
+
*/
|
|
13592
|
+
var decideSync = function(parsed, entry) {
|
|
13593
|
+
var busy = !!( entry && entry.xhr );
|
|
13594
|
+
if ( !busy ) {
|
|
13595
|
+
return { action: 'proceed', abortPrevious: false };
|
|
13596
|
+
}
|
|
13597
|
+
switch ( parsed.strategy ) {
|
|
13598
|
+
case 'drop':
|
|
13599
|
+
return { action: 'drop' };
|
|
13600
|
+
case 'replace':
|
|
13601
|
+
return { action: 'proceed', abortPrevious: true };
|
|
13602
|
+
case 'queue':
|
|
13603
|
+
return { action: 'queue' };
|
|
13604
|
+
}
|
|
13605
|
+
return { action: 'proceed', abortPrevious: false };
|
|
13606
|
+
}
|
|
13607
|
+
|
|
13608
|
+
/**
|
|
13609
|
+
* queueSyncSend
|
|
13610
|
+
*
|
|
13611
|
+
* #gh76 slice 4 — holds a deferred submit until its key's slot is free. Exactly ONE waits
|
|
13612
|
+
* per key: a submit arriving while another already waits replaces it, because what the
|
|
13613
|
+
* user asked for last is what they meant. Drained by the settle chokepoint.
|
|
13614
|
+
*
|
|
13615
|
+
* @param {object} entry - the key's registry entry
|
|
13616
|
+
* @param {Function} replay - re-enters send() with the caller's original arguments
|
|
13617
|
+
*
|
|
13618
|
+
* @returns {undefined}
|
|
13619
|
+
*
|
|
13620
|
+
* @example
|
|
13621
|
+
* queueSyncSend(entry, function () { send.call(receiver, data, options); });
|
|
13622
|
+
*
|
|
13623
|
+
* @inner
|
|
13624
|
+
*/
|
|
13625
|
+
var queueSyncSend = function(entry, replay) {
|
|
13626
|
+
entry.queue = [ replay ];
|
|
13627
|
+
}
|
|
13628
|
+
|
|
13629
|
+
/**
|
|
13630
|
+
* shiftSyncQueue
|
|
13631
|
+
*
|
|
13632
|
+
* #gh76 slice 4 — runs the next deferred submit for a key, once that key's slot is free.
|
|
13633
|
+
* Guarded on the slot ACTUALLY being free: a settle that was superseded by a `replace`
|
|
13634
|
+
* never shifts, because the request that replaced it claims the slot immediately
|
|
13635
|
+
* afterwards and would race the one this shift started.
|
|
13636
|
+
*
|
|
13637
|
+
* @param {object} [entry] - the key's registry entry
|
|
13638
|
+
*
|
|
13639
|
+
* @returns {undefined}
|
|
13640
|
+
*
|
|
13641
|
+
* @example
|
|
13642
|
+
* shiftSyncQueue(syncRegistry.get($list));
|
|
13643
|
+
*
|
|
13644
|
+
* @inner
|
|
13645
|
+
*/
|
|
13646
|
+
var shiftSyncQueue = function(entry) {
|
|
13647
|
+
var next = null;
|
|
13648
|
+
if ( !entry || entry.xhr || !entry.queue || !entry.queue.length ) {
|
|
13649
|
+
return;
|
|
13650
|
+
}
|
|
13651
|
+
next = entry.queue.shift();
|
|
13652
|
+
try {
|
|
13653
|
+
next();
|
|
13654
|
+
} catch (queueErr) {
|
|
13655
|
+
if (envIsDev) {
|
|
13656
|
+
try { console.error('[FormValidator][sync] a queued submit failed: '+ ( queueErr.message || queueErr )); } catch (e) {}
|
|
13657
|
+
}
|
|
13658
|
+
}
|
|
13659
|
+
}
|
|
13660
|
+
|
|
13661
|
+
/**
|
|
13662
|
+
* resolveSyncKey
|
|
13663
|
+
*
|
|
13664
|
+
* #gh76 slice 4 — the element a form coordinates on: its resolved swap target, or the
|
|
13665
|
+
* form itself when it declares none. Keying on the TARGET is what closes the reorder the
|
|
13666
|
+
* issue describes — two forms answering into one `#list` are invisible to a form-keyed
|
|
13667
|
+
* guard. A declared target that does not resolve falls back to the form, and that submit
|
|
13668
|
+
* is refused a few lines further down anyway, so the key it briefly used is moot.
|
|
13669
|
+
*
|
|
13670
|
+
* @param {HTMLFormElement} $formEl - the submitting form
|
|
13671
|
+
*
|
|
13672
|
+
* @returns {HTMLElement}
|
|
13673
|
+
*
|
|
13674
|
+
* @example
|
|
13675
|
+
* resolveSyncKey($form); // => <ul id="list">
|
|
13676
|
+
*
|
|
13677
|
+
* @inner
|
|
13678
|
+
*/
|
|
13679
|
+
var resolveSyncKey = function($formEl) {
|
|
13680
|
+
var attr = $formEl.getAttribute('data-gina-form-target');
|
|
13681
|
+
var resolved = null;
|
|
13682
|
+
if ( attr === null ) {
|
|
13683
|
+
return $formEl;
|
|
13684
|
+
}
|
|
13685
|
+
resolved = resolveSwapTarget($formEl, attr);
|
|
13686
|
+
return ( resolved.target ) ? resolved.target : $formEl;
|
|
13687
|
+
}
|
|
13688
|
+
|
|
13689
|
+
/**
|
|
13690
|
+
* resolveDisabledElts
|
|
13691
|
+
*
|
|
13692
|
+
* #gh76 slice 4 — resolves a `data-gina-form-disabled-elt` value: a comma-separated list,
|
|
13693
|
+
* each part carrying the `data-gina-form-target` grammar (`this`, `closest …`, `find …`,
|
|
13694
|
+
* a CSS selector; `next`/`previous` reserved). A part that resolves to nothing is an
|
|
13695
|
+
* ERROR, not a silent skip: the attribute exists so a user cannot act twice, and a typo
|
|
13696
|
+
* that quietly disables nothing is the very failure it was added to prevent.
|
|
13697
|
+
*
|
|
13698
|
+
* @param {HTMLFormElement} $formEl - the submitting form
|
|
13699
|
+
* @param {string} value - the attribute value
|
|
13700
|
+
*
|
|
13701
|
+
* @returns {{elements: (Array<HTMLElement>|undefined), error: (string|undefined)}}
|
|
13702
|
+
*
|
|
13703
|
+
* @example
|
|
13704
|
+
* resolveDisabledElts($form, 'closest fieldset, #save'); // => { elements: [<fieldset>, <button>] }
|
|
13705
|
+
* resolveDisabledElts($form, '#nope'); // => { error: '`#nope`: no element matches `#nope`' }
|
|
13706
|
+
*
|
|
13707
|
+
* @inner
|
|
13708
|
+
*/
|
|
13709
|
+
var resolveDisabledElts = function($formEl, value) {
|
|
13710
|
+
var raw = ( typeof(value) == 'string' ) ? value : '';
|
|
13711
|
+
var parts = raw.split(',');
|
|
13712
|
+
var out = [], i = 0, part = null, resolved = null;
|
|
13713
|
+
if ( raw.trim() === '' ) {
|
|
13714
|
+
return { error: 'empty value' };
|
|
13715
|
+
}
|
|
13716
|
+
for (; i < parts.length; ++i) {
|
|
13717
|
+
part = parts[i].trim();
|
|
13718
|
+
if ( part === '' ) {
|
|
13719
|
+
return { error: 'empty part at position '+ ( i + 1 ) };
|
|
13720
|
+
}
|
|
13721
|
+
resolved = resolveSwapTarget($formEl, part);
|
|
13722
|
+
if ( resolved.error ) {
|
|
13723
|
+
return { error: '`'+ part +'`: '+ resolved.error };
|
|
13724
|
+
}
|
|
13725
|
+
if ( out.indexOf(resolved.target) < 0 ) {
|
|
13726
|
+
out.push(resolved.target);
|
|
13727
|
+
}
|
|
13728
|
+
}
|
|
13729
|
+
return { elements: out };
|
|
13730
|
+
}
|
|
13731
|
+
|
|
13732
|
+
/**
|
|
13733
|
+
* disableForRequest
|
|
13734
|
+
*
|
|
13735
|
+
* #gh76 slice 4 — holds one element disabled for the life of a request, refcounted so
|
|
13736
|
+
* overlapping requests naming the same element release it once. An element ALREADY
|
|
13737
|
+
* disabled without our marker was disabled by the page: it is left untouched and not
|
|
13738
|
+
* counted, so the release can never clear a state we did not set.
|
|
13739
|
+
*
|
|
13740
|
+
* @param {HTMLElement} $el - the element to hold disabled
|
|
13741
|
+
* @param {string} formId - written to `data-gina-disabled-by` as provenance
|
|
13742
|
+
*
|
|
13743
|
+
* @returns {boolean} whether this request now holds the element
|
|
13744
|
+
*
|
|
13745
|
+
* @example
|
|
13746
|
+
* disableForRequest($fieldset, 'add-row'); // => true
|
|
13747
|
+
*
|
|
13748
|
+
* @inner
|
|
13749
|
+
*/
|
|
13750
|
+
var disableForRequest = function($el, formId) {
|
|
13751
|
+
var n = disabledRefs.get($el) || 0;
|
|
13752
|
+
if ( n === 0 ) {
|
|
13753
|
+
if ( $el.hasAttribute('disabled') ) {
|
|
13754
|
+
return false;
|
|
13755
|
+
}
|
|
13756
|
+
$el.setAttribute('disabled', '');
|
|
13757
|
+
$el.setAttribute('data-gina-disabled-by', formId);
|
|
13758
|
+
}
|
|
13759
|
+
disabledRefs.set($el, n + 1);
|
|
13760
|
+
return true;
|
|
13761
|
+
}
|
|
13762
|
+
|
|
13763
|
+
/**
|
|
13764
|
+
* releaseDisabledElts
|
|
13765
|
+
*
|
|
13766
|
+
* #gh76 slice 4 — releases the elements one request held, at its settle. Decrements each
|
|
13767
|
+
* refcount and clears `disabled` only at zero, and only on an element still carrying our
|
|
13768
|
+
* own `data-gina-disabled-by` marker.
|
|
13769
|
+
*
|
|
13770
|
+
* @param {Array<HTMLElement>} [elements] - what this request held
|
|
13771
|
+
*
|
|
13772
|
+
* @returns {undefined}
|
|
13773
|
+
*
|
|
13774
|
+
* @example
|
|
13775
|
+
* releaseDisabledElts(sendCtx.disabledElts);
|
|
13776
|
+
*
|
|
13777
|
+
* @inner
|
|
13778
|
+
*/
|
|
13779
|
+
var releaseDisabledElts = function(elements) {
|
|
13780
|
+
var i = 0, $el = null, n = 0;
|
|
13781
|
+
if ( !elements || !elements.length ) {
|
|
13782
|
+
return;
|
|
13783
|
+
}
|
|
13784
|
+
for (; i < elements.length; ++i) {
|
|
13785
|
+
$el = elements[i];
|
|
13786
|
+
n = disabledRefs.get($el) || 0;
|
|
13787
|
+
if ( n > 1 ) {
|
|
13788
|
+
disabledRefs.set($el, n - 1);
|
|
13789
|
+
continue;
|
|
13790
|
+
}
|
|
13791
|
+
disabledRefs['delete']($el);
|
|
13792
|
+
if ( $el.getAttribute('data-gina-disabled-by') !== null ) {
|
|
13793
|
+
$el.removeAttribute('disabled');
|
|
13794
|
+
$el.removeAttribute('data-gina-disabled-by');
|
|
13795
|
+
}
|
|
13796
|
+
}
|
|
13797
|
+
}
|
|
13798
|
+
|
|
13799
|
+
/**
|
|
13800
|
+
* warnOobRefusals
|
|
13801
|
+
*
|
|
13802
|
+
* #gh76 slice 3 — dev-mode notice for every out-of-band element that did not swap for a
|
|
13803
|
+
* REASON (no id, no page match, reserved or unknown value, cancelled). `swap="none"`
|
|
13804
|
+
* writes nothing by design and carries no reason, so it is not reported.
|
|
13805
|
+
*
|
|
13806
|
+
* @param {string} id - the form id
|
|
13807
|
+
* @param {Array<{id: (string|null), strategy: string, swapped: boolean, reason: (string|undefined)}>} list - `applyOobSwaps().list`
|
|
13808
|
+
*
|
|
13809
|
+
* @returns {undefined}
|
|
13810
|
+
*
|
|
13811
|
+
* @example
|
|
13812
|
+
* warnOobRefusals(id, run.list);
|
|
13813
|
+
*/
|
|
13814
|
+
var warnOobRefusals = function(id, list) {
|
|
13815
|
+
if ( !envIsDev || !list ) return;
|
|
13816
|
+
for (var i = 0; i < list.length; ++i) {
|
|
13817
|
+
if ( !list[i].swapped && list[i].reason ) {
|
|
13818
|
+
try { console.warn('[FormValidator][swap] form `#'+ id +'`: out-of-band element `'+ (list[i].id || '(no id)') +'` not swapped — '+ list[i].reason); } catch (e) {}
|
|
13819
|
+
}
|
|
13820
|
+
}
|
|
13821
|
+
}
|
|
13822
|
+
|
|
13823
|
+
/**
|
|
13824
|
+
* applyResponseOverrides
|
|
13825
|
+
*
|
|
13826
|
+
* #gh76 §6 — the server's last word on where a `text/html` answer goes and how: three
|
|
13827
|
+
* response headers, read at settle AFTER the answer is known to be HTML and BEFORE the
|
|
13828
|
+
* swap-target fork and `beforeswap` (htmx's order, so a listener keeps the final say):
|
|
13829
|
+
* `X-Gina-Retarget` — the `data-gina-form-target` grammar (`this`, `closest`, `find`,
|
|
13830
|
+
* a selector; `next`/`previous` refused), resolved from the submitting form through
|
|
13831
|
+
* resolveSwapTarget. On success it BECOMES the target and `sendCtx.targetAttr` names
|
|
13832
|
+
* the header: a declaration made by the server, so it wins over the popin exactly as
|
|
13833
|
+
* a declared attribute does, and it creates a target where none was declared.
|
|
13834
|
+
* `X-Gina-Reswap` — one of SWAP_STRATEGIES, replacing the declared strategy.
|
|
13835
|
+
* `X-Gina-Reselect` — a selector applied to the answer, replacing `data-gina-form-select`.
|
|
13836
|
+
* The invalid-value rule is ASYMMETRIC, each half consistent with a rule that already
|
|
13837
|
+
* exists. A Retarget that cannot be resolved means NO swap (`applied: false` + the
|
|
13838
|
+
* reason, and the target is dropped): slice 2's settle-time refusal for a target that
|
|
13839
|
+
* left the document — inserting into the declared target could land the answer in the
|
|
13840
|
+
* wrong DOM when the server plainly meant somewhere else. A Reswap or Reselect that
|
|
13841
|
+
* fails validation is IGNORED and the declared value kept: a modifier on a known target,
|
|
13842
|
+
* and htmx degrades an invalid `HX-Reswap` the same way. Either without a target —
|
|
13843
|
+
* declared or retargeted — is ignored too (`reason: 'noTarget'`). Never a 422: a
|
|
13844
|
+
* misconfiguration surfacing as an error after a successful write is the false-422
|
|
13845
|
+
* shape slice 1 removed. Dev-mode console notices name every refused or ignored value.
|
|
13846
|
+
* SAME-ORIGIN ONLY. All three are honoured only from a response whose origin is the
|
|
13847
|
+
* page's own (`xhr.responseURL` — the URL after redirects — against
|
|
13848
|
+
* `window.location.origin`): a responder elsewhere may not choose the element its
|
|
13849
|
+
* answer lands in, nor reshape or trim the swap the author declared. A cross-origin
|
|
13850
|
+
* Retarget is refused exactly as an unresolvable one (no swap, `reason: 'crossOrigin'`);
|
|
13851
|
+
* a cross-origin Reswap/Reselect is ignored and the declared value kept. Fail-closed: a
|
|
13852
|
+
* missing or unparseable `responseURL`, or an opaque origin on either side, reads as
|
|
13853
|
+
* not-same-origin. htmx has no such read because `selfRequestsOnly` refuses the
|
|
13854
|
+
* cross-origin REQUEST; a form here posts to its raw `action`, so the gate is at the read.
|
|
13855
|
+
*
|
|
13856
|
+
* Returns the report — `null` when the answer carried none of the three — and stores it
|
|
13857
|
+
* on `sendCtx.overrides`, from where `beforeswap` and the success payload carry it.
|
|
13858
|
+
*
|
|
13859
|
+
* @param {XMLHttpRequest} xhr - the settled transport (its `getResponseHeader` and `responseURL`)
|
|
13860
|
+
* @param {object} sendCtx - the per-send capture (`target`/`targetAttr`/`swap`/`select` mutated)
|
|
13861
|
+
* @param {HTMLFormElement} $target - the submitting form (the `this`/`closest`/`find` base)
|
|
13862
|
+
* @param {string} id - the form id (notices)
|
|
13863
|
+
*
|
|
13864
|
+
* @returns {(null|{retarget: (object|undefined), reswap: (object|undefined), reselect: (object|undefined)})}
|
|
13865
|
+
* each entry `{ value, applied, reason }` (`reason` only when not applied)
|
|
13866
|
+
*
|
|
13867
|
+
* @example
|
|
13868
|
+
* applyResponseOverrides(xhr, sendCtx, $target, id); // => { retarget: { value: '#totals', applied: true } }
|
|
13869
|
+
*/
|
|
13870
|
+
var applyResponseOverrides = function(xhr, sendCtx, $target, id) {
|
|
13871
|
+
var read = function(name) {
|
|
13872
|
+
var v = null;
|
|
13873
|
+
try { v = xhr.getResponseHeader(name); } catch (headerErr) { v = null; }
|
|
13874
|
+
return ( typeof(v) == 'string' ) ? v.trim() : null;
|
|
13875
|
+
};
|
|
13876
|
+
var retarget = read('X-Gina-Retarget')
|
|
13877
|
+
, reswap = read('X-Gina-Reswap')
|
|
13878
|
+
, reselect = read('X-Gina-Reselect')
|
|
13879
|
+
, report = null
|
|
13880
|
+
, resolved = null
|
|
13881
|
+
, hasTarget = false
|
|
13882
|
+
, notice = function(msg) {
|
|
13883
|
+
if (envIsDev) {
|
|
13884
|
+
try { console.warn('[FormValidator][swap] form `#'+ id +'`: '+ msg); } catch (e) {}
|
|
13885
|
+
}
|
|
13886
|
+
}
|
|
13887
|
+
;
|
|
13888
|
+
if ( retarget === null && reswap === null && reselect === null ) {
|
|
13889
|
+
return null;
|
|
13890
|
+
}
|
|
13891
|
+
// #gh76 §6 — same-origin only. The headers are honoured from a response whose
|
|
13892
|
+
// origin is the page's own: a responder elsewhere may not choose the element its
|
|
13893
|
+
// answer lands in, nor reshape or trim the swap the author declared. htmx never
|
|
13894
|
+
// needs this read (`selfRequestsOnly` refuses the cross-origin REQUEST); a form
|
|
13895
|
+
// here posts to its raw `action`, so the gate belongs at the read. Fail-closed: no
|
|
13896
|
+
// `responseURL` (a transport that cannot say where the answer came from), an
|
|
13897
|
+
// unparseable one, or an opaque origin on either side (`'null'` — two opaque
|
|
13898
|
+
// origins are never the same origin, whatever the string says) all read as
|
|
13899
|
+
// not-same-origin. `responseURL` is the URL AFTER redirects, so a same-origin
|
|
13900
|
+
// action that was redirected elsewhere is refused too.
|
|
13901
|
+
var sameOrigin = false
|
|
13902
|
+
, resOrigin = 'an unknown origin'
|
|
13903
|
+
;
|
|
13904
|
+
try {
|
|
13905
|
+
var docOrigin = window.location.origin
|
|
13906
|
+
, resURL = xhr.responseURL
|
|
13907
|
+
;
|
|
13908
|
+
if ( typeof(docOrigin) == 'string' && docOrigin !== '' && docOrigin !== 'null'
|
|
13909
|
+
&& typeof(resURL) == 'string' && resURL !== '' ) {
|
|
13910
|
+
resOrigin = new URL(resURL, window.location.href).origin;
|
|
13911
|
+
sameOrigin = ( resOrigin === docOrigin );
|
|
13912
|
+
}
|
|
13913
|
+
} catch (originErr) {
|
|
13914
|
+
sameOrigin = false;
|
|
13915
|
+
}
|
|
13916
|
+
if ( !sameOrigin ) {
|
|
13917
|
+
report = {};
|
|
13918
|
+
if ( retarget !== null ) {
|
|
13919
|
+
report.retarget = { value: retarget, applied: false, reason: 'crossOrigin' };
|
|
13920
|
+
sendCtx.targetAttr = 'X-Gina-Retarget';
|
|
13921
|
+
sendCtx.target = null;
|
|
13922
|
+
notice('`X-Gina-Retarget: '+ retarget +'` refused — the answer came from '+ resOrigin +', not from this page\'s origin; nothing swapped');
|
|
13923
|
+
}
|
|
13924
|
+
if ( reswap !== null ) {
|
|
13925
|
+
report.reswap = { value: reswap, applied: false, reason: 'crossOrigin' };
|
|
13926
|
+
notice('`X-Gina-Reswap: '+ reswap +'` ignored — the answer came from '+ resOrigin +', not from this page\'s origin; `'+ sendCtx.swap +'` kept');
|
|
13927
|
+
}
|
|
13928
|
+
if ( reselect !== null ) {
|
|
13929
|
+
report.reselect = { value: reselect, applied: false, reason: 'crossOrigin' };
|
|
13930
|
+
notice('`X-Gina-Reselect: '+ reselect +'` ignored — the answer came from '+ resOrigin +', not from this page\'s origin; `'+ ( sendCtx.select || 'the whole answer' ) +'` kept');
|
|
13931
|
+
}
|
|
13932
|
+
sendCtx.overrides = report;
|
|
13933
|
+
return report;
|
|
13934
|
+
}
|
|
13935
|
+
report = {};
|
|
13936
|
+
if ( retarget !== null ) {
|
|
13937
|
+
resolved = resolveSwapTarget($target, retarget);
|
|
13938
|
+
sendCtx.targetAttr = 'X-Gina-Retarget';
|
|
13939
|
+
if ( resolved.error ) {
|
|
13940
|
+
report.retarget = { value: retarget, applied: false, reason: resolved.error };
|
|
13941
|
+
sendCtx.target = null;
|
|
13942
|
+
notice('`X-Gina-Retarget: '+ retarget +'` cannot be honoured ('+ resolved.error +') — nothing swapped');
|
|
13943
|
+
} else {
|
|
13944
|
+
report.retarget = { value: retarget, applied: true };
|
|
13945
|
+
sendCtx.target = resolved.target;
|
|
13946
|
+
}
|
|
13947
|
+
}
|
|
13948
|
+
hasTarget = !!sendCtx.target;
|
|
13949
|
+
if ( reswap !== null ) {
|
|
13950
|
+
if ( !hasTarget ) {
|
|
13951
|
+
report.reswap = { value: reswap, applied: false, reason: 'noTarget' };
|
|
13952
|
+
notice('`X-Gina-Reswap: '+ reswap +'` ignored — the answer has no swap target');
|
|
13953
|
+
} else if ( SWAP_STRATEGIES.indexOf(reswap) < 0 ) {
|
|
13954
|
+
report.reswap = { value: reswap, applied: false, reason: 'unknown swap strategy `'+ reswap +'` (one of '+ SWAP_STRATEGIES.join(', ') +')' };
|
|
13955
|
+
notice('`X-Gina-Reswap: '+ reswap +'` ignored — unknown strategy, `'+ sendCtx.swap +'` kept');
|
|
13956
|
+
} else {
|
|
13957
|
+
report.reswap = { value: reswap, applied: true };
|
|
13958
|
+
sendCtx.swap = reswap;
|
|
13959
|
+
}
|
|
13960
|
+
}
|
|
13961
|
+
if ( reselect !== null ) {
|
|
13962
|
+
var selectorOk = ( reselect !== '' );
|
|
13963
|
+
if ( selectorOk ) {
|
|
13964
|
+
try { document.createDocumentFragment().querySelector(reselect); } catch (selectorErr) { selectorOk = false; }
|
|
13965
|
+
}
|
|
13966
|
+
if ( !hasTarget ) {
|
|
13967
|
+
report.reselect = { value: reselect, applied: false, reason: 'noTarget' };
|
|
13968
|
+
notice('`X-Gina-Reselect: '+ reselect +'` ignored — the answer has no swap target');
|
|
13969
|
+
} else if ( !selectorOk ) {
|
|
13970
|
+
report.reselect = { value: reselect, applied: false, reason: 'invalid selector `'+ reselect +'`' };
|
|
13971
|
+
notice('`X-Gina-Reselect: '+ reselect +'` ignored — invalid selector, `'+ ( sendCtx.select || 'the whole answer' ) +'` kept');
|
|
13972
|
+
} else {
|
|
13973
|
+
report.reselect = { value: reselect, applied: true };
|
|
13974
|
+
sendCtx.select = reselect;
|
|
13975
|
+
}
|
|
13976
|
+
}
|
|
13977
|
+
sendCtx.overrides = report;
|
|
13978
|
+
return report;
|
|
13979
|
+
}
|
|
13980
|
+
|
|
13981
|
+
/**
|
|
13982
|
+
* applySwap
|
|
13983
|
+
*
|
|
13984
|
+
* #gh76 slice 2 — swaps a `text/html` answer into the target captured at submit. ONE
|
|
13985
|
+
* parse (`parseXhrHtmlAnswer`, the hidden inputs stripped), the optional
|
|
13986
|
+
* `data-gina-form-select` pick (every match, document order), the attached check, the
|
|
13987
|
+
* cancelable `beforeswap.<id>` event (a listener's `preventDefault()` skips the swap;
|
|
13988
|
+
* `detail.content` is re-read after dispatch, so a listener may rewrite it), the DOM
|
|
13989
|
+
* write by strategy, the region binding through the shared `bindRegion()` policy, then
|
|
13990
|
+
* `afterswap.<id>` (+ `.hform`). Returns the success payload; the caller's shared tail
|
|
13991
|
+
* emits `success`. Fail-soft on a swap that cannot happen (no `select` match, a target
|
|
13992
|
+
* that left the document): `swapped: false`, a dev notice, the payload still delivered
|
|
13993
|
+
* — the data is already saved.
|
|
13994
|
+
*
|
|
13995
|
+
* A swap that replaces or removes the submitting form (`outerHTML`/`delete` on a target
|
|
13996
|
+
* containing it) defers the form's own re-binding: its listeners deliver the events
|
|
13997
|
+
* first, then `finalizeSelfReplacement` binds the same-id replacement.
|
|
13998
|
+
*
|
|
13999
|
+
* @param {object} sendCtx - the per-send capture
|
|
14000
|
+
* @param {object} $form - the form's registry entry
|
|
14001
|
+
* @param {HTMLFormElement} $target - the form element
|
|
14002
|
+
* @param {string} id - the form id
|
|
14003
|
+
* @param {boolean} hFormIsRequired - whether the declared-callback channel is armed
|
|
14004
|
+
* @param {{contentType: string, content: string, status: number}} result - the legacy payload
|
|
14005
|
+
*
|
|
14006
|
+
* @returns {{contentType: string, content: string, status: number, data: (object|null), view: (object|null), target: HTMLElement, swap: string, swapped: boolean}}
|
|
14007
|
+
*
|
|
14008
|
+
* @example
|
|
14009
|
+
* result = applySwap(sendCtx, $form, $target, id, hFormIsRequired, result); // => { …, swapped: true }
|
|
14010
|
+
*/
|
|
14011
|
+
var applySwap = function(sendCtx, $form, $target, id, hFormIsRequired, result) {
|
|
14012
|
+
var strategy = sendCtx.swap || 'innerHTML'
|
|
14013
|
+
, $el = sendCtx.target
|
|
14014
|
+
, parsed = parseXhrHtmlAnswer(result.content)
|
|
14015
|
+
, content = null
|
|
14016
|
+
, $nodes = null
|
|
14017
|
+
, evt = null
|
|
14018
|
+
, $scope = null
|
|
14019
|
+
, attached = false
|
|
14020
|
+
, detachesForm = false
|
|
14021
|
+
, payload = {
|
|
14022
|
+
contentType : result.contentType,
|
|
14023
|
+
content : result.content,
|
|
14024
|
+
status : result.status,
|
|
14025
|
+
data : parsed.data,
|
|
14026
|
+
view : parsed.view,
|
|
14027
|
+
target : $el,
|
|
14028
|
+
swap : strategy,
|
|
14029
|
+
swapped : false
|
|
14030
|
+
}
|
|
14031
|
+
;
|
|
14032
|
+
// #gh76 §6 — present only when the answer carried an override header
|
|
14033
|
+
if ( sendCtx.overrides ) {
|
|
14034
|
+
payload.overrides = sendCtx.overrides;
|
|
14035
|
+
}
|
|
14036
|
+
var warn = function(msg) {
|
|
14037
|
+
if (envIsDev) {
|
|
14038
|
+
try { console.warn('[FormValidator][swap] form `#'+ id +'`: '+ msg); } catch (e) {}
|
|
14039
|
+
}
|
|
14040
|
+
};
|
|
14041
|
+
// dev toolbar
|
|
14042
|
+
try {
|
|
14043
|
+
if ( gina && envIsDev && typeof(window.ginaToolbar) != 'undefined' && window.ginaToolbar ) {
|
|
14044
|
+
if (parsed.data) window.ginaToolbar.update('data-xhr', parsed.data);
|
|
14045
|
+
if (parsed.view) window.ginaToolbar.update('view-xhr', parsed.view);
|
|
14046
|
+
}
|
|
14047
|
+
} catch (toolbarErr) {}
|
|
14048
|
+
|
|
14049
|
+
// #gh76 slice 3 — out-of-band elements first (htmx order: before `select`, before
|
|
14050
|
+
// the main swap), gated on the attribute appearing in the answer so an oob-free
|
|
14051
|
+
// answer pays nothing and stays byte-identical. `content` keeps the RAW answer;
|
|
14052
|
+
// `remainder` is what is left once the oob elements are out.
|
|
14053
|
+
if ( typeof(result.content) == 'string' && result.content.indexOf('data-gina-swap-oob') > -1 ) {
|
|
14054
|
+
var oobRun = applyOobSwaps(parsed.doc, { $target: $target, id: id, hFormIsRequired: hFormIsRequired, gina: gina });
|
|
14055
|
+
warnOobRefusals(id, oobRun.list);
|
|
14056
|
+
payload.oob = oobRun.list;
|
|
14057
|
+
payload.remainder = parsed.doc.body.innerHTML;
|
|
14058
|
+
if ( oobRun.rebindSelf ) sendCtx.rebindSelf = true;
|
|
14059
|
+
}
|
|
14060
|
+
|
|
14061
|
+
if ( strategy === 'textContent' ) {
|
|
14062
|
+
// htmx: the raw answer, not parsed as HTML — but never the out-of-band elements,
|
|
14063
|
+
// which are consumed transport (#gh76 slice 3): once the gate fired, `remainder`
|
|
14064
|
+
// IS the answer without them. Below the gate this is the raw answer, unchanged.
|
|
14065
|
+
content = ( typeof(payload.remainder) == 'string' ) ? payload.remainder : result.content;
|
|
14066
|
+
} else if ( sendCtx.select ) {
|
|
14067
|
+
try {
|
|
14068
|
+
$nodes = parsed.doc.querySelectorAll(sendCtx.select);
|
|
14069
|
+
} catch (selErr) {
|
|
14070
|
+
$nodes = [];
|
|
14071
|
+
}
|
|
14072
|
+
if ( !$nodes.length ) {
|
|
14073
|
+
warn('`data-gina-form-select` matched nothing in the answer (`'+ sendCtx.select +'`) — nothing swapped');
|
|
14074
|
+
return payload;
|
|
14075
|
+
}
|
|
14076
|
+
content = '';
|
|
14077
|
+
for (var n = 0, nLen = $nodes.length; n < nLen; ++n) {
|
|
14078
|
+
content += $nodes[n].outerHTML;
|
|
14079
|
+
}
|
|
14080
|
+
} else {
|
|
14081
|
+
content = parsed.doc.body.innerHTML;
|
|
14082
|
+
}
|
|
14083
|
+
|
|
14084
|
+
attached = ( typeof($el.isConnected) == 'boolean' ) ? $el.isConnected : document.contains($el);
|
|
14085
|
+
if ( !attached ) {
|
|
14086
|
+
warn('the target captured at submit (`'+ sendCtx.targetAttr +'`) is no longer in the document — nothing swapped');
|
|
14087
|
+
return payload;
|
|
14088
|
+
}
|
|
14089
|
+
|
|
14090
|
+
if ( strategy !== 'none' ) {
|
|
14091
|
+
var beforeDetail = { target: $el, content: content, strategy: strategy, select: sendCtx.select };
|
|
14092
|
+
if ( sendCtx.overrides ) {
|
|
14093
|
+
beforeDetail.overrides = sendCtx.overrides;
|
|
14094
|
+
}
|
|
14095
|
+
evt = triggerEvent(gina, $target, 'beforeswap.' + id, beforeDetail);
|
|
14096
|
+
if ( evt && evt.defaultPrevented ) {
|
|
14097
|
+
warn('the swap was cancelled by a `beforeswap` listener');
|
|
14098
|
+
return payload;
|
|
14099
|
+
}
|
|
14100
|
+
if ( evt && evt.detail && typeof(evt.detail.content) == 'string' ) {
|
|
14101
|
+
content = evt.detail.content;
|
|
14102
|
+
}
|
|
14103
|
+
}
|
|
14104
|
+
|
|
14105
|
+
detachesForm = ( strategy === 'outerHTML' || strategy === 'delete' ) && ( $el === $target || $el.contains($target) );
|
|
14106
|
+
|
|
14107
|
+
switch (strategy) {
|
|
14108
|
+
case 'innerHTML':
|
|
14109
|
+
$el.innerHTML = content;
|
|
14110
|
+
$scope = $el;
|
|
14111
|
+
break;
|
|
14112
|
+
case 'outerHTML':
|
|
14113
|
+
$scope = $el.parentNode;
|
|
14114
|
+
$el.outerHTML = content;
|
|
14115
|
+
break;
|
|
14116
|
+
case 'textContent':
|
|
14117
|
+
$el.textContent = content;
|
|
14118
|
+
break;
|
|
14119
|
+
case 'beforebegin':
|
|
14120
|
+
$scope = $el.parentNode;
|
|
14121
|
+
$el.insertAdjacentHTML('beforebegin', content);
|
|
14122
|
+
break;
|
|
14123
|
+
case 'afterbegin':
|
|
14124
|
+
$el.insertAdjacentHTML('afterbegin', content);
|
|
14125
|
+
$scope = $el;
|
|
14126
|
+
break;
|
|
14127
|
+
case 'beforeend':
|
|
14128
|
+
$el.insertAdjacentHTML('beforeend', content);
|
|
14129
|
+
$scope = $el;
|
|
14130
|
+
break;
|
|
14131
|
+
case 'afterend':
|
|
14132
|
+
$scope = $el.parentNode;
|
|
14133
|
+
$el.insertAdjacentHTML('afterend', content);
|
|
14134
|
+
break;
|
|
14135
|
+
case 'delete':
|
|
14136
|
+
if ( $el.parentNode ) $el.parentNode.removeChild($el);
|
|
14137
|
+
break;
|
|
14138
|
+
case 'none':
|
|
14139
|
+
break;
|
|
14140
|
+
}
|
|
14141
|
+
payload.swapped = ( strategy !== 'none' );
|
|
14142
|
+
|
|
14143
|
+
if ( $scope && typeof($scope.getElementsByTagName) == 'function' ) {
|
|
14144
|
+
bindRegion($scope, { deferFormId: ( detachesForm ) ? id : null });
|
|
14145
|
+
}
|
|
14146
|
+
sendCtx.rebindSelf = sendCtx.rebindSelf || detachesForm;
|
|
14147
|
+
|
|
14148
|
+
if ( strategy !== 'none' ) {
|
|
14149
|
+
triggerEvent(gina, $target, 'afterswap.' + id, { target: $el, strategy: strategy, swapped: payload.swapped });
|
|
14150
|
+
if (hFormIsRequired)
|
|
14151
|
+
triggerEvent(gina, $target, 'afterswap.' + id + '.hform', { target: $el, strategy: strategy, swapped: payload.swapped });
|
|
14152
|
+
}
|
|
14153
|
+
return payload;
|
|
14154
|
+
}
|
|
14155
|
+
|
|
14156
|
+
/**
|
|
14157
|
+
* finalizeSelfReplacement
|
|
14158
|
+
*
|
|
14159
|
+
* #gh76 slice 2 — after a swap that replaced or removed the submitting form and after
|
|
14160
|
+
* its events were delivered: retire the stale registry entry (its element left the
|
|
14161
|
+
* document) and bind the same-id replacement when the answer carried one and it opts
|
|
14162
|
+
* in (#B549).
|
|
14163
|
+
*
|
|
14164
|
+
* @param {HTMLFormElement} $oldForm - the submitting form element
|
|
14165
|
+
* @param {string} id - its id
|
|
14166
|
+
*
|
|
14167
|
+
* @returns {undefined}
|
|
14168
|
+
*
|
|
14169
|
+
* @example
|
|
14170
|
+
* finalizeSelfReplacement($target, id);
|
|
14171
|
+
*/
|
|
14172
|
+
var finalizeSelfReplacement = function($oldForm, id) {
|
|
14173
|
+
var $new = document.getElementById(id);
|
|
14174
|
+
var oldAttached = ( typeof($oldForm.isConnected) == 'boolean' ) ? $oldForm.isConnected : document.contains($oldForm);
|
|
14175
|
+
if ( !oldAttached ) {
|
|
14176
|
+
try {
|
|
14177
|
+
destroy(id);
|
|
14178
|
+
} catch (destroyErr) {
|
|
14179
|
+
delete instance.$forms[id];
|
|
14180
|
+
}
|
|
14181
|
+
}
|
|
14182
|
+
if ( $new && $new !== $oldForm && /^FORM$/i.test($new.tagName) && isFormOptedIn($new, local.rules) ) {
|
|
14183
|
+
validateFormById.call(instance, id);
|
|
14184
|
+
}
|
|
14185
|
+
}
|
|
14186
|
+
|
|
14187
|
+
/**
|
|
14188
|
+
* #B549 — does the page opt this form into the validator?
|
|
14189
|
+
*
|
|
14190
|
+
* The boot scan binds a `<form>` ONLY when the markup asks for it: a
|
|
14191
|
+
* `data-gina-form-*` attribute (`data-gina-form-rule`, the submit event
|
|
14192
|
+
* handlers, submit-method/action overrides, upload staging…), an EXISTING id
|
|
14193
|
+
* naming a registered rule (`-` read as `.`), or a virtual `gina-upload-*` id.
|
|
14194
|
+
* Anything else is left untouched — no minted id, no `$forms` entry, no submit
|
|
14195
|
+
* proxy — so a plain form on a rules-bearing page keeps its native submit, as
|
|
14196
|
+
* the guide states. Explicit `validateFormById()` / `getFormById()` calls are
|
|
14197
|
+
* not gated: the call is the opt-in.
|
|
14198
|
+
*
|
|
14199
|
+
* @private
|
|
14200
|
+
* @param {HTMLFormElement} $form
|
|
14201
|
+
* @param {object} rules - the registered rule sets, keyed by dotted name
|
|
14202
|
+
* @returns {boolean}
|
|
14203
|
+
* @example
|
|
14204
|
+
* isFormOptedIn(document.querySelector('form[data-gina-form-rule]'), gina.forms.rules); // true
|
|
14205
|
+
* isFormOptedIn(document.querySelector('form[action="/login"]'), gina.forms.rules); // false — bare
|
|
14206
|
+
*/
|
|
14207
|
+
function isFormOptedIn($form, rules) {
|
|
14208
|
+
if ( !$form || typeof($form.getAttribute) != 'function' ) {
|
|
14209
|
+
return false;
|
|
14210
|
+
}
|
|
14211
|
+
var existingId = $form.getAttribute('id');
|
|
14212
|
+
if ( existingId && /^gina\-upload/i.test(existingId) ) {
|
|
14213
|
+
return true;
|
|
14214
|
+
}
|
|
14215
|
+
if ( existingId && rules && typeof(rules[existingId.replace(/\-/g, '.')]) != 'undefined' ) {
|
|
14216
|
+
return true;
|
|
14217
|
+
}
|
|
14218
|
+
var attrs = $form.attributes;
|
|
14219
|
+
for (var a = 0, aLen = (attrs) ? attrs.length : 0; a < aLen; ++a) {
|
|
14220
|
+
if ( /^data-gina-form-/i.test(attrs[a].name) ) {
|
|
14221
|
+
return true;
|
|
14222
|
+
}
|
|
12751
14223
|
}
|
|
12752
|
-
|
|
12753
|
-
return isPopinInUse;
|
|
14224
|
+
return false;
|
|
12754
14225
|
}
|
|
12755
14226
|
|
|
12756
|
-
|
|
12757
14227
|
/**
|
|
12758
14228
|
* validateFormById
|
|
12759
14229
|
*
|
|
@@ -14251,14 +15721,43 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
14251
15721
|
var XHRData = null;
|
|
14252
15722
|
var isAttachment = null; // handle download
|
|
14253
15723
|
var hFormIsRequired = null;
|
|
15724
|
+
// #gh76 — per-send capture of the popin the SUBMITTING form lives in (or null),
|
|
15725
|
+
// read at the settle below. `$form.eventData` is per form and a second send would
|
|
15726
|
+
// overwrite it; this closure is per send (#B175 made the XHR per send).
|
|
15727
|
+
/** @type {{ popin: (object|null), target: (HTMLElement|null), swap: string, select: (string|null), targetAttr: (string|null), rebindSelf: boolean, overrides: (object|undefined), sync: (string|undefined), syncDerived: (boolean|undefined), syncKey: (HTMLElement|undefined), superseded: (boolean|undefined), disabledElts: (Array<HTMLElement>|null|undefined) }} */
|
|
15728
|
+
var sendCtx = { popin: null, target: null, swap: 'innerHTML', select: null, targetAttr: null, rebindSelf: false };
|
|
15729
|
+
// #gh76 slice 4 — the receiver and the caller's ORIGINAL options, captured BEFORE the
|
|
15730
|
+
// merge below stamps headers into them: a `data-gina-form-sync="queue"` submit is
|
|
15731
|
+
// replayed by re-entering send() from the settle chokepoint, and it must re-enter with
|
|
15732
|
+
// what the caller passed, never with this send's merged copy.
|
|
15733
|
+
var syncReceiver = this, syncRawOptions = options, syncEntry = null, syncParsed = null;
|
|
14254
15734
|
|
|
14255
15735
|
options = (typeof (options) != 'undefined') ? merge(options, xhrOptions) : xhrOptions;
|
|
14256
15736
|
|
|
15737
|
+
// forward callback to HTML data event attribute through `hform` status
|
|
15738
|
+
// success -> data-gina-form-event-on-submit-success
|
|
15739
|
+
// error -> data-gina-form-event-on-submit-error
|
|
15740
|
+
// afterswap -> data-gina-form-event-on-swap (#gh76 slice 2)
|
|
15741
|
+
// #gh76 slice 4 — read here, above the gate, so the coordination decision below can
|
|
15742
|
+
// refuse a submit into the declared error channel. The binding itself still happens
|
|
15743
|
+
// at its own site further down.
|
|
15744
|
+
hFormIsRequired = ( $target.getAttribute('data-gina-form-event-on-submit-success') || $target.getAttribute('data-gina-form-event-on-submit-error') || $target.getAttribute('data-gina-form-event-on-swap') ) ? true : false;
|
|
15745
|
+
|
|
15746
|
+
// #gh76 slice 4 — `data-gina-form-sync` declares what this submit does when another
|
|
15747
|
+
// request already owns the same key. Read before the gate below, because a form that
|
|
15748
|
+
// declares it OWNS its own overlap decision: the gate is `setOptions`-wide with no
|
|
15749
|
+
// per-form form, so `replace` and `queue` could not be reached otherwise.
|
|
15750
|
+
var syncAttr = $target.getAttribute('data-gina-form-sync');
|
|
15751
|
+
|
|
14257
15752
|
// Rate Limit to one request at the time
|
|
14258
15753
|
// Attention: this should be an option
|
|
14259
15754
|
// the request needs to be completed before another can be made
|
|
14260
15755
|
// TODO - Check same url
|
|
15756
|
+
// #gh76 slice 4 — yields to `data-gina-form-sync` when that attribute is declared.
|
|
15757
|
+
// Absent ⇒ byte-identical behaviour to before.
|
|
14261
15758
|
if (
|
|
15759
|
+
syncAttr === null
|
|
15760
|
+
&& (
|
|
14262
15761
|
/^true$/i.test(options.withRateLimit)
|
|
14263
15762
|
&& typeof($form.isSending) != 'undefined'
|
|
14264
15763
|
&& /^true$/i.test($form.isSending)
|
|
@@ -14266,6 +15765,7 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
14266
15765
|
/^true$/i.test(options.withRateLimit)
|
|
14267
15766
|
&& typeof($form.sent) != 'undefined'
|
|
14268
15767
|
&& /^true$/i.test($form.sent)
|
|
15768
|
+
)
|
|
14269
15769
|
) {
|
|
14270
15770
|
// #B247 — deliberately NO loading-state release here. This return means a
|
|
14271
15771
|
// request for this form is already in flight, so the trigger that owns it is
|
|
@@ -14275,6 +15775,83 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
14275
15775
|
return;
|
|
14276
15776
|
}
|
|
14277
15777
|
|
|
15778
|
+
// #gh76 slice 4 — the coordination decision. It runs BEFORE `isSending` is claimed
|
|
15779
|
+
// below because `replace` aborts the in-flight request, and an XHR abort settles its
|
|
15780
|
+
// handler SYNCHRONOUSLY: claiming first would let that settle clear the very flag this
|
|
15781
|
+
// send had just taken (the #B332 class). The same ordering is why `ownedByEarlierSend`
|
|
15782
|
+
// is read after this block — after an abort, nothing is owned by an earlier send.
|
|
15783
|
+
// The strategy is the form's own `data-gina-form-sync` when it declares one, and is
|
|
15784
|
+
// otherwise DERIVED from the swap strategy it already declared — so the case this
|
|
15785
|
+
// slice exists for, two forms replacing one region, is coordinated with nothing
|
|
15786
|
+
// written in the markup. The attribute is the override, not the switch.
|
|
15787
|
+
if ( syncAttr !== null ) {
|
|
15788
|
+
syncParsed = parseSync(syncAttr);
|
|
15789
|
+
} else {
|
|
15790
|
+
syncParsed = deriveSync($target);
|
|
15791
|
+
}
|
|
15792
|
+
// An UNREADABLE value coordinates nothing, so there is nothing to decide here — and the
|
|
15793
|
+
// refusal it owes cannot be delivered from this far up, where the declared callbacks are
|
|
15794
|
+
// not bound yet. It is carried to the pre-flight below, beside its `-target`/`-swap`
|
|
15795
|
+
// siblings, and the gate above has already yielded because the attribute is present, so
|
|
15796
|
+
// the submit is guaranteed to reach it.
|
|
15797
|
+
if ( syncParsed && !syncParsed.error ) {
|
|
15798
|
+
var syncKey = syncParsed.key || resolveSyncKey($target);
|
|
15799
|
+
var syncDecision = decideSync(syncParsed, syncRegistry.get(syncKey));
|
|
15800
|
+
if ( syncDecision.action === 'drop' ) {
|
|
15801
|
+
// #B247's ownership rule, re-derived for a TARGET key: a submit that stops here
|
|
15802
|
+
// never reaches an XHR, so no settle will release the loading state its own click
|
|
15803
|
+
// armed. The module-wide gate could return without releasing because it only ever
|
|
15804
|
+
// fires on the form that already owns the request — its settle does the release.
|
|
15805
|
+
// A target key coordinates DIFFERENT forms, so the one turned away here is usually
|
|
15806
|
+
// not the one running, and nothing else would ever release it. It releases only
|
|
15807
|
+
// what it owns: when this form IS the one in flight, the state belongs to that
|
|
15808
|
+
// request and its settle still owns it.
|
|
15809
|
+
if ( !( /^true$/i.test(instance.$forms[id].isSending) || /^true$/i.test($form.isSending) ) ) {
|
|
15810
|
+
disarmSubmitLoading($form);
|
|
15811
|
+
}
|
|
15812
|
+
syncNotice(id, 'submit dropped (`drop`) — a request already owns this target');
|
|
15813
|
+
return;
|
|
15814
|
+
}
|
|
15815
|
+
if ( syncDecision.action === 'queue' ) {
|
|
15816
|
+
syncEntry = syncRegistry.get(syncKey);
|
|
15817
|
+
// a QUEUED submit keeps its loading state: it is genuinely pending, and the
|
|
15818
|
+
// replay re-enters send() with `armSubmitLoading` first-arm-wins, so the state
|
|
15819
|
+
// carries straight through to the real request's settle. Only a submit turned
|
|
15820
|
+
// AWAY releases.
|
|
15821
|
+
queueSyncSend(syncEntry, function replaySyncSend() { send.call(syncReceiver, data, syncRawOptions); });
|
|
15822
|
+
syncNotice(id, 'submit queued (`queue`) — a request already owns this target');
|
|
15823
|
+
return;
|
|
15824
|
+
}
|
|
15825
|
+
if ( syncDecision.abortPrevious ) {
|
|
15826
|
+
syncEntry = syncRegistry.get(syncKey);
|
|
15827
|
+
if ( syncEntry && syncEntry.xhr ) {
|
|
15828
|
+
if ( syncEntry.ctx ) {
|
|
15829
|
+
syncEntry.ctx.superseded = true;
|
|
15830
|
+
}
|
|
15831
|
+
try { syncEntry.xhr.abort(); } catch (abortErr) {}
|
|
15832
|
+
syncNotice(id, ( syncParsed.derived )
|
|
15833
|
+
? 'the request that owned this target was superseded — a `'+ syncParsed.swap +'` answer replaces the region, so the one still on the wire is moot (declare `data-gina-form-sync` to choose otherwise)'
|
|
15834
|
+
: 'the request that owned this target was superseded (`replace`)');
|
|
15835
|
+
// That settle released the loading state on its way out (`loadend` covers
|
|
15836
|
+
// abort). This send is the one now running, so it takes the state over:
|
|
15837
|
+
// `armSubmitLoading` is first-arm-wins and the stash was just cleared, so
|
|
15838
|
+
// on a same-form replace this is the arm that sticks; on two different
|
|
15839
|
+
// forms the replacing one is already armed and this is a no-op.
|
|
15840
|
+
armSubmitLoading(instance.$forms[id], $submitTrigger);
|
|
15841
|
+
}
|
|
15842
|
+
}
|
|
15843
|
+
sendCtx.sync = syncParsed.strategy;
|
|
15844
|
+
sendCtx.syncDerived = !!syncParsed.derived;
|
|
15845
|
+
sendCtx.syncKey = syncKey;
|
|
15846
|
+
}
|
|
15847
|
+
|
|
15848
|
+
// #gh76 slice 2 — whether a PREVIOUS request still owns `isSending` and the armed
|
|
15849
|
+
// loading state, read BEFORE this attempt claims them. Only reachable with
|
|
15850
|
+
// `withRateLimit: false` (the gate above returns otherwise), and only consulted by
|
|
15851
|
+
// a pre-send refusal below — which must not clear state it does not own.
|
|
15852
|
+
var ownedByEarlierSend = /^true$/i.test(instance.$forms[id].isSending)
|
|
15853
|
+
|| /^true$/i.test($form.isSending);
|
|
15854
|
+
|
|
14278
15855
|
instance.$forms[id].isSending = true;
|
|
14279
15856
|
|
|
14280
15857
|
|
|
@@ -14297,26 +15874,62 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
14297
15874
|
options.headers['X-Gina-Form-Rule'] = $form.target.dataset.ginaFormRule +'@'+ gina.config.bundle;
|
|
14298
15875
|
}
|
|
14299
15876
|
|
|
14300
|
-
|
|
14301
|
-
|
|
14302
|
-
|
|
14303
|
-
|
|
14304
|
-
|
|
14305
|
-
|
|
15877
|
+
// #gh76 — the popin context a request carries is the popin the SUBMITTING form is
|
|
15878
|
+
// rendered in, resolved by containment, never "some popin is open" (the former
|
|
15879
|
+
// isPopinContext() gate): a page form submitted while a non-modal popin was open
|
|
15880
|
+
// told the server it came from that popin — `self.isPopinContext()` read true and
|
|
15881
|
+
// the redirect came back as a popin redirect — the request-side half of the same
|
|
15882
|
+
// defect. Gated on the popin handler existing, as the settle site always was.
|
|
15883
|
+
sendCtx.popin = (
|
|
15884
|
+
typeof(gina.popin) != 'undefined'
|
|
15885
|
+
&& gina.popin
|
|
15886
|
+
&& typeof(gina.popin.getPopinContaining) == 'function'
|
|
15887
|
+
) ? gina.popin.getPopinContaining($target) : null;
|
|
15888
|
+
if ( sendCtx.popin && sendCtx.popin.isOpen ) {
|
|
15889
|
+
if ( typeof(sendCtx.popin.id) != 'undefined' )
|
|
15890
|
+
options.headers['X-Gina-Popin-Id'] = sendCtx.popin.id;
|
|
14306
15891
|
|
|
14307
|
-
|
|
14308
|
-
|
|
14309
|
-
}
|
|
15892
|
+
if ( typeof(sendCtx.popin.name) != 'undefined' )
|
|
15893
|
+
options.headers['X-Gina-Popin-Name'] = sendCtx.popin.name;
|
|
14310
15894
|
}
|
|
14311
15895
|
|
|
14312
15896
|
|
|
14313
|
-
//
|
|
14314
|
-
|
|
14315
|
-
//
|
|
14316
|
-
// error -> data-gina-form-event-on-submit-error
|
|
15897
|
+
// #gh76 slice 4 — `hFormIsRequired` itself is now decided at the top of send(): the
|
|
15898
|
+
// coordination decision up there can refuse a submit, and a refusal must reach the
|
|
15899
|
+
// declared error channel. Only the listener binding stays here, where it always was.
|
|
14317
15900
|
if (hFormIsRequired)
|
|
14318
15901
|
listenToXhrEvents($form);
|
|
14319
15902
|
|
|
15903
|
+
// #gh76 slice 2 — a DECLARED swap target is resolved NOW, from the submitting form,
|
|
15904
|
+
// and carried to the settle in this closure (the htmx contract: the target is a
|
|
15905
|
+
// property of the issuing element, resolved before the request is sent). A target
|
|
15906
|
+
// or strategy that cannot be honoured refuses the submit before anything leaves —
|
|
15907
|
+
// fail-LOUD, deliberately the opposite of `data-gina-dialog-target`'s silent
|
|
15908
|
+
// full-replace fallback.
|
|
15909
|
+
var targetAttr = $target.getAttribute('data-gina-form-target');
|
|
15910
|
+
var swapAttr = $target.getAttribute('data-gina-form-swap');
|
|
15911
|
+
var selectAttr = $target.getAttribute('data-gina-form-select');
|
|
15912
|
+
if ( targetAttr !== null ) {
|
|
15913
|
+
var resolvedTarget = resolveSwapTarget($target, targetAttr);
|
|
15914
|
+
if ( resolvedTarget.error ) {
|
|
15915
|
+
refuseSend($form, $target, id, hFormIsRequired, 'data-gina-form-target', targetAttr, resolvedTarget.error, ownedByEarlierSend);
|
|
15916
|
+
return;
|
|
15917
|
+
}
|
|
15918
|
+
sendCtx.target = resolvedTarget.target;
|
|
15919
|
+
sendCtx.targetAttr = targetAttr;
|
|
15920
|
+
}
|
|
15921
|
+
if ( swapAttr !== null ) {
|
|
15922
|
+
var strategy = swapAttr.trim();
|
|
15923
|
+
if ( SWAP_STRATEGIES.indexOf(strategy) < 0 ) {
|
|
15924
|
+
refuseSend($form, $target, id, hFormIsRequired, 'data-gina-form-swap', swapAttr, 'unknown swap strategy `'+ strategy +'` (one of '+ SWAP_STRATEGIES.join(', ') +')', ownedByEarlierSend);
|
|
15925
|
+
return;
|
|
15926
|
+
}
|
|
15927
|
+
sendCtx.swap = strategy;
|
|
15928
|
+
}
|
|
15929
|
+
if ( selectAttr !== null && selectAttr.trim() !== '' ) {
|
|
15930
|
+
sendCtx.select = selectAttr.trim();
|
|
15931
|
+
}
|
|
15932
|
+
|
|
14320
15933
|
// #R8 — staged-upload send? (virtual `gina-upload-*` form) The upload-progress
|
|
14321
15934
|
// channel below only activates for these sends.
|
|
14322
15935
|
var isUploadXhr = /^gina\-upload/i.test(id);
|
|
@@ -14325,6 +15938,35 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
14325
15938
|
// handler); no default: absent attribute = no `.hform` progress channel
|
|
14326
15939
|
var uploadProgressHFormIsRequired = ( $target.getAttribute('data-gina-form-event-on-upload-progress') ) ? true : false;
|
|
14327
15940
|
|
|
15941
|
+
// #gh76 slice 4 — `data-gina-form-disabled-elt`: elements held disabled for the life
|
|
15942
|
+
// of this request, so a user cannot act on what the answer is about to change. Placed
|
|
15943
|
+
// AFTER every pre-send refusal and after the coordination decision, and BEFORE the XHR
|
|
15944
|
+
// is opened, so a refused submit can never strand a disabled control; released at the
|
|
15945
|
+
// `loadend` fail-safe below — the one chokepoint that covers error, abort and timeout
|
|
15946
|
+
// as well as success.
|
|
15947
|
+
// #gh76 slice 4 — the refusal deferred from the coordination decision above: delivered
|
|
15948
|
+
// HERE, where `listenToXhrEvents` has bound the declared error callback, and outside the
|
|
15949
|
+
// `-target`/`-swap` window so that window's shape is untouched.
|
|
15950
|
+
if ( syncParsed && syncParsed.error ) {
|
|
15951
|
+
refuseSend($form, $target, id, hFormIsRequired, 'data-gina-form-sync', syncAttr, syncParsed.error, ownedByEarlierSend);
|
|
15952
|
+
return;
|
|
15953
|
+
}
|
|
15954
|
+
|
|
15955
|
+
var disabledAttr = $target.getAttribute('data-gina-form-disabled-elt');
|
|
15956
|
+
if ( disabledAttr !== null ) {
|
|
15957
|
+
var resolvedDisabled = resolveDisabledElts($target, disabledAttr);
|
|
15958
|
+
if ( resolvedDisabled.error ) {
|
|
15959
|
+
refuseSend($form, $target, id, hFormIsRequired, 'data-gina-form-disabled-elt', disabledAttr, resolvedDisabled.error, ownedByEarlierSend);
|
|
15960
|
+
return;
|
|
15961
|
+
}
|
|
15962
|
+
sendCtx.disabledElts = [];
|
|
15963
|
+
for (var dIdx = 0; dIdx < resolvedDisabled.elements.length; ++dIdx) {
|
|
15964
|
+
if ( disableForRequest(resolvedDisabled.elements[dIdx], id) ) {
|
|
15965
|
+
sendCtx.disabledElts.push(resolvedDisabled.elements[dIdx]);
|
|
15966
|
+
}
|
|
15967
|
+
}
|
|
15968
|
+
}
|
|
15969
|
+
|
|
14328
15970
|
var url = $target.getAttribute('action') || options.url;
|
|
14329
15971
|
var method = $target.getAttribute('method') || options.method;
|
|
14330
15972
|
method = method.toUpperCase();
|
|
@@ -14442,8 +16084,43 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
14442
16084
|
// #A11Y4 — last, so focus lands on a trigger that is fully settled:
|
|
14443
16085
|
// re-enabled above and no longer flagged loading.
|
|
14444
16086
|
releaseSubmitA11y($form, $submitTrigger);
|
|
16087
|
+
// #gh76 slice 4 — the same one chokepoint releases what this request held
|
|
16088
|
+
// disabled, frees its coordination slot and starts the next deferred
|
|
16089
|
+
// submit. A slot is freed only by the request that owns it (a late settle
|
|
16090
|
+
// must never evict a newer one — the XHR object is that identity), and a
|
|
16091
|
+
// SUPERSEDED settle does not shift: the request that replaced it claims
|
|
16092
|
+
// the slot on the very next statement of the aborting send().
|
|
16093
|
+
releaseDisabledElts(sendCtx.disabledElts);
|
|
16094
|
+
sendCtx.disabledElts = null;
|
|
16095
|
+
if ( sendCtx.syncKey ) {
|
|
16096
|
+
var settledEntry = syncRegistry.get(sendCtx.syncKey);
|
|
16097
|
+
if ( settledEntry && settledEntry.xhr === xhr ) {
|
|
16098
|
+
settledEntry.xhr = null;
|
|
16099
|
+
settledEntry.ctx = null;
|
|
16100
|
+
if ( !sendCtx.superseded ) {
|
|
16101
|
+
shiftSyncQueue(settledEntry);
|
|
16102
|
+
}
|
|
16103
|
+
}
|
|
16104
|
+
}
|
|
14445
16105
|
});
|
|
14446
16106
|
}
|
|
16107
|
+
// #gh76 slice 4 — claiming the coordination slot is deliberately deferred to the
|
|
16108
|
+
// line AFTER `xhr.send()` at each of its three sites: claiming here would leave a
|
|
16109
|
+
// key wedged "busy" forever on the one path that opens an XHR and never sends it
|
|
16110
|
+
// (a `processFiles` failure on the binary branch), because `loadend` — the only
|
|
16111
|
+
// release — never fires for a request that never left.
|
|
16112
|
+
var claimSyncSlot = function() {
|
|
16113
|
+
if ( !sendCtx.syncKey ) {
|
|
16114
|
+
return;
|
|
16115
|
+
}
|
|
16116
|
+
var claimed = syncRegistry.get(sendCtx.syncKey);
|
|
16117
|
+
if ( !claimed ) {
|
|
16118
|
+
claimed = { xhr: null, ctx: null, queue: [] };
|
|
16119
|
+
syncRegistry.set(sendCtx.syncKey, claimed);
|
|
16120
|
+
}
|
|
16121
|
+
claimed.xhr = xhr;
|
|
16122
|
+
claimed.ctx = sendCtx;
|
|
16123
|
+
};
|
|
14447
16124
|
// catching ready state cb
|
|
14448
16125
|
// Data loading ...
|
|
14449
16126
|
if ( /^(1|3)$/.test(xhr.readyState) ) {
|
|
@@ -14461,7 +16138,13 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
14461
16138
|
}
|
|
14462
16139
|
//handleXhrResponse(xhr, $target, id, $form, hFormIsRequired);
|
|
14463
16140
|
xhr.onreadystatechange = function onValidationCallback(event) {
|
|
14464
|
-
|
|
16141
|
+
// #gh76 slice 4 — a request superseded by `data-gina-form-sync="replace"`
|
|
16142
|
+
// settles SYNCHRONOUSLY inside the aborting send()'s own decision, before that
|
|
16143
|
+
// send has claimed this latch: clearing it unconditionally here would clear the
|
|
16144
|
+
// NEW cycle's flag and strand it (the #B332 class).
|
|
16145
|
+
if ( !sendCtx.superseded ) {
|
|
16146
|
+
$form.isSubmitting = false;
|
|
16147
|
+
}
|
|
14465
16148
|
// #B175: `isSending` is no longer cleared here — this handler
|
|
14466
16149
|
// first fires at readyState 1 (synchronously at open()), so an
|
|
14467
16150
|
// early clear made the flag false for almost the whole request
|
|
@@ -14528,6 +16211,26 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
14528
16211
|
// with the `loadend` release, which may already have restored it.
|
|
14529
16212
|
releaseSubmitA11y($form, $submitTrigger);
|
|
14530
16213
|
|
|
16214
|
+
// #gh76 slice 4 — this request was superseded by a later submit on the same
|
|
16215
|
+
// `data-gina-form-sync` key. Its RELEASE arms above have run, and must: the
|
|
16216
|
+
// trigger, the loading state and the a11y state may never outlive their
|
|
16217
|
+
// request. Its DISPATCH arms must not run at all — an aborted XHR settles at
|
|
16218
|
+
// readyState 4 with status 0, which the transport arm below reads as a 408
|
|
16219
|
+
// the server never sent (#B447). A deliberate abort is not an error: it
|
|
16220
|
+
// reaches its own `abort.<id>` channel and stops there, so neither `error`
|
|
16221
|
+
// nor `data-gina-form-event-on-submit-error` ever sees it.
|
|
16222
|
+
if ( sendCtx.superseded ) {
|
|
16223
|
+
triggerEvent(gina, $target, 'abort.' + id, {
|
|
16224
|
+
status : 0,
|
|
16225
|
+
reason : 'superseded',
|
|
16226
|
+
sync : sendCtx.sync || null,
|
|
16227
|
+
// the default needs no attribute, so a page seeing this event may
|
|
16228
|
+
// find nothing in its own markup that asked for it: say which it was
|
|
16229
|
+
derived : !!sendCtx.syncDerived
|
|
16230
|
+
});
|
|
16231
|
+
return;
|
|
16232
|
+
}
|
|
16233
|
+
|
|
14531
16234
|
var $popin = null;
|
|
14532
16235
|
var blob = null;
|
|
14533
16236
|
var contentType = xhr.getResponseHeader("Content-Type");
|
|
@@ -14603,31 +16306,70 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
14603
16306
|
if ( typeof(result.status) == 'undefined' )
|
|
14604
16307
|
result.status = xhr.status;
|
|
14605
16308
|
|
|
16309
|
+
// #gh76 §6 — the server's last word (X-Gina-Retarget / -Reswap /
|
|
16310
|
+
// -Reselect): read once the answer is known to be HTML, before
|
|
16311
|
+
// the target fork and before `beforeswap`, so a listener still
|
|
16312
|
+
// has the final say. A JSON answer never reaches this read.
|
|
16313
|
+
applyResponseOverrides(xhr, sendCtx, $target, id);
|
|
16314
|
+
|
|
16315
|
+
// #gh76 §6 — a Retarget that cannot be honoured: no swap, nowhere
|
|
16316
|
+
// (the popin included) — the server plainly meant somewhere else,
|
|
16317
|
+
// or the answer came from another origin, which may not choose.
|
|
16318
|
+
// The handler still receives the answer, and out-of-band elements
|
|
16319
|
+
// still land below: they are addressed by id, not by the target.
|
|
16320
|
+
if ( sendCtx.overrides && sendCtx.overrides.retarget && !sendCtx.overrides.retarget.applied ) {
|
|
16321
|
+
var parsedRefused = parseXhrHtmlAnswer(result.content);
|
|
16322
|
+
result = {
|
|
16323
|
+
contentType : result.contentType,
|
|
16324
|
+
content : result.content,
|
|
16325
|
+
status : result.status,
|
|
16326
|
+
data : parsedRefused.data,
|
|
16327
|
+
view : parsedRefused.view,
|
|
16328
|
+
target : null,
|
|
16329
|
+
swap : sendCtx.swap,
|
|
16330
|
+
swapped : false,
|
|
16331
|
+
reason : 'retargetError',
|
|
16332
|
+
overrides : sendCtx.overrides
|
|
16333
|
+
};
|
|
16334
|
+
}
|
|
16335
|
+
// #gh76 slice 2 — a DECLARED target wins over containment: the
|
|
16336
|
+
// answer is swapped into the element captured at submit (inside
|
|
16337
|
+
// or outside a popin), bound, and delivered with the richer
|
|
16338
|
+
// payload; the shared tail below emits `success`.
|
|
16339
|
+
else if ( sendCtx.target ) {
|
|
16340
|
+
result = applySwap(sendCtx, $form, $target, id, hFormIsRequired, result);
|
|
16341
|
+
}
|
|
14606
16342
|
// if hasPopinHandler & popinIsBinded
|
|
14607
|
-
if ( typeof(gina.popin) != 'undefined' && gina.hasPopinHandler ) {
|
|
14608
|
-
//
|
|
14609
|
-
|
|
16343
|
+
else if ( typeof(gina.popin) != 'undefined' && gina.hasPopinHandler ) {
|
|
16344
|
+
// #gh76 — the answer goes to the popin the form was captured
|
|
16345
|
+
// in at submit, while it is still open and still contains the
|
|
16346
|
+
// form; anything else is the legacy handler-only payload.
|
|
16347
|
+
// Never "the active popin": that routed a PAGE form's answer
|
|
16348
|
+
// into an open popin (content replaced) or a loading one (a
|
|
16349
|
+
// false 422, `Popin x is not open !`), and with two popins
|
|
16350
|
+
// open picked the first registered one.
|
|
16351
|
+
$popin = popinStillContaining(sendCtx.popin, $target);
|
|
16352
|
+
warnIfOldRuleRouted(sendCtx.popin, $popin, $target, 'answer');
|
|
14610
16353
|
|
|
14611
16354
|
if ($popin) {
|
|
14612
16355
|
|
|
14613
|
-
|
|
16356
|
+
// #B575 — ONE tolerant parse: the hidden inputs are a dev-mode
|
|
16357
|
+
// transport (spliced only under NODE_ENV_IS_DEV), so outside
|
|
16358
|
+
// dev mode they are absent — dereferencing them threw inside
|
|
16359
|
+
// this try and surfaced as a false 422 error callback, and the
|
|
16360
|
+
// popin was never loaded
|
|
16361
|
+
var parsedAnswer = parseXhrHtmlAnswer(result.content);
|
|
16362
|
+
XHRData = parsedAnswer.data;
|
|
16363
|
+
XHRView = parsedAnswer.view;
|
|
14614
16364
|
// update toolbar
|
|
14615
|
-
|
|
14616
16365
|
try {
|
|
14617
|
-
XHRData = new DOMParser().parseFromString(result.content, 'text/html').getElementById('gina-without-layout-xhr-data');
|
|
14618
|
-
XHRData = JSON.parse(decodeURIComponent(XHRData.value));
|
|
14619
|
-
|
|
14620
|
-
XHRView = new DOMParser().parseFromString(result.content, 'text/html').getElementById('gina-without-layout-xhr-view');
|
|
14621
|
-
XHRView = JSON.parse(decodeURIComponent(XHRView.value));
|
|
14622
|
-
|
|
14623
16366
|
// update data tab
|
|
14624
|
-
if ( gina && envIsDev && typeof(window.ginaToolbar) &&
|
|
16367
|
+
if ( gina && envIsDev && typeof(window.ginaToolbar) && XHRData ) {
|
|
14625
16368
|
window.ginaToolbar.update("data-xhr", XHRData);
|
|
14626
16369
|
}
|
|
14627
16370
|
|
|
14628
16371
|
// update view tab
|
|
14629
|
-
|
|
14630
|
-
if ( gina && envIsDev && typeof(window.ginaToolbar) && typeof(XHRView) != 'undefined' ) {
|
|
16372
|
+
if ( gina && envIsDev && typeof(window.ginaToolbar) && XHRView ) {
|
|
14631
16373
|
window.ginaToolbar.update("view-xhr", XHRView);
|
|
14632
16374
|
}
|
|
14633
16375
|
|
|
@@ -14635,15 +16377,69 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
14635
16377
|
throw err
|
|
14636
16378
|
}
|
|
14637
16379
|
|
|
16380
|
+
// #gh76 slice 3 — out-of-band elements are swapped into the PAGE
|
|
16381
|
+
// before the popin receives what is left; an EMPTY remainder means
|
|
16382
|
+
// nothing was addressed to the popin, so it is left as it is
|
|
16383
|
+
// (loading '' would blank the open dialog and unbind its form —
|
|
16384
|
+
// measured). The popin payload below is untouched: the oob
|
|
16385
|
+
// results reach the consumer through the events.
|
|
16386
|
+
var popinContent = result.content, oobRunPopin = null;
|
|
16387
|
+
if ( typeof(result.content) == 'string' && result.content.indexOf('data-gina-swap-oob') > -1 ) {
|
|
16388
|
+
oobRunPopin = applyOobSwaps(parsedAnswer.doc, { $target: $target, id: id, hFormIsRequired: hFormIsRequired, gina: gina });
|
|
16389
|
+
warnOobRefusals(id, oobRunPopin.list);
|
|
16390
|
+
popinContent = parsedAnswer.doc.body.innerHTML;
|
|
16391
|
+
if ( oobRunPopin.rebindSelf ) sendCtx.rebindSelf = true;
|
|
16392
|
+
}
|
|
16393
|
+
if ( oobRunPopin === null || popinContent.trim() !== '' ) {
|
|
16394
|
+
$popin.loadContent(popinContent);
|
|
16395
|
+
}
|
|
14638
16396
|
|
|
14639
|
-
|
|
14640
|
-
|
|
14641
|
-
|
|
16397
|
+
// the parsed xhr-data in dev mode, delivered VERBATIM, or
|
|
16398
|
+
// an object carrying the status outside dev mode where the
|
|
16399
|
+
// transport inputs are absent (the handler contract is "an
|
|
16400
|
+
// object", never null). The parsed data is never mutated:
|
|
16401
|
+
// injecting a `status` key into the action's own data would
|
|
16402
|
+
// change the payload every contained form already receives.
|
|
16403
|
+
result = XHRData || { status: xhr.status };
|
|
16404
|
+
// #B571 — this branch returned after the bare event, so the
|
|
16405
|
+
// DECLARED success callback (bound to the `.hform` channel by
|
|
16406
|
+
// listenToXhrEvents) never ran for a form answering into a
|
|
16407
|
+
// popin; mirror the shared tail below, eventData included.
|
|
16408
|
+
$form.eventData.success = result;
|
|
14642
16409
|
triggerEvent(gina, $target, 'success.' + id, result);
|
|
14643
16410
|
|
|
16411
|
+
if (hFormIsRequired)
|
|
16412
|
+
triggerEvent(gina, $target, 'success.' + id + '.hform', result);
|
|
16413
|
+
|
|
16414
|
+
// #gh76 slice 3 — an oob swap that replaced the submitting form
|
|
16415
|
+
// (the popin left as it was) binds the same-id replacement now,
|
|
16416
|
+
// exactly as the shared tail below does
|
|
16417
|
+
if ( sendCtx.rebindSelf ) {
|
|
16418
|
+
finalizeSelfReplacement($target, id);
|
|
16419
|
+
}
|
|
14644
16420
|
return;
|
|
14645
16421
|
}
|
|
14646
16422
|
}
|
|
16423
|
+
|
|
16424
|
+
// #gh76 slice 3 — the legacy path (no declared target, not inside a
|
|
16425
|
+
// popin): the raw answer still goes to the handler, inserted nowhere,
|
|
16426
|
+
// but its out-of-band elements are swapped into the page first (htmx:
|
|
16427
|
+
// "out of band items will still be processed"). `content` stays raw;
|
|
16428
|
+
// `remainder` is what to insert, `oob` what was handled.
|
|
16429
|
+
if ( !sendCtx.target && typeof(result.content) == 'string' && result.content.indexOf('data-gina-swap-oob') > -1 ) {
|
|
16430
|
+
var parsedLegacy = parseXhrHtmlAnswer(result.content);
|
|
16431
|
+
var oobRunLegacy = applyOobSwaps(parsedLegacy.doc, { $target: $target, id: id, hFormIsRequired: hFormIsRequired, gina: gina });
|
|
16432
|
+
warnOobRefusals(id, oobRunLegacy.list);
|
|
16433
|
+
result.oob = oobRunLegacy.list;
|
|
16434
|
+
result.remainder = parsedLegacy.doc.body.innerHTML;
|
|
16435
|
+
if ( oobRunLegacy.rebindSelf ) sendCtx.rebindSelf = true;
|
|
16436
|
+
}
|
|
16437
|
+
// #gh76 §6 — an override the server sent to a target-less form
|
|
16438
|
+
// (ignored, `reason: 'noTarget'`) is still reported to the handler;
|
|
16439
|
+
// the popin path's parsed data is delivered verbatim and carries none
|
|
16440
|
+
if ( !sendCtx.target && sendCtx.overrides && typeof(result.overrides) == 'undefined' ) {
|
|
16441
|
+
result.overrides = sendCtx.overrides;
|
|
16442
|
+
}
|
|
14647
16443
|
}
|
|
14648
16444
|
|
|
14649
16445
|
$form.eventData.success = result;
|
|
@@ -14684,14 +16480,34 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
14684
16480
|
&& typeof(result.location) != 'undefined'
|
|
14685
16481
|
&& isXhrRedirect
|
|
14686
16482
|
) {
|
|
14687
|
-
|
|
16483
|
+
// #gh76 — route by the SUBMITTING form's popin, never by "the
|
|
16484
|
+
// active popin". A name-less `result.popin` directive (`close`,
|
|
16485
|
+
// `url`) is the one case where the server addresses "the open
|
|
16486
|
+
// popin" explicitly — only a route the app declared as popin
|
|
16487
|
+
// context produces it — so it keeps the open-only fallback; a
|
|
16488
|
+
// plain `location` redirect was chosen by the server BECAUSE it
|
|
16489
|
+
// saw no popin context, so from a page form it falls through to
|
|
16490
|
+
// the page redirect below instead of loading into a popin the
|
|
16491
|
+
// form is not inside.
|
|
16492
|
+
$popin = sendCtx.popin
|
|
16493
|
+
|| ( ( typeof(result.popin) != 'undefined' ) ? gina.popin.getActivePopin() : null );
|
|
16494
|
+
if ( !$popin && typeof(result.popin) == 'undefined' ) {
|
|
16495
|
+
warnIfOldRuleRouted(sendCtx.popin, $popin, $target, 'redirect');
|
|
16496
|
+
}
|
|
14688
16497
|
if ( !$popin && typeof(result.popin) != 'undefined' ) {
|
|
14689
|
-
if ( typeof(result.popin
|
|
14690
|
-
|
|
14691
|
-
|
|
14692
|
-
|
|
14693
|
-
|
|
14694
|
-
|
|
16498
|
+
if ( typeof(result.popin.name) == 'undefined' ) {
|
|
16499
|
+
// a name-less `close` with nothing open to close is a no-op,
|
|
16500
|
+
// not an error: getActivePopin() no longer resolves a not-open
|
|
16501
|
+
// popin for that close() to no-op on (#gh76 §8(a)); only an
|
|
16502
|
+
// actual resolve still throws
|
|
16503
|
+
if ( typeof(result.popin.close) == 'undefined' ) {
|
|
16504
|
+
throw new Error('To get a `$popin` instance, you need at list a `popin.name`.');
|
|
16505
|
+
}
|
|
16506
|
+
} else {
|
|
16507
|
+
$popin = gina.popin.getPopinByName(result.popin.name);
|
|
16508
|
+
if ( !$popin ) {
|
|
16509
|
+
throw new Error('Popin with name: `'+ result.popin.name +'` not found.')
|
|
16510
|
+
}
|
|
14695
16511
|
}
|
|
14696
16512
|
}
|
|
14697
16513
|
|
|
@@ -14699,8 +16515,11 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
14699
16515
|
typeof(result.popin) != 'undefined'
|
|
14700
16516
|
&& typeof(result.popin.close) != 'undefined'
|
|
14701
16517
|
) {
|
|
14702
|
-
|
|
14703
|
-
$popin
|
|
16518
|
+
// #gh76 — `$popin` is null when nothing is open to close
|
|
16519
|
+
if ($popin) {
|
|
16520
|
+
$popin.isRedirecting = false;
|
|
16521
|
+
$popin.close();
|
|
16522
|
+
}
|
|
14704
16523
|
var _reload = (result.popin.reload) ? result.popin.reload : false;
|
|
14705
16524
|
if ( !result.popin.location && !result.popin.url) {
|
|
14706
16525
|
delete result.popin;
|
|
@@ -14786,6 +16605,13 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
14786
16605
|
if (hFormIsRequired)
|
|
14787
16606
|
triggerEvent(gina, $target, 'success.' + id + '.hform', result);
|
|
14788
16607
|
|
|
16608
|
+
// #gh76 slice 2 — a swap that replaced or removed the SUBMITTING form
|
|
16609
|
+
// kept its listeners until the events above were delivered; its
|
|
16610
|
+
// replacement (same id) is bound only now
|
|
16611
|
+
if ( sendCtx.rebindSelf ) {
|
|
16612
|
+
finalizeSelfReplacement($target, id);
|
|
16613
|
+
}
|
|
16614
|
+
|
|
14789
16615
|
} catch (err) {
|
|
14790
16616
|
|
|
14791
16617
|
result = {
|
|
@@ -15234,6 +17060,13 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
15234
17060
|
if ( /^gina\-upload/i.test(id) )
|
|
15235
17061
|
onUpload(gina, $target, 'error', id, err);
|
|
15236
17062
|
|
|
17063
|
+
// #gh76 slice 4 — this send never reaches the wire, so its
|
|
17064
|
+
// `loadend` never fires: release what it held disabled here.
|
|
17065
|
+
// The coordination slot needs nothing — it is claimed after
|
|
17066
|
+
// `xhr.send()`, which this branch never reaches.
|
|
17067
|
+
releaseDisabledElts(sendCtx.disabledElts);
|
|
17068
|
+
sendCtx.disabledElts = null;
|
|
17069
|
+
|
|
15237
17070
|
triggerEvent(gina, $target, 'error.' + id, err);
|
|
15238
17071
|
|
|
15239
17072
|
if (hFormIsRequired)
|
|
@@ -15243,6 +17076,7 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
15243
17076
|
if (done) {
|
|
15244
17077
|
xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);
|
|
15245
17078
|
xhr.send(data);
|
|
17079
|
+
claimSyncSlot(); // #gh76 slice 4 — after send(): a throw leaves no wedged key
|
|
15246
17080
|
|
|
15247
17081
|
$form.sent = true;
|
|
15248
17082
|
if ( envIsDev && isGFFCtx && typeof(window.ginaToolbar) != 'undefined' && window.ginaToolbar ) {
|
|
@@ -15298,6 +17132,7 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
15298
17132
|
xhr.setRequestHeader('Content-Type', sendContentType);
|
|
15299
17133
|
}
|
|
15300
17134
|
xhr.send(data)
|
|
17135
|
+
claimSyncSlot(); // #gh76 slice 4
|
|
15301
17136
|
}
|
|
15302
17137
|
|
|
15303
17138
|
} else {
|
|
@@ -15306,6 +17141,7 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
15306
17141
|
xhr.setRequestHeader('Content-Type', enctype);
|
|
15307
17142
|
}
|
|
15308
17143
|
xhr.send()
|
|
17144
|
+
claimSyncSlot(); // #gh76 slice 4
|
|
15309
17145
|
}
|
|
15310
17146
|
|
|
15311
17147
|
$form.sent = true;
|
|
@@ -16497,6 +18333,16 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
16497
18333
|
$form.on('success.hform', window[htmlSuccesEventCallback])
|
|
16498
18334
|
}
|
|
16499
18335
|
}
|
|
18336
|
+
//data-gina-form-event-on-swap (#gh76 slice 2) -> afterswap.<id>.hform
|
|
18337
|
+
var htmlSwapEventCallback = $form.target.getAttribute('data-gina-form-event-on-swap') || null;
|
|
18338
|
+
if (htmlSwapEventCallback != null) {
|
|
18339
|
+
if ( /\((.*)\)/.test(htmlSwapEventCallback) ) {
|
|
18340
|
+
// #M21a — function-call shape unsupported; register a bare handler on window instead
|
|
18341
|
+
try { console.warn('[gina-form-event] function-call shape not supported on data-gina-form-event-on-swap — use a bare identifier and register the handler on window: '+ htmlSwapEventCallback); } catch (e) {}
|
|
18342
|
+
} else {
|
|
18343
|
+
$form.on('afterswap.hform', window[htmlSwapEventCallback])
|
|
18344
|
+
}
|
|
18345
|
+
}
|
|
16500
18346
|
//data-gina-form-event-on-submit-error
|
|
16501
18347
|
var htmlErrorEventCallback = $form.target.getAttribute('data-gina-form-event-on-submit-error') || null;
|
|
16502
18348
|
if (htmlErrorEventCallback != null) {
|
|
@@ -16751,6 +18597,7 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
16751
18597
|
$validator.unbind = unbindForm;
|
|
16752
18598
|
$validator.bind = bindForm;
|
|
16753
18599
|
$validator.reBind = reBindForm;
|
|
18600
|
+
$validator.bindRegion = bindFormsInRegion;
|
|
16754
18601
|
$validator.destroy = destroy;
|
|
16755
18602
|
|
|
16756
18603
|
var id = null
|
|
@@ -16763,6 +18610,10 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
16763
18610
|
// form has rule ?
|
|
16764
18611
|
for (var f=0, len = $allForms.length; f<len; ++f) {
|
|
16765
18612
|
// preparing prototype (need at least an ID for this)
|
|
18613
|
+
// #B549 — bind only what the page opted in; a bare <form> is left
|
|
18614
|
+
// untouched (no minted id, no $forms entry, no submit proxy), as the
|
|
18615
|
+
// guide states. Explicit validateFormById()/getFormById() stay ungated.
|
|
18616
|
+
if ( !isFormOptedIn($allForms[f], local.rules) ) { continue; }
|
|
16766
18617
|
|
|
16767
18618
|
if ($allForms[f].getAttribute) {
|
|
16768
18619
|
id = $allForms[f].getAttribute('id') || 'form.' + uuid();
|
|
@@ -18410,6 +20261,88 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
18410
20261
|
return $form;
|
|
18411
20262
|
}
|
|
18412
20263
|
|
|
20264
|
+
/**
|
|
20265
|
+
* bindFormsInRegion
|
|
20266
|
+
*
|
|
20267
|
+
* #gh76 slice 2 — binds the forms of a freshly injected region through the SAME gate
|
|
20268
|
+
* the boot scan applies (#B549): a form is bound only when the markup opts it in (a
|
|
20269
|
+
* `data-gina-form-*` attribute, an id naming a registered rule, or a `gina-upload-*`
|
|
20270
|
+
* id); a bare form keeps its native submit, exactly as on the initial page. Called by
|
|
20271
|
+
* the shared `bindRegion()` DOM helper for fragment navigation and form-answer swaps,
|
|
20272
|
+
* so swapped content follows the contract the guide states instead of re-entering
|
|
20273
|
+
* the pre-#B549 bind-everything shape.
|
|
20274
|
+
*
|
|
20275
|
+
* An id-less opted-in form is minted an id, as at boot. A registry entry whose
|
|
20276
|
+
* element is no longer the one in the region (a same-id replacement) is destroyed
|
|
20277
|
+
* first, so `validateFormById` binds the NEW element instead of returning the stale
|
|
20278
|
+
* entry. A form already bound to this very element is left alone.
|
|
20279
|
+
*
|
|
20280
|
+
* @param {HTMLElement} $root - the injected region
|
|
20281
|
+
* @param {object} [options]
|
|
20282
|
+
* @param {string|null} [options.deferFormId] - a form id to skip: the submitting form
|
|
20283
|
+
* of a swap that replaced it keeps its listeners until the swap's own events are
|
|
20284
|
+
* delivered, and is (re)bound by the caller afterwards
|
|
20285
|
+
*
|
|
20286
|
+
* @returns {number} the number of forms bound
|
|
20287
|
+
*
|
|
20288
|
+
* @example
|
|
20289
|
+
* $region.innerHTML = fragment;
|
|
20290
|
+
* gina.validator.bindRegion($region); // => 1
|
|
20291
|
+
*/
|
|
20292
|
+
// NOTE: deliberately NOT named `bindRegion`. `var` hoists over the whole module,
|
|
20293
|
+
// so a local of that name shadows utils/dom's shared `bindRegion()` policy for
|
|
20294
|
+
// every line of this file — which is how a swapped region's `<script src>` came
|
|
20295
|
+
// to be silently dropped (#gh76 slice 2): `applySwap` called the bare name and
|
|
20296
|
+
// got THIS forms-only binder. The PUBLISHED name stays `gina.validator.bindRegion`.
|
|
20297
|
+
var bindFormsInRegion = function($root, options) {
|
|
20298
|
+
var bound = 0
|
|
20299
|
+
, $v = ( this && typeof(this.$forms) != 'undefined' ) ? this : instance
|
|
20300
|
+
, $forms = null
|
|
20301
|
+
, $f = null
|
|
20302
|
+
, _id = null
|
|
20303
|
+
, existing = null
|
|
20304
|
+
, deferId = ( options && options.deferFormId ) ? options.deferFormId : null
|
|
20305
|
+
;
|
|
20306
|
+
if ( !$root || typeof($root.getElementsByTagName) != 'function' ) {
|
|
20307
|
+
return bound;
|
|
20308
|
+
}
|
|
20309
|
+
// a static copy: binding mutates nothing structural, but a live collection is
|
|
20310
|
+
// the wrong thing to iterate while ids are minted
|
|
20311
|
+
$forms = Array.prototype.slice.call($root.getElementsByTagName('form'));
|
|
20312
|
+
for (var f = 0, fLen = $forms.length; f < fLen; ++f) {
|
|
20313
|
+
$f = $forms[f];
|
|
20314
|
+
if ( !isFormOptedIn($f, local.rules) ) continue;
|
|
20315
|
+
|
|
20316
|
+
_id = $f.getAttribute('id') || 'form.' + uuid();
|
|
20317
|
+
if ( _id !== $f.getAttribute('id') ) {
|
|
20318
|
+
$f.setAttribute('id', _id);
|
|
20319
|
+
}
|
|
20320
|
+
if ( deferId && _id === deferId ) continue;
|
|
20321
|
+
|
|
20322
|
+
existing = instance.$forms[_id];
|
|
20323
|
+
if ( existing && existing.target === $f ) continue; // already bound to this element
|
|
20324
|
+
|
|
20325
|
+
if ( existing && existing.target && existing.target !== $f ) {
|
|
20326
|
+
// the region replaced a same-id form: retire the stale entry so the new
|
|
20327
|
+
// element binds (validateFormById returns an existing entry untouched)
|
|
20328
|
+
try {
|
|
20329
|
+
destroy(_id);
|
|
20330
|
+
} catch (staleErr) {
|
|
20331
|
+
// one stale entry must never abort the binding of the others — a form
|
|
20332
|
+
// holding a detached virtual upload input throws inside unbindForm
|
|
20333
|
+
if ( typeof(console) != 'undefined' && console.warn ) {
|
|
20334
|
+
console.warn('[gina][validator] bindRegion: could not retire stale form `' + _id + '`: ' + (staleErr.message || staleErr));
|
|
20335
|
+
}
|
|
20336
|
+
delete instance.$forms[_id];
|
|
20337
|
+
}
|
|
20338
|
+
}
|
|
20339
|
+
|
|
20340
|
+
validateFormById.call($v, _id);
|
|
20341
|
+
bound++;
|
|
20342
|
+
}
|
|
20343
|
+
return bound;
|
|
20344
|
+
}
|
|
20345
|
+
|
|
18413
20346
|
var unbindForm = function($target) {
|
|
18414
20347
|
var $form = null
|
|
18415
20348
|
, _id = null
|
|
@@ -18448,6 +20381,9 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
18448
20381
|
if ($form.target.getAttribute('data-gina-form-event-on-submit-error'))
|
|
18449
20382
|
removeListener(gina, $form, 'error.' + _id + '.hform');
|
|
18450
20383
|
|
|
20384
|
+
if ($form.target.getAttribute('data-gina-form-event-on-swap'))
|
|
20385
|
+
removeListener(gina, $form, 'afterswap.' + _id + '.hform');
|
|
20386
|
+
|
|
18451
20387
|
removeListener(gina, $form, 'validate.' + _id);
|
|
18452
20388
|
removeListener(gina, $form, 'validated.' + _id);
|
|
18453
20389
|
removeListener(gina, $form, 'submit.' + _id);
|
|
@@ -19266,19 +21202,33 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
19266
21202
|
var eventOnError = $el.getAttribute('data-gina-form-upload-on-error');
|
|
19267
21203
|
var eventOnProgress = $el.getAttribute('data-gina-form-upload-on-progress'); // #R8
|
|
19268
21204
|
var errorField = null;
|
|
21205
|
+
// #B572 — which popin, if any, this upload belongs to is decided by
|
|
21206
|
+
// CONTAINMENT (the popin the real form is inside), captured ONCE per
|
|
21207
|
+
// selection and read by every site below — never by "some popin is open"
|
|
21208
|
+
// (`isPopinContext()`), which placed a PAGE form's virtual upload form, its
|
|
21209
|
+
// preview lookups and its staging request inside whatever popin happened
|
|
21210
|
+
// to be open, where the staged metadata never reached the form. The same
|
|
21211
|
+
// capture the submit path makes into `sendCtx.popin`; `$el.form` is the
|
|
21212
|
+
// consumer's real form (the file input is never moved into the virtual one).
|
|
21213
|
+
var $ownerPopin = (
|
|
21214
|
+
typeof(gina.popin) != 'undefined'
|
|
21215
|
+
&& gina.popin
|
|
21216
|
+
&& typeof(gina.popin.getPopinContaining) == 'function'
|
|
21217
|
+
) ? gina.popin.getPopinContaining($el.form || $el) : null;
|
|
21218
|
+
warnIfOldRulePlacedUpload($ownerPopin, $el.form, $el);
|
|
19269
21219
|
|
|
19270
21220
|
if (files.length > 0) {
|
|
19271
21221
|
|
|
19272
21222
|
// create form if not exists
|
|
19273
|
-
var $uploadForm = null
|
|
19274
|
-
if (
|
|
19275
|
-
//
|
|
19276
|
-
|
|
19277
|
-
$
|
|
21223
|
+
var $uploadForm = null;
|
|
21224
|
+
if ( $ownerPopin ) {
|
|
21225
|
+
// the popin CONTAINING the real form (#B572) — its element is where
|
|
21226
|
+
// the virtual form lives and is torn down with it
|
|
21227
|
+
$ownerPopin.$target = new DOMParser().parseFromString($ownerPopin.target.outerHTML, 'text/html');
|
|
19278
21228
|
// binding to DOM
|
|
19279
|
-
$
|
|
21229
|
+
$ownerPopin.$target.getElementById($ownerPopin.id).innerHTML = document.getElementById($ownerPopin.id).innerHTML;
|
|
19280
21230
|
|
|
19281
|
-
$uploadForm = $
|
|
21231
|
+
$uploadForm = $ownerPopin.$target.getElementById(uploadFormId);
|
|
19282
21232
|
} else {
|
|
19283
21233
|
$uploadForm = document.getElementById(uploadFormId);
|
|
19284
21234
|
}
|
|
@@ -19291,8 +21241,8 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
19291
21241
|
}
|
|
19292
21242
|
|
|
19293
21243
|
if (!$uploadForm) {
|
|
19294
|
-
$uploadForm = (
|
|
19295
|
-
? $
|
|
21244
|
+
$uploadForm = ($ownerPopin)
|
|
21245
|
+
? $ownerPopin.$target.createElement('form')
|
|
19296
21246
|
: document.createElement('form');
|
|
19297
21247
|
}
|
|
19298
21248
|
|
|
@@ -19323,8 +21273,8 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
19323
21273
|
// no default id (see bindUploadDropzone)
|
|
19324
21274
|
var dropzoneContainer = $el.getAttribute('data-gina-form-upload-dropzone') || null;
|
|
19325
21275
|
var previewContainer = $el.getAttribute('data-gina-form-upload-preview') || fieldId + '-preview';
|
|
19326
|
-
previewContainer = (
|
|
19327
|
-
? $
|
|
21276
|
+
previewContainer = ($ownerPopin)
|
|
21277
|
+
? $ownerPopin.$target.getElementById(previewContainer)
|
|
19328
21278
|
: document.getElementById(previewContainer);
|
|
19329
21279
|
|
|
19330
21280
|
// #B147 — previewContainer is a getElementById RESULT
|
|
@@ -19477,7 +21427,7 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
19477
21427
|
mandatoryFields : mandatoryFields,
|
|
19478
21428
|
uploadFields : hiddenFields,
|
|
19479
21429
|
hasPreviewContainer : hasPreviewContainer,
|
|
19480
|
-
isPopinContext :
|
|
21430
|
+
isPopinContext : !!$ownerPopin // #B572 — true iff the file's form is inside a popin; nothing reads it, the key is kept for shape
|
|
19481
21431
|
};
|
|
19482
21432
|
if (hasPreviewContainer) {
|
|
19483
21433
|
$uploadForm.uploadProperties.previewContainer = previewContainer;
|
|
@@ -19504,9 +21454,9 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
19504
21454
|
|
|
19505
21455
|
|
|
19506
21456
|
// adding for to current document
|
|
19507
|
-
if (
|
|
19508
|
-
//$
|
|
19509
|
-
document.getElementById($
|
|
21457
|
+
if ($ownerPopin) {
|
|
21458
|
+
//$ownerPopin.$target.appendChild($uploadForm)
|
|
21459
|
+
document.getElementById($ownerPopin.id).appendChild($uploadForm)
|
|
19510
21460
|
} else {
|
|
19511
21461
|
document.body.appendChild($uploadForm)
|
|
19512
21462
|
}
|
|
@@ -23520,6 +25470,7 @@ function ValidatorPlugin(rules, data, formId, culture) {
|
|
|
23520
25470
|
instance.setOptions = setOptions;
|
|
23521
25471
|
instance.getFormById = getFormById;
|
|
23522
25472
|
instance.validateFormById = validateFormById;
|
|
25473
|
+
instance.bindRegion = bindFormsInRegion;
|
|
23523
25474
|
instance.resetErrorsDisplay = resetErrorsDisplay;
|
|
23524
25475
|
instance.resetFields = resetFields;
|
|
23525
25476
|
instance.handleErrorsDisplay = handleErrorsDisplay;
|
|
@@ -23706,6 +25657,7 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/loading-state', 'lib/merge'
|
|
|
23706
25657
|
'$popins' : _sharedPopins, // #B90 — module-shared registry (one object for all instances)
|
|
23707
25658
|
activePopinId : null,
|
|
23708
25659
|
getActivePopin : null, // returns the active $popin
|
|
25660
|
+
getPopinContaining : null, // the popin whose element contains a given element (#gh76)
|
|
23709
25661
|
target : document, // by default
|
|
23710
25662
|
isReady : false,
|
|
23711
25663
|
initialized : false
|
|
@@ -23723,6 +25675,15 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/loading-state', 'lib/merge'
|
|
|
23723
25675
|
'loadContent' : null,
|
|
23724
25676
|
'open' : null,
|
|
23725
25677
|
'isOpen' : false,
|
|
25678
|
+
// #gh76 §8 — true from the moment a `preOpen` popin shows its loading shell
|
|
25679
|
+
// until its real open (or its close / failed load); a default popin shows
|
|
25680
|
+
// nothing while it loads, so it never enters this state
|
|
25681
|
+
'isLoading' : false,
|
|
25682
|
+
// #gh76 §8 — this popin's load sequence and the transport of the load in
|
|
25683
|
+
// flight: a close during the load bumps the sequence, so the landing result
|
|
25684
|
+
// is dropped instead of re-opening a dismissed dialog
|
|
25685
|
+
'_loadSeq' : 0,
|
|
25686
|
+
'_loadXhr' : null,
|
|
23726
25687
|
'isRedirecting' : false,
|
|
23727
25688
|
'close' : null,
|
|
23728
25689
|
'$forms' : [],
|
|
@@ -23833,21 +25794,78 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/loading-state', 'lib/merge'
|
|
|
23833
25794
|
return $popin;
|
|
23834
25795
|
}
|
|
23835
25796
|
|
|
25797
|
+
/**
|
|
25798
|
+
* getActivePopin
|
|
25799
|
+
*
|
|
25800
|
+
* The popin the framework considers active: the one `activePopinId` names WHEN IT
|
|
25801
|
+
* IS OPEN, else the first open popin in registration order, else `null`.
|
|
25802
|
+
*
|
|
25803
|
+
* #gh76 §8(a) — a registered-but-not-open popin is never returned. The former
|
|
25804
|
+
* any-registered fallback on `activePopinId` returned a popin during its own
|
|
25805
|
+
* click-time load window (`popinLoad` sets the id before the content lands), which
|
|
25806
|
+
* routed a page form's HTML answer into a popin the framework itself considered
|
|
25807
|
+
* closed, and `popinLoadContent` then threw on it. With two popins open the id —
|
|
25808
|
+
* the most recently opened one — now wins over registration order.
|
|
25809
|
+
*
|
|
25810
|
+
* @returns {object|null} $popin
|
|
25811
|
+
*
|
|
25812
|
+
* @example
|
|
25813
|
+
* var $popin = gina.popin.getActivePopin();
|
|
25814
|
+
* if ($popin) { $popin.close(); }
|
|
25815
|
+
*/
|
|
23836
25816
|
function getActivePopin() {
|
|
23837
|
-
var $popin =
|
|
25817
|
+
var $popins = gina.popin.$popins, activeId = gina.popin.activePopinId;
|
|
23838
25818
|
|
|
23839
|
-
|
|
23840
|
-
|
|
23841
|
-
|
|
23842
|
-
|
|
25819
|
+
if ( activeId && $popins[activeId] && $popins[activeId].isOpen ) {
|
|
25820
|
+
return $popins[activeId];
|
|
25821
|
+
}
|
|
25822
|
+
for (var p in $popins) {
|
|
25823
|
+
if ( typeof($popins[p].isOpen) != 'undefined' && $popins[p].isOpen ) {
|
|
25824
|
+
return $popins[p];
|
|
23843
25825
|
}
|
|
23844
25826
|
}
|
|
25827
|
+
return null;
|
|
25828
|
+
}
|
|
23845
25829
|
|
|
23846
|
-
|
|
23847
|
-
|
|
23848
|
-
|
|
25830
|
+
/**
|
|
25831
|
+
* getPopinContaining
|
|
25832
|
+
*
|
|
25833
|
+
* The registered popin whose dialog element contains `$el`, or `null` when `$el`
|
|
25834
|
+
* is not inside any popin. Innermost wins when popins nest (an in-page dialog
|
|
25835
|
+
* registered from inside another popin's loaded content).
|
|
25836
|
+
*
|
|
25837
|
+
* #gh76 — the routing test for a form's or a link's HTML answer: the answer goes
|
|
25838
|
+
* to the popin the SUBMITTING element lives in, never to "the active popin". The
|
|
25839
|
+
* element id is the popin id on every flavour (dialog, div, in-page), so the walk
|
|
25840
|
+
* is one `getElementById` + `contains` per registered popin — measured at 0.2 µs
|
|
25841
|
+
* worst case per submit.
|
|
25842
|
+
*
|
|
25843
|
+
* @param {HTMLElement} $el - the form, anchor or any element to locate
|
|
25844
|
+
*
|
|
25845
|
+
* @returns {object|null} $popin
|
|
25846
|
+
*
|
|
25847
|
+
* @example
|
|
25848
|
+
* var $popin = gina.popin.getPopinContaining(document.getElementById('my-form'));
|
|
25849
|
+
* // -> the popin the form is rendered in, or null for a page form
|
|
25850
|
+
*/
|
|
25851
|
+
function getPopinContaining($el) {
|
|
25852
|
+
var $popins = gina.popin.$popins, $found = null, $foundEl = null, $dialog = null;
|
|
23849
25853
|
|
|
23850
|
-
|
|
25854
|
+
if ( !$el || typeof($el.nodeType) == 'undefined' ) {
|
|
25855
|
+
return null;
|
|
25856
|
+
}
|
|
25857
|
+
for (var p in $popins) {
|
|
25858
|
+
$dialog = ( $popins[p].id ) ? document.getElementById($popins[p].id) : null;
|
|
25859
|
+
if ( !$dialog || !$dialog.contains($el) ) {
|
|
25860
|
+
continue;
|
|
25861
|
+
}
|
|
25862
|
+
// innermost wins: take the candidate the previous one contains
|
|
25863
|
+
if ( !$found || $foundEl.contains($dialog) ) {
|
|
25864
|
+
$found = $popins[p];
|
|
25865
|
+
$foundEl = $dialog;
|
|
25866
|
+
}
|
|
25867
|
+
}
|
|
25868
|
+
return $found;
|
|
23851
25869
|
}
|
|
23852
25870
|
|
|
23853
25871
|
|
|
@@ -24101,29 +26119,95 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/loading-state', 'lib/merge'
|
|
|
24101
26119
|
}
|
|
24102
26120
|
}
|
|
24103
26121
|
|
|
26122
|
+
/**
|
|
26123
|
+
* warnPartialTarget
|
|
26124
|
+
*
|
|
26125
|
+
* Dev-mode notice for the partial-swap fallbacks (#B580). Gated on
|
|
26126
|
+
* `gina.config.envIsDev` like the CORS trace below, so production stays
|
|
26127
|
+
* silent exactly as the guide promises; plain `console.warn`, the shape
|
|
26128
|
+
* warnDeprecatedOnce uses. One line per event, naming the popin, the
|
|
26129
|
+
* selector and which fallback ran — the three silent decisions this file
|
|
26130
|
+
* used to make with no trace at all.
|
|
26131
|
+
*
|
|
26132
|
+
* @inner
|
|
26133
|
+
* @param {object|null} $popin - the popin (its `name` goes in the message)
|
|
26134
|
+
* @param {string} partialTarget - the declared `data-gina-dialog-target`
|
|
26135
|
+
* @param {string} what - `invalid` | `noSlot` | `noRegion`
|
|
26136
|
+
* @param {Error} [err] - the engine's error, for `invalid`
|
|
26137
|
+
*
|
|
26138
|
+
* @returns {undefined}
|
|
26139
|
+
*/
|
|
26140
|
+
function warnPartialTarget($popin, partialTarget, what, err) {
|
|
26141
|
+
var isDev = ( typeof(gina) != 'undefined' && gina && gina.config && gina.config.envIsDev ) ? true : false;
|
|
26142
|
+
if ( !isDev || typeof(console) == 'undefined' || typeof(console.warn) != 'function' ) {
|
|
26143
|
+
return;
|
|
26144
|
+
}
|
|
26145
|
+
var name = ( $popin && $popin.name ) ? $popin.name : '(unknown)';
|
|
26146
|
+
var why = ( what === 'invalid' )
|
|
26147
|
+
? 'is not a valid selector ('+ ( err && err.message ? err.message : String(err) ) +') — the whole dialog was replaced instead'
|
|
26148
|
+
: ( what === 'noSlot' )
|
|
26149
|
+
? 'matched nothing in the open dialog — the whole dialog was replaced instead'
|
|
26150
|
+
: 'matched nothing in the answer — the whole answer went into the slot';
|
|
26151
|
+
try {
|
|
26152
|
+
console.warn('[gina/popin] popin `'+ name +'`: `data-gina-dialog-target="'+ partialTarget +'"` '+ why +'.');
|
|
26153
|
+
} catch (e) {}
|
|
26154
|
+
}
|
|
26155
|
+
|
|
24104
26156
|
/**
|
|
24105
26157
|
* applyContent
|
|
24106
26158
|
*
|
|
24107
|
-
*
|
|
24108
|
-
*
|
|
24109
|
-
*
|
|
24110
|
-
*
|
|
24111
|
-
*
|
|
26159
|
+
* Writes a loaded HTML body into the dialog element.
|
|
26160
|
+
*
|
|
26161
|
+
* Full (`partialTarget` absent): `$el.innerHTML = html.trim()`.
|
|
26162
|
+
*
|
|
26163
|
+
* Partial (`partialTarget` set): ONE plain CSS selector, applied on BOTH
|
|
26164
|
+
* sides — it locates the slot in the OPEN DIALOG and the region in the
|
|
26165
|
+
* parsed answer — and only the slot's CONTENTS are swapped, so chrome
|
|
26166
|
+
* (close button, header/footer) and its bindings survive. That makes the
|
|
26167
|
+
* attribute the dialog-scoped sibling of `data-gina-form-select`, not of
|
|
26168
|
+
* `data-gina-form-target`: there is nothing here to resolve relative to an
|
|
26169
|
+
* element, so the `this` / `closest` / `find` grammar does not apply.
|
|
26170
|
+
*
|
|
26171
|
+
* Three fallbacks, all deliberate (a popin open is a read the user can
|
|
26172
|
+
* retry, so working-but-wrong beats refusing) and each now announced in
|
|
26173
|
+
* dev mode (#B580 — before, the first THREW uncaught and the other two were
|
|
26174
|
+
* silent):
|
|
26175
|
+
* - a selector the engine refuses → full replace;
|
|
26176
|
+
* - a selector matching nothing in the dialog → full replace;
|
|
26177
|
+
* - a selector matching nothing in the answer → the whole answer body into the slot.
|
|
24112
26178
|
*
|
|
24113
26179
|
* @inner
|
|
26180
|
+
* @param {HTMLElement} $el - the dialog element
|
|
26181
|
+
* @param {string} html - the loaded body
|
|
26182
|
+
* @param {object|null} $popin - the popin (for the notice)
|
|
26183
|
+
* @param {string|null} partialTarget - the declared selector, or null
|
|
26184
|
+
*
|
|
26185
|
+
* @returns {undefined}
|
|
24114
26186
|
*/
|
|
24115
26187
|
function applyContent($el, html, $popin, partialTarget) {
|
|
24116
26188
|
if ( !partialTarget ) {
|
|
24117
26189
|
$el.innerHTML = ( typeof(html) == 'string' ) ? html.trim() : '';
|
|
24118
26190
|
return;
|
|
24119
26191
|
}
|
|
24120
|
-
var $slot = $
|
|
26192
|
+
var $slot = null, parsed = null, $incoming = null;
|
|
26193
|
+
try {
|
|
26194
|
+
$slot = $el.querySelector(partialTarget);
|
|
26195
|
+
} catch (badSelector) {
|
|
26196
|
+
warnPartialTarget($popin, partialTarget, 'invalid', badSelector);
|
|
26197
|
+
$el.innerHTML = ( typeof(html) == 'string' ) ? html.trim() : '';
|
|
26198
|
+
return;
|
|
26199
|
+
}
|
|
24121
26200
|
if ( !$slot ) {
|
|
26201
|
+
warnPartialTarget($popin, partialTarget, 'noSlot');
|
|
24122
26202
|
$el.innerHTML = ( typeof(html) == 'string' ) ? html.trim() : '';
|
|
24123
26203
|
return;
|
|
24124
26204
|
}
|
|
24125
|
-
|
|
24126
|
-
|
|
26205
|
+
parsed = new DOMParser().parseFromString(html, 'text/html');
|
|
26206
|
+
$incoming = parsed.querySelector(partialTarget);
|
|
26207
|
+
if ( !$incoming ) {
|
|
26208
|
+
warnPartialTarget($popin, partialTarget, 'noRegion');
|
|
26209
|
+
$incoming = parsed.body;
|
|
26210
|
+
}
|
|
24127
26211
|
$slot.innerHTML = $incoming.innerHTML;
|
|
24128
26212
|
}
|
|
24129
26213
|
|
|
@@ -24289,6 +26373,9 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/loading-state', 'lib/merge'
|
|
|
24289
26373
|
// A preOpen popin still gets its instant born-modal skeleton now; the
|
|
24290
26374
|
// adopted preload swaps in the real content on arrival — matching the
|
|
24291
26375
|
// popinLoad preOpen flow this consume path bypasses (#B54).
|
|
26376
|
+
// #gh76 §8 — this adoption's sequence, on the same counter popinLoad uses:
|
|
26377
|
+
// a close during the wait bumps it and the body is dropped on arrival
|
|
26378
|
+
var seq = ( $popin ) ? ( $popin._loadSeq = ( $popin._loadSeq || 0 ) + 1 ) : null;
|
|
24292
26379
|
if ( $popin && $popin.options && $popin.options.preOpen ) {
|
|
24293
26380
|
showLoadingShell($popin, ensurePopinDialog($popin));
|
|
24294
26381
|
}
|
|
@@ -24299,6 +26386,10 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/loading-state', 'lib/merge'
|
|
|
24299
26386
|
// #B285 — the adopted wait is over (body or failure): settle FIRST,
|
|
24300
26387
|
// matching the cold path's release-before-apply order.
|
|
24301
26388
|
_settle();
|
|
26389
|
+
// #gh76 §8 — cancelled while it waited: released above, body dropped
|
|
26390
|
+
if ( seq !== null && seq !== $popin._loadSeq ) {
|
|
26391
|
+
return;
|
|
26392
|
+
}
|
|
24302
26393
|
if ( body == null ) {
|
|
24303
26394
|
if ( typeof(onMiss) == 'function' ) { onMiss(); }
|
|
24304
26395
|
} else {
|
|
@@ -24650,6 +26741,13 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/loading-state', 'lib/merge'
|
|
|
24650
26741
|
if ( typeof(gina.events[loadedEvt]) == 'undefined' ) {
|
|
24651
26742
|
addListener(gina, existing.target, loadedEvt, function (loadedEvent) {
|
|
24652
26743
|
loadedEvent.preventDefault();
|
|
26744
|
+
// #B579 — popinLoadContent's redirect emit fires this same event with the
|
|
26745
|
+
// POPIN OBJECT as detail (the legacy listener only binds + opens on it): a
|
|
26746
|
+
// load landing on an already-open popin routes through it, and applying a
|
|
26747
|
+
// non-string would blank the dialog whose content that call just wrote
|
|
26748
|
+
if ( typeof(loadedEvent.detail) != 'string' ) {
|
|
26749
|
+
return;
|
|
26750
|
+
}
|
|
24653
26751
|
handleLoadedBody(loadedEvent.detail, existing, ensurePopinDialog(existing));
|
|
24654
26752
|
});
|
|
24655
26753
|
}
|
|
@@ -24984,7 +27082,7 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/loading-state', 'lib/merge'
|
|
|
24984
27082
|
|
|
24985
27083
|
if (!fired) {
|
|
24986
27084
|
fired = true;
|
|
24987
|
-
popinLoadContent(e.detail);
|
|
27085
|
+
popinLoadContent.call($popin, e.detail);
|
|
24988
27086
|
}
|
|
24989
27087
|
});
|
|
24990
27088
|
}
|
|
@@ -25564,6 +27662,106 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/loading-state', 'lib/merge'
|
|
|
25564
27662
|
$overlay.classList.add('gina-popin-is-active');
|
|
25565
27663
|
}
|
|
25566
27664
|
}
|
|
27665
|
+
|
|
27666
|
+
// #gh76 §8 — the shell is showing and the load is in flight: an explicit state.
|
|
27667
|
+
// `isOpen` stays false, so the two `!isOpen ⇒ popinOpen` consumers still run the
|
|
27668
|
+
// real open when the content lands. A dialog shell gets the native `close` sync
|
|
27669
|
+
// NOW, not only at popinOpen: an Escape during the load must route through
|
|
27670
|
+
// popinClose, or the landing content would re-open a dismissed dialog.
|
|
27671
|
+
$popin.isLoading = true;
|
|
27672
|
+
bindNativeCloseSync($popin, $el);
|
|
27673
|
+
}
|
|
27674
|
+
|
|
27675
|
+
/**
|
|
27676
|
+
* bindNativeCloseSync
|
|
27677
|
+
*
|
|
27678
|
+
* #gh76 §8 — one native `close` listener per dialog element, bound at the loading
|
|
27679
|
+
* shell AND at popinOpen, whichever comes first (`_ginaCloseSyncBound` keeps it to
|
|
27680
|
+
* one across element reuse). Listens to `close`, not `cancel`: `close` fires for
|
|
27681
|
+
* BOTH Escape and method="dialog". De-dup with the plugin's own close: popinClose
|
|
27682
|
+
* (and closeLoadingShell) set isOpen / isLoading false SYNCHRONOUSLY before the
|
|
27683
|
+
* queued `close` event fires, so the guard sees neither and no-ops; a `$el.close()`
|
|
27684
|
+
* on an already-closed dialog is a spec no-op, so there is no recursion. Native
|
|
27685
|
+
* addEventListener — NOT gina's addListener (the custom event bus). Gated on
|
|
27686
|
+
* useDialogMode: a <div> has no `close` event.
|
|
27687
|
+
*
|
|
27688
|
+
* @param {object} $popin - the registered popin
|
|
27689
|
+
* @param {HTMLElement} $el - its dialog element
|
|
27690
|
+
*
|
|
27691
|
+
* @returns {undefined}
|
|
27692
|
+
*
|
|
27693
|
+
* @example
|
|
27694
|
+
* bindNativeCloseSync($popin, $el);
|
|
27695
|
+
*/
|
|
27696
|
+
function bindNativeCloseSync($popin, $el) {
|
|
27697
|
+
if ( self.options.useDialogMode && $el && !$el._ginaCloseSyncBound ) {
|
|
27698
|
+
$el._ginaCloseSyncBound = true;
|
|
27699
|
+
$el.addEventListener('close', function () {
|
|
27700
|
+
if ( $popin.isOpen || $popin.isLoading ) {
|
|
27701
|
+
popinClose($popin.name);
|
|
27702
|
+
}
|
|
27703
|
+
});
|
|
27704
|
+
}
|
|
27705
|
+
}
|
|
27706
|
+
|
|
27707
|
+
/**
|
|
27708
|
+
* closeLoadingShell
|
|
27709
|
+
*
|
|
27710
|
+
* #gh76 §8 — closes a popin whose loading shell is showing but whose real open never
|
|
27711
|
+
* happened (`isLoading` true, `isOpen` false). The load itself is CANCELLED: the
|
|
27712
|
+
* sequence is bumped so the landing result — or an adopted preload's body — is
|
|
27713
|
+
* dropped instead of re-opening a dismissed dialog, and a transport still in flight
|
|
27714
|
+
* is aborted (its own readyState-4 handler sees the bumped sequence and returns
|
|
27715
|
+
* right after its release block, so no `error` fires for a cancel). Tears down what
|
|
27716
|
+
* the shell built BY THE SHELL'S OWN STATE — not by the `gina-popin-is-active` class
|
|
27717
|
+
* popinClose's teardown keys on, which a dialog shell never carries — releases the
|
|
27718
|
+
* trigger, and fires `close.<id>`: the honest "this dialog went away" signal, even
|
|
27719
|
+
* though `open.<id>` never fired.
|
|
27720
|
+
*
|
|
27721
|
+
* @param {object} $popin - the loading popin
|
|
27722
|
+
*
|
|
27723
|
+
* @returns {undefined}
|
|
27724
|
+
*
|
|
27725
|
+
* @example
|
|
27726
|
+
* if ( $popin.isLoading && !$popin.isOpen ) closeLoadingShell($popin);
|
|
27727
|
+
*/
|
|
27728
|
+
function closeLoadingShell($popin) {
|
|
27729
|
+
var $el = document.getElementById($popin.id) || null;
|
|
27730
|
+
var $popinTrigger = document.getElementById($popin.openTrigger) || null;
|
|
27731
|
+
// cancel the load: the landing result is dropped by the sequence check
|
|
27732
|
+
$popin._loadSeq = ( $popin._loadSeq || 0 ) + 1;
|
|
27733
|
+
if ( $popin._loadXhr && $popin._loadXhr.readyState !== 4 ) {
|
|
27734
|
+
try { $popin._loadXhr.abort(); } catch (abortErr) { /* already dead */ }
|
|
27735
|
+
}
|
|
27736
|
+
$popin._loadXhr = null;
|
|
27737
|
+
// BEFORE the native close below: its queued `close` event must find no state
|
|
27738
|
+
$popin.isLoading = false;
|
|
27739
|
+
if ( $el ) {
|
|
27740
|
+
if ( $el.tagName === 'DIALOG' ) {
|
|
27741
|
+
if ( typeof($el.close) === 'function' && $el.hasAttribute('open') ) {
|
|
27742
|
+
try { $el.close(); } catch (closeErr) { $el.removeAttribute('open'); }
|
|
27743
|
+
} else {
|
|
27744
|
+
$el.removeAttribute('open');
|
|
27745
|
+
}
|
|
27746
|
+
} else {
|
|
27747
|
+
$el.classList.remove('gina-popin-is-active');
|
|
27748
|
+
var $overlay = $el.parentElement;
|
|
27749
|
+
if ( $overlay ) { $overlay.classList.remove('gina-popin-is-active'); }
|
|
27750
|
+
}
|
|
27751
|
+
$el.innerHTML = '';
|
|
27752
|
+
$el.removeAttribute('data-gina-popin-loading');
|
|
27753
|
+
}
|
|
27754
|
+
if ( $popin.target && typeof($popin.target.removeAttribute) == 'function' ) {
|
|
27755
|
+
$popin.target.removeAttribute('data-gina-popin-loading');
|
|
27756
|
+
}
|
|
27757
|
+
releasePopinTrigger($popinTrigger);
|
|
27758
|
+
// operands reversed on purpose: popin-registry.test.js counts the raw WRITE
|
|
27759
|
+
// shape of the active id (assignment to the instance field) at exactly one
|
|
27760
|
+
// site, and a `===` read written the other way round carries that prefix
|
|
27761
|
+
if ( $popin.id === instance.activePopinId ) {
|
|
27762
|
+
setActivePopinId(null);
|
|
27763
|
+
}
|
|
27764
|
+
triggerEvent(gina, $popin.target, 'close.'+ $popin.id, $popin);
|
|
25567
27765
|
}
|
|
25568
27766
|
|
|
25569
27767
|
/**
|
|
@@ -25652,6 +27850,9 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/loading-state', 'lib/merge'
|
|
|
25652
27850
|
var $popin = getPopinByName(name);
|
|
25653
27851
|
var id = $popin.id;
|
|
25654
27852
|
var $popinTrigger = document.getElementById($popin.openTrigger) || null;
|
|
27853
|
+
// #gh76 §8 — this load's sequence: a close during the load bumps it, and the
|
|
27854
|
+
// landing result below is dropped instead of re-opening a dismissed dialog
|
|
27855
|
+
var seq = $popin._loadSeq = ( $popin._loadSeq || 0 ) + 1;
|
|
25655
27856
|
|
|
25656
27857
|
// #B139 — record the content's source URL for the close-time cache
|
|
25657
27858
|
// clear (covers every click-time load path, incl. validator redirects).
|
|
@@ -25752,6 +27953,9 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/loading-state', 'lib/merge'
|
|
|
25752
27953
|
catch (e) { try { xhr = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {} }
|
|
25753
27954
|
}
|
|
25754
27955
|
|
|
27956
|
+
// #gh76 §8 — stashed so a close during the load can abort it
|
|
27957
|
+
$popin._loadXhr = xhr;
|
|
27958
|
+
|
|
25755
27959
|
if ( options.withCredentials ) { // Preflighted requests
|
|
25756
27960
|
if ('withCredentials' in xhr) {
|
|
25757
27961
|
// XHR for Chrome/Firefox/Opera/Safari.
|
|
@@ -25832,6 +28036,13 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/loading-state', 'lib/merge'
|
|
|
25832
28036
|
}
|
|
25833
28037
|
loadingState.disarm($popinTrigger);
|
|
25834
28038
|
}
|
|
28039
|
+
// #gh76 §8 — a load cancelled by a close (or superseded by a newer
|
|
28040
|
+
// load of the same popin) has released above; its result is dropped
|
|
28041
|
+
// here, so landing content cannot re-open a dismissed dialog
|
|
28042
|
+
if ( seq !== $popin._loadSeq ) {
|
|
28043
|
+
return;
|
|
28044
|
+
}
|
|
28045
|
+
$popin._loadXhr = null;
|
|
25835
28046
|
// 200, 201, 201' etc ...
|
|
25836
28047
|
var result = null;
|
|
25837
28048
|
|
|
@@ -25865,7 +28076,7 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/loading-state', 'lib/merge'
|
|
|
25865
28076
|
!isJsonContent && $popin.isOpen && isRedirecting && !$popin.partialTarget
|
|
25866
28077
|
) {
|
|
25867
28078
|
// console.debug('Popin now redirecting [1]');
|
|
25868
|
-
popinLoadContent(result, isRedirecting);
|
|
28079
|
+
popinLoadContent.call($popin, result, isRedirecting);
|
|
25869
28080
|
} else {
|
|
25870
28081
|
|
|
25871
28082
|
if (
|
|
@@ -26025,6 +28236,13 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/loading-state', 'lib/merge'
|
|
|
26025
28236
|
updateToolbar(result, resultIsObject);
|
|
26026
28237
|
|
|
26027
28238
|
triggerEvent(gina, $el, 'error.' + id, result)
|
|
28239
|
+
// #gh76 §8 — a failed load never leaves the shell behind: the
|
|
28240
|
+
// `error` listener above ran FIRST (it may have loaded content,
|
|
28241
|
+
// which completes the open and keeps the dialog); if nobody did,
|
|
28242
|
+
// close the shell — a spinner on a load that will never land
|
|
28243
|
+
if ( $popin.isLoading && !$popin.isOpen ) {
|
|
28244
|
+
closeLoadingShell($popin);
|
|
28245
|
+
}
|
|
26028
28246
|
}
|
|
26029
28247
|
}
|
|
26030
28248
|
};
|
|
@@ -26089,21 +28307,43 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/loading-state', 'lib/merge'
|
|
|
26089
28307
|
/**
|
|
26090
28308
|
* popinLoadContent
|
|
26091
28309
|
*
|
|
26092
|
-
*
|
|
26093
|
-
*
|
|
28310
|
+
* Replaces the popin's content with `stringContent` and re-binds it. Called as a
|
|
28311
|
+
* METHOD (`$popin.loadContent(html)`, or the internal `.call($popin, …)` sites) it
|
|
28312
|
+
* loads into THAT popin; called on the manager (`gina.popin.loadContent(html)`) it
|
|
28313
|
+
* loads into the active popin. Throws when the resolved popin is not open.
|
|
28314
|
+
*
|
|
28315
|
+
* #gh76 precision 4 — the former unconditional `getActivePopin()` re-resolve landed
|
|
28316
|
+
* a form's answer in whichever open popin was registered first when two were open
|
|
28317
|
+
* (measured); honouring `this` is what lets the validator load into the popin it
|
|
28318
|
+
* captured at submit.
|
|
28319
|
+
*
|
|
28320
|
+
* @this {object} [$popin] - when a registry entry, the popin to load into
|
|
28321
|
+
* @param {string} stringContent - the HTML to inject (plain text)
|
|
28322
|
+
* @param {boolean} [isRedirecting] - to handle a link inside a popin without a form
|
|
28323
|
+
*
|
|
28324
|
+
* @example
|
|
28325
|
+
* gina.popin.getPopinByName('details').loadContent('<p>updated</p>');
|
|
26094
28326
|
*/
|
|
26095
28327
|
function popinLoadContent(stringContent, isRedirecting) {
|
|
26096
28328
|
|
|
26097
|
-
var $popin =
|
|
28329
|
+
var $popin = ( this && this.id && instance.$popins[this.id] === this )
|
|
28330
|
+
? this
|
|
28331
|
+
: getActivePopin();
|
|
26098
28332
|
if ( !$popin ) {
|
|
26099
28333
|
return;
|
|
26100
28334
|
}
|
|
26101
|
-
|
|
28335
|
+
// #gh76 §8 — a popin whose loading shell is showing accepts content: it lands
|
|
28336
|
+
// in the shell and the open is completed below (a load still in flight lands
|
|
28337
|
+
// afterwards as any second loadContent would — last write wins)
|
|
28338
|
+
var completingOpen = ( !$popin.isOpen && $popin.isLoading );
|
|
28339
|
+
if ( !$popin.isOpen && !completingOpen )
|
|
26102
28340
|
throw new Error('Popin `'+$popin.name+'` is not open !');
|
|
26103
28341
|
|
|
26104
28342
|
$popin.isRedirecting = ( typeof(isRedirecting) != 'undefined' ) ? isRedirecting : false;
|
|
26105
28343
|
|
|
26106
|
-
|
|
28344
|
+
// while loading, `target` still names the shared container (popinOpen re-points
|
|
28345
|
+
// it at the dialog) — inject into the dialog element itself
|
|
28346
|
+
var $el = ( completingOpen ) ? ( document.getElementById($popin.id) || $popin.target ) : $popin.target;
|
|
26107
28347
|
// if (
|
|
26108
28348
|
// typeof(stringContent) != 'undefined'
|
|
26109
28349
|
// && typeof(stringContent.trim) == 'function'
|
|
@@ -26120,7 +28360,12 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/loading-state', 'lib/merge'
|
|
|
26120
28360
|
refreshCSS();
|
|
26121
28361
|
}
|
|
26122
28362
|
|
|
26123
|
-
if (
|
|
28363
|
+
if ( completingOpen ) {
|
|
28364
|
+
// the real open: the a11y label, isOpen, the trigger re-point, `open.<id>` —
|
|
28365
|
+
// the bind above is deduped, and the shell already showed the dialog so
|
|
28366
|
+
// popinOpen's own showModal() is skipped
|
|
28367
|
+
popinOpen($popin.name);
|
|
28368
|
+
} else if ( !$popin.isRedirecting ) {
|
|
26124
28369
|
triggerEvent(gina, instance.target, 'open.'+ $popin.id, $popin);
|
|
26125
28370
|
} else {
|
|
26126
28371
|
// console.debug('Popin now redirecting [1-b]');
|
|
@@ -26292,7 +28537,7 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/loading-state', 'lib/merge'
|
|
|
26292
28537
|
// Fix today's name-based aria-labelledby: associate a REAL title element.
|
|
26293
28538
|
associateLabel($el);
|
|
26294
28539
|
|
|
26295
|
-
if ( self.options.useDialogMode && !$el.
|
|
28540
|
+
if ( self.options.useDialogMode && !$el.hasAttribute('open') ) {
|
|
26296
28541
|
// Modal vs non-modal. The new `data-gina-dialog` API defaults to
|
|
26297
28542
|
// non-modal — openFromTrigger sets `$popin.modal`. Any path that did NOT
|
|
26298
28543
|
// set it (legacy `data-gina-popin-*` triggers, direct popinOpen() calls)
|
|
@@ -26303,8 +28548,12 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/loading-state', 'lib/merge'
|
|
|
26303
28548
|
// showModal() promotes the dialog to the top layer with a native
|
|
26304
28549
|
// ::backdrop and inerts the rest of the page. Consumers that
|
|
26305
28550
|
// preemptively open the dialog (skeleton-loading) MUST also use
|
|
26306
|
-
// showModal() so it is born modal; the !
|
|
28551
|
+
// showModal() so it is born modal; the !hasAttribute('open') guard
|
|
26307
28552
|
// above then skips this call (re-showModal on an open dialog throws).
|
|
28553
|
+
// #B574 — it has to be hasAttribute: showModal() sets `open` to the
|
|
28554
|
+
// EMPTY string, which getAttribute() returned as a falsy value, so the
|
|
28555
|
+
// guard never skipped a shell-opened dialog and the modeless branch
|
|
28556
|
+
// below called show() on a modal dialog (InvalidStateError).
|
|
26308
28557
|
$el.showModal();
|
|
26309
28558
|
} else {
|
|
26310
28559
|
// Non-modal: .show() loses the native ::backdrop / Escape /
|
|
@@ -26333,16 +28582,11 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/loading-state', 'lib/merge'
|
|
|
26333
28582
|
// there is no recursion. `_ginaCloseSyncBound` keeps it to one listener across
|
|
26334
28583
|
// element reuse; gated on useDialogMode (non-dialog mode is a <div>, no `close`
|
|
26335
28584
|
// event). Native addEventListener — NOT gina's addListener (the custom event bus).
|
|
26336
|
-
|
|
26337
|
-
|
|
26338
|
-
$el.addEventListener('close', function () {
|
|
26339
|
-
if ( $popin.isOpen ) {
|
|
26340
|
-
popinClose($popin.name);
|
|
26341
|
-
}
|
|
26342
|
-
});
|
|
26343
|
-
}
|
|
28585
|
+
// #gh76 §8 — shared with the loading shell, which may have bound it already
|
|
28586
|
+
bindNativeCloseSync($popin, $el);
|
|
26344
28587
|
|
|
26345
28588
|
$popin.isOpen = true;
|
|
28589
|
+
$popin.isLoading = false;
|
|
26346
28590
|
// so it can be forwarded to the handler who is listening
|
|
26347
28591
|
$popin.target = $el;
|
|
26348
28592
|
|
|
@@ -26481,7 +28725,7 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/loading-state', 'lib/merge'
|
|
|
26481
28725
|
function popinClose(name) {
|
|
26482
28726
|
|
|
26483
28727
|
var $popin = null;
|
|
26484
|
-
if ( typeof(name) == 'undefined' && /^true$/.test(this.isOpen) ) {
|
|
28728
|
+
if ( typeof(name) == 'undefined' && ( /^true$/.test(this.isOpen) || /^true$/.test(this.isLoading) ) ) {
|
|
26485
28729
|
name = this.name;
|
|
26486
28730
|
$popin = this;
|
|
26487
28731
|
} else {
|
|
@@ -26496,6 +28740,16 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/loading-state', 'lib/merge'
|
|
|
26496
28740
|
if ( !$popin && typeof(name) != 'undefined' ) {
|
|
26497
28741
|
throw new Error('Popin `'+name+'` not found !');
|
|
26498
28742
|
}
|
|
28743
|
+
// #gh76 §8 — a popin whose loading shell is showing closes too: the load is
|
|
28744
|
+
// cancelled and the shell torn down. The `!isOpen` return below would otherwise
|
|
28745
|
+
// leave a preOpen shell un-closeable for the whole round trip.
|
|
28746
|
+
if ( !$popin.isOpen && $popin.isLoading ) {
|
|
28747
|
+
if ( $popin.isRedirecting ) {
|
|
28748
|
+
return;
|
|
28749
|
+
}
|
|
28750
|
+
closeLoadingShell($popin);
|
|
28751
|
+
return;
|
|
28752
|
+
}
|
|
26499
28753
|
if (!$popin.isOpen)
|
|
26500
28754
|
return;
|
|
26501
28755
|
|
|
@@ -26593,8 +28847,9 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/loading-state', 'lib/merge'
|
|
|
26593
28847
|
id = $popin.id;
|
|
26594
28848
|
name = $popin.name;
|
|
26595
28849
|
|
|
26596
|
-
// Close first if still open
|
|
26597
|
-
|
|
28850
|
+
// Close first if still open — or still LOADING (#gh76 §8): the loading close
|
|
28851
|
+
// cancels the load and tears the shell down
|
|
28852
|
+
if ( $popin.isOpen || $popin.isLoading ) {
|
|
26598
28853
|
$popin.isRedirecting = false;
|
|
26599
28854
|
popinClose(name);
|
|
26600
28855
|
}
|
|
@@ -26760,6 +29015,7 @@ define('gina/popin', [ 'require', 'lib/domain', 'lib/loading-state', 'lib/merge'
|
|
|
26760
29015
|
instance.load = popinLoad;
|
|
26761
29016
|
instance.loadContent = popinLoadContent;
|
|
26762
29017
|
instance.getActivePopin = getActivePopin;
|
|
29018
|
+
instance.getPopinContaining = getPopinContaining;
|
|
26763
29019
|
instance.open = popinOpen;
|
|
26764
29020
|
instance.close = popinClose;
|
|
26765
29021
|
instance.destroy = popinDestroy;
|
|
@@ -27218,7 +29474,7 @@ if ( ( typeof(module) !== 'undefined' ) && module.exports ) {
|
|
|
27218
29474
|
// Publish as AMD module
|
|
27219
29475
|
define('gina/storage', ['helpers/dateFormat', 'helpers/prototypes'],function() { return StoragePlugin })
|
|
27220
29476
|
};
|
|
27221
|
-
define('gina/nav', [ 'require', 'lib/merge', 'lib/uuid', 'utils/events' ], function (require) {
|
|
29477
|
+
define('gina/nav', [ 'require', 'lib/merge', 'lib/uuid', 'utils/events', 'utils/dom' ], function (require) {
|
|
27222
29478
|
|
|
27223
29479
|
var merge = require('lib/merge');
|
|
27224
29480
|
var uuid = require('lib/uuid');
|
|
@@ -27254,12 +29510,15 @@ define('gina/nav', [ 'require', 'lib/merge', 'lib/uuid', 'utils/events' ], funct
|
|
|
27254
29510
|
* `X-Requested-With: XMLHttpRequest` (without it the render path
|
|
27255
29511
|
* re-wraps a layoutless body into a full `<html>` shell);
|
|
27256
29512
|
* 4. a genuine negotiated answer — 2xx, HTML, `Vary` advertising
|
|
27257
|
-
* `X-Gina-Navigate` — replaces the region's content,
|
|
27258
|
-
* fragment's src-bearing scripts the document does not already have
|
|
27259
|
-
* (inline scripts are NOT executed — same contract as popin content),
|
|
27260
|
-
* rebinds id-bearing forms through the live validator, closes any open
|
|
29513
|
+
* `X-Gina-Navigate` — replaces the region's content, closes any open
|
|
27261
29514
|
* popin (a full navigation would have unloaded it), pushes a history
|
|
27262
|
-
* entry, restores focus/scroll,
|
|
29515
|
+
* entry, restores focus/scroll, binds the new region through the shared
|
|
29516
|
+
* `bindRegion()` policy (utils/dom: the fragment's src-bearing scripts
|
|
29517
|
+
* the document does not already have are re-created — inline scripts
|
|
29518
|
+
* are NOT executed, same contract as popin content — the forms that opt
|
|
29519
|
+
* in are bound through the live validator (#B549: a bare form keeps its
|
|
29520
|
+
* native submit, as on the initial page), `data-gina-link` anchors
|
|
29521
|
+
* through the live link plugin), and fires `success`;
|
|
27263
29522
|
* 5. ANY other answer falls back to a full-page navigation: non-2xx,
|
|
27264
29523
|
* timeout, network error, an un-negotiated 2xx (the `Vary` check —
|
|
27265
29524
|
* a client/server matching disagreement degrades to a normal page
|
|
@@ -27322,11 +29581,6 @@ define('gina/nav', [ 'require', 'lib/merge', 'lib/uuid', 'utils/events' ], funct
|
|
|
27322
29581
|
// arrives from an async fetch and can land after `isFrameworkLoaded`.
|
|
27323
29582
|
var _compiled = null;
|
|
27324
29583
|
var _compiledFor = null;
|
|
27325
|
-
// Scripts present at activation + scripts nav injected across swaps
|
|
27326
|
-
// (the popin `parentScripts` model): a fragment's src-bearing script is
|
|
27327
|
-
// injected once, never re-executed on later swaps.
|
|
27328
|
-
var _parentScripts = [];
|
|
27329
|
-
var _injectedScripts = [];
|
|
27330
29584
|
// pathname+search last rendered — popstate events that do not change it
|
|
27331
29585
|
// are hash-only moves and are left to the browser.
|
|
27332
29586
|
var _currentUrl = null;
|
|
@@ -27533,75 +29787,6 @@ define('gina/nav', [ 'require', 'lib/merge', 'lib/uuid', 'utils/events' ], funct
|
|
|
27533
29787
|
return null;
|
|
27534
29788
|
};
|
|
27535
29789
|
|
|
27536
|
-
/**
|
|
27537
|
-
* Snapshots the src of every script present in the document at
|
|
27538
|
-
* activation — the popin `parentScripts` model. Scripts already loaded
|
|
27539
|
-
* with the page are never re-injected from a fragment.
|
|
27540
|
-
*
|
|
27541
|
-
* @inner
|
|
27542
|
-
* @private
|
|
27543
|
-
*/
|
|
27544
|
-
var snapshotParentScripts = function() {
|
|
27545
|
-
var scripts = document.getElementsByTagName('script');
|
|
27546
|
-
for (var i = 0, len = scripts.length; i < len; ++i) {
|
|
27547
|
-
if ( scripts[i].src ) {
|
|
27548
|
-
_parentScripts.push(scripts[i].src);
|
|
27549
|
-
}
|
|
27550
|
-
}
|
|
27551
|
-
};
|
|
27552
|
-
|
|
27553
|
-
/**
|
|
27554
|
-
* Re-injects the fragment's src-bearing scripts the document does not
|
|
27555
|
-
* already have (mirrors popinOpen's external-resource loading). Inline
|
|
27556
|
-
* scripts are NOT executed — innerHTML semantics, the same contract
|
|
27557
|
-
* popin content has always had. Injected scripts persist across later
|
|
27558
|
-
* swaps (a page never "closes"); the registry keeps them single-shot.
|
|
27559
|
-
*
|
|
27560
|
-
* @inner
|
|
27561
|
-
* @private
|
|
27562
|
-
* @param {object} $target - the swapped region
|
|
27563
|
-
*/
|
|
27564
|
-
var injectFragmentScripts = function($target) {
|
|
27565
|
-
var scripts = $target.getElementsByTagName('script');
|
|
27566
|
-
for (var i = 0, len = scripts.length; i < len; ++i) {
|
|
27567
|
-
var src = scripts[i].src; // resolved absolute URL
|
|
27568
|
-
if ( !src ) continue;
|
|
27569
|
-
if ( _parentScripts.indexOf(src) > -1 || _injectedScripts.indexOf(src) > -1 ) continue;
|
|
27570
|
-
var s = document.createElement('script');
|
|
27571
|
-
s.src = src;
|
|
27572
|
-
document.head.appendChild(s);
|
|
27573
|
-
_injectedScripts.push(src);
|
|
27574
|
-
}
|
|
27575
|
-
};
|
|
27576
|
-
|
|
27577
|
-
/**
|
|
27578
|
-
* Rebinds id-bearing forms of the swapped region through the live
|
|
27579
|
-
* validator (`gina.validator.validateFormById` — the popinBind model).
|
|
27580
|
-
* Id-less forms are skipped: validation rules are keyed by form id, so
|
|
27581
|
-
* no rule can target them. A missing/ruleless validator is a no-op.
|
|
27582
|
-
*
|
|
27583
|
-
* @inner
|
|
27584
|
-
* @private
|
|
27585
|
-
* @param {object} $target - the swapped region
|
|
27586
|
-
*/
|
|
27587
|
-
var rebindValidator = function($target) {
|
|
27588
|
-
try {
|
|
27589
|
-
if ( !gina.hasValidator || !gina.validator || typeof(gina.validator.validateFormById) != 'function' ) {
|
|
27590
|
-
return;
|
|
27591
|
-
}
|
|
27592
|
-
var $forms = $target.getElementsByTagName('form');
|
|
27593
|
-
for (var i = 0, len = $forms.length; i < len; ++i) {
|
|
27594
|
-
var _id = $forms[i].getAttribute('id');
|
|
27595
|
-
if ( !_id ) continue;
|
|
27596
|
-
try {
|
|
27597
|
-
gina.validator.validateFormById(_id);
|
|
27598
|
-
} catch (bindErr) {
|
|
27599
|
-
console.warn('[gina][nav] validator rebind skipped for `'+ _id +'`: '+ (bindErr.message || bindErr));
|
|
27600
|
-
}
|
|
27601
|
-
}
|
|
27602
|
-
} catch (validatorErr) {}
|
|
27603
|
-
};
|
|
27604
|
-
|
|
27605
29790
|
/**
|
|
27606
29791
|
* Closes the active popin when one is open: a full-page navigation
|
|
27607
29792
|
* would have unloaded it, and after a fragment swap it would sit over
|
|
@@ -27639,10 +29824,11 @@ define('gina/nav', [ 'require', 'lib/merge', 'lib/uuid', 'utils/events' ], funct
|
|
|
27639
29824
|
};
|
|
27640
29825
|
|
|
27641
29826
|
/**
|
|
27642
|
-
* Applies a fetched fragment: swaps the region content,
|
|
27643
|
-
*
|
|
27644
|
-
*
|
|
27645
|
-
*
|
|
29827
|
+
* Applies a fetched fragment: swaps the region content, applies the
|
|
29828
|
+
* optional `[data-gina-nav-title]` document title, pushes/updates
|
|
29829
|
+
* history, restores scroll and focus, closes any open popin, binds the
|
|
29830
|
+
* new region (scripts, opted-in forms, links — the shared `bindRegion()`
|
|
29831
|
+
* policy) and fires `success`.
|
|
27646
29832
|
*
|
|
27647
29833
|
* @inner
|
|
27648
29834
|
* @private
|
|
@@ -27655,8 +29841,6 @@ define('gina/nav', [ 'require', 'lib/merge', 'lib/uuid', 'utils/events' ], funct
|
|
|
27655
29841
|
var $target = instance.target;
|
|
27656
29842
|
$target.innerHTML = ( typeof(html) == 'string' ) ? html.trim() : '';
|
|
27657
29843
|
|
|
27658
|
-
injectFragmentScripts($target);
|
|
27659
|
-
|
|
27660
29844
|
// Optional per-page document title: the first element of the
|
|
27661
29845
|
// fragment carrying `data-gina-nav-title` (the fragment is
|
|
27662
29846
|
// layoutless, so it cannot carry a <title> of its own).
|
|
@@ -27702,7 +29886,11 @@ define('gina/nav', [ 'require', 'lib/merge', 'lib/uuid', 'utils/events' ], funct
|
|
|
27702
29886
|
}
|
|
27703
29887
|
|
|
27704
29888
|
closeActivePopin();
|
|
27705
|
-
|
|
29889
|
+
// #gh76 — the one region-binding policy nav shares with the validator's
|
|
29890
|
+
// form-answer swaps (utils/dom). Forms follow the #B549 opt-in gate here
|
|
29891
|
+
// too: a bare id-bearing form in a fragment used to be bound — and its
|
|
29892
|
+
// submit turned into an XHR — by a rebind that bypassed the gate.
|
|
29893
|
+
bindRegion($target);
|
|
27706
29894
|
|
|
27707
29895
|
instance.eventData.success = { 'url': url, 'target': $target, 'isPopState': isPopState };
|
|
27708
29896
|
triggerEvent(gina, instance.target, 'success.' + instance.id, instance.eventData.success);
|
|
@@ -27954,7 +30142,6 @@ define('gina/nav', [ 'require', 'lib/merge', 'lib/uuid', 'utils/events' ], funct
|
|
|
27954
30142
|
|
|
27955
30143
|
instance.target = $marker;
|
|
27956
30144
|
_currentUrl = window.location.pathname + window.location.search;
|
|
27957
|
-
snapshotParentScripts();
|
|
27958
30145
|
try {
|
|
27959
30146
|
if ( window.history && 'scrollRestoration' in window.history ) {
|
|
27960
30147
|
window.history.scrollRestoration = 'manual';
|