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) {
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)}`
}
u += `&widget_data=${encodeURIComponent(store.widgetDataJson)}`
u += store.consentParameter
return u
})

View File

@@ -16,32 +16,6 @@ const formMethod = computed(() => {
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)
@@ -57,13 +31,13 @@ defineExpose({
<template lang="pug">
.pretix-widget-wrapper
.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", :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(type="hidden", name="widget_data", :value="store.widgetDataJson")
input(v-if="store.consentParameterValue", type="hidden", name="consent", :value="store.consentParameterValue")
input(
v-for="item in store.items",
:key="item.item",
@@ -76,6 +50,5 @@ defineExpose({
Overlay
</template>
<style lang="sass">
</style>

View File

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

View File

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

View File

@@ -26,9 +26,10 @@ 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
o['pretix-widget-has-events'] = true // TODO static
let best = 'red'
let allLow = true
// TODO decopypasta
for (const ev of props.day.events) {
if (ev.availability.color === 'green') {
best = 'green'
@@ -47,11 +48,12 @@ const classObject = computed(() => {
return o
})
function selectDay(e: Event) {
function selectDay (e: Event) {
if (!props.day || !props.day.events.length || !props.mobile) return
e.preventDefault()
e.stopPropagation()
// TODO decopypasta
if (props.day.events.length === 1) {
const ev = props.day.events[0]
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
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()
selectDay(e)
}
}
function attachListeners() {
function attachListeners () {
if (role.value === 'button' && cellEl.value) {
cellEl.value.addEventListener('click', selectDay)
cellEl.value.addEventListener('keydown', onKeyDown)
}
}
function detachListeners() {
function detachListeners () {
if (cellEl.value) {
cellEl.value.removeEventListener('click', selectDay)
cellEl.value.removeEventListener('keydown', onKeyDown)
@@ -92,6 +95,7 @@ onMounted(() => {
attachListeners()
})
// TODO why different from old version?
watch(role, (newValue, oldValue) => {
if (newValue === 'button' && oldValue !== 'button') {
attachListeners()
@@ -100,7 +104,6 @@ watch(role, (newValue, oldValue) => {
}
})
</script>
<template lang="pug">
td(
ref="cellEl",
@@ -113,6 +116,5 @@ td(
.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>

View File

@@ -21,7 +21,7 @@ const classObject = computed(() => {
return o
})
function select() {
function select () {
store.parentStack.push(store.targetUrl)
store.targetUrl = props.event.event_url
store.error = null
@@ -30,18 +30,16 @@ function select() {
store.reload()
}
</script>
<template lang="pug">
a.pretix-widget-event-calendar-event(
href="#",
: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 }}
.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>

View File

@@ -7,11 +7,9 @@ defineProps<{
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>

View File

@@ -11,12 +11,9 @@ 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 displayEventInfo = computed(() => store.displayEventInfo || (store.displayEventInfo === null && (store.events || store.weeks || store.days)))
const idCartExistsMsg = computed(() => `${store.htmlId}-cart-exists`)
const buyLabel = computed(() => {
@@ -43,78 +40,16 @@ const hiddenParams = computed(() => {
const params = new URL(store.getVoucherFormTarget()).searchParams
params.delete('iframe')
params.delete('take_cart_id')
return Array.from(params.entries())
return [...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) {
// reset if we are not in a series
store.name = null
store.frontpageText = null
}
@@ -123,13 +58,20 @@ function backToList () {
store.appendEvents = false
store.triggerLoadCallback()
if (store.events !== null) {
if (store.events !== undefined && store.events !== null) {
store.view = 'events'
} else if (store.days !== null) {
} else if (store.days !== undefined && store.days !== null) {
store.view = 'days'
} else {
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 () {
@@ -208,14 +150,14 @@ watch(() => store.overlay?.frameShown, (newValue) => {
form(
ref="form",
method="post",
:action="formAction",
:target="formTarget",
:action="store.formAction",
:target="store.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")
input(type="hidden", name="widget_data", :value="store.widgetDataJson")
input(v-if="store.consentParameterValue", type="hidden", name="consent", :value="store.consentParameterValue")
//- Error message
.pretix-widget-error-message(v-if="store.error") {{ store.error }}
@@ -242,7 +184,7 @@ watch(() => store.overlay?.frameShown, (newValue) => {
| {{ STRINGS.waiting_list }}
.pretix-widget-clear
//- Product list
//- Actual Product list
Category(v-for="category in store.categories", :key="category.id", :category="category")
//- Buy button
@@ -293,6 +235,5 @@ watch(() => store.overlay?.frameShown, (newValue) => {
button(@click="handleRedeem") {{ STRINGS.redeem }}
.pretix-widget-clear
</template>
<style lang="sass">
</style>

View File

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

View File

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

View File

@@ -16,13 +16,11 @@ const currentValue = computed(() => {
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>

View File

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

View File

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

View File

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

View File

@@ -69,8 +69,11 @@ function errorClose (e: Event) {
store.overlay.errorUrlAfterNewTab = false
}
function close () {
function close (e) {
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()
return
}
@@ -82,6 +85,7 @@ function close () {
}
function cancel (e: Event) {
// do not allow to cancel while frame is loading as we cannot abort the operation
if (store.overlay.frameLoading) {
e.preventDefault()
const target = e.target as HTMLElement
@@ -111,14 +115,14 @@ function triggerCloseCallback () {
})
}
// TODO check if watchEffect is better for the following watchers
watch(() => store.overlay.lightbox, (newValue, oldValue) => {
if (newValue) {
if (newValue.image !== oldValue?.image) {
newValue.loading = true
}
if (!oldValue) {
lightboxDialog.value?.showModal()
}
if (!newValue) return
if (newValue.image !== oldValue?.image) {
newValue.loading = true
}
if (!oldValue) {
lightboxDialog.value?.showModal()
}
})
@@ -128,19 +132,19 @@ watch(() => store.overlay.errorMessage, (newValue, oldValue) => {
}
})
watch(() => store.overlay.frameShown, (newValue) => {
if (newValue) {
nextTick(() => {
closeButton.value?.focus()
})
}
watch(() => store.overlay.frameShown, async (newValue) => {
if (!newValue) return
await nextTick()
closeButton.value?.focus()
})
watch(() => store.overlay.frameSrc, (newValue, oldValue) => {
// show loading spinner only when previously no frame_src was set
if (newValue && !oldValue) {
store.overlay.frameLoading = true
}
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'
}
})
@@ -151,7 +155,7 @@ watch(() => store.overlay.frameLoading, (newValue) => {
frameDialog.value.showModal()
}
} 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()
}
}
@@ -169,7 +173,6 @@ onUnmounted(() => {
<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")
@@ -193,8 +196,6 @@ Teleport(to="body")
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 }}
@@ -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.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")
@@ -227,6 +227,5 @@ Teleport(to="body")
)
figcaption(v-if="store.overlay.lightbox.description") {{ store.overlay.lightbox.description }}
</template>
<style lang="sass">
</style>

View File

@@ -36,12 +36,14 @@ onMounted(() => {
resizeObserver.observe(wrapper.value)
}
store.reload()
emit('mounted')
store.reload() // TODO call earlier?
emit('mounted') // TODO where does this go?
})
watch(() => store.view, (newValue, oldValue) => {
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()
if (rect.top < 0) {
wrapper.value.scrollIntoView()

View File

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