fix inconsistencies from automatic migration

This commit is contained in:
rash
2026-03-09 18:31:27 +01:00
parent 00cb77de8f
commit 504191c005
17 changed files with 146 additions and 240 deletions

View File

@@ -61,11 +61,8 @@ const waitingListUrl = computed(() => {
if (store.subevent) { if (store.subevent) {
u += `&subevent=${store.subevent}` u += `&subevent=${store.subevent}`
} }
const widgetDataJson = JSON.stringify(store.widgetData) u += `&widget_data=${encodeURIComponent(store.widgetDataJson)}`
u += `&widget_data=${encodeURIComponent(widgetDataJson)}` u += store.consentParameter
if (store.widgetData.consent) {
u += `&consent=${encodeURIComponent(store.widgetData.consent)}`
}
return u return u
}) })

View File

@@ -16,32 +16,6 @@ const formMethod = computed(() => {
return 'post' 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) { function handleBuy (event: Event) {
if (form.value) { if (form.value) {
const formData = new FormData(form.value) const formData = new FormData(form.value)
@@ -57,13 +31,13 @@ defineExpose({
<template lang="pug"> <template lang="pug">
.pretix-widget-wrapper .pretix-widget-wrapper
.pretix-widget-button-container .pretix-widget-button-container
form(ref="form", :method="formMethod", :action="formAction", :target="formTarget") form(ref="form", :method="formMethod", :action="store.formAction", :target="store.formTarget")
input(v-if="store.voucherCode", type="hidden", name="_voucher_code", :value="store.voucherCode") 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(v-if="store.voucherCode", type="hidden", name="voucher", :value="store.voucherCode")
input(type="hidden", name="subevent", :value="store.subevent") input(type="hidden", name="subevent", :value="store.subevent")
input(type="hidden", name="locale", :value="lang") input(type="hidden", name="locale", :value="lang")
input(type="hidden", name="widget_data", :value="widgetDataJson") input(type="hidden", name="widget_data", :value="store.widgetDataJson")
input(v-if="consentParameterValue", type="hidden", name="consent", :value="consentParameterValue") input(v-if="store.consentParameterValue", type="hidden", name="consent", :value="store.consentParameterValue")
input( input(
v-for="item in store.items", v-for="item in store.items",
:key="item.item", :key="item.item",
@@ -76,6 +50,5 @@ defineExpose({
Overlay Overlay
</template> </template>
<style lang="sass"> <style lang="sass">
</style> </style>

View File

@@ -19,6 +19,5 @@ defineProps<{
:category="category" :category="category"
) )
</template> </template>
<style lang="sass"> <style lang="sass">
</style> </style>

View File

@@ -16,6 +16,7 @@ const calendar = ref<HTMLDivElement>()
const displayEventInfo = computed(() => store.displayEventInfo || (store.displayEventInfo === null && store.parentStack.length > 0)) const displayEventInfo = computed(() => store.displayEventInfo || (store.displayEventInfo === null && store.parentStack.length > 0))
const monthname = computed(() => { const monthname = computed(() => {
// TODO proper date formatting?
if (!store.date) return '' if (!store.date) return ''
const monthNum = store.date.substr(5, 2) const monthNum = store.date.substr(5, 2)
const year = store.date.substr(0, 4) const year = store.date.substr(0, 4)
@@ -26,9 +27,8 @@ const id = computed(() => `${store.htmlId}-event-calendar-table`)
const ariaLabelledby = computed(() => `${store.htmlId}-event-calendar-table-label`) const ariaLabelledby = computed(() => `${store.htmlId}-event-calendar-table-label`)
const showFilters = computed(() => !store.disableFilters && store.metaFilterFields.length > 0)
function backToList () { function backToList () {
// TODO should be in store
store.weeks = null store.weeks = null
store.view = 'events' store.view = 'events'
store.name = null store.name = null
@@ -63,7 +63,6 @@ function nextmonth () {
store.reload({ focus: `#${id.value}` }) store.reload({ focus: `#${id.value}` })
} }
</script> </script>
<template lang="pug"> <template lang="pug">
.pretix-widget-event-calendar(ref="calendar") .pretix-widget-event-calendar(ref="calendar")
//- Back navigation //- Back navigation
@@ -80,7 +79,7 @@ function nextmonth () {
) )
//- Filter //- Filter
EventListFilterForm(v-if="showFilters") EventListFilterForm(v-if="!store.disableFilters && store.metaFilterFields.length > 0")
//- Calendar navigation //- Calendar navigation
.pretix-widget-event-calendar-head .pretix-widget-event-calendar-head
@@ -92,7 +91,7 @@ function nextmonth () {
a.pretix-widget-event-calendar-next-month(href="#", @click.prevent.stop="nextmonth") a.pretix-widget-event-calendar-next-month(href="#", @click.prevent.stop="nextmonth")
| {{ STRINGS.next_month }} &raquo; | {{ STRINGS.next_month }} &raquo;
//- Calendar table //- Calendar
table.pretix-widget-event-calendar-table( table.pretix-widget-event-calendar-table(
:id="id", :id="id",
tabindex="0", tabindex="0",
@@ -115,6 +114,5 @@ function nextmonth () {
:mobile="mobile" :mobile="mobile"
) )
</template> </template>
<style lang="sass"> <style lang="sass">
</style> </style>

View File

@@ -26,9 +26,10 @@ const tabindex = computed(() => role.value === 'button' ? '0' : '-1')
const classObject = computed(() => { const classObject = computed(() => {
const o: Record<string, boolean> = {} const o: Record<string, boolean> = {}
if (props.day && props.day.events.length > 0) { if (props.day && props.day.events.length > 0) {
o['pretix-widget-has-events'] = true o['pretix-widget-has-events'] = true // TODO static
let best = 'red' let best = 'red'
let allLow = true let allLow = true
// TODO decopypasta
for (const ev of props.day.events) { for (const ev of props.day.events) {
if (ev.availability.color === 'green') { if (ev.availability.color === 'green') {
best = 'green' best = 'green'
@@ -47,11 +48,12 @@ const classObject = computed(() => {
return o return o
}) })
function selectDay(e: Event) { function selectDay (e: Event) {
if (!props.day || !props.day.events.length || !props.mobile) return if (!props.day || !props.day.events.length || !props.mobile) return
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
// TODO decopypasta
if (props.day.events.length === 1) { if (props.day.events.length === 1) {
const ev = props.day.events[0] const ev = props.day.events[0]
store.parentStack.push(store.targetUrl) store.parentStack.push(store.targetUrl)
@@ -66,22 +68,23 @@ function selectDay(e: Event) {
} }
} }
function onKeyDown(e: KeyboardEvent) { function onKeyDown (e: KeyboardEvent) {
const keyDown = e.key ?? e.keyCode const keyDown = e.key ?? e.keyCode
if (keyDown === 'Enter' || keyDown === 13 || ['Spacebar', ' '].includes(keyDown as string) || keyDown === 32) { if (keyDown === 'Enter' || keyDown === 13 || ['Spacebar', ' '].includes(keyDown as string) || keyDown === 32) {
// (prevent default so the page doesn't scroll when pressing space)
e.preventDefault() e.preventDefault()
selectDay(e) selectDay(e)
} }
} }
function attachListeners() { function attachListeners () {
if (role.value === 'button' && cellEl.value) { if (role.value === 'button' && cellEl.value) {
cellEl.value.addEventListener('click', selectDay) cellEl.value.addEventListener('click', selectDay)
cellEl.value.addEventListener('keydown', onKeyDown) cellEl.value.addEventListener('keydown', onKeyDown)
} }
} }
function detachListeners() { function detachListeners () {
if (cellEl.value) { if (cellEl.value) {
cellEl.value.removeEventListener('click', selectDay) cellEl.value.removeEventListener('click', selectDay)
cellEl.value.removeEventListener('keydown', onKeyDown) cellEl.value.removeEventListener('keydown', onKeyDown)
@@ -92,6 +95,7 @@ onMounted(() => {
attachListeners() attachListeners()
}) })
// TODO why different from old version?
watch(role, (newValue, oldValue) => { watch(role, (newValue, oldValue) => {
if (newValue === 'button' && oldValue !== 'button') { if (newValue === 'button' && oldValue !== 'button') {
attachListeners() attachListeners()
@@ -100,7 +104,6 @@ watch(role, (newValue, oldValue) => {
} }
}) })
</script> </script>
<template lang="pug"> <template lang="pug">
td( td(
ref="cellEl", ref="cellEl",
@@ -113,6 +116,5 @@ td(
.pretix-widget-event-calendar-events(v-if="day") .pretix-widget-event-calendar-events(v-if="day")
EventCalendarEvent(v-for="e in day.events", :key="e.event_url", :event="e") EventCalendarEvent(v-for="e in day.events", :key="e.event_url", :event="e")
</template> </template>
<style lang="sass"> <style lang="sass">
</style> </style>

View File

@@ -21,7 +21,7 @@ const classObject = computed(() => {
return o return o
}) })
function select() { function select () {
store.parentStack.push(store.targetUrl) store.parentStack.push(store.targetUrl)
store.targetUrl = props.event.event_url store.targetUrl = props.event.event_url
store.error = null store.error = null
@@ -30,18 +30,16 @@ function select() {
store.reload() store.reload()
} }
</script> </script>
<template lang="pug"> <template lang="pug">
a.pretix-widget-event-calendar-event( a.pretix-widget-event-calendar-event(
href="#", href="#",
:class="classObject", :class="classObject",
@click.prevent.stop="select", :aria-describedby="describedby",
:aria-describedby="describedby" @click.prevent.stop="select"
) )
strong.pretix-widget-event-calendar-event-name {{ event.name }} 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-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 }} .pretix-widget-event-calendar-event-availability(v-if="!event.continued && event.availability.text") {{ event.availability.text }}
</template> </template>
<style lang="sass"> <style lang="sass">
</style> </style>

View File

@@ -7,11 +7,9 @@ defineProps<{
mobile: boolean mobile: boolean
}>() }>()
</script> </script>
<template lang="pug"> <template lang="pug">
tr tr
EventCalendarCell(v-for="(d, idx) in week", :key="idx", :day="d", :mobile="mobile") EventCalendarCell(v-for="(d, idx) in week", :key="idx", :day="d", :mobile="mobile")
</template> </template>
<style lang="sass"> <style lang="sass">
</style> </style>

View File

@@ -11,12 +11,9 @@ const voucherinput = ref<HTMLInputElement>()
const isItemsSelected = ref(false) const isItemsSelected = ref(false)
const localVoucher = ref('') 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 idVoucherInput = computed(() => `${store.htmlId}-voucher-input`)
const ariaLabelledby = computed(() => `${store.htmlId}-voucher-headline`) const ariaLabelledby = computed(() => `${store.htmlId}-voucher-headline`)
const displayEventInfo = computed(() => store.displayEventInfo || (store.displayEventInfo === null && (store.events || store.weeks || store.days)))
const idCartExistsMsg = computed(() => `${store.htmlId}-cart-exists`) const idCartExistsMsg = computed(() => `${store.htmlId}-cart-exists`)
const buyLabel = computed(() => { const buyLabel = computed(() => {
@@ -43,78 +40,16 @@ const hiddenParams = computed(() => {
const params = new URL(store.getVoucherFormTarget()).searchParams const params = new URL(store.getVoucherFormTarget()).searchParams
params.delete('iframe') params.delete('iframe')
params.delete('take_cart_id') params.delete('take_cart_id')
return Array.from(params.entries()) return [...params.entries()]
}) })
const showVoucherForm = computed(() => store.vouchersExist && !store.disableVouchers && !store.voucherCode) 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 () { function backToList () {
store.targetUrl = store.parentStack.pop() || store.targetUrl store.targetUrl = store.parentStack.pop() || store.targetUrl
store.error = null store.error = null
if (!store.subevent) { if (!store.subevent) {
// reset if we are not in a series
store.name = null store.name = null
store.frontpageText = null store.frontpageText = null
} }
@@ -123,13 +58,20 @@ function backToList () {
store.appendEvents = false store.appendEvents = false
store.triggerLoadCallback() store.triggerLoadCallback()
if (store.events !== null) { if (store.events !== undefined && store.events !== null) {
store.view = 'events' store.view = 'events'
} else if (store.days !== null) { } else if (store.days !== undefined && store.days !== null) {
store.view = 'days' store.view = 'days'
} else { } else {
store.view = 'weeks' store.view = 'weeks'
} }
// TODO
// let $el = this.$root.$el
// this.$root.$nextTick(function () {
// // wait for redraw, then focus content element for better a11y
// $el.focus()
// })
} }
function calcItemsSelected () { function calcItemsSelected () {
@@ -208,14 +150,14 @@ watch(() => store.overlay?.frameShown, (newValue) => {
form( form(
ref="form", ref="form",
method="post", method="post",
:action="formAction", :action="store.formAction",
:target="formTarget", :target="store.formTarget",
@submit="handleBuy" @submit="handleBuy"
) )
input(v-if="store.voucherCode", type="hidden", name="_voucher_code", :value="store.voucherCode") 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="subevent", :value="store.subevent")
input(type="hidden", name="widget_data", :value="widgetDataJson") input(type="hidden", name="widget_data", :value="store.widgetDataJson")
input(v-if="consentParameterValue", type="hidden", name="consent", :value="consentParameterValue") input(v-if="store.consentParameterValue", type="hidden", name="consent", :value="store.consentParameterValue")
//- Error message //- Error message
.pretix-widget-error-message(v-if="store.error") {{ store.error }} .pretix-widget-error-message(v-if="store.error") {{ store.error }}
@@ -242,7 +184,7 @@ watch(() => store.overlay?.frameShown, (newValue) => {
| {{ STRINGS.waiting_list }} | {{ STRINGS.waiting_list }}
.pretix-widget-clear .pretix-widget-clear
//- Product list //- Actual Product list
Category(v-for="category in store.categories", :key="category.id", :category="category") Category(v-for="category in store.categories", :key="category.id", :category="category")
//- Buy button //- Buy button
@@ -293,6 +235,5 @@ watch(() => store.overlay?.frameShown, (newValue) => {
button(@click="handleRedeem") {{ STRINGS.redeem }} button(@click="handleRedeem") {{ STRINGS.redeem }}
.pretix-widget-clear .pretix-widget-clear
</template> </template>
<style lang="sass"> <style lang="sass">
</style> </style>

View File

@@ -9,11 +9,12 @@ const store = inject(StoreKey)!
const displayEventInfo = computed(() => store.displayEventInfo || (store.displayEventInfo === null && store.parentStack.length > 0)) const displayEventInfo = computed(() => store.displayEventInfo || (store.displayEventInfo === null && store.parentStack.length > 0))
const showBackButton = computed(() => store.weeks || store.parentStack.length > 0) function backToCalendar () {
// TODO
const showFilters = computed(() => !store.disableFilters && store.metaFilterFields.length > 0) // make sure to always focus content element
// this.$nextTick(function () {
function backToCalendar() { // this.$root.$el.focus()
// })
store.offset = 0 store.offset = 0
store.appendEvents = false store.appendEvents = false
@@ -30,17 +31,16 @@ function backToCalendar() {
} }
} }
function loadMore() { function loadMore () {
store.appendEvents = true store.appendEvents = true
store.offset += 50 store.offset += 50
store.loading++ store.loading++
store.reload() store.reload()
} }
</script> </script>
<template lang="pug"> <template lang="pug">
.pretix-widget-event-list .pretix-widget-event-list
.pretix-widget-back(v-if="showBackButton") .pretix-widget-back(v-if="store.weeks || store.parentStack.length > 0")
a(href="#", rel="prev", @click.prevent.stop="backToCalendar") a(href="#", rel="prev", @click.prevent.stop="backToCalendar")
| &lsaquo; {{ STRINGS.back }} | &lsaquo; {{ STRINGS.back }}
@@ -52,7 +52,7 @@ function loadMore() {
v-html="store.frontpageText" v-html="store.frontpageText"
) )
EventListFilterForm(v-if="showFilters") EventListFilterForm(v-if="!store.disableFilters && store.metaFilterFields.length > 0")
EventListEntry( EventListEntry(
v-for="event in store.events", v-for="event in store.events",
@@ -63,6 +63,5 @@ function loadMore() {
p.pretix-widget-event-list-load-more(v-if="store.hasMoreEvents") p.pretix-widget-event-list-load-more(v-if="store.hasMoreEvents")
button(@click.prevent.stop="loadMore") {{ STRINGS.load_more }} button(@click.prevent.stop="loadMore") {{ STRINGS.load_more }}
</template> </template>
<style lang="sass"> <style lang="sass">
</style> </style>

View File

@@ -22,7 +22,7 @@ const classObject = computed(() => {
const location = computed(() => props.event.location.replace(/\s*\n\s*/g, ', ')) const location = computed(() => props.event.location.replace(/\s*\n\s*/g, ', '))
function select() { function select () {
store.parentStack.push(store.targetUrl) store.parentStack.push(store.targetUrl)
store.targetUrl = props.event.event_url store.targetUrl = props.event.event_url
store.error = null store.error = null
@@ -31,15 +31,14 @@ function select() {
store.reload() store.reload()
} }
</script> </script>
<template lang="pug"> <template lang="pug">
a.pretix-widget-event-list-entry(href="#", :class="classObject", @click.prevent.stop="select") 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-name {{ event.name }}
.pretix-widget-event-list-entry-date {{ event.date_range }} .pretix-widget-event-list-entry-date {{ event.date_range }}
//- hidden by css for now, but used by a few people
.pretix-widget-event-list-entry-location {{ location }} .pretix-widget-event-list-entry-location {{ location }}
.pretix-widget-event-list-entry-availability .pretix-widget-event-list-entry-availability
span {{ event.availability.text }} span {{ event.availability.text }}
</template> </template>
<style lang="sass"> <style lang="sass">
</style> </style>

View File

@@ -16,13 +16,11 @@ const currentValue = computed(() => {
return filterParams.get(props.field.key) || '' return filterParams.get(props.field.key) || ''
}) })
</script> </script>
<template lang="pug"> <template lang="pug">
.pretix-widget-event-list-filter-field .pretix-widget-event-list-filter-field
label(:for="id") {{ field.label }} label(:for="id") {{ field.label }}
select(:id="id", :name="field.key", :value="currentValue") select(:id="id", :name="field.key", :value="currentValue")
option(v-for="choice in field.choices", :key="choice[0]", :value="choice[0]") {{ choice[1] }} option(v-for="choice in field.choices", :key="choice[0]", :value="choice[0]") {{ choice[1] }}
</template> </template>
<style lang="sass"> <style lang="sass">
</style> </style>

View File

@@ -7,7 +7,7 @@ import EventListFilterField from './EventListFilterField.vue'
const store = inject(StoreKey)! const store = inject(StoreKey)!
const filterform = ref<HTMLFormElement>() const filterform = ref<HTMLFormElement>()
function onSubmit(e: Event) { function onSubmit (e: Event) {
e.preventDefault() e.preventDefault()
if (!filterform.value) return if (!filterform.value) return
@@ -25,7 +25,6 @@ function onSubmit(e: Event) {
store.reload() store.reload()
} }
</script> </script>
<template lang="pug"> <template lang="pug">
form.pretix-widget-event-list-filter-form(ref="filterform", @submit="onSubmit") form.pretix-widget-event-list-filter-form(ref="filterform", @submit="onSubmit")
fieldset.pretix-widget-event-list-filter-fieldset fieldset.pretix-widget-event-list-filter-fieldset
@@ -37,6 +36,5 @@ form.pretix-widget-event-list-filter-form(ref="filterform", @submit="onSubmit")
) )
button {{ STRINGS.filter }} button {{ STRINGS.filter }}
</template> </template>
<style lang="sass"> <style lang="sass">
</style> </style>

View File

@@ -24,16 +24,14 @@ const weekname = computed(() => {
const id = computed(() => `${store.htmlId}-event-week-table`) const id = computed(() => `${store.htmlId}-event-week-table`)
const showFilters = computed(() => !store.disableFilters && store.metaFilterFields.length > 0) function backToList () {
function backToList() {
store.weeks = null store.weeks = null
store.name = null store.name = null
store.frontpageText = null store.frontpageText = null
store.view = 'events' store.view = 'events'
} }
function prevweek() { function prevweek () {
if (!store.week) return if (!store.week) return
let curWeek = store.week[1] let curWeek = store.week[1]
let curYear = store.week[0] let curYear = store.week[0]
@@ -47,7 +45,7 @@ function prevweek() {
store.reload({ focus: `#${id.value}` }) store.reload({ focus: `#${id.value}` })
} }
function nextweek() { function nextweek () {
if (!store.week) return if (!store.week) return
let curWeek = store.week[1] let curWeek = store.week[1]
let curYear = store.week[0] let curYear = store.week[0]
@@ -61,12 +59,11 @@ function nextweek() {
store.reload({ focus: `#${id.value}` }) store.reload({ focus: `#${id.value}` })
} }
</script> </script>
<template lang="pug"> <template lang="pug">
.pretix-widget-event-calendar.pretix-widget-event-week-calendar(ref="weekcalendar") .pretix-widget-event-calendar.pretix-widget-event-week-calendar(ref="weekcalendar")
//- Back navigation //- Back navigation
.pretix-widget-back(v-if="store.events !== null") .pretix-widget-back(v-if="store.events !== null")
a(href="#", @click.prevent.stop="backToList", role="button") a(href="#", role="button", @click.prevent.stop="backToList")
| &lsaquo; {{ STRINGS.back }} | &lsaquo; {{ STRINGS.back }}
//- Event header //- Event header
@@ -74,7 +71,7 @@ function nextweek() {
strong {{ store.name }} strong {{ store.name }}
//- Filter //- Filter
EventListFilterForm(v-if="showFilters") EventListFilterForm(v-if="!store.disableFilters && store.metaFilterFields.length > 0")
//- Calendar navigation //- Calendar navigation
.pretix-widget-event-description( .pretix-widget-event-description(
@@ -82,12 +79,12 @@ function nextweek() {
v-html="store.frontpageText" v-html="store.frontpageText"
) )
.pretix-widget-event-calendar-head .pretix-widget-event-calendar-head
a.pretix-widget-event-calendar-previous-month(href="#", @click.prevent.stop="prevweek", role="button") a.pretix-widget-event-calendar-previous-month(href="#", role="button", @click.prevent.stop="prevweek")
| &laquo; {{ STRINGS.previous_week }} | &laquo; {{ STRINGS.previous_week }}
| |
strong {{ weekname }} strong {{ weekname }}
| |
a.pretix-widget-event-calendar-next-month(href="#", @click.prevent.stop="nextweek", role="button") a.pretix-widget-event-calendar-next-month(href="#", role="button", @click.prevent.stop="nextweek")
| {{ STRINGS.next_week }} &raquo; | {{ STRINGS.next_week }} &raquo;
//- Actual calendar //- Actual calendar
@@ -95,6 +92,5 @@ function nextweek() {
.pretix-widget-event-week-col(v-for="d in store.days", :key="d?.date || ''") .pretix-widget-event-week-col(v-for="d in store.days", :key="d?.date || ''")
EventWeekCell(:day="d", :mobile="mobile") EventWeekCell(:day="d", :mobile="mobile")
</template> </template>
<style lang="sass"> <style lang="sass">
</style> </style>

View File

@@ -6,7 +6,7 @@ import EventCalendarEvent from './EventCalendarEvent.vue'
const props = defineProps<{ const props = defineProps<{
day: DayEntry | null day: DayEntry | null
mobile: boolean mobile: boolean // TODO inject?
}>() }>()
const store = inject(StoreKey)! const store = inject(StoreKey)!
@@ -47,6 +47,7 @@ function selectDay () {
if (props.day.events.length === 1) { if (props.day.events.length === 1) {
const ev = props.day.events[0] const ev = props.day.events[0]
// TODO store mutation bad
store.parentStack.push(store.targetUrl) store.parentStack.push(store.targetUrl)
store.targetUrl = ev.event_url store.targetUrl = ev.event_url
store.error = null store.error = null
@@ -59,7 +60,6 @@ function selectDay () {
} }
} }
</script> </script>
<template lang="pug"> <template lang="pug">
div(:class="classObject", @click.prevent.stop="selectDay") div(:class="classObject", @click.prevent.stop="selectDay")
.pretix-widget-event-calendar-day(v-if="day", :id="id") {{ dayhead }} .pretix-widget-event-calendar-day(v-if="day", :id="id") {{ dayhead }}
@@ -71,6 +71,5 @@ div(:class="classObject", @click.prevent.stop="selectDay")
:describedby="id" :describedby="id"
) )
</template> </template>
<style lang="sass"> <style lang="sass">
</style> </style>

View File

@@ -69,8 +69,11 @@ function errorClose (e: Event) {
store.overlay.errorUrlAfterNewTab = false store.overlay.errorUrlAfterNewTab = false
} }
function close () { function close (e) {
if (store.overlay.frameLoading) { if (store.overlay.frameLoading) {
// Chrome does not allow blocking dialog.cancel event more than once
// => wiggle the loading-element and re-open the modal
cancel(e)
frameDialog.value?.showModal() frameDialog.value?.showModal()
return return
} }
@@ -82,6 +85,7 @@ function close () {
} }
function cancel (e: Event) { function cancel (e: Event) {
// do not allow to cancel while frame is loading as we cannot abort the operation
if (store.overlay.frameLoading) { if (store.overlay.frameLoading) {
e.preventDefault() e.preventDefault()
const target = e.target as HTMLElement const target = e.target as HTMLElement
@@ -111,15 +115,15 @@ function triggerCloseCallback () {
}) })
} }
// TODO check if watchEffect is better for the following watchers
watch(() => store.overlay.lightbox, (newValue, oldValue) => { watch(() => store.overlay.lightbox, (newValue, oldValue) => {
if (newValue) { if (!newValue) return
if (newValue.image !== oldValue?.image) { if (newValue.image !== oldValue?.image) {
newValue.loading = true newValue.loading = true
} }
if (!oldValue) { if (!oldValue) {
lightboxDialog.value?.showModal() lightboxDialog.value?.showModal()
} }
}
}) })
watch(() => store.overlay.errorMessage, (newValue, oldValue) => { watch(() => store.overlay.errorMessage, (newValue, oldValue) => {
@@ -128,19 +132,19 @@ watch(() => store.overlay.errorMessage, (newValue, oldValue) => {
} }
}) })
watch(() => store.overlay.frameShown, (newValue) => { watch(() => store.overlay.frameShown, async (newValue) => {
if (newValue) { if (!newValue) return
nextTick(() => { await nextTick()
closeButton.value?.focus() closeButton.value?.focus()
})
}
}) })
watch(() => store.overlay.frameSrc, (newValue, oldValue) => { watch(() => store.overlay.frameSrc, (newValue, oldValue) => {
// show loading spinner only when previously no frame_src was set
if (newValue && !oldValue) { if (newValue && !oldValue) {
store.overlay.frameLoading = true store.overlay.frameLoading = true
} }
if (iframe.value) { if (iframe.value) {
// to close and unload the iframe, frame_src can be empty -> make it valid HTML with about:blank
iframe.value.src = newValue || 'about:blank' iframe.value.src = newValue || 'about:blank'
} }
}) })
@@ -151,7 +155,7 @@ watch(() => store.overlay.frameLoading, (newValue) => {
frameDialog.value.showModal() frameDialog.value.showModal()
} }
} else { } else {
if (!store.overlay.frameSrc && frameDialog.value?.open) { if (!store.overlay.frameSrc && frameDialog.value?.open) { // finished loading, but no iframe to display => close
frameDialog.value.close() frameDialog.value.close()
} }
} }
@@ -169,7 +173,6 @@ onUnmounted(() => {
<template lang="pug"> <template lang="pug">
Teleport(to="body") Teleport(to="body")
.pretix-widget-overlay .pretix-widget-overlay
//- Iframe dialog
dialog(ref="frameDialog", :class="frameClasses", :aria-label="STRINGS.checkout", @close="close", @cancel="cancel") dialog(ref="frameDialog", :class="frameClasses", :aria-label="STRINGS.checkout", @close="close", @cancel="cancel")
.pretix-widget-frame-loading(v-show="store.overlay.frameLoading") .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") svg(width="256", height="256", viewBox="0 0 1792 1792", xmlns="http://www.w3.org/2000/svg")
@@ -193,8 +196,6 @@ Teleport(to="body")
referrerpolicy="origin", referrerpolicy="origin",
@load="iframeLoaded" @load="iframeLoaded"
) Please enable frames in your browser! ) Please enable frames in your browser!
//- Alert dialog
dialog(ref="alertDialog", :class="alertClasses", role="alertdialog", :aria-labelledby="errorMessageId", @close="errorClose") dialog(ref="alertDialog", :class="alertClasses", role="alertdialog", :aria-labelledby="errorMessageId", @close="errorClose")
form.pretix-widget-alert-box(method="dialog") form.pretix-widget-alert-box(method="dialog")
p(:id="errorMessageId") {{ store.overlay.errorMessage }} p(:id="errorMessageId") {{ store.overlay.errorMessage }}
@@ -207,7 +208,6 @@ Teleport(to="body")
path(style="fill:#ffffff;", d="M 599.86438,303.72882 H 1203.5254 V 1503.4576 H 599.86438 Z") 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") 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") dialog(ref="lightboxDialog", :class="lightboxClasses", role="alertdialog", @close="lightboxClose")
.pretix-widget-lightbox-loading(v-if="store.overlay.lightbox?.loading") .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") svg(width="256", height="256", viewBox="0 0 1792 1792", xmlns="http://www.w3.org/2000/svg")
@@ -227,6 +227,5 @@ Teleport(to="body")
) )
figcaption(v-if="store.overlay.lightbox.description") {{ store.overlay.lightbox.description }} figcaption(v-if="store.overlay.lightbox.description") {{ store.overlay.lightbox.description }}
</template> </template>
<style lang="sass"> <style lang="sass">
</style> </style>

View File

@@ -36,12 +36,14 @@ onMounted(() => {
resizeObserver.observe(wrapper.value) resizeObserver.observe(wrapper.value)
} }
store.reload() store.reload() // TODO call earlier?
emit('mounted') emit('mounted') // TODO where does this go?
}) })
watch(() => store.view, (newValue, oldValue) => { watch(() => store.view, (newValue, oldValue) => {
if (oldValue && wrapper.value) { if (oldValue && wrapper.value) {
// always make sure the widget is scrolled to the top
// as we only check top, we do not need to wait for a redraw
const rect = wrapper.value.getBoundingClientRect() const rect = wrapper.value.getBoundingClientRect()
if (rect.top < 0) { if (rect.top < 0) {
wrapper.value.scrollIntoView() wrapper.value.scrollIntoView()

View File

@@ -125,22 +125,23 @@ export function createWidgetStore (config: {
if ((window as any).crossOriginIsolated === true) return false if ((window as any).crossOriginIsolated === true) return false
return !this.disableIframe && (this.skipSsl || /https.*/.test(document.location.protocol)) return !this.disableIframe && (this.skipSsl || /https.*/.test(document.location.protocol))
}, },
cookieName (): string { cookieName (): string {
return `pretix_widget_${this.targetUrl.replace(/[^a-zA-Z0-9]+/g, '_')}` return `pretix_widget_${this.targetUrl.replace(/[^a-zA-Z0-9]+/g, '_')}`
}, },
cartIdFromCookie (): string | null { cartIdFromCookie (): string | null {
return getCookie(this.cookieName) ?? null return getCookie(this.cookieName) ?? null
}, },
widgetDataJson (): string {
const cloned = { ...this.widgetData }
delete cloned.consent
return JSON.stringify(cloned)
},
consentParameter (): string { consentParameter (): string {
if (this.widgetData.consent) { if (this.widgetData.consent) {
return `&consent=${encodeURIComponent(this.widgetData.consent)}` return `&consent=${encodeURIComponent(this.widgetData.consent)}`
} }
return '' return ''
}, },
additionalURLParams (): string { additionalURLParams (): string {
if (!window.location.search.includes('utm_')) { if (!window.location.search.includes('utm_')) {
return '' return ''
@@ -153,12 +154,46 @@ export function createWidgetStore (config: {
} }
return params.toString() return params.toString()
}, },
newTabTarget (): string { newTabTarget (): string {
return this.subevent ? `${this.targetUrl}${this.subevent}/` : this.targetUrl return this.subevent ? `${this.targetUrl}${this.subevent}/` : this.targetUrl
}, },
formTarget (): string {
const isFirefox = navigator.userAgent.toLowerCase().includes('firefox')
const isAndroid = navigator.userAgent.toLowerCase().includes('android')
if (isAndroid && isFirefox) {
return '_top'
}
return '_blank'
}, },
consentParameterValue (): string {
if (this.widgetData.consent) {
return encodeURIComponent(this.widgetData.consent)
}
return ''
},
formAction (): 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
},
},
actions: { actions: {
triggerLoadCallback () { triggerLoadCallback () {
nextTick(() => { nextTick(() => {
@@ -167,7 +202,6 @@ export function createWidgetStore (config: {
} }
}) })
}, },
async reload (opt: { focus?: string } = {}) { async reload (opt: { focus?: string } = {}) {
if (this.isButton) return if (this.isButton) return
@@ -303,28 +337,6 @@ export function createWidgetStore (config: {
} }
} }
}, },
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 { getVoucherFormTarget (): string {
let formTarget = `${this.targetUrl}w/${globalWidgetId}/redeem?iframe=1&locale=${LANG}` let formTarget = `${this.targetUrl}w/${globalWidgetId}/redeem?iframe=1&locale=${LANG}`
if (this.cartIdFromCookie) { if (this.cartIdFromCookie) {
@@ -334,7 +346,7 @@ export function createWidgetStore (config: {
formTarget += `&subevent=${this.subevent}` formTarget += `&subevent=${this.subevent}`
} }
if (this.widgetData) { if (this.widgetData) {
formTarget += `&widget_data=${encodeURIComponent(JSON.stringify(this.widgetData))}` formTarget += `&widget_data=${encodeURIComponent(this.widgetDataJson)}`
} }
formTarget += this.consentParameter formTarget += this.consentParameter
if (this.additionalURLParams) { if (this.additionalURLParams) {
@@ -349,12 +361,11 @@ export function createWidgetStore (config: {
setCookie(this.cookieName, data.cart_id, 30) setCookie(this.cookieName, data.cart_id, 30)
} }
let redirectUrl = data.redirect let url = data.redirect
if (redirectUrl.substring(0, 1) === '/') { if (url.substring(0, 1) === '/') {
redirectUrl = `${this.targetUrl.replace(/^([^/]+:\/\/[^/]+)\/.*$/, '$1')}${redirectUrl}` url = `${this.targetUrl.replace(/^([^/]+:\/\/[^/]+)\/.*$/, '$1')}${url}`
} }
let url = redirectUrl
if (url.includes('?')) { if (url.includes('?')) {
url = `${url}&iframe=1&locale=${LANG}&take_cart_id=${this.cartId}` url = `${url}&iframe=1&locale=${LANG}&take_cart_id=${this.cartId}`
} else { } else {
@@ -375,9 +386,11 @@ export function createWidgetStore (config: {
} else { } else {
this.overlay.frameSrc = url this.overlay.frameSrc = url
} }
} else if (data.async_id && data.check_url) { } else {
this.asyncTaskId = data.async_id this.asyncTaskId = data.async_id
if (data.check_url) {
this.asyncTaskCheckUrl = `${this.targetUrl.replace(/^([^/]+:\/\/[^/]+)\/.*$/, '$1')}${data.check_url}` this.asyncTaskCheckUrl = `${this.targetUrl.replace(/^([^/]+:\/\/[^/]+)\/.*$/, '$1')}${data.check_url}`
}
this.asyncTaskTimeout = window.setTimeout(() => this.pollAsyncTask(), this.asyncTaskInterval) this.asyncTaskTimeout = window.setTimeout(() => this.pollAsyncTask(), this.asyncTaskInterval)
this.asyncTaskInterval = 250 this.asyncTaskInterval = 250
} }
@@ -397,11 +410,8 @@ export function createWidgetStore (config: {
} }
}, },
async buy (formData: FormData, event?: Event) { async buy (formData: FormData, event?: Event) {
if (this.useIframe) { if (!this.useIframe) return
if (event) event.preventDefault() if (event) event.preventDefault()
} else {
return
}
if (this.isButton && this.items.length === 0) { if (this.isButton && this.items.length === 0) {
if (this.voucherCode) { if (this.voucherCode) {
@@ -412,7 +422,7 @@ export function createWidgetStore (config: {
return return
} }
const url = `${this.getFormAction()}&locale=${LANG}&ajax=1` const url = `${this.formAction}&locale=${LANG}&ajax=1`
this.overlay.frameLoading = true this.overlay.frameLoading = true
this.asyncTaskInterval = 100 this.asyncTaskInterval = 100
@@ -427,25 +437,24 @@ export function createWidgetStore (config: {
this.overlay.errorUrlAfter = this.newTabTarget this.overlay.errorUrlAfter = this.newTabTarget
this.overlay.errorUrlAfterNewTab = true this.overlay.errorUrlAfterNewTab = true
} else if (e.status === 405) { } else if (e.status === 405) {
// Likely a redirect!
this.targetUrl = e.responseUrl.substring(0, e.responseUrl.indexOf('/cart/add') - 18) this.targetUrl = e.responseUrl.substring(0, e.responseUrl.indexOf('/cart/add') - 18)
this.overlay.frameLoading = false this.overlay.frameLoading = false
} else { this.buy(formData)
return
}
}
this.overlay.errorMessage = STRINGS.cart_error this.overlay.errorMessage = STRINGS.cart_error
this.overlay.frameLoading = false this.overlay.frameLoading = false
} }
} else {
this.overlay.errorMessage = STRINGS.cart_error
this.overlay.frameLoading = false
}
}
}, },
redeem (voucherCode: string, event?: Event) { redeem (voucherCode: string, event?: Event) {
if (this.useIframe) { if (!this.useIframe) return
if (event) event.preventDefault() if (event) event.preventDefault()
this.voucherOpen(voucherCode) this.voucherOpen(voucherCode)
}
}, },
voucherOpen (voucherCode: string) { voucherOpen (voucherCode: string) {
// TODO just use https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
const redirectUrl = `${this.getVoucherFormTarget()}&voucher=${encodeURIComponent(voucherCode)}` const redirectUrl = `${this.getVoucherFormTarget()}&voucher=${encodeURIComponent(voucherCode)}`
if (this.useIframe) { if (this.useIframe) {
this.overlay.frameSrc = redirectUrl this.overlay.frameSrc = redirectUrl
@@ -456,6 +465,7 @@ export function createWidgetStore (config: {
resume () { resume () {
let redirectUrl = `${this.targetUrl}w/${globalWidgetId}/` let redirectUrl = `${this.targetUrl}w/${globalWidgetId}/`
if (this.subevent && !this.cartId) { if (this.subevent && !this.cartId) {
// button with subevent but no items
redirectUrl += `${this.subevent}/` redirectUrl += `${this.subevent}/`
} }
redirectUrl += `?iframe=1&locale=${LANG}` redirectUrl += `?iframe=1&locale=${LANG}`
@@ -463,7 +473,7 @@ export function createWidgetStore (config: {
redirectUrl += `&take_cart_id=${this.cartId}` redirectUrl += `&take_cart_id=${this.cartId}`
} }
if (this.widgetData) { if (this.widgetData) {
redirectUrl += `&widget_data=${encodeURIComponent(JSON.stringify(this.widgetData))}` redirectUrl += `&widget_data=${encodeURIComponent(this.widgetDataJson)}`
} }
redirectUrl += this.consentParameter redirectUrl += this.consentParameter
if (this.additionalURLParams) { if (this.additionalURLParams) {
@@ -502,7 +512,7 @@ export function createWidgetStore (config: {
redirectUrl += `&take_cart_id=${this.cartId}` redirectUrl += `&take_cart_id=${this.cartId}`
} }
if (this.widgetData) { if (this.widgetData) {
redirectUrl += `&widget_data=${encodeURIComponent(JSON.stringify(this.widgetData))}` redirectUrl += `&widget_data=${encodeURIComponent(this.widgetDataJson)}`
} }
redirectUrl += this.consentParameter redirectUrl += this.consentParameter
if (this.additionalURLParams) { if (this.additionalURLParams) {