mirror of
https://github.com/pretix/pretix.git
synced 2026-05-09 15:54:03 +00:00
first draft migrating widget to vue3/vite
This commit is contained in:
871
package-lock.json
generated
871
package-lock.json
generated
@@ -16,12 +16,15 @@
|
|||||||
"@stylistic/eslint-plugin": "^5.7.1",
|
"@stylistic/eslint-plugin": "^5.7.1",
|
||||||
"@types/jquery": "^3.5.33",
|
"@types/jquery": "^3.5.33",
|
||||||
"@types/moment": "^2.11.29",
|
"@types/moment": "^2.11.29",
|
||||||
|
"@types/node": "^25.2.2",
|
||||||
"@vitejs/plugin-vue": "^6.0.4",
|
"@vitejs/plugin-vue": "^6.0.4",
|
||||||
"@vue/eslint-config-typescript": "^14.6.0",
|
"@vue/eslint-config-typescript": "^14.6.0",
|
||||||
|
"@vue/language-plugin-pug": "^3.2.4",
|
||||||
"eslint": "^9.39.2",
|
"eslint": "^9.39.2",
|
||||||
"eslint-plugin-vue": "^10.7.0",
|
"eslint-plugin-vue": "^10.7.0",
|
||||||
"eslint-plugin-vue-pug": "^1.0.0-alpha.5",
|
"eslint-plugin-vue-pug": "^1.0.0-alpha.5",
|
||||||
"pug": "^3.0.3",
|
"pug": "^3.0.3",
|
||||||
|
"sass-embedded": "^1.97.3",
|
||||||
"stylus": "^0.64.0",
|
"stylus": "^0.64.0",
|
||||||
"typescript-eslint": "^8.54.0",
|
"typescript-eslint": "^8.54.0",
|
||||||
"vite": "^7.3.1"
|
"vite": "^7.3.1"
|
||||||
@@ -80,6 +83,13 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@bufbuild/protobuf": {
|
||||||
|
"version": "2.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz",
|
||||||
|
"integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "(Apache-2.0 AND BSD-3-Clause)"
|
||||||
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
|
||||||
@@ -828,6 +838,316 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@parcel/watcher": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"detect-libc": "^2.0.3",
|
||||||
|
"is-glob": "^4.0.3",
|
||||||
|
"node-addon-api": "^7.0.0",
|
||||||
|
"picomatch": "^4.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@parcel/watcher-android-arm64": "2.5.6",
|
||||||
|
"@parcel/watcher-darwin-arm64": "2.5.6",
|
||||||
|
"@parcel/watcher-darwin-x64": "2.5.6",
|
||||||
|
"@parcel/watcher-freebsd-x64": "2.5.6",
|
||||||
|
"@parcel/watcher-linux-arm-glibc": "2.5.6",
|
||||||
|
"@parcel/watcher-linux-arm-musl": "2.5.6",
|
||||||
|
"@parcel/watcher-linux-arm64-glibc": "2.5.6",
|
||||||
|
"@parcel/watcher-linux-arm64-musl": "2.5.6",
|
||||||
|
"@parcel/watcher-linux-x64-glibc": "2.5.6",
|
||||||
|
"@parcel/watcher-linux-x64-musl": "2.5.6",
|
||||||
|
"@parcel/watcher-win32-arm64": "2.5.6",
|
||||||
|
"@parcel/watcher-win32-ia32": "2.5.6",
|
||||||
|
"@parcel/watcher-win32-x64": "2.5.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-android-arm64": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-darwin-arm64": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-darwin-x64": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-freebsd-x64": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-linux-arm-glibc": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-linux-arm-musl": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-linux-arm64-glibc": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-linux-arm64-musl": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-linux-x64-glibc": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-linux-x64-musl": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-win32-arm64": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-win32-ia32": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-win32-x64": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@pkgjs/parseargs": {
|
"node_modules/@pkgjs/parseargs": {
|
||||||
"version": "0.11.0",
|
"version": "0.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||||
@@ -1249,6 +1569,17 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "25.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.2.tgz",
|
||||||
|
"integrity": "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~7.16.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/sizzle": {
|
"node_modules/@types/sizzle": {
|
||||||
"version": "2.3.10",
|
"version": "2.3.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.10.tgz",
|
||||||
@@ -1504,6 +1835,13 @@
|
|||||||
"vue": "^3.2.25"
|
"vue": "^3.2.25"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@volar/source-map": {
|
||||||
|
"version": "2.4.27",
|
||||||
|
"resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.27.tgz",
|
||||||
|
"integrity": "sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@vue/compiler-core": {
|
"node_modules/@vue/compiler-core": {
|
||||||
"version": "3.5.27",
|
"version": "3.5.27",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.27.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.27.tgz",
|
||||||
@@ -1580,6 +1918,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@vue/language-plugin-pug": {
|
||||||
|
"version": "3.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/language-plugin-pug/-/language-plugin-pug-3.2.4.tgz",
|
||||||
|
"integrity": "sha512-DpvubnPdc8e8NHKpuipf9hq7v/bfrwb7QY0ltPRfWtIR7uNXIMoQ97kndrRNFjnZvJJM6bFjB+0HEHYJ07p/2Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@volar/source-map": "2.4.27",
|
||||||
|
"muggle-string": "^0.4.1",
|
||||||
|
"pug-lexer": "^5.0.1",
|
||||||
|
"pug-parser": "^6.0.0",
|
||||||
|
"vscode-languageserver-textdocument": "^1.0.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vue/reactivity": {
|
"node_modules/@vue/reactivity": {
|
||||||
"version": "3.5.27",
|
"version": "3.5.27",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.27.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.27.tgz",
|
||||||
@@ -1839,6 +2191,23 @@
|
|||||||
"is-regex": "^1.0.3"
|
"is-regex": "^1.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/chokidar": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"readdirp": "^4.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14.16.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
@@ -1859,6 +2228,13 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/colorjs.io": {
|
||||||
|
"version": "0.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz",
|
||||||
|
"integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
@@ -1936,6 +2312,17 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/detect-libc": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/doctypes": {
|
"node_modules/doctypes": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz",
|
||||||
@@ -2649,6 +3036,13 @@
|
|||||||
"node": ">= 4"
|
"node": ">= 4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/immutable": {
|
||||||
|
"version": "5.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz",
|
||||||
|
"integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/import-fresh": {
|
"node_modules/import-fresh": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||||
@@ -3003,6 +3397,13 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/muggle-string": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.11",
|
"version": "3.3.11",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||||
@@ -3028,6 +3429,14 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/node-addon-api": {
|
||||||
|
"version": "7.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||||
|
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/nth-check": {
|
"node_modules/nth-check": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||||
@@ -3413,6 +3822,21 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/readdirp": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14.18.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.11",
|
"version": "1.22.11",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
||||||
@@ -3524,6 +3948,402 @@
|
|||||||
"queue-microtask": "^1.2.2"
|
"queue-microtask": "^1.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rxjs": {
|
||||||
|
"version": "7.8.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||||
|
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass": {
|
||||||
|
"version": "1.97.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass/-/sass-1.97.3.tgz",
|
||||||
|
"integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"chokidar": "^4.0.0",
|
||||||
|
"immutable": "^5.0.2",
|
||||||
|
"source-map-js": ">=0.6.2 <2.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"sass": "sass.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@parcel/watcher": "^2.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded": {
|
||||||
|
"version": "1.97.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.97.3.tgz",
|
||||||
|
"integrity": "sha512-eKzFy13Nk+IRHhlAwP3sfuv+PzOrvzUkwJK2hdoCKYcWGSdmwFpeGpWmyewdw8EgBnsKaSBtgf/0b2K635ecSA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@bufbuild/protobuf": "^2.5.0",
|
||||||
|
"colorjs.io": "^0.5.0",
|
||||||
|
"immutable": "^5.0.2",
|
||||||
|
"rxjs": "^7.4.0",
|
||||||
|
"supports-color": "^8.1.1",
|
||||||
|
"sync-child-process": "^1.0.2",
|
||||||
|
"varint": "^6.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"sass": "dist/bin/sass.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"sass-embedded-all-unknown": "1.97.3",
|
||||||
|
"sass-embedded-android-arm": "1.97.3",
|
||||||
|
"sass-embedded-android-arm64": "1.97.3",
|
||||||
|
"sass-embedded-android-riscv64": "1.97.3",
|
||||||
|
"sass-embedded-android-x64": "1.97.3",
|
||||||
|
"sass-embedded-darwin-arm64": "1.97.3",
|
||||||
|
"sass-embedded-darwin-x64": "1.97.3",
|
||||||
|
"sass-embedded-linux-arm": "1.97.3",
|
||||||
|
"sass-embedded-linux-arm64": "1.97.3",
|
||||||
|
"sass-embedded-linux-musl-arm": "1.97.3",
|
||||||
|
"sass-embedded-linux-musl-arm64": "1.97.3",
|
||||||
|
"sass-embedded-linux-musl-riscv64": "1.97.3",
|
||||||
|
"sass-embedded-linux-musl-x64": "1.97.3",
|
||||||
|
"sass-embedded-linux-riscv64": "1.97.3",
|
||||||
|
"sass-embedded-linux-x64": "1.97.3",
|
||||||
|
"sass-embedded-unknown-all": "1.97.3",
|
||||||
|
"sass-embedded-win32-arm64": "1.97.3",
|
||||||
|
"sass-embedded-win32-x64": "1.97.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-all-unknown": {
|
||||||
|
"version": "1.97.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.97.3.tgz",
|
||||||
|
"integrity": "sha512-t6N46NlPuXiY3rlmG6/+1nwebOBOaLFOOVqNQOC2cJhghOD4hh2kHNQQTorCsbY9S1Kir2la1/XLBwOJfui0xg==",
|
||||||
|
"cpu": [
|
||||||
|
"!arm",
|
||||||
|
"!arm64",
|
||||||
|
"!riscv64",
|
||||||
|
"!x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"sass": "1.97.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-android-arm": {
|
||||||
|
"version": "1.97.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.97.3.tgz",
|
||||||
|
"integrity": "sha512-cRTtf/KV/q0nzGZoUzVkeIVVFv3L/tS1w4WnlHapphsjTXF/duTxI8JOU1c/9GhRPiMdfeXH7vYNcMmtjwX7jg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-android-arm64": {
|
||||||
|
"version": "1.97.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.97.3.tgz",
|
||||||
|
"integrity": "sha512-aiZ6iqiHsUsaDx0EFbbmmA0QgxicSxVVN3lnJJ0f1RStY0DthUkquGT5RJ4TPdaZ6ebeJWkboV4bra+CP766eA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-android-riscv64": {
|
||||||
|
"version": "1.97.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.97.3.tgz",
|
||||||
|
"integrity": "sha512-zVEDgl9JJodofGHobaM/q6pNETG69uuBIGQHRo789jloESxxZe82lI3AWJQuPmYCOG5ElfRthqgv89h3gTeLYA==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-android-x64": {
|
||||||
|
"version": "1.97.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.97.3.tgz",
|
||||||
|
"integrity": "sha512-3ke0le7ZKepyXn/dKKspYkpBC0zUk/BMciyP5ajQUDy4qJwobd8zXdAq6kOkdiMB+d9UFJOmEkvgFJHl3lqwcw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-darwin-arm64": {
|
||||||
|
"version": "1.97.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.97.3.tgz",
|
||||||
|
"integrity": "sha512-fuqMTqO4gbOmA/kC5b9y9xxNYw6zDEyfOtMgabS7Mz93wimSk2M1quQaTJnL98Mkcsl2j+7shNHxIS/qpcIDDA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-darwin-x64": {
|
||||||
|
"version": "1.97.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.97.3.tgz",
|
||||||
|
"integrity": "sha512-b/2RBs/2bZpP8lMkyZ0Px0vkVkT8uBd0YXpOwK7iOwYkAT8SsO4+WdVwErsqC65vI5e1e5p1bb20tuwsoQBMVA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-linux-arm": {
|
||||||
|
"version": "1.97.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.97.3.tgz",
|
||||||
|
"integrity": "sha512-2lPQ7HQQg4CKsH18FTsj2hbw5GJa6sBQgDsls+cV7buXlHjqF8iTKhAQViT6nrpLK/e8nFCoaRgSqEC8xMnXuA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-linux-arm64": {
|
||||||
|
"version": "1.97.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.97.3.tgz",
|
||||||
|
"integrity": "sha512-IP1+2otCT3DuV46ooxPaOKV1oL5rLjteRzf8ldZtfIEcwhSgSsHgA71CbjYgLEwMY9h4jeal8Jfv3QnedPvSjg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-linux-musl-arm": {
|
||||||
|
"version": "1.97.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.97.3.tgz",
|
||||||
|
"integrity": "sha512-cBTMU68X2opBpoYsSZnI321gnoaiMBEtc+60CKCclN6PCL3W3uXm8g4TLoil1hDD6mqU9YYNlVG6sJ+ZNef6Lg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-linux-musl-arm64": {
|
||||||
|
"version": "1.97.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.97.3.tgz",
|
||||||
|
"integrity": "sha512-Lij0SdZCsr+mNRSyDZ7XtJpXEITrYsaGbOTz5e6uFLJ9bmzUbV7M8BXz2/cA7bhfpRPT7/lwRKPdV4+aR9Ozcw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-linux-musl-riscv64": {
|
||||||
|
"version": "1.97.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.97.3.tgz",
|
||||||
|
"integrity": "sha512-sBeLFIzMGshR4WmHAD4oIM7WJVkSoCIEwutzptFtGlSlwfNiijULp+J5hA2KteGvI6Gji35apR5aWj66wEn/iA==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-linux-musl-x64": {
|
||||||
|
"version": "1.97.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.97.3.tgz",
|
||||||
|
"integrity": "sha512-/oWJ+OVrDg7ADDQxRLC/4g1+Nsz1g4mkYS2t6XmyMJKFTFK50FVI2t5sOdFH+zmMp+nXHKM036W94y9m4jjEcw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-linux-riscv64": {
|
||||||
|
"version": "1.97.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.97.3.tgz",
|
||||||
|
"integrity": "sha512-l3IfySApLVYdNx0Kjm7Zehte1CDPZVcldma3dZt+TfzvlAEerM6YDgsk5XEj3L8eHBCgHgF4A0MJspHEo2WNfA==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-linux-x64": {
|
||||||
|
"version": "1.97.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.97.3.tgz",
|
||||||
|
"integrity": "sha512-Kwqwc/jSSlcpRjULAOVbndqEy2GBzo6OBmmuBVINWUaJLJ8Kczz3vIsDUWLfWz/kTEw9FHBSiL0WCtYLVAXSLg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-unknown-all": {
|
||||||
|
"version": "1.97.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.97.3.tgz",
|
||||||
|
"integrity": "sha512-/GHajyYJmvb0IABUQHbVHf1nuHPtIDo/ClMZ81IDr59wT5CNcMe7/dMNujXwWugtQVGI5UGmqXWZQCeoGnct8Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"!android",
|
||||||
|
"!darwin",
|
||||||
|
"!linux",
|
||||||
|
"!win32"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"sass": "1.97.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-win32-arm64": {
|
||||||
|
"version": "1.97.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.97.3.tgz",
|
||||||
|
"integrity": "sha512-RDGtRS1GVvQfMGAmVXNxYiUOvPzn9oO1zYB/XUM9fudDRnieYTcUytpNTQZLs6Y1KfJxgt5Y+giRceC92fT8Uw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded-win32-x64": {
|
||||||
|
"version": "1.97.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.97.3.tgz",
|
||||||
|
"integrity": "sha512-SFRa2lED9UEwV6vIGeBXeBOLKF+rowF3WmNfb/BzhxmdAsKofCXrJ8ePW7OcDVrvNEbTOGwhsReIsF5sH8fVaw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-embedded/node_modules/supports-color": {
|
||||||
|
"version": "8.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||||
|
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"has-flag": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/sax": {
|
"node_modules/sax": {
|
||||||
"version": "1.4.4",
|
"version": "1.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz",
|
||||||
@@ -3769,6 +4589,29 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sync-child-process": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"sync-message-port": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sync-message-port": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-gAQ9qrUN/UCypHtGFbbe7Rc/f9bzO88IwrG8TDo/aMKAApKyD6E3W4Cm0EfhfBb6Z6SKt59tTCTfD+n1xmAvMg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tinyglobby": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.15",
|
"version": "0.2.15",
|
||||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||||
@@ -3819,6 +4662,13 @@
|
|||||||
"typescript": ">=4.8.4"
|
"typescript": ">=4.8.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/type-check": {
|
"node_modules/type-check": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||||
@@ -3871,6 +4721,13 @@
|
|||||||
"typescript": ">=4.8.4 <6.0.0"
|
"typescript": ">=4.8.4 <6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "7.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||||
|
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/uri-js": {
|
"node_modules/uri-js": {
|
||||||
"version": "4.4.1",
|
"version": "4.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||||
@@ -3888,6 +4745,13 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/varint": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "7.3.1",
|
"version": "7.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
|
||||||
@@ -3974,6 +4838,13 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vscode-languageserver-textdocument": {
|
||||||
|
"version": "1.0.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz",
|
||||||
|
"integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/vue": {
|
"node_modules/vue": {
|
||||||
"version": "3.5.27",
|
"version": "3.5.27",
|
||||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.27.tgz",
|
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.27.tgz",
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
"dev:widget": "vite src/pretix/static/pretixpresale/widget",
|
||||||
|
"build:widget": "vite build --config src/pretix/static/pretixpresale/widget/vite.config.ts",
|
||||||
"lint:eslint": "eslint . --ext .js,.ts,.vue",
|
"lint:eslint": "eslint . --ext .js,.ts,.vue",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
@@ -31,12 +33,15 @@
|
|||||||
"@stylistic/eslint-plugin": "^5.7.1",
|
"@stylistic/eslint-plugin": "^5.7.1",
|
||||||
"@types/jquery": "^3.5.33",
|
"@types/jquery": "^3.5.33",
|
||||||
"@types/moment": "^2.11.29",
|
"@types/moment": "^2.11.29",
|
||||||
|
"@types/node": "^25.2.2",
|
||||||
"@vitejs/plugin-vue": "^6.0.4",
|
"@vitejs/plugin-vue": "^6.0.4",
|
||||||
"@vue/eslint-config-typescript": "^14.6.0",
|
"@vue/eslint-config-typescript": "^14.6.0",
|
||||||
|
"@vue/language-plugin-pug": "^3.2.4",
|
||||||
"eslint": "^9.39.2",
|
"eslint": "^9.39.2",
|
||||||
"eslint-plugin-vue": "^10.7.0",
|
"eslint-plugin-vue": "^10.7.0",
|
||||||
"eslint-plugin-vue-pug": "^1.0.0-alpha.5",
|
"eslint-plugin-vue-pug": "^1.0.0-alpha.5",
|
||||||
"pug": "^3.0.3",
|
"pug": "^3.0.3",
|
||||||
|
"sass-embedded": "^1.97.3",
|
||||||
"stylus": "^0.64.0",
|
"stylus": "^0.64.0",
|
||||||
"typescript-eslint": "^8.54.0",
|
"typescript-eslint": "^8.54.0",
|
||||||
"vite": "^7.3.1"
|
"vite": "^7.3.1"
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ class SecurityMiddleware(MiddlewareMixin):
|
|||||||
'script-src': ["{static}"] + (["http://localhost:5173", "ws://localhost:5173"] if settings.VITE_DEV_MODE else []),
|
'script-src': ["{static}"] + (["http://localhost:5173", "ws://localhost:5173"] if settings.VITE_DEV_MODE else []),
|
||||||
'object-src': ["'none'"],
|
'object-src': ["'none'"],
|
||||||
'frame-src': ['{static}'],
|
'frame-src': ['{static}'],
|
||||||
'style-src': ["{static}", "{media}"],
|
'style-src': ["{static}", "{media}"]+ (["unsafe-inline"] if settings.VITE_DEV_MODE else []),
|
||||||
'connect-src': ["{dynamic}", "{media}"] + (["http://localhost:5173", "ws://localhost:5173"] if settings.VITE_DEV_MODE else []),
|
'connect-src': ["{dynamic}", "{media}"] + (["http://localhost:5173", "ws://localhost:5173"] if settings.VITE_DEV_MODE else []),
|
||||||
'img-src': ["{static}", "{media}", "data:"] + img_src,
|
'img-src': ["{static}", "{media}", "data:"] + img_src,
|
||||||
'font-src': ["{static}"] + list(font_src),
|
'font-src': ["{static}"] + list(font_src),
|
||||||
|
|||||||
2
src/pretix/static/pretixpresale/widget/TODOS.md
Normal file
2
src/pretix/static/pretixpresale/widget/TODOS.md
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
- modernize the sometimes native form submitting?
|
||||||
|
- destructure props?
|
||||||
15
src/pretix/static/pretixpresale/widget/index.html
Normal file
15
src/pretix/static/pretixpresale/widget/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Pretix Widget</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="http://localhost:8000/testorg/testevent/widget/v2.css" crossorigin>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<pretix-widget event="http://localhost:8000/testorg/testevent/"></pretix-widget>
|
||||||
|
<!-- <script type="text/javascript" src="http://localhost:8000/widget/v2.en.js" async crossorigin></script> -->
|
||||||
|
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
85
src/pretix/static/pretixpresale/widget/src/api.ts
Normal file
85
src/pretix/static/pretixpresale/widget/src/api.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import type { Category, DayEntry, EventEntry, MetaFilterField } from '~/types'
|
||||||
|
|
||||||
|
export class ApiError extends Error {
|
||||||
|
status: number
|
||||||
|
responseUrl: string
|
||||||
|
|
||||||
|
constructor (status: number, responseUrl: string) {
|
||||||
|
super(`HTTP ${status}`)
|
||||||
|
this.status = status
|
||||||
|
this.responseUrl = responseUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Product list ---
|
||||||
|
|
||||||
|
export interface ProductListResponse {
|
||||||
|
target_url?: string
|
||||||
|
subevent?: string | number
|
||||||
|
name?: string
|
||||||
|
frontpage_text?: string
|
||||||
|
date_range?: string
|
||||||
|
location?: string
|
||||||
|
items_by_category?: Category[]
|
||||||
|
currency?: string
|
||||||
|
display_net_prices?: boolean
|
||||||
|
voucher_explanation_text?: string
|
||||||
|
error?: string
|
||||||
|
display_add_to_cart?: boolean
|
||||||
|
waiting_list_enabled?: boolean
|
||||||
|
show_variations_expanded?: boolean
|
||||||
|
cart_exists?: boolean
|
||||||
|
vouchers_exist?: boolean
|
||||||
|
has_seating_plan?: boolean
|
||||||
|
has_seating_plan_waitinglist?: boolean
|
||||||
|
itemnum?: number
|
||||||
|
poweredby?: string
|
||||||
|
events?: EventEntry[]
|
||||||
|
has_more_events?: boolean
|
||||||
|
meta_filter_fields?: MetaFilterField[]
|
||||||
|
weeks?: DayEntry[][]
|
||||||
|
date?: string
|
||||||
|
days?: DayEntry[]
|
||||||
|
week?: [number, number]
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchProductList (url: string) {
|
||||||
|
const response = await fetch(url)
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new ApiError(response.status, response.url)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
data: await response.json() as ProductListResponse,
|
||||||
|
responseUrl: response.url,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CartResponse {
|
||||||
|
redirect?: string
|
||||||
|
cart_id?: string
|
||||||
|
success?: boolean
|
||||||
|
message?: string
|
||||||
|
has_cart?: boolean
|
||||||
|
async_id?: string
|
||||||
|
check_url?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function submitCart (endpoint: string, formData: FormData) {
|
||||||
|
const response = await fetch(endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
body: new URLSearchParams(formData as any).toString(),
|
||||||
|
})
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new ApiError(response.status, response.url)
|
||||||
|
}
|
||||||
|
return await response.json() as CartResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkAsyncTask (url: string) {
|
||||||
|
const response = await fetch(url)
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new ApiError(response.status, response.url)
|
||||||
|
}
|
||||||
|
return await response.json() as CartResponse
|
||||||
|
}
|
||||||
73
src/pretix/static/pretixpresale/widget/src/button.ts
Normal file
73
src/pretix/static/pretixpresale/widget/src/button.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { createApp, type App } from 'vue'
|
||||||
|
import ButtonComponent from '~/components/Button.vue'
|
||||||
|
import { createWidgetStore, StoreKey } from '~/sharedStore'
|
||||||
|
import { makeid } from '~/utils'
|
||||||
|
import type { WidgetData } from '~/types'
|
||||||
|
|
||||||
|
export function createButtonInstance (element: Element, htmlId?: string): App {
|
||||||
|
let targetUrl = element.attributes.event.value
|
||||||
|
if (!targetUrl.match(/\/$/)) {
|
||||||
|
targetUrl += '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
const widgetData: WidgetData = JSON.parse(JSON.stringify(window.PretixWidget.widget_data))
|
||||||
|
|
||||||
|
for (const attr of Array.from(element.attributes)) {
|
||||||
|
if (attr.name.match(/^data-.*$/)) {
|
||||||
|
widgetData[attr.name.replace(/^data-/, '')] = attr.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawItems = element.attributes.items?.value || ''
|
||||||
|
|
||||||
|
// Parse items string (format: "item_1=2,item_3=1")
|
||||||
|
const buttonItems: { item: string; count: string }[] = []
|
||||||
|
for (const itemStr of rawItems.split(',')) {
|
||||||
|
if (itemStr.includes('=')) {
|
||||||
|
const [item, count] = itemStr.split('=')
|
||||||
|
buttonItems.push({ item, count })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = createWidgetStore({
|
||||||
|
targetUrl,
|
||||||
|
voucher: element.attributes.voucher?.value || null,
|
||||||
|
subevent: element.attributes.subevent?.value || null,
|
||||||
|
skipSsl: 'skip-ssl-check' in element.attributes,
|
||||||
|
disableIframe: 'disable-iframe' in element.attributes,
|
||||||
|
widgetData,
|
||||||
|
htmlId: htmlId || element.id || makeid(16),
|
||||||
|
isButton: true,
|
||||||
|
buttonItems,
|
||||||
|
buttonText: element.innerHTML
|
||||||
|
})
|
||||||
|
|
||||||
|
const observer = new MutationObserver((mutationList) => {
|
||||||
|
for (const mutation of mutationList) {
|
||||||
|
if (mutation.type === 'attributes' && mutation.attributeName?.startsWith('data-')) {
|
||||||
|
const attrName = mutation.attributeName.substring(5)
|
||||||
|
const attrValue = (mutation.target as Element).getAttribute(mutation.attributeName)
|
||||||
|
if (attrValue !== null) {
|
||||||
|
store.widgetData[attrName] = attrValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO I don't think we need this anymore in vue3
|
||||||
|
// if (element.tagName !== 'pretix-button') {
|
||||||
|
// element.innerHTML = '<pretix-button>' + element.innerHTML + '</pretix-button>'
|
||||||
|
// // Vue does not replace the container, so watch container as well
|
||||||
|
// observer.observe(element, observerOptions)
|
||||||
|
// }
|
||||||
|
|
||||||
|
const app = createApp(ButtonComponent)
|
||||||
|
app.provide(StoreKey, store)
|
||||||
|
app.config.errorHandler = (error, _vm, info) => {
|
||||||
|
console.error('[pretix-button]', info, error)
|
||||||
|
}
|
||||||
|
app.mount(element)
|
||||||
|
observer.observe(element, { attributes: true })
|
||||||
|
|
||||||
|
return app
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, inject, onMounted } from 'vue'
|
||||||
|
import type { Item, Variation } from '~/types'
|
||||||
|
import { StoreKey, globalWidgetId } from '~/sharedStore'
|
||||||
|
import { STRINGS } from '~/i18n'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
item: Item
|
||||||
|
variation?: Variation
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const store = inject(StoreKey)!
|
||||||
|
const quantity = ref<HTMLInputElement>()
|
||||||
|
|
||||||
|
const avail = computed(() => props.item.has_variations ? props.variation.avail : props.item.avail)
|
||||||
|
|
||||||
|
const orderMax = computed(() => props.item.has_variations ? props.variation.order_max : props.item.order_max)
|
||||||
|
|
||||||
|
const inputName = computed(() => {
|
||||||
|
if (props.item.has_variations) {
|
||||||
|
return `variation_${props.item.id}_${props.variation.id}`
|
||||||
|
}
|
||||||
|
return `item_${props.item.id}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const unavailabilityReasonMessage = computed(() => {
|
||||||
|
const reason = props.item.current_unavailability_reason || props.variation?.current_unavailability_reason
|
||||||
|
if (reason) {
|
||||||
|
return STRINGS[`unavailable_${reason}`] || reason
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const voucherJumpLink = computed(() => `#${store.htmlId}-voucher-input`)
|
||||||
|
|
||||||
|
const ariaLabelledby = computed(() => `${store.htmlId}-item-label-${props.item.id}`)
|
||||||
|
|
||||||
|
const decLabel = computed(() => {
|
||||||
|
// TODO
|
||||||
|
const name = props.item.has_variations ? props.variation.value : props.item.name
|
||||||
|
return `- ${name}: ${STRINGS.quantity_dec}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const incLabel = computed(() => {
|
||||||
|
const name = props.item.has_variations ? props.variation.value : props.item.name
|
||||||
|
return `+ ${name}: ${STRINGS.quantity_inc}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const labelSelectItem = computed(() => {
|
||||||
|
if (props.item.has_variations) return STRINGS.select_variant.replace('%s', props.variation.value)
|
||||||
|
return STRINGS.select_item.replace('%s', props.item.name)
|
||||||
|
})
|
||||||
|
|
||||||
|
const waitingListShow = computed(() => avail.value[0] < 100 && store.waitingListEnabled && props.item.allow_waitinglist)
|
||||||
|
|
||||||
|
const waitingListUrl = computed(() => {
|
||||||
|
let u = `${store.targetUrl}w/${globalWidgetId}/waitinglist/?locale=${LANG}&item=${props.item.id}`
|
||||||
|
if (props.item.has_variations && props.variation) {
|
||||||
|
u += `&var=${props.variation.id}`
|
||||||
|
}
|
||||||
|
if (store.subevent) {
|
||||||
|
u += `&subevent=${store.subevent}`
|
||||||
|
}
|
||||||
|
const widgetDataJson = JSON.stringify(store.widgetData)
|
||||||
|
u += `&widget_data=${encodeURIComponent(widgetDataJson)}`
|
||||||
|
if (store.widgetData.consent) {
|
||||||
|
u += `&consent=${encodeURIComponent(store.widgetData.consent)}`
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
})
|
||||||
|
|
||||||
|
function onStep (e: Event) {
|
||||||
|
const target = e.target as HTMLElement
|
||||||
|
const button = target.tagName === 'BUTTON' ? target : target.closest('button')
|
||||||
|
if (!button || !quantity.value) return
|
||||||
|
|
||||||
|
const step = parseFloat(button.getAttribute('data-step') || '0')
|
||||||
|
const input = quantity.value
|
||||||
|
const min = parseFloat(input.min) || 0
|
||||||
|
const max = parseFloat(input.max) || Number.MAX_SAFE_INTEGER
|
||||||
|
const currentValue = parseInt(input.value || '0')
|
||||||
|
input.value = String(Math.max(min, Math.min(max, currentValue + step)))
|
||||||
|
input.dispatchEvent(new CustomEvent('change', { bubbles: true }))
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// Auto-select first item if single item with no variations
|
||||||
|
if (
|
||||||
|
store.itemnum === 1
|
||||||
|
&& (!store.categories[0]?.items[0]?.has_variations || store.categories[0]?.items[0]?.variations.length < 2)
|
||||||
|
&& !store.hasSeatingPlan
|
||||||
|
&& quantity.value
|
||||||
|
) {
|
||||||
|
quantity.value.value = '1'
|
||||||
|
if (orderMax.value === 1 && quantity.value.type === 'checkbox') {
|
||||||
|
;(quantity.value as HTMLInputElement).checked = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<template lang="pug">
|
||||||
|
.pretix-widget-availability-box
|
||||||
|
.pretix-widget-availability-unavailable(v-if="item.current_unavailability_reason === 'require_voucher'")
|
||||||
|
small
|
||||||
|
a(:href="voucherJumpLink", :aria-describedby="ariaLabelledby") {{ unavailabilityReasonMessage }}
|
||||||
|
.pretix-widget-availability-unavailable(v-else-if="unavailabilityReasonMessage")
|
||||||
|
small {{ unavailabilityReasonMessage }}
|
||||||
|
.pretix-widget-availability-unavailable(v-else-if="avail[0] < 100 && avail[0] > 10") {{ STRINGS.reserved }}
|
||||||
|
.pretix-widget-availability-gone(v-else-if="avail[0] <= 10") {{ STRINGS.sold_out }}
|
||||||
|
.pretix-widget-waiting-list-link(v-if="waitingListShow && !unavailabilityReasonMessage")
|
||||||
|
a(:href="waitingListUrl", target="_blank", @click="$root.open_link_in_frame") {{ STRINGS.waiting_list }}
|
||||||
|
.pretix-widget-availability-available(v-if="!unavailabilityReasonMessage && avail[0] === 100")
|
||||||
|
label.pretix-widget-item-count-single-label.pretix-widget-btn-checkbox(v-if="orderMax === 1")
|
||||||
|
input(
|
||||||
|
ref="quantity",
|
||||||
|
type="checkbox",
|
||||||
|
value="1",
|
||||||
|
:name="inputName",
|
||||||
|
:aria-label="labelSelectItem"
|
||||||
|
)
|
||||||
|
span.pretix-widget-icon-cart(aria-hidden="true")
|
||||||
|
| {{ STRINGS.select }}
|
||||||
|
|
||||||
|
.pretix-widget-item-count-group(v-else, role="group", :aria-label="item.name")
|
||||||
|
button.pretix-widget-btn-default.pretix-widget-item-count-dec(
|
||||||
|
type="button",
|
||||||
|
data-step="-1",
|
||||||
|
:data-controls="`input_${inputName}`",
|
||||||
|
:aria-label="decLabel",
|
||||||
|
@click.prevent.stop="onStep"
|
||||||
|
)
|
||||||
|
span -
|
||||||
|
input.pretix-widget-item-count-multiple(
|
||||||
|
:id="`input_${inputName}`",
|
||||||
|
ref="quantity",
|
||||||
|
type="number",
|
||||||
|
inputmode="numeric",
|
||||||
|
pattern="\\d*",
|
||||||
|
placeholder="0",
|
||||||
|
min="0",
|
||||||
|
:max="orderMax",
|
||||||
|
:name="inputName",
|
||||||
|
:aria-labelledby="ariaLabelledby"
|
||||||
|
)
|
||||||
|
button.pretix-widget-btn-default.pretix-widget-item-count-inc(
|
||||||
|
type="button",
|
||||||
|
data-step="1",
|
||||||
|
:data-controls="`input_${inputName}`",
|
||||||
|
:aria-label="incLabel",
|
||||||
|
@click.prevent.stop="onStep"
|
||||||
|
)
|
||||||
|
span +
|
||||||
|
</template>
|
||||||
|
<style lang="sass">
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, inject, ref } from 'vue'
|
||||||
|
import { StoreKey } from '~/sharedStore'
|
||||||
|
import Overlay from './Overlay.vue'
|
||||||
|
|
||||||
|
const lang = LANG // we need this so the template sees the variable
|
||||||
|
|
||||||
|
const store = inject(StoreKey)!
|
||||||
|
|
||||||
|
const form = ref<HTMLFormElement>()
|
||||||
|
|
||||||
|
const formMethod = computed(() => {
|
||||||
|
if (!store.useIframe && store.isButton && store.items.length === 0) {
|
||||||
|
return 'get'
|
||||||
|
}
|
||||||
|
return 'post'
|
||||||
|
})
|
||||||
|
|
||||||
|
const formAction = computed(() => store.getFormAction())
|
||||||
|
|
||||||
|
const formTarget = computed(() => {
|
||||||
|
const isFirefox = navigator.userAgent.toLowerCase().includes('firefox')
|
||||||
|
const isAndroid = navigator.userAgent.toLowerCase().includes('android')
|
||||||
|
if (isAndroid && isFirefox) {
|
||||||
|
return '_top'
|
||||||
|
}
|
||||||
|
return '_blank'
|
||||||
|
})
|
||||||
|
|
||||||
|
const consentParameterValue = computed(() => {
|
||||||
|
if (store.widgetData.consent) {
|
||||||
|
return encodeURIComponent(store.widgetData.consent)
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const widgetDataJson = computed(() => {
|
||||||
|
const clonedData = { ...store.widgetData }
|
||||||
|
if (clonedData.consent) {
|
||||||
|
delete clonedData.consent
|
||||||
|
}
|
||||||
|
return JSON.stringify(clonedData)
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleBuy (event: Event) {
|
||||||
|
if (form.value) {
|
||||||
|
const formData = new FormData(form.value)
|
||||||
|
store.buy(formData, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
form,
|
||||||
|
buy: handleBuy,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<template lang="pug">
|
||||||
|
.pretix-widget-wrapper
|
||||||
|
.pretix-widget-button-container
|
||||||
|
form(ref="form", :method="formMethod", :action="formAction", :target="formTarget")
|
||||||
|
input(v-if="store.voucherCode", type="hidden", name="_voucher_code", :value="store.voucherCode")
|
||||||
|
input(v-if="store.voucherCode", type="hidden", name="voucher", :value="store.voucherCode")
|
||||||
|
input(type="hidden", name="subevent", :value="store.subevent")
|
||||||
|
input(type="hidden", name="locale", :value="lang")
|
||||||
|
input(type="hidden", name="widget_data", :value="widgetDataJson")
|
||||||
|
input(v-if="consentParameterValue", type="hidden", name="consent", :value="consentParameterValue")
|
||||||
|
input(
|
||||||
|
v-for="item in store.items",
|
||||||
|
:key="item.item",
|
||||||
|
type="hidden",
|
||||||
|
:name="item.item",
|
||||||
|
:value="item.count"
|
||||||
|
)
|
||||||
|
button.pretix-button(@click="handleBuy", v-html="store.buttonText")
|
||||||
|
.pretix-widget-clear
|
||||||
|
|
||||||
|
Overlay
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { Category } from '~/types'
|
||||||
|
import Item from './Item.vue'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
category: Category
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template lang="pug">
|
||||||
|
.pretix-widget-category(:data-id="category.id")
|
||||||
|
h3.pretix-widget-category-name(v-if="category.name") {{ category.name }}
|
||||||
|
.pretix-widget-category-description(v-if="category.description", v-html="category.description")
|
||||||
|
.pretix-widget-category-items
|
||||||
|
Item(
|
||||||
|
v-for="item in category.items",
|
||||||
|
:key="item.id",
|
||||||
|
:item="item",
|
||||||
|
:category="category"
|
||||||
|
)
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, inject, ref } from 'vue'
|
||||||
|
import { StoreKey } from '~/sharedStore'
|
||||||
|
import { STRINGS } from '~/i18n'
|
||||||
|
import { padNumber } from '~/utils'
|
||||||
|
import EventCalendarRow from './EventCalendarRow.vue'
|
||||||
|
import EventListFilterForm from './EventListFilterForm.vue'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
mobile: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const store = inject(StoreKey)!
|
||||||
|
const calendar = ref<HTMLDivElement>()
|
||||||
|
|
||||||
|
const displayEventInfo = computed(() => store.displayEventInfo || (store.displayEventInfo === null && store.parentStack.length > 0))
|
||||||
|
|
||||||
|
const monthname = computed(() => {
|
||||||
|
if (!store.date) return ''
|
||||||
|
const monthNum = store.date.substr(5, 2)
|
||||||
|
const year = store.date.substr(0, 4)
|
||||||
|
return `${STRINGS.months[monthNum]} ${year}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const id = computed(() => `${store.htmlId}-event-calendar-table`)
|
||||||
|
|
||||||
|
const ariaLabelledby = computed(() => `${store.htmlId}-event-calendar-table-label`)
|
||||||
|
|
||||||
|
const showFilters = computed(() => !store.disableFilters && store.metaFilterFields.length > 0)
|
||||||
|
|
||||||
|
function backToList () {
|
||||||
|
store.weeks = null
|
||||||
|
store.view = 'events'
|
||||||
|
store.name = null
|
||||||
|
store.frontpageText = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function prevmonth () {
|
||||||
|
if (!store.date) return
|
||||||
|
let curMonth = parseInt(store.date.substr(5, 2))
|
||||||
|
let curYear = parseInt(store.date.substr(0, 4))
|
||||||
|
curMonth--
|
||||||
|
if (curMonth < 1) {
|
||||||
|
curMonth = 12
|
||||||
|
curYear--
|
||||||
|
}
|
||||||
|
store.date = `${curYear}-${padNumber(curMonth, 2)}-01`
|
||||||
|
store.loading++
|
||||||
|
store.reload({ focus: `#${id.value}` })
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextmonth () {
|
||||||
|
if (!store.date) return
|
||||||
|
let curMonth = parseInt(store.date.substr(5, 2))
|
||||||
|
let curYear = parseInt(store.date.substr(0, 4))
|
||||||
|
curMonth++
|
||||||
|
if (curMonth > 12) {
|
||||||
|
curMonth = 1
|
||||||
|
curYear++
|
||||||
|
}
|
||||||
|
store.date = `${curYear}-${padNumber(curMonth, 2)}-01`
|
||||||
|
store.loading++
|
||||||
|
store.reload({ focus: `#${id.value}` })
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template lang="pug">
|
||||||
|
.pretix-widget-event-calendar(ref="calendar")
|
||||||
|
//- Back navigation
|
||||||
|
.pretix-widget-back(v-if="store.events !== null")
|
||||||
|
a(href="#", role="button", @click.prevent.stop="backToList")
|
||||||
|
| ‹ {{ STRINGS.back }}
|
||||||
|
|
||||||
|
//- Headline
|
||||||
|
.pretix-widget-event-header(v-if="displayEventInfo")
|
||||||
|
strong {{ store.name }}
|
||||||
|
.pretix-widget-event-description(
|
||||||
|
v-if="displayEventInfo && store.frontpageText",
|
||||||
|
v-html="store.frontpageText"
|
||||||
|
)
|
||||||
|
|
||||||
|
//- Filter
|
||||||
|
EventListFilterForm(v-if="showFilters")
|
||||||
|
|
||||||
|
//- Calendar navigation
|
||||||
|
.pretix-widget-event-calendar-head
|
||||||
|
a.pretix-widget-event-calendar-previous-month(href="#", @click.prevent.stop="prevmonth")
|
||||||
|
| « {{ STRINGS.previous_month }}
|
||||||
|
|
|
||||||
|
strong(:id="ariaLabelledby") {{ monthname }}
|
||||||
|
|
|
||||||
|
a.pretix-widget-event-calendar-next-month(href="#", @click.prevent.stop="nextmonth")
|
||||||
|
| {{ STRINGS.next_month }} »
|
||||||
|
|
||||||
|
//- Calendar table
|
||||||
|
table.pretix-widget-event-calendar-table(
|
||||||
|
:id="id",
|
||||||
|
tabindex="0",
|
||||||
|
:aria-labelledby="ariaLabelledby"
|
||||||
|
)
|
||||||
|
thead
|
||||||
|
tr
|
||||||
|
th(:aria-label="STRINGS.days.MONDAY") {{ STRINGS.days.MO }}
|
||||||
|
th(:aria-label="STRINGS.days.TUESDAY") {{ STRINGS.days.TU }}
|
||||||
|
th(:aria-label="STRINGS.days.WEDNESDAY") {{ STRINGS.days.WE }}
|
||||||
|
th(:aria-label="STRINGS.days.THURSDAY") {{ STRINGS.days.TH }}
|
||||||
|
th(:aria-label="STRINGS.days.FRIDAY") {{ STRINGS.days.FR }}
|
||||||
|
th(:aria-label="STRINGS.days.SATURDAY") {{ STRINGS.days.SA }}
|
||||||
|
th(:aria-label="STRINGS.days.SUNDAY") {{ STRINGS.days.SU }}
|
||||||
|
tbody
|
||||||
|
EventCalendarRow(
|
||||||
|
v-for="(week, idx) in store.weeks",
|
||||||
|
:key="idx",
|
||||||
|
:week="week",
|
||||||
|
:mobile="mobile"
|
||||||
|
)
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, inject, ref, onMounted, watch } from 'vue'
|
||||||
|
import type { DayEntry } from '~/types'
|
||||||
|
import { StoreKey } from '~/sharedStore'
|
||||||
|
import EventCalendarEvent from './EventCalendarEvent.vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
day: DayEntry | null
|
||||||
|
mobile: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const store = inject(StoreKey)!
|
||||||
|
const cellEl = ref<HTMLTableCellElement>()
|
||||||
|
|
||||||
|
const daynum = computed(() => {
|
||||||
|
if (!props.day) return ''
|
||||||
|
return props.day.date.substr(8)
|
||||||
|
})
|
||||||
|
|
||||||
|
const dateStr = computed(() => props.day ? new Date(props.day.date).toLocaleDateString() : '')
|
||||||
|
|
||||||
|
const role = computed(() => !props.day || !props.day.events.length || !props.mobile ? 'cell' : 'button')
|
||||||
|
|
||||||
|
const tabindex = computed(() => role.value === 'button' ? '0' : '-1')
|
||||||
|
|
||||||
|
const classObject = computed(() => {
|
||||||
|
const o: Record<string, boolean> = {}
|
||||||
|
if (props.day && props.day.events.length > 0) {
|
||||||
|
o['pretix-widget-has-events'] = true
|
||||||
|
let best = 'red'
|
||||||
|
let allLow = true
|
||||||
|
for (const ev of props.day.events) {
|
||||||
|
if (ev.availability.color === 'green') {
|
||||||
|
best = 'green'
|
||||||
|
if (ev.availability.reason !== 'low') {
|
||||||
|
allLow = false
|
||||||
|
}
|
||||||
|
} else if (ev.availability.color === 'orange' && best !== 'green') {
|
||||||
|
best = 'orange'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o[`pretix-widget-day-availability-${best}`] = true
|
||||||
|
if (best === 'green' && allLow) {
|
||||||
|
o['pretix-widget-day-availability-low'] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
})
|
||||||
|
|
||||||
|
function selectDay(e: Event) {
|
||||||
|
if (!props.day || !props.day.events.length || !props.mobile) return
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
if (props.day.events.length === 1) {
|
||||||
|
const ev = props.day.events[0]
|
||||||
|
store.parentStack.push(store.targetUrl)
|
||||||
|
store.targetUrl = ev.event_url
|
||||||
|
store.error = null
|
||||||
|
store.subevent = ev.subevent ?? null
|
||||||
|
store.loading++
|
||||||
|
store.reload()
|
||||||
|
} else {
|
||||||
|
store.events = props.day.events
|
||||||
|
store.view = 'events'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeyDown(e: KeyboardEvent) {
|
||||||
|
const keyDown = e.key ?? e.keyCode
|
||||||
|
if (keyDown === 'Enter' || keyDown === 13 || ['Spacebar', ' '].includes(keyDown as string) || keyDown === 32) {
|
||||||
|
e.preventDefault()
|
||||||
|
selectDay(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachListeners() {
|
||||||
|
if (role.value === 'button' && cellEl.value) {
|
||||||
|
cellEl.value.addEventListener('click', selectDay)
|
||||||
|
cellEl.value.addEventListener('keydown', onKeyDown)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function detachListeners() {
|
||||||
|
if (cellEl.value) {
|
||||||
|
cellEl.value.removeEventListener('click', selectDay)
|
||||||
|
cellEl.value.removeEventListener('keydown', onKeyDown)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
attachListeners()
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(role, (newValue, oldValue) => {
|
||||||
|
if (newValue === 'button' && oldValue !== 'button') {
|
||||||
|
attachListeners()
|
||||||
|
} else if (newValue !== 'button' && oldValue === 'button') {
|
||||||
|
detachListeners()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template lang="pug">
|
||||||
|
td(
|
||||||
|
ref="cellEl",
|
||||||
|
:class="classObject",
|
||||||
|
:role="role",
|
||||||
|
:tabindex="tabindex",
|
||||||
|
:aria-label="dateStr"
|
||||||
|
)
|
||||||
|
.pretix-widget-event-calendar-day(v-if="day", :aria-label="dateStr") {{ daynum }}
|
||||||
|
.pretix-widget-event-calendar-events(v-if="day")
|
||||||
|
EventCalendarEvent(v-for="e in day.events", :key="e.event_url", :event="e")
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, inject } from 'vue'
|
||||||
|
import type { EventEntry } from '~/types'
|
||||||
|
import { StoreKey } from '~/sharedStore'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
event: EventEntry
|
||||||
|
describedby?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const store = inject(StoreKey)!
|
||||||
|
|
||||||
|
const classObject = computed(() => {
|
||||||
|
const o: Record<string, boolean> = {
|
||||||
|
'pretix-widget-event-calendar-event': true,
|
||||||
|
}
|
||||||
|
o[`pretix-widget-event-availability-${props.event.availability.color}`] = true
|
||||||
|
if (props.event.availability.reason) {
|
||||||
|
o[`pretix-widget-event-availability-${props.event.availability.reason}`] = true
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
})
|
||||||
|
|
||||||
|
function select() {
|
||||||
|
store.parentStack.push(store.targetUrl)
|
||||||
|
store.targetUrl = props.event.event_url
|
||||||
|
store.error = null
|
||||||
|
store.subevent = props.event.subevent ?? null
|
||||||
|
store.loading++
|
||||||
|
store.reload()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template lang="pug">
|
||||||
|
a.pretix-widget-event-calendar-event(
|
||||||
|
href="#",
|
||||||
|
:class="classObject",
|
||||||
|
@click.prevent.stop="select",
|
||||||
|
:aria-describedby="describedby"
|
||||||
|
)
|
||||||
|
strong.pretix-widget-event-calendar-event-name {{ event.name }}
|
||||||
|
.pretix-widget-event-calendar-event-date(v-if="!event.continued && event.time") {{ event.time }}
|
||||||
|
.pretix-widget-event-calendar-event-availability(v-if="!event.continued && event.availability.text") {{ event.availability.text }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { DayEntry } from '~/types'
|
||||||
|
import EventCalendarCell from './EventCalendarCell.vue'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
week: (DayEntry | null)[]
|
||||||
|
mobile: boolean
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template lang="pug">
|
||||||
|
tr
|
||||||
|
EventCalendarCell(v-for="(d, idx) in week", :key="idx", :day="d", :mobile="mobile")
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, inject, ref, watch, onMounted, onBeforeUnmount } from 'vue'
|
||||||
|
import { StoreKey } from '~/sharedStore'
|
||||||
|
import { STRINGS } from '~/i18n'
|
||||||
|
import Category from './Category.vue'
|
||||||
|
|
||||||
|
const store = inject(StoreKey)!
|
||||||
|
|
||||||
|
const form = ref<HTMLFormElement>()
|
||||||
|
const voucherinput = ref<HTMLInputElement>()
|
||||||
|
const isItemsSelected = ref(false)
|
||||||
|
const localVoucher = ref('')
|
||||||
|
|
||||||
|
const displayEventInfo = computed(() => store.displayEventInfo || (store.displayEventInfo === null && (store.events || store.weeks || store.days)))
|
||||||
|
|
||||||
|
const idVoucherInput = computed(() => `${store.htmlId}-voucher-input`)
|
||||||
|
|
||||||
|
const ariaLabelledby = computed(() => `${store.htmlId}-voucher-headline`)
|
||||||
|
|
||||||
|
const idCartExistsMsg = computed(() => `${store.htmlId}-cart-exists`)
|
||||||
|
|
||||||
|
const buyLabel = computed(() => {
|
||||||
|
let allFree = true
|
||||||
|
for (const cat of store.categories) {
|
||||||
|
for (const item of cat.items) {
|
||||||
|
for (const v of item.variations) {
|
||||||
|
if (v.price.gross !== '0.00') {
|
||||||
|
allFree = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((item.variations.length === 0 && item.price.gross !== '0.00') || item.mandatory_priced_addons) {
|
||||||
|
allFree = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!allFree) break
|
||||||
|
}
|
||||||
|
return allFree ? STRINGS.register : STRINGS.buy
|
||||||
|
})
|
||||||
|
|
||||||
|
const hiddenParams = computed(() => {
|
||||||
|
const params = new URL(store.getVoucherFormTarget()).searchParams
|
||||||
|
params.delete('iframe')
|
||||||
|
params.delete('take_cart_id')
|
||||||
|
return Array.from(params.entries())
|
||||||
|
})
|
||||||
|
|
||||||
|
const showVoucherForm = computed(() => store.vouchersExist && !store.disableVouchers && !store.voucherCode)
|
||||||
|
|
||||||
|
const consentParameterValue = computed(() => {
|
||||||
|
if (store.widgetData.consent) {
|
||||||
|
return encodeURIComponent(store.widgetData.consent)
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const widgetDataJson = computed(() => {
|
||||||
|
const clonedData = { ...store.widgetData }
|
||||||
|
if (clonedData.consent) {
|
||||||
|
delete clonedData.consent
|
||||||
|
}
|
||||||
|
return JSON.stringify(clonedData)
|
||||||
|
})
|
||||||
|
|
||||||
|
const formAction = computed(() => {
|
||||||
|
const additionalParams = getAdditionalURLParams()
|
||||||
|
let checkoutUrl = `/${store.targetUrl.replace(/^[^\/]+:\/\/([^\/]+)\//, '')}w/${store.widgetId.replace('pretix-widget-', '')}/`
|
||||||
|
if (!store.cartExists) {
|
||||||
|
checkoutUrl += 'checkout/start'
|
||||||
|
}
|
||||||
|
if (additionalParams) {
|
||||||
|
checkoutUrl += `?${additionalParams}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const cookieName = `pretix_widget_${store.targetUrl.replace(/[^a-zA-Z0-9]+/g, '_')}`
|
||||||
|
const cartIdCookie = document.cookie
|
||||||
|
.split('; ')
|
||||||
|
.find((row) => row.startsWith(`${cookieName}=`))
|
||||||
|
?.split('=')[1] || null
|
||||||
|
|
||||||
|
let formTarget = `${store.targetUrl}w/${store.widgetId.replace('pretix-widget-', '')}/cart/add?iframe=1&next=${encodeURIComponent(checkoutUrl)}`
|
||||||
|
if (cartIdCookie) {
|
||||||
|
formTarget += `&take_cart_id=${cartIdCookie}`
|
||||||
|
}
|
||||||
|
if (store.widgetData.consent) {
|
||||||
|
formTarget += `&consent=${encodeURIComponent(store.widgetData.consent)}`
|
||||||
|
}
|
||||||
|
return formTarget
|
||||||
|
})
|
||||||
|
|
||||||
|
const formTarget = computed(() => {
|
||||||
|
const isFirefox = navigator.userAgent.toLowerCase().includes('firefox')
|
||||||
|
const isAndroid = navigator.userAgent.toLowerCase().includes('android')
|
||||||
|
if (isAndroid && isFirefox) {
|
||||||
|
return '_top'
|
||||||
|
}
|
||||||
|
return '_blank'
|
||||||
|
})
|
||||||
|
|
||||||
|
function getAdditionalURLParams (): string {
|
||||||
|
if (!window.location.search.includes('utm_')) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
const params = new URLSearchParams(window.location.search)
|
||||||
|
for (const [key] of params.entries()) {
|
||||||
|
if (!key.startsWith('utm_')) {
|
||||||
|
params.delete(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return params.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
function backToList () {
|
||||||
|
store.targetUrl = store.parentStack.pop() || store.targetUrl
|
||||||
|
store.error = null
|
||||||
|
if (!store.subevent) {
|
||||||
|
store.name = null
|
||||||
|
store.frontpageText = null
|
||||||
|
}
|
||||||
|
store.subevent = null
|
||||||
|
store.offset = 0
|
||||||
|
store.appendEvents = false
|
||||||
|
store.triggerLoadCallback()
|
||||||
|
|
||||||
|
if (store.events !== null) {
|
||||||
|
store.view = 'events'
|
||||||
|
} else if (store.days !== null) {
|
||||||
|
store.view = 'days'
|
||||||
|
} else {
|
||||||
|
store.view = 'weeks'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcItemsSelected () {
|
||||||
|
if (!form.value) return
|
||||||
|
const checkboxes = form.value.querySelectorAll<HTMLInputElement>('input[type=checkbox], input[type=radio]')
|
||||||
|
const hasChecked = Array.from(checkboxes).some((el) => el.checked)
|
||||||
|
const numberInputs = form.value.querySelectorAll<HTMLInputElement>('.pretix-widget-item-count-group input')
|
||||||
|
const hasQuantity = Array.from(numberInputs).some((el) => parseInt(el.value || '0') > 0)
|
||||||
|
isItemsSelected.value = hasChecked || hasQuantity
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusVoucherField () {
|
||||||
|
voucherinput.value?.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBuy (event: Event) {
|
||||||
|
if (form.value) {
|
||||||
|
const formData = new FormData(form.value)
|
||||||
|
store.buy(formData, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRedeem (event: Event) {
|
||||||
|
store.redeem(localVoucher.value, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (form.value) {
|
||||||
|
form.value.addEventListener('change', calcItemsSelected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (form.value) {
|
||||||
|
form.value.removeEventListener('change', calcItemsSelected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => store.overlay?.frameShown, (newValue) => {
|
||||||
|
if (!newValue && form.value) {
|
||||||
|
form.value.reset()
|
||||||
|
calcItemsSelected()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template lang="pug">
|
||||||
|
.pretix-widget-event-form
|
||||||
|
//- Back navigation
|
||||||
|
.pretix-widget-event-list-back(v-if="store.events || store.weeks || store.days")
|
||||||
|
a(v-if="!store.subevent", href="#", rel="back", @click.prevent.stop="backToList")
|
||||||
|
| ‹ {{ STRINGS.back_to_list }}
|
||||||
|
a(v-if="store.subevent", href="#", rel="back", @click.prevent.stop="backToList")
|
||||||
|
| ‹ {{ STRINGS.back_to_dates }}
|
||||||
|
|
||||||
|
//- Event name
|
||||||
|
.pretix-widget-event-header(v-if="displayEventInfo")
|
||||||
|
strong(role="heading", aria-level="2") {{ store.name }}
|
||||||
|
|
||||||
|
//- Date range
|
||||||
|
.pretix-widget-event-details(v-if="displayEventInfo && store.dateRange") {{ store.dateRange }}
|
||||||
|
|
||||||
|
//- Location
|
||||||
|
.pretix-widget-event-location(
|
||||||
|
v-if="displayEventInfo && store.location",
|
||||||
|
v-html="store.location"
|
||||||
|
)
|
||||||
|
|
||||||
|
//- Description
|
||||||
|
.pretix-widget-event-description(
|
||||||
|
v-if="displayEventInfo && store.frontpageText",
|
||||||
|
v-html="store.frontpageText"
|
||||||
|
)
|
||||||
|
|
||||||
|
//- Form start
|
||||||
|
form(
|
||||||
|
ref="form",
|
||||||
|
method="post",
|
||||||
|
:action="formAction",
|
||||||
|
:target="formTarget",
|
||||||
|
@submit="handleBuy"
|
||||||
|
)
|
||||||
|
input(v-if="store.voucherCode", type="hidden", name="_voucher_code", :value="store.voucherCode")
|
||||||
|
input(type="hidden", name="subevent", :value="store.subevent")
|
||||||
|
input(type="hidden", name="widget_data", :value="widgetDataJson")
|
||||||
|
input(v-if="consentParameterValue", type="hidden", name="consent", :value="consentParameterValue")
|
||||||
|
|
||||||
|
//- Error message
|
||||||
|
.pretix-widget-error-message(v-if="store.error") {{ store.error }}
|
||||||
|
|
||||||
|
//- Resume cart
|
||||||
|
.pretix-widget-info-message.pretix-widget-clickable(v-if="store.cartExists")
|
||||||
|
span(:id="idCartExistsMsg") {{ STRINGS.cart_exists }}
|
||||||
|
button.pretix-widget-resume-button(
|
||||||
|
type="button",
|
||||||
|
:aria-describedby="idCartExistsMsg",
|
||||||
|
@click.prevent.stop="store.resume()"
|
||||||
|
) {{ STRINGS.resume_checkout }}
|
||||||
|
|
||||||
|
//- Seating plan
|
||||||
|
.pretix-widget-seating-link-wrapper(v-if="store.hasSeatingPlan")
|
||||||
|
button.pretix-widget-seating-link(type="button", @click.prevent.stop="store.startseating()")
|
||||||
|
| {{ STRINGS.show_seating }}
|
||||||
|
|
||||||
|
//- Waiting list for seating plan
|
||||||
|
.pretix-widget-seating-waitinglist(v-if="store.hasSeatingPlan && store.hasSeatingPlanWaitinglist")
|
||||||
|
.pretix-widget-seating-waitinglist-text {{ STRINGS.seating_plan_waiting_list }}
|
||||||
|
.pretix-widget-seating-waitinglist-button-wrap
|
||||||
|
button.pretix-widget-seating-waitinglist-button(@click.prevent.stop="store.startwaiting()")
|
||||||
|
| {{ STRINGS.waiting_list }}
|
||||||
|
.pretix-widget-clear
|
||||||
|
|
||||||
|
//- Product list
|
||||||
|
Category(v-for="category in store.categories", :key="category.id", :category="category")
|
||||||
|
|
||||||
|
//- Buy button
|
||||||
|
.pretix-widget-action(v-if="store.displayAddToCart")
|
||||||
|
button(
|
||||||
|
v-if="!store.cartExists || isItemsSelected",
|
||||||
|
type="submit",
|
||||||
|
:aria-describedby="idCartExistsMsg"
|
||||||
|
) {{ buyLabel }}
|
||||||
|
button(
|
||||||
|
v-else,
|
||||||
|
type="button",
|
||||||
|
:aria-describedby="idCartExistsMsg",
|
||||||
|
@click.prevent.stop="store.resume()"
|
||||||
|
) {{ STRINGS.resume_checkout }}
|
||||||
|
|
||||||
|
//- Voucher form
|
||||||
|
form(
|
||||||
|
v-if="showVoucherForm",
|
||||||
|
method="get",
|
||||||
|
:action="store.getVoucherFormTarget()",
|
||||||
|
target="_blank"
|
||||||
|
)
|
||||||
|
.pretix-widget-voucher
|
||||||
|
h3.pretix-widget-voucher-headline(:id="ariaLabelledby") {{ STRINGS.redeem_voucher }}
|
||||||
|
.pretix-widget-voucher-text(
|
||||||
|
v-if="store.voucherExplanationText",
|
||||||
|
v-html="store.voucherExplanationText"
|
||||||
|
)
|
||||||
|
.pretix-widget-voucher-input-wrap
|
||||||
|
input.pretix-widget-voucher-input(
|
||||||
|
:id="idVoucherInput",
|
||||||
|
ref="voucherinput",
|
||||||
|
v-model="localVoucher",
|
||||||
|
type="text",
|
||||||
|
name="voucher",
|
||||||
|
:placeholder="STRINGS.voucher_code",
|
||||||
|
:aria-labelledby="ariaLabelledby"
|
||||||
|
)
|
||||||
|
input(
|
||||||
|
v-for="p in hiddenParams",
|
||||||
|
:key="p[0]",
|
||||||
|
type="hidden",
|
||||||
|
:name="p[0]",
|
||||||
|
:value="p[1]"
|
||||||
|
)
|
||||||
|
.pretix-widget-voucher-button-wrap
|
||||||
|
button(@click="handleRedeem") {{ STRINGS.redeem }}
|
||||||
|
.pretix-widget-clear
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, inject, nextTick } from 'vue'
|
||||||
|
import { StoreKey } from '~/sharedStore'
|
||||||
|
import { STRINGS } from '~/i18n'
|
||||||
|
import EventListEntry from './EventListEntry.vue'
|
||||||
|
import EventListFilterForm from './EventListFilterForm.vue'
|
||||||
|
|
||||||
|
const store = inject(StoreKey)!
|
||||||
|
|
||||||
|
const displayEventInfo = computed(() => store.displayEventInfo || (store.displayEventInfo === null && store.parentStack.length > 0))
|
||||||
|
|
||||||
|
const showBackButton = computed(() => store.weeks || store.parentStack.length > 0)
|
||||||
|
|
||||||
|
const showFilters = computed(() => !store.disableFilters && store.metaFilterFields.length > 0)
|
||||||
|
|
||||||
|
function backToCalendar() {
|
||||||
|
store.offset = 0
|
||||||
|
store.appendEvents = false
|
||||||
|
|
||||||
|
if (store.weeks) {
|
||||||
|
store.events = null
|
||||||
|
store.view = 'weeks'
|
||||||
|
store.name = null
|
||||||
|
store.frontpageText = null
|
||||||
|
} else {
|
||||||
|
store.loading++
|
||||||
|
store.targetUrl = store.parentStack.pop() || store.targetUrl
|
||||||
|
store.error = null
|
||||||
|
store.reload()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadMore() {
|
||||||
|
store.appendEvents = true
|
||||||
|
store.offset += 50
|
||||||
|
store.loading++
|
||||||
|
store.reload()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template lang="pug">
|
||||||
|
.pretix-widget-event-list
|
||||||
|
.pretix-widget-back(v-if="showBackButton")
|
||||||
|
a(href="#", rel="prev", @click.prevent.stop="backToCalendar")
|
||||||
|
| ‹ {{ STRINGS.back }}
|
||||||
|
|
||||||
|
.pretix-widget-event-header(v-if="displayEventInfo")
|
||||||
|
strong {{ store.name }}
|
||||||
|
|
||||||
|
.pretix-widget-event-description(
|
||||||
|
v-if="displayEventInfo && store.frontpageText",
|
||||||
|
v-html="store.frontpageText"
|
||||||
|
)
|
||||||
|
|
||||||
|
EventListFilterForm(v-if="showFilters")
|
||||||
|
|
||||||
|
EventListEntry(
|
||||||
|
v-for="event in store.events",
|
||||||
|
:key="event.event_url",
|
||||||
|
:event="event"
|
||||||
|
)
|
||||||
|
|
||||||
|
p.pretix-widget-event-list-load-more(v-if="store.hasMoreEvents")
|
||||||
|
button(@click.prevent.stop="loadMore") {{ STRINGS.load_more }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, inject } from 'vue'
|
||||||
|
import type { EventEntry } from '~/types'
|
||||||
|
import { StoreKey } from '~/sharedStore'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
event: EventEntry
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const store = inject(StoreKey)!
|
||||||
|
|
||||||
|
const classObject = computed(() => {
|
||||||
|
const o: Record<string, boolean> = {
|
||||||
|
'pretix-widget-event-list-entry': true,
|
||||||
|
}
|
||||||
|
o[`pretix-widget-event-availability-${props.event.availability.color}`] = true
|
||||||
|
if (props.event.availability.reason) {
|
||||||
|
o[`pretix-widget-event-availability-${props.event.availability.reason}`] = true
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
})
|
||||||
|
|
||||||
|
const location = computed(() => props.event.location.replace(/\s*\n\s*/g, ', '))
|
||||||
|
|
||||||
|
function select() {
|
||||||
|
store.parentStack.push(store.targetUrl)
|
||||||
|
store.targetUrl = props.event.event_url
|
||||||
|
store.error = null
|
||||||
|
store.subevent = props.event.subevent ?? null
|
||||||
|
store.loading++
|
||||||
|
store.reload()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template lang="pug">
|
||||||
|
a.pretix-widget-event-list-entry(href="#", :class="classObject", @click.prevent.stop="select")
|
||||||
|
.pretix-widget-event-list-entry-name {{ event.name }}
|
||||||
|
.pretix-widget-event-list-entry-date {{ event.date_range }}
|
||||||
|
.pretix-widget-event-list-entry-location {{ location }}
|
||||||
|
.pretix-widget-event-list-entry-availability
|
||||||
|
span {{ event.availability.text }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, inject } from 'vue'
|
||||||
|
import type { MetaFilterField } from '~/types'
|
||||||
|
import { StoreKey, globalWidgetId } from '~/sharedStore'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
field: MetaFilterField
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const store = inject(StoreKey)!
|
||||||
|
|
||||||
|
const id = computed(() => `${globalWidgetId}_${props.field.key}`)
|
||||||
|
|
||||||
|
const currentValue = computed(() => {
|
||||||
|
const filterParams = new URLSearchParams(store.filter || '')
|
||||||
|
return filterParams.get(props.field.key) || ''
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template lang="pug">
|
||||||
|
.pretix-widget-event-list-filter-field
|
||||||
|
label(:for="id") {{ field.label }}
|
||||||
|
select(:id="id", :name="field.key", :value="currentValue")
|
||||||
|
option(v-for="choice in field.choices", :key="choice[0]", :value="choice[0]") {{ choice[1] }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { inject, ref } from 'vue'
|
||||||
|
import { StoreKey } from '~/sharedStore'
|
||||||
|
import { STRINGS } from '~/i18n'
|
||||||
|
import EventListFilterField from './EventListFilterField.vue'
|
||||||
|
|
||||||
|
const store = inject(StoreKey)!
|
||||||
|
const filterform = ref<HTMLFormElement>()
|
||||||
|
|
||||||
|
function onSubmit(e: Event) {
|
||||||
|
e.preventDefault()
|
||||||
|
if (!filterform.value) return
|
||||||
|
|
||||||
|
const formData = new FormData(filterform.value)
|
||||||
|
const filterParams = new URLSearchParams()
|
||||||
|
|
||||||
|
formData.forEach((value, key) => {
|
||||||
|
if (value !== '') {
|
||||||
|
filterParams.set(key, value as string)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
store.filter = filterParams.toString()
|
||||||
|
store.loading++
|
||||||
|
store.reload()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template lang="pug">
|
||||||
|
form.pretix-widget-event-list-filter-form(ref="filterform", @submit="onSubmit")
|
||||||
|
fieldset.pretix-widget-event-list-filter-fieldset
|
||||||
|
legend {{ STRINGS.filter_events_by }}
|
||||||
|
EventListFilterField(
|
||||||
|
v-for="field in store.metaFilterFields",
|
||||||
|
:key="field.key",
|
||||||
|
:field="field"
|
||||||
|
)
|
||||||
|
button {{ STRINGS.filter }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, inject, ref } from 'vue'
|
||||||
|
import { StoreKey } from '~/sharedStore'
|
||||||
|
import { STRINGS } from '~/i18n'
|
||||||
|
import { getISOWeeks } from '~/utils'
|
||||||
|
import EventWeekCell from './EventWeekCell.vue'
|
||||||
|
import EventListFilterForm from './EventListFilterForm.vue'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
mobile: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const store = inject(StoreKey)!
|
||||||
|
const weekcalendar = ref<HTMLDivElement>()
|
||||||
|
|
||||||
|
const displayEventInfo = computed(() => store.displayEventInfo || (store.displayEventInfo === null && store.parentStack.length > 0))
|
||||||
|
|
||||||
|
const weekname = computed(() => {
|
||||||
|
if (!store.week) return ''
|
||||||
|
const curWeek = store.week[1]
|
||||||
|
const curYear = store.week[0]
|
||||||
|
return `${curWeek} / ${curYear}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const id = computed(() => `${store.htmlId}-event-week-table`)
|
||||||
|
|
||||||
|
const showFilters = computed(() => !store.disableFilters && store.metaFilterFields.length > 0)
|
||||||
|
|
||||||
|
function backToList() {
|
||||||
|
store.weeks = null
|
||||||
|
store.name = null
|
||||||
|
store.frontpageText = null
|
||||||
|
store.view = 'events'
|
||||||
|
}
|
||||||
|
|
||||||
|
function prevweek() {
|
||||||
|
if (!store.week) return
|
||||||
|
let curWeek = store.week[1]
|
||||||
|
let curYear = store.week[0]
|
||||||
|
curWeek--
|
||||||
|
if (curWeek < 1) {
|
||||||
|
curYear--
|
||||||
|
curWeek = getISOWeeks(curYear)
|
||||||
|
}
|
||||||
|
store.week = [curYear, curWeek]
|
||||||
|
store.loading++
|
||||||
|
store.reload({ focus: `#${id.value}` })
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextweek() {
|
||||||
|
if (!store.week) return
|
||||||
|
let curWeek = store.week[1]
|
||||||
|
let curYear = store.week[0]
|
||||||
|
curWeek++
|
||||||
|
if (curWeek > getISOWeeks(curYear)) {
|
||||||
|
curWeek = 1
|
||||||
|
curYear++
|
||||||
|
}
|
||||||
|
store.week = [curYear, curWeek]
|
||||||
|
store.loading++
|
||||||
|
store.reload({ focus: `#${id.value}` })
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template lang="pug">
|
||||||
|
.pretix-widget-event-calendar.pretix-widget-event-week-calendar(ref="weekcalendar")
|
||||||
|
//- Back navigation
|
||||||
|
.pretix-widget-back(v-if="store.events !== null")
|
||||||
|
a(href="#", @click.prevent.stop="backToList", role="button")
|
||||||
|
| ‹ {{ STRINGS.back }}
|
||||||
|
|
||||||
|
//- Event header
|
||||||
|
.pretix-widget-event-header(v-if="displayEventInfo")
|
||||||
|
strong {{ store.name }}
|
||||||
|
|
||||||
|
//- Filter
|
||||||
|
EventListFilterForm(v-if="showFilters")
|
||||||
|
|
||||||
|
//- Calendar navigation
|
||||||
|
.pretix-widget-event-description(
|
||||||
|
v-if="store.frontpageText && displayEventInfo",
|
||||||
|
v-html="store.frontpageText"
|
||||||
|
)
|
||||||
|
.pretix-widget-event-calendar-head
|
||||||
|
a.pretix-widget-event-calendar-previous-month(href="#", @click.prevent.stop="prevweek", role="button")
|
||||||
|
| « {{ STRINGS.previous_week }}
|
||||||
|
|
|
||||||
|
strong {{ weekname }}
|
||||||
|
|
|
||||||
|
a.pretix-widget-event-calendar-next-month(href="#", @click.prevent.stop="nextweek", role="button")
|
||||||
|
| {{ STRINGS.next_week }} »
|
||||||
|
|
||||||
|
//- Actual calendar
|
||||||
|
.pretix-widget-event-week-table(:id="id", tabindex="0", :aria-label="weekname")
|
||||||
|
.pretix-widget-event-week-col(v-for="d in store.days", :key="d?.date || ''")
|
||||||
|
EventWeekCell(:day="d", :mobile="mobile")
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, inject } from 'vue'
|
||||||
|
import type { DayEntry } from '~/types'
|
||||||
|
import { StoreKey } from '~/sharedStore'
|
||||||
|
import EventCalendarEvent from './EventCalendarEvent.vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
day: DayEntry | null
|
||||||
|
mobile: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const store = inject(StoreKey)!
|
||||||
|
|
||||||
|
const id = computed(() => props.day ? `${store.htmlId}-${props.day.date}` : '')
|
||||||
|
|
||||||
|
const dayhead = computed(() => {
|
||||||
|
if (!props.day) return ''
|
||||||
|
return props.day.day_formatted
|
||||||
|
})
|
||||||
|
|
||||||
|
const classObject = computed(() => {
|
||||||
|
const o: Record<string, boolean> = {}
|
||||||
|
if (props.day && props.day.events.length > 0) {
|
||||||
|
o['pretix-widget-has-events'] = true
|
||||||
|
let best = 'red'
|
||||||
|
let allLow = true
|
||||||
|
for (const ev of props.day.events) {
|
||||||
|
if (ev.availability.color === 'green') {
|
||||||
|
best = 'green'
|
||||||
|
if (ev.availability.reason !== 'low') {
|
||||||
|
allLow = false
|
||||||
|
}
|
||||||
|
} else if (ev.availability.color === 'orange' && best !== 'green') {
|
||||||
|
best = 'orange'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o[`pretix-widget-day-availability-${best}`] = true
|
||||||
|
if (best === 'green' && allLow) {
|
||||||
|
o['pretix-widget-day-availability-low'] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
})
|
||||||
|
|
||||||
|
function selectDay () {
|
||||||
|
if (!props.day || !props.day.events.length || !props.mobile) return
|
||||||
|
|
||||||
|
if (props.day.events.length === 1) {
|
||||||
|
const ev = props.day.events[0]
|
||||||
|
store.parentStack.push(store.targetUrl)
|
||||||
|
store.targetUrl = ev.event_url
|
||||||
|
store.error = null
|
||||||
|
store.subevent = ev.subevent ?? null
|
||||||
|
store.loading++
|
||||||
|
store.reload()
|
||||||
|
} else {
|
||||||
|
store.events = props.day.events
|
||||||
|
store.view = 'events'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template lang="pug">
|
||||||
|
div(:class="classObject", @click.prevent.stop="selectDay")
|
||||||
|
.pretix-widget-event-calendar-day(v-if="day", :id="id") {{ dayhead }}
|
||||||
|
.pretix-widget-event-calendar-events(v-if="day")
|
||||||
|
EventCalendarEvent(
|
||||||
|
v-for="e in day.events",
|
||||||
|
:key="e.event_url",
|
||||||
|
:event="e",
|
||||||
|
:describedby="id"
|
||||||
|
)
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
</style>
|
||||||
218
src/pretix/static/pretixpresale/widget/src/components/Item.vue
Normal file
218
src/pretix/static/pretixpresale/widget/src/components/Item.vue
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, inject, ref, watch, onMounted, nextTick } from 'vue'
|
||||||
|
import type { Item, Category } from '~/types'
|
||||||
|
import { StoreKey } from '~/sharedStore'
|
||||||
|
import { STRINGS, interpolate } from '~/i18n'
|
||||||
|
import { floatformat } from '~/utils'
|
||||||
|
import AvailBox from './AvailBox.vue'
|
||||||
|
import PriceBox from './PriceBox.vue'
|
||||||
|
import Variation from './Variation.vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
item: Item
|
||||||
|
category: Category
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const store = inject(StoreKey)!
|
||||||
|
|
||||||
|
const expanded = ref(store.showVariationsExpanded)
|
||||||
|
const variations = ref<HTMLDivElement>()
|
||||||
|
|
||||||
|
const classObject = computed(() => ({
|
||||||
|
'pretix-widget-item': true,
|
||||||
|
'pretix-widget-item-with-picture': !!props.item.picture,
|
||||||
|
'pretix-widget-item-with-variations': props.item.has_variations,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const varClasses = computed(() => ({
|
||||||
|
'pretix-widget-item-variations': true,
|
||||||
|
'pretix-widget-item-variations-expanded': expanded.value,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const pictureAltText = computed(() => interpolate(STRINGS.image_of, [props.item.name]))
|
||||||
|
|
||||||
|
const headingLevel = computed(() => props.category.name ? '4' : '3')
|
||||||
|
|
||||||
|
const itemLabelId = computed(() => `${store.htmlId}-item-label-${props.item.id}`)
|
||||||
|
|
||||||
|
const itemDescId = computed(() => `${store.htmlId}-item-desc-${props.item.id}`)
|
||||||
|
|
||||||
|
const itemPriceId = computed(() => `${store.htmlId}-item-price-${props.item.id}`)
|
||||||
|
|
||||||
|
const ariaLabelledby = computed(() => `${itemLabelId.value} ${itemPriceId.value}`)
|
||||||
|
|
||||||
|
const minOrderStr = computed(() => interpolate(STRINGS.order_min, [props.item.order_min]))
|
||||||
|
|
||||||
|
const quotaLeftStr = computed(() => interpolate(STRINGS.quota_left, [props.item.avail[1]]))
|
||||||
|
|
||||||
|
const showToggle = computed(() => props.item.has_variations && !store.showVariationsExpanded)
|
||||||
|
|
||||||
|
// TODO dedupe?
|
||||||
|
const showPrices = computed(() => {
|
||||||
|
let hasPriced = false
|
||||||
|
let cntItems = 0
|
||||||
|
for (const cat of store.categories) {
|
||||||
|
for (const item of cat.items) {
|
||||||
|
if (item.has_variations) {
|
||||||
|
cntItems += item.variations.length
|
||||||
|
hasPriced = true
|
||||||
|
} else {
|
||||||
|
cntItems++
|
||||||
|
hasPriced = hasPriced || item.price.gross !== '0.00' || item.free_price
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hasPriced || cntItems > 1
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO XSS
|
||||||
|
const pricerange = computed(() => {
|
||||||
|
if (props.item.free_price) {
|
||||||
|
return interpolate(
|
||||||
|
STRINGS.price_from,
|
||||||
|
{
|
||||||
|
currency: store.currency,
|
||||||
|
price: floatformat(props.item.min_price || '0', 2),
|
||||||
|
},
|
||||||
|
true
|
||||||
|
).replace(
|
||||||
|
store.currency,
|
||||||
|
`<span class="pretix-widget-pricebox-currency">${store.currency}</span>`
|
||||||
|
)
|
||||||
|
} else if (props.item.min_price !== props.item.max_price) {
|
||||||
|
return `<span class="pretix-widget-pricebox-currency">${store.currency}</span> ${floatformat(props.item.min_price || '0', 2)} – ${floatformat(props.item.max_price || '0', 2)}`
|
||||||
|
} else if (props.item.min_price === '0.00' && props.item.max_price === '0.00') {
|
||||||
|
if (props.item.mandatory_priced_addons) {
|
||||||
|
return '\xA0' // nbsp, because an empty string would cause the HTML element to collapse
|
||||||
|
}
|
||||||
|
return STRINGS.free
|
||||||
|
} else {
|
||||||
|
return `<span class="pretix-widget-pricebox-currency">${store.currency}</span> ${floatformat(props.item.min_price || '0', 2)}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const variationsToggleLabel = computed(() => expanded.value ? STRINGS.hide_variations : STRINGS.variations)
|
||||||
|
|
||||||
|
function expand () {
|
||||||
|
expanded.value = !expanded.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function lightbox () {
|
||||||
|
if (store.overlay) {
|
||||||
|
store.overlay.lightbox = {
|
||||||
|
image: props.item.picture_fullsize || '',
|
||||||
|
description: props.item.name,
|
||||||
|
loading: true, // TODO why?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (variations.value && !expanded.value) {
|
||||||
|
variations.value.hidden = true
|
||||||
|
|
||||||
|
variations.value.addEventListener('transitionend', function (event) {
|
||||||
|
if (event.target === variations.value) {
|
||||||
|
if (variations.value) {
|
||||||
|
variations.value.hidden = !expanded.value
|
||||||
|
variations.value.style.maxHeight = 'none'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(expanded, (newValue) => {
|
||||||
|
const v = variations.value
|
||||||
|
if (!v) return
|
||||||
|
|
||||||
|
v.hidden = false
|
||||||
|
v.style.maxHeight = `${newValue ? 0 : v.scrollHeight}px`
|
||||||
|
|
||||||
|
// Vue.nextTick does not work here
|
||||||
|
setTimeout(() => {
|
||||||
|
v.style.maxHeight = `${!newValue ? 0 : v.scrollHeight}px`
|
||||||
|
}, 50)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<template lang="pug">
|
||||||
|
div(
|
||||||
|
:class="classObject",
|
||||||
|
:data-id="item.id",
|
||||||
|
role="group",
|
||||||
|
:aria-labelledby="ariaLabelledby",
|
||||||
|
:aria-describedby="itemDescId"
|
||||||
|
)
|
||||||
|
.pretix-widget-item-row.pretix-widget-main-item-row
|
||||||
|
//- Product description
|
||||||
|
.pretix-widget-item-info-col
|
||||||
|
a.pretix-widget-item-picture-link(
|
||||||
|
v-if="item.picture",
|
||||||
|
:href="item.picture_fullsize",
|
||||||
|
@click.prevent.stop="lightbox"
|
||||||
|
)
|
||||||
|
img.pretix-widget-item-picture(:src="item.picture", :alt="pictureAltText")
|
||||||
|
.pretix-widget-item-title-and-description
|
||||||
|
strong.pretix-widget-item-title(
|
||||||
|
:id="itemLabelId",
|
||||||
|
role="heading",
|
||||||
|
:aria-level="headingLevel"
|
||||||
|
) {{ item.name }}
|
||||||
|
.pretix-widget-item-description(
|
||||||
|
v-if="item.description",
|
||||||
|
:id="itemDescId",
|
||||||
|
v-html="item.description"
|
||||||
|
)
|
||||||
|
p.pretix-widget-item-meta(v-if="item.order_min && item.order_min > 1")
|
||||||
|
small {{ minOrderStr }}
|
||||||
|
p.pretix-widget-item-meta(
|
||||||
|
v-if="!item.has_variations && item.avail[1] !== null && item.avail[0] === 100"
|
||||||
|
)
|
||||||
|
small {{ quotaLeftStr }}
|
||||||
|
|
||||||
|
//- Price
|
||||||
|
.pretix-widget-item-price-col(:id="itemPriceId")
|
||||||
|
PriceBox(
|
||||||
|
v-if="!item.has_variations && showPrices",
|
||||||
|
:price="item.price",
|
||||||
|
:freePrice="item.free_price",
|
||||||
|
:mandatoryPricedAddons="item.mandatory_priced_addons",
|
||||||
|
:suggestedPrice="item.suggested_price",
|
||||||
|
:fieldName="`price_${item.id}`",
|
||||||
|
:originalPrice="item.original_price",
|
||||||
|
:itemId="item.id"
|
||||||
|
)
|
||||||
|
.pretix-widget-pricebox(v-if="item.has_variations && showPrices", v-html="pricerange")
|
||||||
|
span(v-if="!showPrices")
|
||||||
|
|
||||||
|
//- Availability
|
||||||
|
.pretix-widget-item-availability-col
|
||||||
|
button.pretix-widget-collapse-indicator(
|
||||||
|
v-if="showToggle",
|
||||||
|
type="button",
|
||||||
|
:aria-expanded="expanded ? 'true' : 'false'",
|
||||||
|
:aria-controls="`${item.id}-variants`",
|
||||||
|
:aria-describedby="itemDescId",
|
||||||
|
@click.prevent.stop="expand"
|
||||||
|
) {{ variationsToggleLabel }}
|
||||||
|
AvailBox(v-if="!item.has_variations", :item="item")
|
||||||
|
|
||||||
|
.pretix-widget-clear
|
||||||
|
|
||||||
|
//- Variations
|
||||||
|
div(
|
||||||
|
v-if="item.has_variations",
|
||||||
|
:id="`${item.id}-variants`",
|
||||||
|
ref="variations",
|
||||||
|
:class="varClasses"
|
||||||
|
)
|
||||||
|
Variation(
|
||||||
|
v-for="variation in item.variations",
|
||||||
|
:key="variation.id",
|
||||||
|
:variation="variation",
|
||||||
|
:item="item",
|
||||||
|
:category="category"
|
||||||
|
)
|
||||||
|
</template>
|
||||||
|
<style lang="sass">
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, watch, onMounted, onUnmounted, inject, nextTick } from 'vue'
|
||||||
|
import { StoreKey } from '~/sharedStore'
|
||||||
|
import { STRINGS } from '~/i18n'
|
||||||
|
|
||||||
|
const store = inject(StoreKey)!
|
||||||
|
|
||||||
|
const cancelBlocked = ref(false)
|
||||||
|
const lightboxImage = ref<HTMLImageElement>()
|
||||||
|
const frameDialog = ref<HTMLDialogElement>()
|
||||||
|
const alertDialog = ref<HTMLDialogElement>()
|
||||||
|
const lightboxDialog = ref<HTMLDialogElement>()
|
||||||
|
const iframe = ref<HTMLIFrameElement>()
|
||||||
|
const closeButton = ref<HTMLButtonElement>()
|
||||||
|
|
||||||
|
const frameClasses = computed(() => ({
|
||||||
|
'pretix-widget-frame-holder': true,
|
||||||
|
'pretix-widget-frame-shown': store.overlay.frameShown || store.overlay.frameLoading,
|
||||||
|
'pretix-widget-frame-isloading': store.overlay.frameLoading,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const alertClasses = computed(() => ({
|
||||||
|
'pretix-widget-alert-holder': true,
|
||||||
|
'pretix-widget-alert-shown': store.overlay.errorMessage,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const lightboxClasses = computed(() => ({
|
||||||
|
'pretix-widget-lightbox-holder': true,
|
||||||
|
'pretix-widget-lightbox-shown': store.overlay.lightbox,
|
||||||
|
'pretix-widget-lightbox-isloading': store.overlay.lightbox?.loading,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const cancelBlockedClasses = computed(() => ({
|
||||||
|
'pretix-widget-visibility-hidden': !cancelBlocked.value,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const errorMessageId = computed(() => `${store.htmlId}-error-message`)
|
||||||
|
|
||||||
|
function onMessage (e: MessageEvent) {
|
||||||
|
if (e.data.type && e.data.type === 'pretix:widget:title') {
|
||||||
|
if (iframe.value) {
|
||||||
|
iframe.value.title = e.data.title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function lightboxClose () {
|
||||||
|
store.overlay.lightbox = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function lightboxLoaded () {
|
||||||
|
if (store.overlay.lightbox) {
|
||||||
|
store.overlay.lightbox.loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function errorClose (e: Event) {
|
||||||
|
const dialog = e.target as HTMLDialogElement
|
||||||
|
if (dialog.returnValue === 'continue' && store.overlay.errorUrlAfter) {
|
||||||
|
if (store.overlay.errorUrlAfterNewTab) {
|
||||||
|
window.open(store.overlay.errorUrlAfter)
|
||||||
|
} else {
|
||||||
|
store.overlay.frameSrc = store.overlay.errorUrlAfter
|
||||||
|
store.overlay.frameLoading = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
store.overlay.errorMessage = null
|
||||||
|
store.overlay.errorUrlAfter = null
|
||||||
|
store.overlay.errorUrlAfterNewTab = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function close () {
|
||||||
|
if (store.overlay.frameLoading) {
|
||||||
|
frameDialog.value?.showModal()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
store.overlay.frameShown = false
|
||||||
|
store.frameDismissed = true
|
||||||
|
store.overlay.frameSrc = ''
|
||||||
|
store.reload()
|
||||||
|
triggerCloseCallback()
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancel (e: Event) {
|
||||||
|
if (store.overlay.frameLoading) {
|
||||||
|
e.preventDefault()
|
||||||
|
const target = e.target as HTMLElement
|
||||||
|
target.addEventListener('animationend', function () {
|
||||||
|
target.classList.remove('pretix-widget-shake-once')
|
||||||
|
}, { once: true })
|
||||||
|
target.classList.add('pretix-widget-shake-once')
|
||||||
|
cancelBlocked.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function iframeLoaded () {
|
||||||
|
if (store.overlay.frameLoading) {
|
||||||
|
store.overlay.frameLoading = false
|
||||||
|
cancelBlocked.value = false
|
||||||
|
if (store.overlay.frameSrc) {
|
||||||
|
store.overlay.frameShown = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function triggerCloseCallback () {
|
||||||
|
nextTick(() => {
|
||||||
|
for (const callback of (window as any).PretixWidget._closed || []) {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => store.overlay.lightbox, (newValue, oldValue) => {
|
||||||
|
if (newValue) {
|
||||||
|
if (newValue.image !== oldValue?.image) {
|
||||||
|
newValue.loading = true
|
||||||
|
}
|
||||||
|
if (!oldValue) {
|
||||||
|
lightboxDialog.value?.showModal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => store.overlay.errorMessage, (newValue, oldValue) => {
|
||||||
|
if (newValue && !oldValue) {
|
||||||
|
alertDialog.value?.showModal()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => store.overlay.frameShown, (newValue) => {
|
||||||
|
if (newValue) {
|
||||||
|
nextTick(() => {
|
||||||
|
closeButton.value?.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => store.overlay.frameSrc, (newValue, oldValue) => {
|
||||||
|
if (newValue && !oldValue) {
|
||||||
|
store.overlay.frameLoading = true
|
||||||
|
}
|
||||||
|
if (iframe.value) {
|
||||||
|
iframe.value.src = newValue || 'about:blank'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => store.overlay.frameLoading, (newValue) => {
|
||||||
|
if (newValue) {
|
||||||
|
if (frameDialog.value && !frameDialog.value.open) {
|
||||||
|
frameDialog.value.showModal()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!store.overlay.frameSrc && frameDialog.value?.open) {
|
||||||
|
frameDialog.value.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('message', onMessage, false)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('message', onMessage, false)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template lang="pug">
|
||||||
|
Teleport(to="body")
|
||||||
|
.pretix-widget-overlay
|
||||||
|
//- Iframe dialog
|
||||||
|
dialog(ref="frameDialog", :class="frameClasses", :aria-label="STRINGS.checkout", @close="close", @cancel="cancel")
|
||||||
|
.pretix-widget-frame-loading(v-show="store.overlay.frameLoading")
|
||||||
|
svg(width="256", height="256", viewBox="0 0 1792 1792", xmlns="http://www.w3.org/2000/svg")
|
||||||
|
path.pretix-widget-primary-color(d="M1152 896q0-106-75-181t-181-75-181 75-75 181 75 181 181 75 181-75 75-181zm512-109v222q0 12-8 23t-20 13l-185 28q-19 54-39 91 35 50 107 138 10 12 10 25t-9 23q-27 37-99 108t-94 71q-12 0-26-9l-138-108q-44 23-91 38-16 136-29 186-7 28-36 28h-222q-14 0-24.5-8.5t-11.5-21.5l-28-184q-49-16-90-37l-141 107q-10 9-25 9-14 0-25-11-126-114-165-168-7-10-7-23 0-12 8-23 15-21 51-66.5t54-70.5q-27-50-41-99l-183-27q-13-2-21-12.5t-8-23.5v-222q0-12 8-23t19-13l186-28q14-46 39-92-40-57-107-138-10-12-10-24 0-10 9-23 26-36 98.5-107.5t94.5-71.5q13 0 26 10l138 107q44-23 91-38 16-136 29-186 7-28 36-28h222q14 0 24.5 8.5t11.5 21.5l28 184q49 16 90 37l142-107q9-9 24-9 13 0 25 10 129 119 165 170 7 8 7 22 0 12-8 23-15 21-51 66.5t-54 70.5q26 50 41 98l183 28q13 2 21 12.5t8 23.5z")
|
||||||
|
p(:class="cancelBlockedClasses")
|
||||||
|
strong {{ STRINGS.cancel_blocked }}
|
||||||
|
.pretix-widget-frame-inner(v-show="store.overlay.frameShown")
|
||||||
|
form.pretix-widget-frame-close(method="dialog")
|
||||||
|
button(ref="closeButton", :aria-label="STRINGS.close_checkout", autofocus)
|
||||||
|
svg(:alt="STRINGS.close", height="16", viewBox="0 0 512 512", width="16", xmlns="http://www.w3.org/2000/svg")
|
||||||
|
path(fill="#fff", d="M437.5,386.6L306.9,256l130.6-130.6c14.1-14.1,14.1-36.8,0-50.9c-14.1-14.1-36.8-14.1-50.9,0L256,205.1L125.4,74.5 c-14.1-14.1-36.8-14.1-50.9,0c-14.1,14.1-14.1,36.8,0,50.9L205.1,256L74.5,386.6c-14.1,14.1-14.1,36.8,0,50.9 c14.1,14.1,36.8,14.1,50.9,0L256,306.9l130.6,130.6c14.1,14.1,36.8,14.1,50.9,0C451.5,423.4,451.5,400.6,437.5,386.6z")
|
||||||
|
iframe(
|
||||||
|
ref="iframe",
|
||||||
|
frameborder="0",
|
||||||
|
width="650",
|
||||||
|
height="650",
|
||||||
|
:name="store.widgetId",
|
||||||
|
src="about:blank",
|
||||||
|
allow="autoplay *; camera *; fullscreen *; payment *",
|
||||||
|
:title="STRINGS.checkout",
|
||||||
|
referrerpolicy="origin",
|
||||||
|
@load="iframeLoaded"
|
||||||
|
) Please enable frames in your browser!
|
||||||
|
|
||||||
|
//- Alert dialog
|
||||||
|
dialog(ref="alertDialog", :class="alertClasses", role="alertdialog", :aria-labelledby="errorMessageId", @close="errorClose")
|
||||||
|
form.pretix-widget-alert-box(method="dialog")
|
||||||
|
p(:id="errorMessageId") {{ store.overlay.errorMessage }}
|
||||||
|
p
|
||||||
|
button(v-if="store.overlay.errorUrlAfter", value="continue", autofocus, :aria-describedby="errorMessageId")
|
||||||
|
| {{ STRINGS.continue }}
|
||||||
|
button(v-else, autofocus, :aria-describedby="errorMessageId") {{ STRINGS.close }}
|
||||||
|
transition(name="bounce")
|
||||||
|
svg.pretix-widget-alert-icon(v-if="store.overlay.errorMessage", width="64", height="64", viewBox="0 0 1792 1792", xmlns="http://www.w3.org/2000/svg")
|
||||||
|
path(style="fill:#ffffff;", d="M 599.86438,303.72882 H 1203.5254 V 1503.4576 H 599.86438 Z")
|
||||||
|
path.pretix-widget-primary-color(d="M896 128q209 0 385.5 103t279.5 279.5 103 385.5-103 385.5-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103zm128 1247v-190q0-14-9-23.5t-22-9.5h-192q-13 0-23 10t-10 23v190q0 13 10 23t23 10h192q13 0 22-9.5t9-23.5zm-2-344l18-621q0-12-10-18-10-8-24-8h-220q-14 0-24 8-10 6-10 18l17 621q0 10 10 17.5t24 7.5h185q14 0 23.5-7.5t10.5-17.5z")
|
||||||
|
|
||||||
|
//- Lightbox dialog
|
||||||
|
dialog(ref="lightboxDialog", :class="lightboxClasses", role="alertdialog", @close="lightboxClose")
|
||||||
|
.pretix-widget-lightbox-loading(v-if="store.overlay.lightbox?.loading")
|
||||||
|
svg(width="256", height="256", viewBox="0 0 1792 1792", xmlns="http://www.w3.org/2000/svg")
|
||||||
|
path.pretix-widget-primary-color(d="M1152 896q0-106-75-181t-181-75-181 75-75 181 75 181 181 75 181-75 75-181zm512-109v222q0 12-8 23t-20 13l-185 28q-19 54-39 91 35 50 107 138 10 12 10 25t-9 23q-27 37-99 108t-94 71q-12 0-26-9l-138-108q-44 23-91 38-16 136-29 186-7 28-36 28h-222q-14 0-24.5-8.5t-11.5-21.5l-28-184q-49-16-90-37l-141 107q-10 9-25 9-14 0-25-11-126-114-165-168-7-10-7-23 0-12 8-23 15-21 51-66.5t54-70.5q-27-50-41-99l-183-27q-13-2-21-12.5t-8-23.5v-222q0-12 8-23t19-13l186-28q14-46 39-92-40-57-107-138-10-12-10-24 0-10 9-23 26-36 98.5-107.5t94.5-71.5q13 0 26 10l138 107q44-23 91-38 16-136 29-186 7-28 36-28h222q14 0 24.5 8.5t11.5 21.5l28 184q49 16 90 37l142-107q9-9 24-9 13 0 25 10 129 119 165 170 7 8 7 22 0 12-8 23-15 21-51 66.5t-54 70.5q26 50 41 98l183 28q13 2 21 12.5t8 23.5z")
|
||||||
|
.pretix-widget-lightbox-inner(v-if="store.overlay.lightbox")
|
||||||
|
form.pretix-widget-lightbox-close(method="dialog")
|
||||||
|
button(:aria-label="STRINGS.close", autofocus)
|
||||||
|
svg(:alt="STRINGS.close", height="16", viewBox="0 0 512 512", width="16", xmlns="http://www.w3.org/2000/svg")
|
||||||
|
path(fill="#fff", d="M437.5,386.6L306.9,256l130.6-130.6c14.1-14.1,14.1-36.8,0-50.9c-14.1-14.1-36.8-14.1-50.9,0L256,205.1L125.4,74.5 c-14.1-14.1-36.8-14.1-50.9,0c-14.1,14.1-14.1,36.8,0,50.9L205.1,256L74.5,386.6c-14.1,14.1-14.1,36.8,0,50.9 c14.1,14.1,36.8,14.1,50.9,0L256,306.9l130.6,130.6c14.1,14.1,36.8,14.1,50.9,0C451.5,423.4,451.5,400.6,437.5,386.6z")
|
||||||
|
figure.pretix-widget-lightbox-image
|
||||||
|
img(
|
||||||
|
ref="lightboxImage",
|
||||||
|
:src="store.overlay.lightbox.image",
|
||||||
|
:alt="store.overlay.lightbox.description",
|
||||||
|
crossorigin,
|
||||||
|
@load="lightboxLoaded"
|
||||||
|
)
|
||||||
|
figcaption(v-if="store.overlay.lightbox.description") {{ store.overlay.lightbox.description }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, inject } from 'vue'
|
||||||
|
import type { Price } from '~/types'
|
||||||
|
import { StoreKey } from '~/sharedStore'
|
||||||
|
import { STRINGS, interpolate } from '~/i18n'
|
||||||
|
import { floatformat, autofloatformat, stripHTML } from '~/utils'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
price: Price
|
||||||
|
freePrice: boolean
|
||||||
|
fieldName: string
|
||||||
|
suggestedPrice?: Price | null
|
||||||
|
originalPrice?: string | null
|
||||||
|
mandatoryPricedAddons?: boolean
|
||||||
|
itemId: number
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const store = inject(StoreKey)!
|
||||||
|
|
||||||
|
const priceBoxId = computed(() => `${store.htmlId}-item-pricebox-${props.itemId}`)
|
||||||
|
|
||||||
|
const priceDescId = computed(() => `${store.htmlId}-item-pricedesc-${props.itemId}`)
|
||||||
|
|
||||||
|
const ariaLabelledby = computed(() => `${store.htmlId}-item-label-${props.itemId} ${priceBoxId.value}`)
|
||||||
|
|
||||||
|
const displayPrice = computed(() => {
|
||||||
|
if (store.displayNetPrices) {
|
||||||
|
return floatformat(parseFloat(props.price.net), 2)
|
||||||
|
}
|
||||||
|
return floatformat(parseFloat(props.price.gross), 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
const displayPriceNonlocalized = computed(() => {
|
||||||
|
if (store.displayNetPrices) {
|
||||||
|
return parseFloat(props.price.net).toFixed(2)
|
||||||
|
}
|
||||||
|
return parseFloat(props.price.gross).toFixed(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
const suggestedPriceNonlocalized = computed(() => {
|
||||||
|
const price = props.suggestedPrice ?? props.price
|
||||||
|
if (store.displayNetPrices) {
|
||||||
|
return parseFloat(price.net).toFixed(2)
|
||||||
|
}
|
||||||
|
return parseFloat(price.gross).toFixed(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO BAD
|
||||||
|
const originalLine = computed(() => {
|
||||||
|
if (!props.originalPrice) return ''
|
||||||
|
return `<span class="pretix-widget-pricebox-currency">${store.currency}</span> ${floatformat(parseFloat(props.originalPrice), 2)}`
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO BAD
|
||||||
|
const priceline = computed(() => {
|
||||||
|
if (props.price.gross === '0.00') {
|
||||||
|
if (props.mandatoryPricedAddons && !props.originalPrice) {
|
||||||
|
return '\u00A0' // nbsp
|
||||||
|
}
|
||||||
|
return STRINGS.free
|
||||||
|
}
|
||||||
|
return `<span class="pretix-widget-pricebox-currency">${store.currency}</span> ${displayPrice.value}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const originalPriceAriaLabel = computed(() => interpolate(STRINGS.original_price, [stripHTML(originalLine.value)]))
|
||||||
|
|
||||||
|
const newPriceAriaLabel = computed(() => interpolate(STRINGS.new_price, [stripHTML(priceline.value)]))
|
||||||
|
|
||||||
|
const taxline = computed(() => {
|
||||||
|
if (store.displayNetPrices) {
|
||||||
|
if (props.price.includes_mixed_tax_rate) {
|
||||||
|
return STRINGS.tax_plus_mixed
|
||||||
|
}
|
||||||
|
return interpolate(STRINGS.tax_plus, {
|
||||||
|
rate: autofloatformat(props.price.rate, 2),
|
||||||
|
taxname: props.price.name,
|
||||||
|
}, true)
|
||||||
|
} else {
|
||||||
|
if (props.price.includes_mixed_tax_rate) {
|
||||||
|
return STRINGS.tax_incl_mixed
|
||||||
|
}
|
||||||
|
return interpolate(STRINGS.tax_incl, {
|
||||||
|
rate: autofloatformat(props.price.rate, 2),
|
||||||
|
taxname: props.price.name,
|
||||||
|
}, true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const showTaxline = computed(() => props.price.rate !== '0.00' && props.price.gross !== '0.00')
|
||||||
|
</script>
|
||||||
|
<template lang="pug">
|
||||||
|
.pretix-widget-pricebox
|
||||||
|
span(v-if="!freePrice && !originalPrice", v-html="priceline")
|
||||||
|
span(v-if="!freePrice && originalPrice")
|
||||||
|
del.pretix-widget-pricebox-original-price(:aria-label="originalPriceAriaLabel", v-html="originalLine")
|
||||||
|
|
|
||||||
|
ins.pretix-widget-pricebox-new-price(:aria-label="newPriceAriaLabel", v-html="priceline")
|
||||||
|
div(v-if="freePrice")
|
||||||
|
span.pretix-widget-pricebox-currency(:id="priceBoxId") {{ store.currency }}
|
||||||
|
|
|
||||||
|
input.pretix-widget-pricebox-price-input(
|
||||||
|
type="number",
|
||||||
|
placeholder="0",
|
||||||
|
:min="displayPriceNonlocalized",
|
||||||
|
:value="suggestedPriceNonlocalized",
|
||||||
|
:name="fieldName",
|
||||||
|
step="any",
|
||||||
|
:aria-labelledby="ariaLabelledby",
|
||||||
|
:aria-describedby="priceDescId"
|
||||||
|
)
|
||||||
|
small.pretix-widget-pricebox-tax(v-if="showTaxline", :id="priceDescId") {{ taxline }}
|
||||||
|
</template>
|
||||||
|
<style lang="sass">
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, inject } from 'vue'
|
||||||
|
import type { Variation, Item, Category } from '~/types'
|
||||||
|
import { StoreKey } from '~/sharedStore'
|
||||||
|
import { STRINGS, interpolate } from '~/i18n'
|
||||||
|
import AvailBox from './AvailBox.vue'
|
||||||
|
import PriceBox from './PriceBox.vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
variation: Variation
|
||||||
|
item: Item
|
||||||
|
category: Category
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const store = inject(StoreKey)!
|
||||||
|
|
||||||
|
const origPrice = computed(() => props.variation.original_price || props.item.original_price)
|
||||||
|
|
||||||
|
const quotaLeftStr = computed(() => interpolate(STRINGS.quota_left, [props.variation.avail[1]]))
|
||||||
|
|
||||||
|
const variationLabelId = computed(() => `${store.htmlId}-variation-label-${props.item.id}-${props.variation.id}`)
|
||||||
|
|
||||||
|
const variationDescId = computed(() => `${store.htmlId}-variation-desc-${props.item.id}-${props.variation.id}`)
|
||||||
|
|
||||||
|
const variationPriceId = computed(() => `${store.htmlId}-variation-price-${props.item.id}-${props.variation.id}`)
|
||||||
|
|
||||||
|
const ariaLabelledby = computed(() => `${variationLabelId.value} ${variationPriceId.value}`)
|
||||||
|
|
||||||
|
const headingLevel = computed(() => props.category.name ? '5' : '4')
|
||||||
|
|
||||||
|
const showQuotaLeft = computed(() => props.variation.avail[1] !== null && props.variation.avail[0] === 100)
|
||||||
|
|
||||||
|
// TODO dedupe?
|
||||||
|
const showPrices = computed(() => {
|
||||||
|
// Determine if prices should be shown
|
||||||
|
let hasPriced = false
|
||||||
|
let cntItems = 0
|
||||||
|
for (const cat of store.categories) {
|
||||||
|
for (const item of cat.items) {
|
||||||
|
if (item.has_variations) {
|
||||||
|
cntItems += item.variations.length
|
||||||
|
hasPriced = true
|
||||||
|
} else {
|
||||||
|
cntItems++
|
||||||
|
hasPriced = hasPriced || item.price.gross !== '0.00' || item.free_price
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hasPriced || cntItems > 1
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<template lang="pug">
|
||||||
|
.pretix-widget-variation(
|
||||||
|
:data-id="variation.id",
|
||||||
|
role="group",
|
||||||
|
:aria-labelledby="ariaLabelledby",
|
||||||
|
:aria-describedby="variationDescId"
|
||||||
|
)
|
||||||
|
.pretix-widget-item-row
|
||||||
|
//- Variation description
|
||||||
|
.pretix-widget-item-info-col
|
||||||
|
.pretix-widget-item-title-and-description
|
||||||
|
strong.pretix-widget-item-title(
|
||||||
|
:id="variationLabelId",
|
||||||
|
role="heading",
|
||||||
|
:aria-level="headingLevel"
|
||||||
|
) {{ variation.value }}
|
||||||
|
.pretix-widget-item-description(
|
||||||
|
v-if="variation.description",
|
||||||
|
:id="variationDescId",
|
||||||
|
v-html="variation.description"
|
||||||
|
)
|
||||||
|
p.pretix-widget-item-meta(v-if="showQuotaLeft")
|
||||||
|
small {{ quotaLeftStr }}
|
||||||
|
|
||||||
|
//- Price
|
||||||
|
.pretix-widget-item-price-col(:id="variationPriceId")
|
||||||
|
PriceBox(
|
||||||
|
v-if="showPrices",
|
||||||
|
:price="variation.price",
|
||||||
|
:freePrice="item.free_price",
|
||||||
|
:originalPrice="origPrice",
|
||||||
|
:mandatoryPricedAddons="item.mandatory_priced_addons",
|
||||||
|
:suggestedPrice="variation.suggested_price",
|
||||||
|
:fieldName="`price_${item.id}_${variation.id}`",
|
||||||
|
:itemId="item.id"
|
||||||
|
)
|
||||||
|
span(v-if="!showPrices")
|
||||||
|
|
||||||
|
//- Availability
|
||||||
|
.pretix-widget-item-availability-col
|
||||||
|
AvailBox(:item="item", :variation="variation")
|
||||||
|
|
||||||
|
.pretix-widget-clear
|
||||||
|
</template>
|
||||||
|
<style lang="sass">
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, inject, ref, onMounted, watch } from 'vue'
|
||||||
|
import { StoreKey } from '~/sharedStore'
|
||||||
|
import { STRINGS } from '~/i18n'
|
||||||
|
import EventForm from './EventForm.vue'
|
||||||
|
import EventList from './EventList.vue'
|
||||||
|
import EventCalendar from './EventCalendar.vue'
|
||||||
|
import EventWeekCalendar from './EventWeekCalendar.vue'
|
||||||
|
import Overlay from './Overlay.vue'
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
mounted: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const store = inject(StoreKey)!
|
||||||
|
|
||||||
|
const wrapper = ref<HTMLDivElement>()
|
||||||
|
const formcomp = ref<InstanceType<typeof EventForm>>()
|
||||||
|
const mobile = ref(false)
|
||||||
|
|
||||||
|
const classObject = computed(() => ({
|
||||||
|
'pretix-widget': true,
|
||||||
|
'pretix-widget-mobile': mobile.value,
|
||||||
|
'pretix-widget-use-custom-spinners': true,
|
||||||
|
}))
|
||||||
|
|
||||||
|
watch(mobile, (newValue) => {
|
||||||
|
store.mobile = newValue
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (wrapper.value) {
|
||||||
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
|
mobile.value = entries[0].contentRect.width <= 800
|
||||||
|
})
|
||||||
|
resizeObserver.observe(wrapper.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
store.reload()
|
||||||
|
emit('mounted')
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => store.view, (newValue, oldValue) => {
|
||||||
|
if (oldValue && wrapper.value) {
|
||||||
|
const rect = wrapper.value.getBoundingClientRect()
|
||||||
|
if (rect.top < 0) {
|
||||||
|
wrapper.value.scrollIntoView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<template lang="pug">
|
||||||
|
.pretix-widget-wrapper(ref="wrapper", tabindex="0", role="article", :aria-label="store.name")
|
||||||
|
div(:class="classObject")
|
||||||
|
.pretix-widget-loading(v-show="store.loading > 0")
|
||||||
|
svg(width="128", height="128", viewBox="0 0 1792 1792", xmlns="http://www.w3.org/2000/svg")
|
||||||
|
path.pretix-widget-primary-color(d="M1152 896q0-106-75-181t-181-75-181 75-75 181 75 181 181 75 181-75 75-181zm512-109v222q0 12-8 23t-20 13l-185 28q-19 54-39 91 35 50 107 138 10 12 10 25t-9 23q-27 37-99 108t-94 71q-12 0-26-9l-138-108q-44 23-91 38-16 136-29 186-7 28-36 28h-222q-14 0-24.5-8.5t-11.5-21.5l-28-184q-49-16-90-37l-141 107q-10 9-25 9-14 0-25-11-126-114-165-168-7-10-7-23 0-12 8-23 15-21 51-66.5t54-70.5q-27-50-41-99l-183-27q-13-2-21-12.5t-8-23.5v-222q0-12 8-23t19-13l186-28q14-46 39-92-40-57-107-138-10-12-10-24 0-10 9-23 26-36 98.5-107.5t94.5-71.5q13 0 26 10l138 107q44-23 91-38 16-136 29-186 7-28 36-28h222q14 0 24.5 8.5t11.5 21.5l28 184q49 16 90 37l142-107q9-9 24-9 13 0 25 10 129 119 165 170 7 8 7 22 0 12-8 23-15 21-51 66.5t-54 70.5q26 50 41 98l183 28q13 2 21 12.5t8 23.5z")
|
||||||
|
|
||||||
|
.pretix-widget-error-message(v-if="store.error && store.view !== 'event'") {{ store.error }}
|
||||||
|
.pretix-widget-error-action(v-if="store.error && store.connectionError")
|
||||||
|
a.pretix-widget-button(:href="store.newTabTarget", target="_blank") {{ STRINGS.open_new_tab }}
|
||||||
|
|
||||||
|
EventForm(v-if="store.view === 'event'", ref="formcomp")
|
||||||
|
EventList(v-if="store.view === 'events'")
|
||||||
|
EventCalendar(v-if="store.view === 'weeks'", :mobile="mobile")
|
||||||
|
EventWeekCalendar(v-if="store.view === 'days'", :mobile="mobile")
|
||||||
|
|
||||||
|
.pretix-widget-clear
|
||||||
|
.pretix-widget-attribution(v-if="store.poweredby", v-html="store.poweredby")
|
||||||
|
|
||||||
|
Overlay
|
||||||
|
</template>
|
||||||
|
<style lang="sass">
|
||||||
|
</style>
|
||||||
3
src/pretix/static/pretixpresale/widget/src/globals.d.ts
vendored
Normal file
3
src/pretix/static/pretixpresale/widget/src/globals.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
interface NamedNodeMap {
|
||||||
|
[key: string]: Attr | undefined;
|
||||||
|
}
|
||||||
117
src/pretix/static/pretixpresale/widget/src/i18n.ts
Normal file
117
src/pretix/static/pretixpresale/widget/src/i18n.ts
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
// Internationalization strings for the pretix widget
|
||||||
|
// Django's i18n file expects `this` to be the global object, but ES modules
|
||||||
|
// have `this` as undefined. Import as raw text and execute with a local context.
|
||||||
|
|
||||||
|
// TODO hack
|
||||||
|
import djangoI18nScript from '../../../jsi18n/en/djangojs.js?raw'
|
||||||
|
|
||||||
|
interface Django {
|
||||||
|
pgettext: (context: string, text: string) => string
|
||||||
|
gettext: (text: string) => string
|
||||||
|
interpolate: (fmt: string, obj: Record<string, unknown> | unknown[], named?: boolean) => string
|
||||||
|
get_format: (formatType: string) => string | number
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a local context object to capture django without polluting window
|
||||||
|
const context: { django?: Django } = {}
|
||||||
|
new Function(djangoI18nScript).call(context)
|
||||||
|
const django = context.django!
|
||||||
|
|
||||||
|
export const STRINGS = {
|
||||||
|
quantity: django.pgettext('widget', 'Quantity'),
|
||||||
|
quantity_dec: django.pgettext('widget', 'Decrease quantity'),
|
||||||
|
quantity_inc: django.pgettext('widget', 'Increase quantity'),
|
||||||
|
filter_events_by: django.pgettext('widget', 'Filter events by'),
|
||||||
|
filter: django.pgettext('widget', 'Filter'),
|
||||||
|
price: django.pgettext('widget', 'Price'),
|
||||||
|
original_price: django.pgettext('widget', 'Original price: %s'),
|
||||||
|
new_price: django.pgettext('widget', 'New price: %s'),
|
||||||
|
select: django.pgettext('widget', 'Select'),
|
||||||
|
select_item: django.pgettext('widget', 'Select %s'),
|
||||||
|
select_variant: django.pgettext('widget', 'Select variant %s'),
|
||||||
|
sold_out: django.pgettext('widget', 'Sold out'),
|
||||||
|
buy: django.pgettext('widget', 'Buy'),
|
||||||
|
register: django.pgettext('widget', 'Register'),
|
||||||
|
reserved: django.pgettext('widget', 'Reserved'),
|
||||||
|
free: django.pgettext('widget', 'FREE'),
|
||||||
|
price_from: django.pgettext('widget', 'from %(currency)s %(price)s'),
|
||||||
|
image_of: django.pgettext('widget', 'Image of %s'),
|
||||||
|
tax_incl: django.pgettext('widget', 'incl. %(rate)s% %(taxname)s'),
|
||||||
|
tax_plus: django.pgettext('widget', 'plus %(rate)s% %(taxname)s'),
|
||||||
|
tax_incl_mixed: django.pgettext('widget', 'incl. taxes'),
|
||||||
|
tax_plus_mixed: django.pgettext('widget', 'plus taxes'),
|
||||||
|
quota_left: django.pgettext('widget', 'currently available: %s'),
|
||||||
|
unavailable_require_voucher: django.pgettext('widget', 'Only available with a voucher'),
|
||||||
|
unavailable_available_from: django.pgettext('widget', 'Not yet available'),
|
||||||
|
unavailable_available_until: django.pgettext('widget', 'Not available anymore'),
|
||||||
|
unavailable_active: django.pgettext('widget', 'Currently not available'),
|
||||||
|
unavailable_hidden_if_item_available: django.pgettext('widget', 'Not yet available'),
|
||||||
|
order_min: django.pgettext('widget', 'minimum amount to order: %s'),
|
||||||
|
exit: django.pgettext('widget', 'Close ticket shop'),
|
||||||
|
loading_error: django.pgettext('widget', 'The ticket shop could not be loaded.'),
|
||||||
|
loading_error_429: django.pgettext('widget', 'There are currently a lot of users in this ticket shop. Please open the shop in a new tab to continue.'),
|
||||||
|
open_new_tab: django.pgettext('widget', 'Open ticket shop'),
|
||||||
|
checkout: django.pgettext('widget', 'Checkout'),
|
||||||
|
cart_error: django.pgettext('widget', 'The cart could not be created. Please try again later'),
|
||||||
|
cart_error_429: django.pgettext('widget', 'We could not create your cart, since there are currently too many users in this ticket shop. Please click "Continue" to retry in a new tab.'),
|
||||||
|
waiting_list: django.pgettext('widget', 'Waiting list'),
|
||||||
|
cart_exists: django.pgettext('widget', 'You currently have an active cart for this event. If you select more products, they will be added to your existing cart.'),
|
||||||
|
resume_checkout: django.pgettext('widget', 'Resume checkout'),
|
||||||
|
redeem_voucher: django.pgettext('widget', 'Redeem a voucher'),
|
||||||
|
redeem: django.pgettext('widget', 'Redeem'),
|
||||||
|
voucher_code: django.pgettext('widget', 'Voucher code'),
|
||||||
|
close: django.pgettext('widget', 'Close'),
|
||||||
|
close_checkout: django.pgettext('widget', 'Close checkout'),
|
||||||
|
cancel_blocked: django.pgettext('widget', 'You cannot cancel this operation. Please wait for loading to finish.'),
|
||||||
|
continue: django.pgettext('widget', 'Continue'),
|
||||||
|
variations: django.pgettext('widget', 'Show variants'),
|
||||||
|
hide_variations: django.pgettext('widget', 'Hide variants'),
|
||||||
|
back_to_list: django.pgettext('widget', 'Choose a different event'),
|
||||||
|
back_to_dates: django.pgettext('widget', 'Choose a different date'),
|
||||||
|
back: django.pgettext('widget', 'Back'),
|
||||||
|
next_month: django.pgettext('widget', 'Next month'),
|
||||||
|
previous_month: django.pgettext('widget', 'Previous month'),
|
||||||
|
next_week: django.pgettext('widget', 'Next week'),
|
||||||
|
previous_week: django.pgettext('widget', 'Previous week'),
|
||||||
|
show_seating: django.pgettext('widget', 'Open seat selection'),
|
||||||
|
seating_plan_waiting_list: django.pgettext('widget', 'Some or all ticket categories are currently sold out. If you want, you can add yourself to the waiting list. We will then notify if seats are available again.'),
|
||||||
|
load_more: django.pgettext('widget', 'Load more'),
|
||||||
|
days: {
|
||||||
|
MO: django.gettext('Mo'),
|
||||||
|
TU: django.gettext('Tu'),
|
||||||
|
WE: django.gettext('We'),
|
||||||
|
TH: django.gettext('Th'),
|
||||||
|
FR: django.gettext('Fr'),
|
||||||
|
SA: django.gettext('Sa'),
|
||||||
|
SU: django.gettext('Su'),
|
||||||
|
MONDAY: django.gettext('Monday'),
|
||||||
|
TUESDAY: django.gettext('Tuesday'),
|
||||||
|
WEDNESDAY: django.gettext('Wednesday'),
|
||||||
|
THURSDAY: django.gettext('Thursday'),
|
||||||
|
FRIDAY: django.gettext('Friday'),
|
||||||
|
SATURDAY: django.gettext('Saturday'),
|
||||||
|
SUNDAY: django.gettext('Sunday'),
|
||||||
|
},
|
||||||
|
months: {
|
||||||
|
'01': django.gettext('January'),
|
||||||
|
'02': django.gettext('February'),
|
||||||
|
'03': django.gettext('March'),
|
||||||
|
'04': django.gettext('April'),
|
||||||
|
'05': django.gettext('May'),
|
||||||
|
'06': django.gettext('June'),
|
||||||
|
'07': django.gettext('July'),
|
||||||
|
'08': django.gettext('August'),
|
||||||
|
'09': django.gettext('September'),
|
||||||
|
10: django.gettext('October'),
|
||||||
|
11: django.gettext('November'),
|
||||||
|
12: django.gettext('December'),
|
||||||
|
} as Record<string, string>,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export function interpolate (fmt: string, obj: Record<string, unknown> | unknown[], named = false): string {
|
||||||
|
return django.interpolate(fmt, obj, named)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFormat (formatType: string): string | number {
|
||||||
|
return django.get_format(formatType)
|
||||||
|
}
|
||||||
80
src/pretix/static/pretixpresale/widget/src/lib/store.ts
Normal file
80
src/pretix/static/pretixpresale/widget/src/lib/store.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { reactive, computed, watch } from 'vue'
|
||||||
|
import type { WatchCallback, WatchOptions, UnwrapNestedRefs } from 'vue'
|
||||||
|
|
||||||
|
interface StoreMethods {
|
||||||
|
$reset: () => void
|
||||||
|
$watch: <T>(source: () => T, callback: WatchCallback<T>, options?: WatchOptions) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetterReturnTypes<G> = {
|
||||||
|
readonly [K in keyof G]: G[K] extends () => infer R ? R : never
|
||||||
|
}
|
||||||
|
|
||||||
|
type Store<S, G, A> = UnwrapNestedRefs<S> & GetterReturnTypes<G> & A & StoreMethods
|
||||||
|
type GettersTree<S> = Record<string, (this: S, state: S) => any> | Record<string, () => any>
|
||||||
|
type ActionsTree = Record<string, (...args: any[]) => any>
|
||||||
|
|
||||||
|
export function createStore<
|
||||||
|
S extends object,
|
||||||
|
G extends GettersTree<S>,
|
||||||
|
A extends ActionsTree
|
||||||
|
> (
|
||||||
|
// name: string,
|
||||||
|
config: {
|
||||||
|
state: () => S
|
||||||
|
getters?: G & ThisType<UnwrapNestedRefs<S> & GetterReturnTypes<G> & A & StoreMethods>
|
||||||
|
actions?: A & ThisType<UnwrapNestedRefs<S> & GetterReturnTypes<G> & A & StoreMethods>
|
||||||
|
}
|
||||||
|
): Store<S, G, A> {
|
||||||
|
type StoreType = Store<S, G, A>
|
||||||
|
const store = reactive(config.state()) as StoreType
|
||||||
|
|
||||||
|
// Add getters as computed properties
|
||||||
|
if (config.getters) {
|
||||||
|
for (const key of Object.keys(config.getters) as (keyof G)[]) {
|
||||||
|
const getter = config.getters[key]
|
||||||
|
const computedRef = computed(() => (getter as () => unknown).call(store))
|
||||||
|
Object.defineProperty(store, key, {
|
||||||
|
get: () => computedRef.value,
|
||||||
|
enumerable: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add actions bound to the store
|
||||||
|
if (config.actions) {
|
||||||
|
for (const key of Object.keys(config.actions) as (keyof A)[]) {
|
||||||
|
const action = config.actions[key]
|
||||||
|
;(store as Record<string, unknown>)[key as string] = (action as (...args: unknown[]) => unknown).bind(store)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
store.$reset = function () {
|
||||||
|
const cleanState = config.state()
|
||||||
|
const cleanKeys = new Set([
|
||||||
|
'$reset',
|
||||||
|
'$watch',
|
||||||
|
...Object.keys(cleanState),
|
||||||
|
...Object.keys(config.getters ?? {}),
|
||||||
|
...Object.keys(config.actions ?? {})
|
||||||
|
])
|
||||||
|
|
||||||
|
// Delete any keys that aren't in clean state and aren't known non-state keys
|
||||||
|
for (const key of Object.keys(store)) {
|
||||||
|
if (!cleanKeys.has(key)) {
|
||||||
|
delete (store as Record<string, unknown>)[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set all state values from clean state
|
||||||
|
for (const key of Object.keys(cleanState) as (keyof S)[]) {
|
||||||
|
;(store as S)[key] = cleanState[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
store.$watch = function <T>(source: () => T, callback: WatchCallback<T>, options?: WatchOptions) {
|
||||||
|
watch(source.bind(store), callback.bind(store), options)
|
||||||
|
}
|
||||||
|
|
||||||
|
return store
|
||||||
|
}
|
||||||
130
src/pretix/static/pretixpresale/widget/src/main.ts
Normal file
130
src/pretix/static/pretixpresale/widget/src/main.ts
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import { createApp, nextTick, type App } from 'vue'
|
||||||
|
import { createWidgetInstance } from '~/widget'
|
||||||
|
import { createButtonInstance } from '~/button'
|
||||||
|
import { createWidgetStore, StoreKey } from '~/sharedStore'
|
||||||
|
import ButtonComponent from '~/components/Button.vue'
|
||||||
|
import { docReady, makeid } from '~/utils'
|
||||||
|
import type { WidgetData } from '~/types'
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
PretixWidget: PretixWidgetAPI
|
||||||
|
pretixWidgetCallback?: () => void
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PretixWidgetAPI {
|
||||||
|
build_widgets: boolean
|
||||||
|
widget_data: WidgetData
|
||||||
|
buildWidgets: () => void
|
||||||
|
open: (
|
||||||
|
targetUrl: string,
|
||||||
|
voucher?: string | null,
|
||||||
|
subevent?: string | number | null,
|
||||||
|
items?: { item: string; count: string }[],
|
||||||
|
widgetData?: Record<string, string>,
|
||||||
|
skipSslCheck?: boolean,
|
||||||
|
disableIframe?: boolean
|
||||||
|
) => void
|
||||||
|
addLoadListener: (callback: () => void) => void
|
||||||
|
addCloseListener: (callback: () => void) => void
|
||||||
|
_loaded: Array<() => void>
|
||||||
|
_closed: Array<() => void>
|
||||||
|
}
|
||||||
|
|
||||||
|
const widgetlist: App[] = []
|
||||||
|
const buttonlist: App[] = []
|
||||||
|
|
||||||
|
window.PretixWidget = {
|
||||||
|
build_widgets: true,
|
||||||
|
widget_data: { referer: location.href },
|
||||||
|
// TODO move somewhere else and rename?
|
||||||
|
_loaded: [],
|
||||||
|
_closed: [],
|
||||||
|
buildWidgets,
|
||||||
|
open: openWidget,
|
||||||
|
addLoadListener (f) { this._loaded.push(f) },
|
||||||
|
addCloseListener (f) { this._closed.push(f) },
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildWidgets () {
|
||||||
|
// TODO what does this do?
|
||||||
|
document.createElement('pretix-widget')
|
||||||
|
document.createElement('pretix-button')
|
||||||
|
|
||||||
|
await docReady()
|
||||||
|
const widgetElements = document.querySelectorAll('pretix-widget, div.pretix-widget-compat')
|
||||||
|
for (const [i, el] of Array.from(widgetElements).entries()) {
|
||||||
|
widgetlist.push(createWidgetInstance(el, el.id || `pretix-widget-${i}`))
|
||||||
|
}
|
||||||
|
const buttonElements = document.querySelectorAll('pretix-button, div.pretix-button-compat')
|
||||||
|
for (const [i, el] of Array.from(buttonElements).entries()) {
|
||||||
|
buttonlist.push(createButtonInstance(el, el.id || `pretix-button-${i}`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openWidget (
|
||||||
|
targetUrl: string,
|
||||||
|
voucher?: string | null,
|
||||||
|
subevent?: string | number | null,
|
||||||
|
items?: { item: string; count: string }[],
|
||||||
|
widgetData?: Record<string, string>,
|
||||||
|
skipSslCheck?: boolean,
|
||||||
|
disableIframe?: boolean
|
||||||
|
): void {
|
||||||
|
if (!targetUrl.match(/\/$/)) {
|
||||||
|
targetUrl += '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
const allWidgetData: WidgetData = JSON.parse(JSON.stringify(window.PretixWidget.widget_data))
|
||||||
|
if (widgetData) {
|
||||||
|
Object.assign(allWidgetData, widgetData)
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = document.createElement('div')
|
||||||
|
document.body.appendChild(root)
|
||||||
|
root.classList.add('pretix-widget-hidden')
|
||||||
|
|
||||||
|
const store = createWidgetStore({
|
||||||
|
targetUrl,
|
||||||
|
voucher: voucher ?? null,
|
||||||
|
subevent: subevent ?? null,
|
||||||
|
skipSsl: skipSslCheck ?? false,
|
||||||
|
disableIframe: disableIframe ?? false,
|
||||||
|
widgetData: allWidgetData,
|
||||||
|
htmlId: makeid(16),
|
||||||
|
isButton: true,
|
||||||
|
buttonItems: items ?? [],
|
||||||
|
buttonText: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const app = createApp(ButtonComponent)
|
||||||
|
app.provide(StoreKey, store)
|
||||||
|
app.config.errorHandler = (error, _vm, info) => {
|
||||||
|
console.error('[pretix-widget-open]', info, error)
|
||||||
|
}
|
||||||
|
app.mount(root)
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
if (store.useIframe) {
|
||||||
|
const form = root.querySelector('form') as HTMLFormElement
|
||||||
|
if (form) {
|
||||||
|
const formData = new FormData(form)
|
||||||
|
store.buy(formData)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const form = root.querySelector('form') as HTMLFormElement
|
||||||
|
if (form) form.submit()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof window.pretixWidgetCallback !== 'undefined') {
|
||||||
|
window.pretixWidgetCallback()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.PretixWidget.build_widgets) {
|
||||||
|
window.PretixWidget.buildWidgets()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO debug exposes
|
||||||
519
src/pretix/static/pretixpresale/widget/src/sharedStore.ts
Normal file
519
src/pretix/static/pretixpresale/widget/src/sharedStore.ts
Normal file
@@ -0,0 +1,519 @@
|
|||||||
|
import { nextTick, type InjectionKey } from 'vue'
|
||||||
|
import { createStore } from '~/lib/store'
|
||||||
|
import { fetchProductList, submitCart, checkAsyncTask, ApiError } from '~/api'
|
||||||
|
import type { CartResponse } from '~/api'
|
||||||
|
import { STRINGS } from '~/i18n'
|
||||||
|
import { setCookie, getCookie, makeid } from '~/utils'
|
||||||
|
import type { Category, DayEntry, EventEntry, LightboxState, MetaFilterField, WidgetData } from '~/types'
|
||||||
|
|
||||||
|
export const globalWidgetId = makeid(16)
|
||||||
|
|
||||||
|
export type WidgetStore = ReturnType<typeof createWidgetStore>
|
||||||
|
export const StoreKey: InjectionKey<WidgetStore> = Symbol('WidgetStore')
|
||||||
|
|
||||||
|
export function createWidgetStore (config: {
|
||||||
|
targetUrl: string
|
||||||
|
isButton?: boolean
|
||||||
|
voucher?: string | null
|
||||||
|
subevent?: string | number | null
|
||||||
|
listType?: string | null
|
||||||
|
skipSsl?: boolean
|
||||||
|
disableIframe?: boolean
|
||||||
|
disableVouchers?: boolean
|
||||||
|
disableFilters?: boolean
|
||||||
|
displayEventInfo?: boolean | null
|
||||||
|
filter?: string | null
|
||||||
|
items?: string | null
|
||||||
|
categories?: string | null
|
||||||
|
variations?: string | null
|
||||||
|
widgetData: WidgetData
|
||||||
|
htmlId: string
|
||||||
|
// Button-specific
|
||||||
|
buttonItems?: { item: string; count: string }[]
|
||||||
|
buttonText?: string
|
||||||
|
}) {
|
||||||
|
return createStore({
|
||||||
|
state: () => ({
|
||||||
|
// Target/URL state
|
||||||
|
targetUrl: config.targetUrl,
|
||||||
|
parentStack: [] as string[],
|
||||||
|
subevent: config.subevent ?? null as string | number | null,
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
voucherCode: config.voucher ?? null as string | null,
|
||||||
|
skipSsl: config.skipSsl ?? false,
|
||||||
|
disableIframe: config.disableIframe ?? false,
|
||||||
|
disableVouchers: config.disableVouchers ?? false,
|
||||||
|
disableFilters: config.disableFilters ?? false,
|
||||||
|
displayEventInfo: config.displayEventInfo ?? null as boolean | null,
|
||||||
|
filter: config.filter ?? null as string | null,
|
||||||
|
itemFilter: config.items ?? null as string | null,
|
||||||
|
categoryFilter: config.categories ?? null as string | null,
|
||||||
|
variationFilter: config.variations ?? null as string | null,
|
||||||
|
style: config.listType ?? null as string | null,
|
||||||
|
widgetData: config.widgetData,
|
||||||
|
widgetId: `pretix-widget-${globalWidgetId}`,
|
||||||
|
htmlId: config.htmlId,
|
||||||
|
|
||||||
|
// View state
|
||||||
|
view: null as 'event' | 'events' | 'weeks' | 'days' | null,
|
||||||
|
loading: config.isButton ? 0 : 1,
|
||||||
|
error: null as string | null,
|
||||||
|
connectionError: false,
|
||||||
|
frameDismissed: false,
|
||||||
|
|
||||||
|
// Event data
|
||||||
|
name: null as string | null,
|
||||||
|
dateRange: null as string | null,
|
||||||
|
location: null as string | null,
|
||||||
|
frontpageText: null as string | null,
|
||||||
|
categories: [] as Category[],
|
||||||
|
currency: '',
|
||||||
|
displayNetPrices: false,
|
||||||
|
voucherExplanationText: null as string | null,
|
||||||
|
displayAddToCart: false,
|
||||||
|
waitingListEnabled: false,
|
||||||
|
showVariationsExpanded: !!config.variations,
|
||||||
|
cartId: null as string | null,
|
||||||
|
cartExists: false,
|
||||||
|
vouchersExist: false,
|
||||||
|
hasSeatingPlan: false,
|
||||||
|
hasSeatingPlanWaitinglist: false,
|
||||||
|
itemnum: 0,
|
||||||
|
poweredby: '',
|
||||||
|
|
||||||
|
// Calendar/list data
|
||||||
|
events: null as EventEntry[] | null,
|
||||||
|
weeks: null as DayEntry[][] | null,
|
||||||
|
days: null as DayEntry[] | null,
|
||||||
|
date: null as string | null,
|
||||||
|
week: null as [number, number] | null,
|
||||||
|
hasMoreEvents: false,
|
||||||
|
offset: 0,
|
||||||
|
appendEvents: false,
|
||||||
|
metaFilterFields: [] as MetaFilterField[],
|
||||||
|
|
||||||
|
// UI state
|
||||||
|
mobile: false,
|
||||||
|
|
||||||
|
// Button-specific
|
||||||
|
isButton: config.isButton ?? false,
|
||||||
|
items: (config.buttonItems ?? []) as { item: string; count: string }[],
|
||||||
|
buttonText: config.buttonText ?? '',
|
||||||
|
|
||||||
|
// Overlay (always initialized, no null guards)
|
||||||
|
overlay: {
|
||||||
|
frameSrc: '',
|
||||||
|
frameLoading: false,
|
||||||
|
frameShown: false,
|
||||||
|
errorMessage: null as string | null,
|
||||||
|
errorUrlAfter: null as string | null,
|
||||||
|
errorUrlAfterNewTab: false,
|
||||||
|
lightbox: null as LightboxState | null,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Async task state
|
||||||
|
asyncTaskId: null as string | null,
|
||||||
|
asyncTaskCheckUrl: null as string | null,
|
||||||
|
asyncTaskTimeout: null as ReturnType<typeof setTimeout> | null,
|
||||||
|
asyncTaskInterval: 100,
|
||||||
|
voucher: null as string | null,
|
||||||
|
}),
|
||||||
|
|
||||||
|
getters: {
|
||||||
|
useIframe (): boolean {
|
||||||
|
if ((window as any).crossOriginIsolated === true) return false
|
||||||
|
return !this.disableIframe && (this.skipSsl || /https.*/.test(document.location.protocol))
|
||||||
|
},
|
||||||
|
|
||||||
|
cookieName (): string {
|
||||||
|
return `pretix_widget_${this.targetUrl.replace(/[^a-zA-Z0-9]+/g, '_')}`
|
||||||
|
},
|
||||||
|
|
||||||
|
cartIdFromCookie (): string | null {
|
||||||
|
return getCookie(this.cookieName) ?? null
|
||||||
|
},
|
||||||
|
|
||||||
|
consentParameter (): string {
|
||||||
|
if (this.widgetData.consent) {
|
||||||
|
return `&consent=${encodeURIComponent(this.widgetData.consent)}`
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
},
|
||||||
|
|
||||||
|
additionalURLParams (): string {
|
||||||
|
if (!window.location.search.includes('utm_')) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
const params = new URLSearchParams(window.location.search)
|
||||||
|
for (const [key] of params.entries()) {
|
||||||
|
if (!key.startsWith('utm_')) {
|
||||||
|
params.delete(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return params.toString()
|
||||||
|
},
|
||||||
|
|
||||||
|
newTabTarget (): string {
|
||||||
|
return this.subevent ? `${this.targetUrl}${this.subevent}/` : this.targetUrl
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
triggerLoadCallback () {
|
||||||
|
nextTick(() => {
|
||||||
|
for (const callback of (window as any).PretixWidget._loaded || []) {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
async reload (opt: { focus?: string } = {}) {
|
||||||
|
if (this.isButton) return
|
||||||
|
|
||||||
|
let url: string
|
||||||
|
if (this.subevent) {
|
||||||
|
url = `${this.targetUrl}${this.subevent}/widget/product_list?lang=${LANG}`
|
||||||
|
} else {
|
||||||
|
url = `${this.targetUrl}widget/product_list?lang=${LANG}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.offset) url += `&offset=${this.offset}`
|
||||||
|
if (this.filter) url += `&${this.filter}`
|
||||||
|
if (this.itemFilter) url += `&items=${encodeURIComponent(this.itemFilter)}`
|
||||||
|
if (this.categoryFilter) url += `&categories=${encodeURIComponent(this.categoryFilter)}`
|
||||||
|
if (this.variationFilter) url += `&variations=${encodeURIComponent(this.variationFilter)}`
|
||||||
|
if (this.voucherCode) url += `&voucher=${encodeURIComponent(this.voucherCode)}`
|
||||||
|
|
||||||
|
const cartIdCookie = this.cartIdFromCookie
|
||||||
|
if (cartIdCookie) url += `&cart_id=${encodeURIComponent(cartIdCookie)}`
|
||||||
|
if (this.date !== null) {
|
||||||
|
url += `&date=${this.date.substring(0, 7)}`
|
||||||
|
} else if (this.week !== null) {
|
||||||
|
url += `&date=${this.week[0]}-W${this.week[1]}`
|
||||||
|
}
|
||||||
|
if (this.style !== null) url += `&style=${encodeURIComponent(this.style)}`
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data, responseUrl } = await fetchProductList(url)
|
||||||
|
|
||||||
|
// Check for redirect
|
||||||
|
const newUrl = responseUrl.substring(0, responseUrl.indexOf('/widget/product_list?') + 1)
|
||||||
|
const oldUrl = url.substring(0, url.indexOf('/widget/product_list?') + 1)
|
||||||
|
if (newUrl !== oldUrl) {
|
||||||
|
let adjustedUrl = newUrl
|
||||||
|
if (this.subevent) {
|
||||||
|
adjustedUrl = adjustedUrl.substring(0, adjustedUrl.lastIndexOf('/', adjustedUrl.length - 1) + 1)
|
||||||
|
}
|
||||||
|
this.targetUrl = adjustedUrl
|
||||||
|
this.reload()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.connectionError = false
|
||||||
|
|
||||||
|
if (data.weeks !== undefined) {
|
||||||
|
this.weeks = data.weeks
|
||||||
|
this.date = data.date ?? null
|
||||||
|
this.week = null
|
||||||
|
this.events = null
|
||||||
|
this.view = 'weeks'
|
||||||
|
this.name = data.name ?? null
|
||||||
|
this.frontpageText = data.frontpage_text ?? null
|
||||||
|
this.metaFilterFields = data.meta_filter_fields ?? []
|
||||||
|
} else if (data.days !== undefined) {
|
||||||
|
this.days = data.days
|
||||||
|
this.date = null
|
||||||
|
this.week = data.week ?? null
|
||||||
|
this.events = null
|
||||||
|
this.view = 'days'
|
||||||
|
this.name = data.name ?? null
|
||||||
|
this.frontpageText = data.frontpage_text ?? null
|
||||||
|
this.metaFilterFields = data.meta_filter_fields ?? []
|
||||||
|
} else if (data.events !== undefined) {
|
||||||
|
this.events = this.appendEvents && this.events
|
||||||
|
? this.events.concat(data.events)
|
||||||
|
: data.events
|
||||||
|
this.appendEvents = false
|
||||||
|
this.weeks = null
|
||||||
|
this.view = 'events'
|
||||||
|
this.name = data.name ?? null
|
||||||
|
this.frontpageText = data.frontpage_text ?? null
|
||||||
|
this.hasMoreEvents = data.has_more_events ?? false
|
||||||
|
this.metaFilterFields = data.meta_filter_fields ?? []
|
||||||
|
} else {
|
||||||
|
this.view = 'event'
|
||||||
|
this.targetUrl = data.target_url ?? this.targetUrl
|
||||||
|
this.subevent = data.subevent ?? null
|
||||||
|
this.name = data.name ?? null
|
||||||
|
this.frontpageText = data.frontpage_text ?? null
|
||||||
|
this.dateRange = data.date_range ?? null
|
||||||
|
this.location = data.location ?? null
|
||||||
|
this.categories = data.items_by_category ?? []
|
||||||
|
this.currency = data.currency ?? ''
|
||||||
|
this.displayNetPrices = data.display_net_prices ?? false
|
||||||
|
this.voucherExplanationText = data.voucher_explanation_text ?? null
|
||||||
|
this.error = data.error ?? null
|
||||||
|
this.displayAddToCart = data.display_add_to_cart ?? false
|
||||||
|
this.waitingListEnabled = data.waiting_list_enabled ?? false
|
||||||
|
this.showVariationsExpanded = data.show_variations_expanded || !!this.variationFilter
|
||||||
|
this.cartId = cartIdCookie
|
||||||
|
this.cartExists = data.cart_exists ?? false
|
||||||
|
this.vouchersExist = data.vouchers_exist ?? false
|
||||||
|
this.hasSeatingPlan = data.has_seating_plan ?? false
|
||||||
|
this.hasSeatingPlanWaitinglist = data.has_seating_plan_waitinglist ?? false
|
||||||
|
this.itemnum = data.itemnum ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
this.poweredby = data.poweredby ?? ''
|
||||||
|
|
||||||
|
if (this.loading > 0) {
|
||||||
|
this.loading--
|
||||||
|
this.triggerLoadCallback()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-open seating plan if applicable
|
||||||
|
if (
|
||||||
|
this.parentStack.length > 0
|
||||||
|
&& this.hasSeatingPlan
|
||||||
|
&& this.categories.length === 0
|
||||||
|
&& !this.frameDismissed
|
||||||
|
&& this.useIframe
|
||||||
|
&& !this.error
|
||||||
|
&& !this.hasSeatingPlanWaitinglist
|
||||||
|
) {
|
||||||
|
this.startseating()
|
||||||
|
} else if (opt.focus) {
|
||||||
|
nextTick(() => {
|
||||||
|
document.querySelector<HTMLElement>(opt.focus!)?.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.categories = []
|
||||||
|
this.currency = ''
|
||||||
|
if (e instanceof ApiError && e.status === 429) {
|
||||||
|
this.error = STRINGS.loading_error_429
|
||||||
|
} else {
|
||||||
|
this.error = STRINGS.loading_error
|
||||||
|
}
|
||||||
|
this.connectionError = true
|
||||||
|
if (this.loading > 0) {
|
||||||
|
this.loading--
|
||||||
|
this.triggerLoadCallback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getFormAction (): string {
|
||||||
|
if (!this.useIframe && this.isButton && this.items.length === 0) {
|
||||||
|
if (this.voucherCode) return `${this.targetUrl}redeem`
|
||||||
|
if (this.subevent) return `${this.targetUrl}${this.subevent}/`
|
||||||
|
return this.targetUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
let checkoutUrl = `/${this.targetUrl.replace(/^[^/]+:\/\/([^/]+)\//, '')}w/${globalWidgetId}/`
|
||||||
|
if (!this.cartExists) {
|
||||||
|
checkoutUrl += 'checkout/start'
|
||||||
|
}
|
||||||
|
if (this.additionalURLParams) {
|
||||||
|
checkoutUrl += `?${this.additionalURLParams}`
|
||||||
|
}
|
||||||
|
|
||||||
|
let formTarget = `${this.targetUrl}w/${globalWidgetId}/cart/add?iframe=1&next=${encodeURIComponent(checkoutUrl)}`
|
||||||
|
if (this.cartIdFromCookie) {
|
||||||
|
formTarget += `&take_cart_id=${this.cartIdFromCookie}`
|
||||||
|
}
|
||||||
|
formTarget += this.consentParameter
|
||||||
|
return formTarget
|
||||||
|
},
|
||||||
|
getVoucherFormTarget (): string {
|
||||||
|
let formTarget = `${this.targetUrl}w/${globalWidgetId}/redeem?iframe=1&locale=${LANG}`
|
||||||
|
if (this.cartIdFromCookie) {
|
||||||
|
formTarget += `&take_cart_id=${this.cartIdFromCookie}`
|
||||||
|
}
|
||||||
|
if (this.subevent) {
|
||||||
|
formTarget += `&subevent=${this.subevent}`
|
||||||
|
}
|
||||||
|
if (this.widgetData) {
|
||||||
|
formTarget += `&widget_data=${encodeURIComponent(JSON.stringify(this.widgetData))}`
|
||||||
|
}
|
||||||
|
formTarget += this.consentParameter
|
||||||
|
if (this.additionalURLParams) {
|
||||||
|
formTarget += `&${this.additionalURLParams}`
|
||||||
|
}
|
||||||
|
return formTarget
|
||||||
|
},
|
||||||
|
handleCartResponse (data: CartResponse) {
|
||||||
|
if (data.redirect) {
|
||||||
|
if (data.cart_id) {
|
||||||
|
this.cartId = data.cart_id
|
||||||
|
setCookie(this.cookieName, data.cart_id, 30)
|
||||||
|
}
|
||||||
|
|
||||||
|
let redirectUrl = data.redirect
|
||||||
|
if (redirectUrl.substring(0, 1) === '/') {
|
||||||
|
redirectUrl = `${this.targetUrl.replace(/^([^/]+:\/\/[^/]+)\/.*$/, '$1')}${redirectUrl}`
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = redirectUrl
|
||||||
|
if (url.includes('?')) {
|
||||||
|
url = `${url}&iframe=1&locale=${LANG}&take_cart_id=${this.cartId}`
|
||||||
|
} else {
|
||||||
|
url = `${url}?iframe=1&locale=${LANG}&take_cart_id=${this.cartId}`
|
||||||
|
}
|
||||||
|
url += this.consentParameter
|
||||||
|
if (this.additionalURLParams) {
|
||||||
|
url += `&${this.additionalURLParams}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.success === false) {
|
||||||
|
url = url.replace(/checkout\/start/g, '')
|
||||||
|
this.overlay.errorMessage = data.message ?? null
|
||||||
|
if (data.has_cart) {
|
||||||
|
this.overlay.errorUrlAfter = url
|
||||||
|
}
|
||||||
|
this.overlay.frameLoading = false
|
||||||
|
} else {
|
||||||
|
this.overlay.frameSrc = url
|
||||||
|
}
|
||||||
|
} else if (data.async_id && data.check_url) {
|
||||||
|
this.asyncTaskId = data.async_id
|
||||||
|
this.asyncTaskCheckUrl = `${this.targetUrl.replace(/^([^/]+:\/\/[^/]+)\/.*$/, '$1')}${data.check_url}`
|
||||||
|
this.asyncTaskTimeout = window.setTimeout(() => this.pollAsyncTask(), this.asyncTaskInterval)
|
||||||
|
this.asyncTaskInterval = 250
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async pollAsyncTask () {
|
||||||
|
if (!this.asyncTaskCheckUrl) return
|
||||||
|
try {
|
||||||
|
const data = await checkAsyncTask(this.asyncTaskCheckUrl)
|
||||||
|
this.handleCartResponse(data)
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof ApiError && (e.status === 200 || (e.status >= 400 && e.status < 500))) {
|
||||||
|
this.overlay.errorMessage = STRINGS.cart_error
|
||||||
|
this.overlay.frameLoading = false
|
||||||
|
} else {
|
||||||
|
this.asyncTaskTimeout = window.setTimeout(() => this.pollAsyncTask(), 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async buy (formData: FormData, event?: Event) {
|
||||||
|
if (this.useIframe) {
|
||||||
|
if (event) event.preventDefault()
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isButton && this.items.length === 0) {
|
||||||
|
if (this.voucherCode) {
|
||||||
|
this.voucherOpen(this.voucherCode)
|
||||||
|
} else {
|
||||||
|
this.resume()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `${this.getFormAction()}&locale=${LANG}&ajax=1`
|
||||||
|
this.overlay.frameLoading = true
|
||||||
|
this.asyncTaskInterval = 100
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await submitCart(url, formData)
|
||||||
|
this.handleCartResponse(data)
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof ApiError) {
|
||||||
|
if (e.status === 429) {
|
||||||
|
this.overlay.errorMessage = STRINGS.cart_error_429
|
||||||
|
this.overlay.frameLoading = false
|
||||||
|
this.overlay.errorUrlAfter = this.newTabTarget
|
||||||
|
this.overlay.errorUrlAfterNewTab = true
|
||||||
|
} else if (e.status === 405) {
|
||||||
|
this.targetUrl = e.responseUrl.substring(0, e.responseUrl.indexOf('/cart/add') - 18)
|
||||||
|
this.overlay.frameLoading = false
|
||||||
|
} else {
|
||||||
|
this.overlay.errorMessage = STRINGS.cart_error
|
||||||
|
this.overlay.frameLoading = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.overlay.errorMessage = STRINGS.cart_error
|
||||||
|
this.overlay.frameLoading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
redeem (voucherCode: string, event?: Event) {
|
||||||
|
if (this.useIframe) {
|
||||||
|
if (event) event.preventDefault()
|
||||||
|
this.voucherOpen(voucherCode)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
voucherOpen (voucherCode: string) {
|
||||||
|
const redirectUrl = `${this.getVoucherFormTarget()}&voucher=${encodeURIComponent(voucherCode)}`
|
||||||
|
if (this.useIframe) {
|
||||||
|
this.overlay.frameSrc = redirectUrl
|
||||||
|
} else {
|
||||||
|
window.open(redirectUrl)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resume () {
|
||||||
|
let redirectUrl = `${this.targetUrl}w/${globalWidgetId}/`
|
||||||
|
if (this.subevent && !this.cartId) {
|
||||||
|
redirectUrl += `${this.subevent}/`
|
||||||
|
}
|
||||||
|
redirectUrl += `?iframe=1&locale=${LANG}`
|
||||||
|
if (this.cartId) {
|
||||||
|
redirectUrl += `&take_cart_id=${this.cartId}`
|
||||||
|
}
|
||||||
|
if (this.widgetData) {
|
||||||
|
redirectUrl += `&widget_data=${encodeURIComponent(JSON.stringify(this.widgetData))}`
|
||||||
|
}
|
||||||
|
redirectUrl += this.consentParameter
|
||||||
|
if (this.additionalURLParams) {
|
||||||
|
redirectUrl += `&${this.additionalURLParams}`
|
||||||
|
}
|
||||||
|
if (this.useIframe) {
|
||||||
|
this.overlay.frameSrc = redirectUrl
|
||||||
|
} else {
|
||||||
|
window.open(redirectUrl)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
startwaiting () {
|
||||||
|
let redirectUrl = `${this.targetUrl}w/${globalWidgetId}/waitinglist/?iframe=1&locale=${LANG}`
|
||||||
|
if (this.subevent) {
|
||||||
|
redirectUrl += `&subevent=${this.subevent}`
|
||||||
|
}
|
||||||
|
if (this.additionalURLParams) {
|
||||||
|
redirectUrl += `&${this.additionalURLParams}`
|
||||||
|
}
|
||||||
|
if (this.useIframe) {
|
||||||
|
this.overlay.frameSrc = redirectUrl
|
||||||
|
} else {
|
||||||
|
window.open(redirectUrl)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
startseating () {
|
||||||
|
let redirectUrl = `${this.targetUrl}w/${globalWidgetId}`
|
||||||
|
if (this.subevent) {
|
||||||
|
redirectUrl += `/${this.subevent}`
|
||||||
|
}
|
||||||
|
redirectUrl += `/seatingframe/?iframe=1&locale=${LANG}`
|
||||||
|
if (this.voucherCode) {
|
||||||
|
redirectUrl += `&voucher=${encodeURIComponent(this.voucherCode)}`
|
||||||
|
}
|
||||||
|
if (this.cartId) {
|
||||||
|
redirectUrl += `&take_cart_id=${this.cartId}`
|
||||||
|
}
|
||||||
|
if (this.widgetData) {
|
||||||
|
redirectUrl += `&widget_data=${encodeURIComponent(JSON.stringify(this.widgetData))}`
|
||||||
|
}
|
||||||
|
redirectUrl += this.consentParameter
|
||||||
|
if (this.additionalURLParams) {
|
||||||
|
redirectUrl += `&${this.additionalURLParams}`
|
||||||
|
}
|
||||||
|
if (this.useIframe) {
|
||||||
|
this.overlay.frameSrc = redirectUrl
|
||||||
|
} else {
|
||||||
|
window.open(redirectUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
92
src/pretix/static/pretixpresale/widget/src/types.ts
Normal file
92
src/pretix/static/pretixpresale/widget/src/types.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
// Domain model types for the pretix widget
|
||||||
|
|
||||||
|
export interface Price {
|
||||||
|
gross: string
|
||||||
|
net: string
|
||||||
|
rate: string
|
||||||
|
name: string
|
||||||
|
includes_mixed_tax_rate?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Availability {
|
||||||
|
color: 'green' | 'orange' | 'red'
|
||||||
|
text?: string
|
||||||
|
reason?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Variation {
|
||||||
|
id: number
|
||||||
|
value: string
|
||||||
|
description?: string
|
||||||
|
price: Price
|
||||||
|
suggested_price?: Price
|
||||||
|
original_price?: string
|
||||||
|
avail: [number, number | null]
|
||||||
|
order_max: number
|
||||||
|
current_unavailability_reason?: string
|
||||||
|
allow_waitinglist?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Item {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
picture?: string
|
||||||
|
picture_fullsize?: string
|
||||||
|
price: Price
|
||||||
|
suggested_price?: Price
|
||||||
|
original_price?: string
|
||||||
|
avail: [number, number | null]
|
||||||
|
order_min?: number
|
||||||
|
order_max: number
|
||||||
|
has_variations: boolean
|
||||||
|
variations: Variation[]
|
||||||
|
free_price: boolean
|
||||||
|
min_price?: string
|
||||||
|
max_price?: string
|
||||||
|
mandatory_priced_addons?: boolean
|
||||||
|
current_unavailability_reason?: string
|
||||||
|
allow_waitinglist?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Category {
|
||||||
|
id: number
|
||||||
|
name?: string
|
||||||
|
description?: string
|
||||||
|
items: Item[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EventEntry {
|
||||||
|
name: string
|
||||||
|
event_url: string
|
||||||
|
subevent?: number | string
|
||||||
|
date_range: string
|
||||||
|
location: string
|
||||||
|
time?: string
|
||||||
|
continued?: boolean
|
||||||
|
availability: Availability
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DayEntry {
|
||||||
|
date: string
|
||||||
|
day_formatted: string
|
||||||
|
events: EventEntry[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MetaFilterField {
|
||||||
|
key: string
|
||||||
|
label: string
|
||||||
|
choices: [string, string][]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LightboxState {
|
||||||
|
image: string
|
||||||
|
description: string
|
||||||
|
loading?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WidgetData {
|
||||||
|
referer: string
|
||||||
|
consent?: string
|
||||||
|
[key: string]: string | undefined
|
||||||
|
}
|
||||||
100
src/pretix/static/pretixpresale/widget/src/utils.ts
Normal file
100
src/pretix/static/pretixpresale/widget/src/utils.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
// Utility functions for the pretix widget
|
||||||
|
|
||||||
|
import { getFormat } from '~/i18n'
|
||||||
|
|
||||||
|
// Cookie utilities
|
||||||
|
export function setCookie (cname: string, cvalue: string, exdays: number): void {
|
||||||
|
const d = new Date()
|
||||||
|
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000)
|
||||||
|
const expires = `expires=${d.toUTCString()}`
|
||||||
|
document.cookie = `${cname}=${cvalue};${expires};path=/`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCookie (name: string): string | null {
|
||||||
|
const value = `; ${document.cookie}`
|
||||||
|
const parts = value.split(`; ${name}=`)
|
||||||
|
if (parts.length === 2) {
|
||||||
|
return parts.pop()?.split(';').shift() || null
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number formatting
|
||||||
|
export function roundTo (n: number, digits = 0): number {
|
||||||
|
const multiplicator = Math.pow(10, digits)
|
||||||
|
n = parseFloat((n * multiplicator).toFixed(11))
|
||||||
|
return Math.round(n) / multiplicator
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO use https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat instead?
|
||||||
|
export function floatformat (val: number | string, places = 2): string {
|
||||||
|
if (typeof val === 'string') {
|
||||||
|
val = parseFloat(val)
|
||||||
|
}
|
||||||
|
const parts = roundTo(val, places).toFixed(places).split('.')
|
||||||
|
if (places === 0) {
|
||||||
|
return parts[0]
|
||||||
|
}
|
||||||
|
const grouping = getFormat('NUMBER_GROUPING') as number
|
||||||
|
const thousandSep = getFormat('THOUSAND_SEPARATOR') as string
|
||||||
|
const decimalSep = getFormat('DECIMAL_SEPARATOR') as string
|
||||||
|
parts[0] = parts[0].replace(
|
||||||
|
new RegExp(`\\B(?=(\\d{${grouping}})+(?!\\d))`, 'g'),
|
||||||
|
thousandSep
|
||||||
|
)
|
||||||
|
return `${parts[0]}${decimalSep}${parts[1]}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function autofloatformat (val: number | string, places = 2): string {
|
||||||
|
const numVal = typeof val === 'string' ? parseFloat(val) : val
|
||||||
|
if (numVal === roundTo(numVal, 0)) {
|
||||||
|
places = 0
|
||||||
|
}
|
||||||
|
return floatformat(numVal, places)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String/number utilities
|
||||||
|
export function padNumber (number: number, size = 2): string {
|
||||||
|
let s = String(number)
|
||||||
|
while (s.length < size) {
|
||||||
|
s = `0${s}`
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getISOWeeks (year: number): number {
|
||||||
|
const d = new Date(year, 0, 1)
|
||||||
|
const isLeap = new Date(year, 1, 29).getMonth() === 1
|
||||||
|
// Check for a Jan 1 that's a Thursday or a leap year that has a Wednesday Jan 1
|
||||||
|
return d.getDay() === 4 || (isLeap && d.getDay() === 3) ? 53 : 52
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makeid (length: number): string {
|
||||||
|
let text = ''
|
||||||
|
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
text += possible.charAt(Math.floor(Math.random() * possible.length))
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
export function siteIsSecure (): boolean {
|
||||||
|
return /https.*/.test(document.location.protocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML utility
|
||||||
|
export function stripHTML (s: string): string {
|
||||||
|
const div = document.createElement('div')
|
||||||
|
div.innerHTML = s
|
||||||
|
return div.textContent || div.innerText || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// docReady - DOM ready detection (returns a Promise)
|
||||||
|
export function docReady (): Promise<void> {
|
||||||
|
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
return new Promise(resolve => {
|
||||||
|
document.addEventListener('DOMContentLoaded', () => resolve(), { once: true })
|
||||||
|
})
|
||||||
|
}
|
||||||
1
src/pretix/static/pretixpresale/widget/src/vite-env.d.ts
vendored
Normal file
1
src/pretix/static/pretixpresale/widget/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
declare const LANG: string
|
||||||
69
src/pretix/static/pretixpresale/widget/src/widget.ts
Normal file
69
src/pretix/static/pretixpresale/widget/src/widget.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import { createApp, type App } from 'vue'
|
||||||
|
import WidgetComponent from '~/components/Widget.vue'
|
||||||
|
import { createWidgetStore, StoreKey } from '~/sharedStore'
|
||||||
|
import { makeid } from '~/utils'
|
||||||
|
import type { WidgetData } from '~/types'
|
||||||
|
|
||||||
|
export function createWidgetInstance (element: Element, htmlId?: string): App {
|
||||||
|
let targetUrl = element.attributes.event.value
|
||||||
|
if (!targetUrl.match(/\/$/)) {
|
||||||
|
targetUrl += '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayEventInfoAttr = element.attributes['display-event-info']?.value
|
||||||
|
// null means "auto" (as before), everything other than "false" is true
|
||||||
|
const displayEventInfo: boolean | null
|
||||||
|
= 'display-event-info' in element.attributes && displayEventInfoAttr !== 'auto' ? displayEventInfoAttr !== 'false' : null
|
||||||
|
|
||||||
|
const widgetData: WidgetData = JSON.parse(JSON.stringify(window.PretixWidget.widget_data))
|
||||||
|
for (const attr of Array.from(element.attributes)) {
|
||||||
|
if (attr.name.match(/^data-.*$/)) {
|
||||||
|
widgetData[attr.name.replace(/^data-/, '')] = attr.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = createWidgetStore({
|
||||||
|
targetUrl,
|
||||||
|
voucher: element.attributes.voucher?.value || null,
|
||||||
|
subevent: element.attributes.subevent?.value || null,
|
||||||
|
listType: element.attributes['list-type']?.value || element.attributes.style?.value || null,
|
||||||
|
skipSsl: 'skip-ssl-check' in element.attributes,
|
||||||
|
disableIframe: 'disable-iframe' in element.attributes,
|
||||||
|
disableVouchers: 'disable-vouchers' in element.attributes,
|
||||||
|
disableFilters: 'disable-filters' in element.attributes,
|
||||||
|
displayEventInfo,
|
||||||
|
filter: element.attributes.filter?.value || null,
|
||||||
|
items: element.attributes.items?.value || null,
|
||||||
|
categories: element.attributes.categories?.value || null,
|
||||||
|
variations: element.attributes.variations?.value || null,
|
||||||
|
widgetData,
|
||||||
|
htmlId: htmlId || element.id || makeid(16),
|
||||||
|
})
|
||||||
|
|
||||||
|
const observer = new MutationObserver((mutationList) => {
|
||||||
|
for (const mutation of mutationList) {
|
||||||
|
if (mutation.type === 'attributes' && mutation.attributeName?.startsWith('data-')) {
|
||||||
|
const attrName = mutation.attributeName.substring(5)
|
||||||
|
const attrValue = (mutation.target as Element).getAttribute(mutation.attributeName)
|
||||||
|
store.widgetData[attrName] = attrValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO I don't think we need this anymore in vue3
|
||||||
|
// if (element.tagName !== 'pretix-widget') {
|
||||||
|
// element.innerHTML = '<pretix-widget></pretix-widget>'
|
||||||
|
// // we need to watch the container as well as the replaced root-node (see mounted())
|
||||||
|
// observer.observe(element, observerOptions)
|
||||||
|
// }
|
||||||
|
|
||||||
|
const app = createApp(WidgetComponent)
|
||||||
|
app.provide(StoreKey, store)
|
||||||
|
app.config.errorHandler = (error, _vm, info) => {
|
||||||
|
console.error('[pretix-widget]', info, error)
|
||||||
|
}
|
||||||
|
app.mount(element)
|
||||||
|
observer.observe(element, { attributes: true })
|
||||||
|
|
||||||
|
return app
|
||||||
|
}
|
||||||
11
src/pretix/static/pretixpresale/widget/tsconfig.json
Normal file
11
src/pretix/static/pretixpresale/widget/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../../../../tsconfig.json",
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"~/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
"types": ["node", "vite/client"]
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/pretix/static/pretixpresale/widget/vite.config.ts
Normal file
33
src/pretix/static/pretixpresale/widget/vite.config.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue()
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'~': import.meta.dirname + '/src',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
manifest: true,
|
||||||
|
outDir: import.meta.dirname + '/../../../../../static.dist/vite/widget',
|
||||||
|
rollupOptions: {
|
||||||
|
input: {
|
||||||
|
main: import.meta.dirname + '/src/main.ts',
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
format: 'iife',
|
||||||
|
entryFileNames: 'widget.js',
|
||||||
|
assetFileNames: 'widget.[ext]',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
optimizeDeps: {
|
||||||
|
exclude: ['moment', 'jquery']
|
||||||
|
},
|
||||||
|
define: {
|
||||||
|
LANG: JSON.stringify(process.env.PRETIX_WIDGET_LANG || 'en')
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
{
|
{
|
||||||
"include": ["src/pretix/static/**/*", "src/pretix/static/**/*.vue"],
|
"include": [
|
||||||
|
"src/pretix/static/**/*",
|
||||||
|
"src/pretix/static/**/*.vue",
|
||||||
|
"src/pretix/plugins/webcheckin/**/*",
|
||||||
|
"src/pretix/plugins/webcheckin/**/*.vue"
|
||||||
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {},
|
||||||
},
|
|
||||||
"strict": false,
|
"strict": false,
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"checkJs": true,
|
"checkJs": true,
|
||||||
@@ -15,5 +19,8 @@
|
|||||||
"noImplicitThis": true,
|
"noImplicitThis": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"types": ["node", "events"]
|
"types": ["node", "events"]
|
||||||
|
},
|
||||||
|
"vueCompilerOptions": {
|
||||||
|
"plugins": ["@vue/language-plugin-pug"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user