mirror of
https://github.com/pretix/pretix.git
synced 2026-09-11 15:54:42 +00:00
Compare commits
30
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86cc719651 | ||
|
|
742a69f09f | ||
|
|
c05dee7af5 | ||
|
|
e399a49c8b | ||
|
|
b9b951e56b | ||
|
|
88bb1f4008 | ||
|
|
24e7db7ddd | ||
|
|
96ed6fe1a6 | ||
|
|
d85e52c83e | ||
|
|
d9b5fa5b16 | ||
|
|
fb948cc7f4 | ||
|
|
c358f412fd | ||
|
|
b78de6386b | ||
|
|
50c00b47ae | ||
|
|
6025c7942f | ||
|
|
23a3412154 | ||
|
|
3635f6fb0c | ||
|
|
2aadfa2ce4 | ||
|
|
dd0d78242e | ||
|
|
42c5de895c | ||
|
|
e91718e73b | ||
|
|
2c15d8b074 | ||
|
|
edb4069e18 | ||
|
|
dc7d5c6029 | ||
|
|
caa6fb187b | ||
|
|
7d93cae2a9 | ||
|
|
58a58eff83 | ||
|
|
a84a02c298 | ||
|
|
4390403b9a | ||
|
|
9d53cf840b |
@@ -0,0 +1,2 @@
|
||||
# Format pre-vue code with eslint where possible (2026-09-10)
|
||||
d85e52c83ed3639e040372fd0052e842e4899d90
|
||||
@@ -566,7 +566,7 @@ organizer level.
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"imprint_url": "https://pretix.eu",
|
||||
"region": "DE",
|
||||
…
|
||||
}
|
||||
|
||||
@@ -579,12 +579,14 @@ organizer level.
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"imprint_url":
|
||||
"region":
|
||||
{
|
||||
"value": "https://pretix.eu",
|
||||
"label": "Imprint URL",
|
||||
"value": "DE",
|
||||
"label": "Region",
|
||||
"readonly": false,
|
||||
"help_text": "This should point e.g. to a part of your website that has your contact details and legal information."
|
||||
"help_text": "Will be used to determine date and time formatting as well as default country for customer
|
||||
addresses and phone numbers. For formatting, this takes less priority than the language and
|
||||
is therefore mostly relevant for languages used in different regions globally (like English)."
|
||||
}
|
||||
},
|
||||
…
|
||||
@@ -620,7 +622,7 @@ organizer level.
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"imprint_url": "https://example.org/imprint/"
|
||||
"region": "DE"
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
@@ -632,7 +634,7 @@ organizer level.
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"imprint_url": "https://example.org/imprint/",
|
||||
"region": "DE",
|
||||
…
|
||||
}
|
||||
|
||||
|
||||
+58
-1
@@ -8,7 +8,64 @@ import vuePug from 'eslint-plugin-vue-pug'
|
||||
|
||||
const ignores = globalIgnores([
|
||||
'**/node_modules',
|
||||
'**/dist'
|
||||
'**/dist',
|
||||
// Vendored code
|
||||
'src/pretix/static/leaflet',
|
||||
'src/pretix/static/clipboard',
|
||||
'src/pretix/static/cropper',
|
||||
'src/pretix/static/lightbox',
|
||||
'src/pretix/static/are-you-sure',
|
||||
'src/pretix/static/vuejs',
|
||||
'src/pretix/static/fontawesome',
|
||||
'src/pretix/static/typeahead',
|
||||
'src/pretix/static/moment',
|
||||
'src/pretix/static/pdfjs',
|
||||
'src/pretix/static/sortable',
|
||||
'src/pretix/static/iframeresizer',
|
||||
'src/pretix/static/bootstrap',
|
||||
'src/pretix/static/d3',
|
||||
'src/pretix/static/jsi18n',
|
||||
'src/pretix/static/fabric',
|
||||
'src/pretix/static/datetimepicker',
|
||||
'src/pretix/static/charts',
|
||||
'src/pretix/static/fileupload',
|
||||
'src/pretix/static/seating',
|
||||
'src/pretix/static/rest_framework',
|
||||
'src/pretix/static/select2',
|
||||
'src/pretix/static/schema',
|
||||
'src/pretix/static/slider',
|
||||
'src/pretix/static/jquery',
|
||||
'src/pretix/static/colorpicker',
|
||||
'src/pretix/static/rrule',
|
||||
'src/pretix/static/pretixcontrol/js/jquery.qrcode.min.js',
|
||||
'src/pretix/static/pretixpresale/js/widget/docready.js',
|
||||
// Pre-vue JS code
|
||||
'src/pretix/static/pretixbase/js/addressform.js',
|
||||
'src/pretix/static/pretixbase/js/asynctask.js',
|
||||
'src/pretix/static/pretixbase/js/details.js',
|
||||
'src/pretix/static/pretixbase/js/gettextstub.js',
|
||||
'src/pretix/static/pretixbase/js/i18nstring.js',
|
||||
'src/pretix/static/pretixcontrol/js/menu.js',
|
||||
'src/pretix/static/pretixcontrol/js/ui/editor.js',
|
||||
'src/pretix/static/pretixcontrol/js/ui/geo.js',
|
||||
'src/pretix/static/pretixcontrol/js/ui/main.js',
|
||||
'src/pretix/static/pretixcontrol/js/ui/plugins.js',
|
||||
'src/pretix/static/pretixcontrol/js/ui/subevent.js',
|
||||
'src/pretix/static/pretixcontrol/js/ui/variations.js',
|
||||
'src/pretix/static/pretixcontrol/js/ui/webauthn.js',
|
||||
'src/pretix/static/pretixpresale/js/ui/cart.js',
|
||||
'src/pretix/static/pretixpresale/js/ui/main.js',
|
||||
'src/pretix/static/pretixpresale/js/ui/questions.js',
|
||||
'src/pretix/static/pretixpresale/js/widget/floatformat.js',
|
||||
'src/pretix/static/pretixpresale/js/widget/widget.js',
|
||||
'src/pretix/plugins/banktransfer/static',
|
||||
'src/pretix/plugins/paypal2/static',
|
||||
'src/pretix/plugins/statistics/static',
|
||||
'src/pretix/plugins/stripe/static',
|
||||
// Plugin checkouts
|
||||
'local',
|
||||
// docs
|
||||
'doc',
|
||||
])
|
||||
|
||||
export default defineConfig([
|
||||
|
||||
+2
-2
@@ -56,7 +56,7 @@ dependencies = [
|
||||
"django-querytagger==0.0.3",
|
||||
"django-redis==7.0.*",
|
||||
"django-scopes==2.1.*",
|
||||
"django-statici18n==2.7.*",
|
||||
"django-statici18n==2.8.*",
|
||||
"djangorestframework==3.17.*",
|
||||
"dnspython==2.8.*",
|
||||
"drf_ujson2==1.7.*",
|
||||
@@ -115,7 +115,7 @@ dev = [
|
||||
"fakeredis==2.37.*",
|
||||
"flake8==7.3.*",
|
||||
"freezegun",
|
||||
"isort==9.0.*",
|
||||
"isort==8.0.*",
|
||||
"pep8-naming==0.15.*",
|
||||
"potypo",
|
||||
"pytest-asyncio>=1.4.0",
|
||||
|
||||
@@ -899,7 +899,7 @@ class BaseQuestionsForm(forms.Form):
|
||||
field.widget.attrs['data-question-dependency-values'] = escapejson_attr(json.dumps(q.dependency_values))
|
||||
if q.type != 'M':
|
||||
field.widget.attrs['required'] = q.required and not self.all_optional
|
||||
field._required = q.required and not self.all_optional
|
||||
field._required = q.required and not self.all_optional
|
||||
field.required = False
|
||||
return field
|
||||
|
||||
@@ -1497,7 +1497,8 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
pass # Skip re-validation if it is validated
|
||||
elif self.validate_vat_id and vat_id_applicable:
|
||||
try:
|
||||
normalized_id = validate_vat_id(data.get('vat_id'), str(data.get('country')))
|
||||
requester_id = self.request.event.settings.invoice_address_from_vat_id
|
||||
normalized_id = validate_vat_id(data.get('vat_id'), str(data.get('country')), requester_id)
|
||||
self.instance.vat_id_validated = bool(normalized_id)
|
||||
self.instance.vat_id = data['vat_id'] = normalized_id
|
||||
except VATIDFinalError as e:
|
||||
|
||||
@@ -626,47 +626,14 @@ class Order(LockModel, LoggedModel):
|
||||
self.save(update_fields=['last_modified'])
|
||||
|
||||
def set_expires(self, now_dt=None, subevents=None):
|
||||
now_dt = now_dt or now()
|
||||
tz = ZoneInfo(self.event.settings.timezone)
|
||||
from pretix.base.services.payment import compute_payment_deadline
|
||||
|
||||
sales_channel_suffix = "_" + self.sales_channel.identifier.replace(".", "_")
|
||||
if not (mode := self.event.settings.get(f'payment_term_mode{sales_channel_suffix}')):
|
||||
mode = self.event.settings.get('payment_term_mode')
|
||||
sales_channel_suffix = ""
|
||||
|
||||
if mode == 'days':
|
||||
exp_by_date = now_dt.astimezone(tz) + timedelta(days=self.event.settings.get(f'payment_term_days{sales_channel_suffix}', as_type=int))
|
||||
exp_by_date = exp_by_date.astimezone(tz).replace(hour=23, minute=59, second=59, microsecond=0)
|
||||
if self.event.settings.get('payment_term_weekdays'):
|
||||
if exp_by_date.weekday() == 5:
|
||||
exp_by_date += timedelta(days=2)
|
||||
elif exp_by_date.weekday() == 6:
|
||||
exp_by_date += timedelta(days=1)
|
||||
elif mode == 'minutes':
|
||||
exp_by_date = now_dt.astimezone(tz) + timedelta(minutes=self.event.settings.get(f'payment_term_minutes{sales_channel_suffix}', as_type=int))
|
||||
else:
|
||||
raise ValueError("'payment_term_mode' has an invalid value '{}'.".format(mode))
|
||||
|
||||
self.expires = exp_by_date
|
||||
|
||||
term_last = self.event.settings.get('payment_term_last', as_type=RelativeDateWrapper)
|
||||
if term_last:
|
||||
if self.event.has_subevents and subevents:
|
||||
terms = [
|
||||
term_last.datetime(se).date()
|
||||
for se in subevents
|
||||
]
|
||||
if not terms:
|
||||
return
|
||||
term_last = min(terms)
|
||||
else:
|
||||
term_last = term_last.datetime(self.event).date()
|
||||
term_last = make_aware(datetime.combine(
|
||||
term_last,
|
||||
time(hour=23, minute=59, second=59)
|
||||
), tz)
|
||||
if term_last < self.expires:
|
||||
self.expires = term_last
|
||||
self.expires = compute_payment_deadline(
|
||||
event=self.event,
|
||||
sales_channel=self.sales_channel,
|
||||
now_dt=now_dt,
|
||||
subevents=subevents,
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def tax_total(self):
|
||||
|
||||
@@ -1605,6 +1605,7 @@ def add_payment_to_cart_session(cart_session, provider, min_value: Decimal=None,
|
||||
'max_value': str(max_value) if max_value is not None else None,
|
||||
'info_data': info_data or {},
|
||||
})
|
||||
cart_session['payments_postpone'] = False
|
||||
|
||||
|
||||
def add_payment_to_cart(request, provider, min_value: Decimal=None, max_value: Decimal=None, info_data: dict=None):
|
||||
|
||||
@@ -961,7 +961,7 @@ def _check_positions(event: Event, now_dt: datetime, time_machine_now_dt: dateti
|
||||
|
||||
|
||||
def _apply_rounding_and_fees(positions: List[CartPosition], payment_requests: List[dict], address: InvoiceAddress,
|
||||
meta_info: dict, event: Event, require_approval=False):
|
||||
meta_info: dict, event: Event, sales_channel: SalesChannel, require_approval=False):
|
||||
fees = []
|
||||
# Pre-rounding, pre-fee total is used for fee calculation
|
||||
total = sum([c.gross_price_before_rounding for c in positions])
|
||||
@@ -1021,7 +1021,14 @@ def _apply_rounding_and_fees(positions: List[CartPosition], payment_requests: Li
|
||||
payments_assigned += to_pay
|
||||
p['payment_amount'] = to_pay
|
||||
|
||||
if total != payments_assigned and not require_approval:
|
||||
allow_postponed_payment = (
|
||||
require_approval or
|
||||
(
|
||||
sales_channel.identifier in event.settings.payment_choice_postpone_allowed_channels and not payment_requests
|
||||
)
|
||||
)
|
||||
|
||||
if total != payments_assigned and not allow_postponed_payment:
|
||||
raise OrderError(_("The selected payment methods do not cover the total balance."))
|
||||
|
||||
return fees
|
||||
@@ -1043,7 +1050,15 @@ def _create_order(event: Event, *, email: str, positions: List[CartPosition], no
|
||||
|
||||
# Final calculation of fees, also performs final rounding
|
||||
try:
|
||||
fees = _apply_rounding_and_fees(positions, payment_requests, address, meta_info, event, require_approval=require_approval)
|
||||
fees = _apply_rounding_and_fees(
|
||||
positions,
|
||||
payment_requests,
|
||||
address,
|
||||
meta_info,
|
||||
event,
|
||||
sales_channel=sales_channel,
|
||||
require_approval=require_approval
|
||||
)
|
||||
except TaxRule.SaleNotAllowed:
|
||||
raise OrderError(error_messages['country_blocked'])
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from datetime import datetime, time, timedelta
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from django.utils.timezone import make_aware, now
|
||||
|
||||
from pretix.base.models import Event, SalesChannel
|
||||
from pretix.base.reldate import RelativeDateWrapper
|
||||
|
||||
|
||||
def compute_payment_deadline(event: Event, sales_channel: SalesChannel, now_dt=None, subevents=None) -> datetime:
|
||||
now_dt = now_dt or now()
|
||||
tz = ZoneInfo(event.settings.timezone)
|
||||
|
||||
sales_channel_suffix = "_" + sales_channel.identifier.replace(".", "_")
|
||||
if not (mode := event.settings.get(f'payment_term_mode{sales_channel_suffix}')):
|
||||
mode = event.settings.get('payment_term_mode')
|
||||
sales_channel_suffix = ""
|
||||
|
||||
if mode == 'days':
|
||||
exp_by_date = now_dt.astimezone(tz) + timedelta(
|
||||
days=event.settings.get(f'payment_term_days{sales_channel_suffix}', as_type=int))
|
||||
exp_by_date = exp_by_date.astimezone(tz).replace(hour=23, minute=59, second=59, microsecond=0)
|
||||
if event.settings.get('payment_term_weekdays'):
|
||||
if exp_by_date.weekday() == 5:
|
||||
exp_by_date += timedelta(days=2)
|
||||
elif exp_by_date.weekday() == 6:
|
||||
exp_by_date += timedelta(days=1)
|
||||
elif mode == 'minutes':
|
||||
exp_by_date = now_dt.astimezone(tz) + timedelta(
|
||||
minutes=event.settings.get(f'payment_term_minutes{sales_channel_suffix}', as_type=int))
|
||||
else:
|
||||
raise ValueError("'payment_term_mode' has an invalid value '{}'.".format(mode))
|
||||
|
||||
expires = exp_by_date
|
||||
|
||||
term_last = event.settings.get('payment_term_last', as_type=RelativeDateWrapper)
|
||||
if term_last:
|
||||
if event.has_subevents and subevents:
|
||||
terms = [
|
||||
term_last.datetime(se).date()
|
||||
for se in subevents
|
||||
]
|
||||
if not terms:
|
||||
return expires
|
||||
term_last = min(terms)
|
||||
else:
|
||||
term_last = term_last.datetime(event).date()
|
||||
term_last = make_aware(datetime.combine(
|
||||
term_last,
|
||||
time(hour=23, minute=59, second=59)
|
||||
), tz)
|
||||
if term_last < expires:
|
||||
return term_last
|
||||
|
||||
return expires
|
||||
@@ -343,6 +343,66 @@ def _validate_vat_id_EU(vat_id, country_code):
|
||||
return vat_id
|
||||
|
||||
|
||||
def _validate_vat_id_EU_fallback_germany(vat_id, country_code, requester_id):
|
||||
# We can skip most static validation checks because _validate_vat_id_EU always runs before
|
||||
vat_id = normalize_vat_id(vat_id, country_code)
|
||||
|
||||
# The VIES service of the European commission is overused and down due to rate limits A LOT. There is another
|
||||
# API by German BZSt, but it only works if the requester is German and the requested is not.
|
||||
# https://www.bzst.de/DE/Unternehmen/Identifikationsnummern/Umsatzsteuer-Identifikationsnummer/AuslaendischeUSt-IdNr/auslaendische_ust_idnr_node.html
|
||||
try:
|
||||
r = requests.post(
|
||||
"https://api.evatr.vies.bzst.de/app/v1/abfrage",
|
||||
json={
|
||||
"anfragendeUstid": requester_id,
|
||||
"angefragteUstid": vat_id,
|
||||
},
|
||||
timeout=10,
|
||||
)
|
||||
d = r.json()
|
||||
if r.status_code == 200:
|
||||
if d['status'] in ('evatr-0000', 'evatr-2008'):
|
||||
# evatr-0000: Die angefragte Ust-IdNr. ist zum Anfragezeitpunkt gültig.
|
||||
# evatr-2008: Die angefragte Ust-IdNr. ist zum Anfragezeitpunkt gültig.
|
||||
# Für die qualifizierte Bestätigungsanfrage liegt einer Besonderheit vor.
|
||||
# Für Rückfragen wenden Sie sich an das BZSt.
|
||||
return vat_id
|
||||
# evatr-2002: Die angefragte USt-IdNr. ist zum Anfragezeitpunkt nicht gültig.
|
||||
# Sie ist erst gültig ab dem Datum im Feld gueltigAb.
|
||||
# evatr-2006: Die angefragte Ust-IdNr. ist zum Anfragezeitpunkt nicht gültig.
|
||||
# Sie war gültig im Zeitraum, der durch die Werte in den Feldern gueltigAb und gueltigBis beschrieben ist.
|
||||
raise VATIDFinalError(error_messages['invalid'])
|
||||
elif r.status_code == 400:
|
||||
if d['status'] in ('evatr-0002', 'evatr-0004', 'evatr-0008'):
|
||||
# evatr-0002: Mindestens eins der Pflichtfelder ist nicht besetzt.
|
||||
# evatr-0004: Die anfragende DE Ust-IdNr. ist syntaktisch falsch. Sie passt nicht in das deutsche Erzeugungsschema.
|
||||
# evatr-0008: Die maximale Anzahl von qualifizierten Bestätigungsabfragen für diese Session wurde erreicht.
|
||||
# Bitte starten Sie erneut mit einer einfachen Bestätigungsabfrage.
|
||||
raise VATIDTemporaryError(error_messages['unavailable'])
|
||||
# evatr-0005: Die angegebene angefragte Ust-IdNr. ist syntaktisch falsch.
|
||||
# evatr-0012: Die angefrage USt-IdNr. ist syntaktisch falsch. Sie passt nicht in das Erzeugungsschema.
|
||||
# evatr-2003: Das angegebene Länderkennzeichen der angefragten USt-IdNr. ist nicht gültig.
|
||||
raise VATIDFinalError(error_messages['invalid'])
|
||||
elif r.status_code == 403:
|
||||
# evatr-0006: Die anfragende DE USt-IdNr. ist nicht berechtigt eine DE Ust-IdNr. anzufragen.
|
||||
# evatr-0007: Fehlerhafter Aufruf.
|
||||
raise VATIDTemporaryError(error_messages['unavailable'])
|
||||
elif r.status_code == 404:
|
||||
if d['status'] in ('evatr-2005'):
|
||||
# evatr-2005: Die angegebene eigene DE Ust-IdNr. ist zum Anfragezeitpunkt nicht gültig.
|
||||
raise VATIDTemporaryError(error_messages['unavailable'])
|
||||
# evatr-2001: Die angefragte USt-IdNr. ist zum Anfragezeitpunkt nicht vergeben.
|
||||
raise VATIDFinalError(error_messages['invalid'])
|
||||
else: # 500, 503
|
||||
raise VATIDTemporaryError(error_messages['unavailable'])
|
||||
except requests.RequestException:
|
||||
logger.exception('VAT ID checking failed for country {}'.format(country_code))
|
||||
raise VATIDTemporaryError(error_messages['unavailable'])
|
||||
except ValueError: # JSON parsing failed
|
||||
logger.exception('VAT ID checking failed for country {}'.format(country_code))
|
||||
raise VATIDTemporaryError(error_messages['unavailable'])
|
||||
|
||||
|
||||
def _validate_vat_id_CH(vat_id, country_code):
|
||||
if vat_id[:3] != 'CHE':
|
||||
raise VATIDFinalError(error_messages['country_mismatch'])
|
||||
@@ -394,12 +454,18 @@ def _validate_vat_id_CH(vat_id, country_code):
|
||||
return vat_id
|
||||
|
||||
|
||||
def validate_vat_id(vat_id, country_code):
|
||||
def validate_vat_id(vat_id, country_code, requester_id=None):
|
||||
if not vat_id:
|
||||
return vat_id
|
||||
country_code = str(country_code)
|
||||
if is_eu_country(country_code):
|
||||
return _validate_vat_id_EU(vat_id, country_code)
|
||||
try:
|
||||
return _validate_vat_id_EU(vat_id, country_code)
|
||||
except VATIDTemporaryError:
|
||||
if requester_id and requester_id.startswith("DE") and not vat_id.startswith("DE"):
|
||||
return _validate_vat_id_EU_fallback_germany(vat_id, country_code, requester_id)
|
||||
else:
|
||||
raise
|
||||
elif country_code == 'CH':
|
||||
return _validate_vat_id_CH(vat_id, country_code)
|
||||
elif country_code == 'NO':
|
||||
|
||||
+24
-11
File diff suppressed because one or more lines are too long
@@ -135,6 +135,8 @@ class BaseQuestionsViewMixin:
|
||||
question_field.initial = getattr(question_field, 'initial', None) or src['initial']
|
||||
if 'validators' in src:
|
||||
question_field.validators += src['validators']
|
||||
if 'label' in src:
|
||||
question_field.label = src['label']
|
||||
|
||||
if len(form.fields) > 0:
|
||||
formlist.append(form)
|
||||
|
||||
@@ -172,7 +172,9 @@ class CachedFileInput(forms.ClearableFileInput):
|
||||
from ...base.models import CachedFile
|
||||
v = super().value_from_datadict(data, files, name)
|
||||
if v is None and data.get(name + '-cachedfile'): # An explicit "[x] clear" would be False, not None
|
||||
return CachedFile.objects.filter(id=data[name + '-cachedfile']).first()
|
||||
v = CachedFile.objects.filter(id=data[name + '-cachedfile']).first()
|
||||
if not v.allowed_for_session(self.request):
|
||||
v = None
|
||||
return v
|
||||
|
||||
def get_context(self, name, value, attrs):
|
||||
@@ -244,6 +246,11 @@ class ExtFileField(ExtValidationMixin, SizeFileField):
|
||||
class CachedFileField(ExtFileField):
|
||||
widget = CachedFileInput
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.request = kwargs.pop("request", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.widget.request = self.request
|
||||
|
||||
def to_python(self, data):
|
||||
from ...base.models import CachedFile
|
||||
|
||||
@@ -271,6 +278,8 @@ class CachedFileField(ExtFileField):
|
||||
filename=data.name,
|
||||
type=data.content_type,
|
||||
)
|
||||
if self.request:
|
||||
cf.bind_to_session(self.request) # no salt because we want direct web access
|
||||
cf.file.save(data.name, data.file)
|
||||
cf.save()
|
||||
data._uploaded_to = cf
|
||||
@@ -294,6 +303,8 @@ class CachedFileField(ExtFileField):
|
||||
filename=data.name,
|
||||
type=data.content_type,
|
||||
)
|
||||
if self.request:
|
||||
cf.bind_to_session(self.request) # no salt because we want direct web access
|
||||
cf.file.save(data.name, data.file)
|
||||
cf.save()
|
||||
data._uploaded_to = cf
|
||||
|
||||
@@ -855,14 +855,21 @@ class PaymentSettingsForm(EventSettingsValidationMixin, SettingsForm):
|
||||
'payment_term_accept_late',
|
||||
'payment_pending_hidden',
|
||||
'payment_explanation',
|
||||
'payment_choice_postpone_allowed_channels',
|
||||
'tax_rule_payment',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
channels = list(self.obj.organizer.sales_channels.all())
|
||||
self.fields['payment_choice_postpone_allowed_channels'].choices = [
|
||||
(c.identifier, c.label) for c in channels
|
||||
if c.type_instance.payment_restrictions_supported
|
||||
]
|
||||
|
||||
self.term_channel_fields = {}
|
||||
for c in self.obj.organizer.sales_channels.all():
|
||||
for c in channels:
|
||||
if c.type_instance.payment_restrictions_supported and c.identifier != "web":
|
||||
# At the moment, it seems sufficient to allow this for the same channel types as other payment settings
|
||||
# We can always introduce more flags later if needed
|
||||
|
||||
@@ -105,12 +105,12 @@ class GlobalSettingsForm(SettingsForm):
|
||||
domain=settings.SITE_URL
|
||||
)
|
||||
)),
|
||||
('widget_vite_origins', forms.CharField(
|
||||
('widget_vue2_origins', forms.CharField(
|
||||
widget=forms.Textarea(attrs={'rows': '3'}),
|
||||
required=False,
|
||||
# Not translated on purpose, this is a temporary feature and contains too many special case words
|
||||
label="Vite widget origins",
|
||||
help_text="One origin per line (e.g. https://example.com). Requests from these origins will be served the new vite-based widget.",
|
||||
label="Vue2 widget origins",
|
||||
help_text="One origin per line (e.g. https://example.com). Requests from these origins will be served the old vue2-based widget.",
|
||||
))
|
||||
])
|
||||
responses = register_global_settings.send(self)
|
||||
|
||||
@@ -87,6 +87,7 @@ class RRuleForm(forms.Form):
|
||||
('1', pgettext_lazy('rrule', 'first')),
|
||||
('2', pgettext_lazy('rrule', 'second')),
|
||||
('3', pgettext_lazy('rrule', 'third')),
|
||||
('4', pgettext_lazy('rrule', 'fourth')),
|
||||
('-1', pgettext_lazy('rrule', 'last')),
|
||||
],
|
||||
required=False
|
||||
@@ -134,6 +135,7 @@ class RRuleForm(forms.Form):
|
||||
('1', pgettext_lazy('rrule', 'first')),
|
||||
('2', pgettext_lazy('rrule', 'second')),
|
||||
('3', pgettext_lazy('rrule', 'third')),
|
||||
('4', pgettext_lazy('rrule', 'fourth')),
|
||||
('-1', pgettext_lazy('rrule', 'last')),
|
||||
],
|
||||
required=False
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
{% bootstrap_form_errors form layout="control" %}
|
||||
{% bootstrap_field form.tax_rule_payment layout="control" %}
|
||||
{% bootstrap_field form.payment_explanation layout="control" %}
|
||||
{% bootstrap_field form.payment_choice_postpone_allowed_channels layout="control" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
{% if "event.settings.payment:write" in request.eventpermset %}
|
||||
|
||||
@@ -64,6 +64,7 @@ from pretix.base.forms.auth import (
|
||||
)
|
||||
from pretix.base.metrics import pretix_failed_logins, pretix_successful_logins
|
||||
from pretix.base.models import TeamInvite, U2FDevice, User, WebAuthnDevice
|
||||
from pretix.helpers import OF_SELF
|
||||
from pretix.helpers.http import get_client_ip, redirect_to_url
|
||||
from pretix.helpers.ratelimit import rate_limit, rate_limit_reset
|
||||
from pretix.helpers.security import handle_login_source, session_login
|
||||
@@ -395,15 +396,17 @@ class Recover(TemplateView):
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if self.form.is_valid():
|
||||
try:
|
||||
user = User.objects.get(id=self.request.GET.get('id'), auth_backend='native')
|
||||
except User.DoesNotExist:
|
||||
return self.invalid('unknownuser')
|
||||
if not default_token_generator.check_token(user, self.request.GET.get('token')):
|
||||
return self.invalid('invalid')
|
||||
user.set_password(self.form.cleaned_data['password'])
|
||||
user.needs_password_change = False
|
||||
user.save()
|
||||
with transaction.atomic():
|
||||
# Check token in transaction to prevent race condition
|
||||
try:
|
||||
user = User.objects.select_for_update(of=OF_SELF).get(id=self.request.GET.get('id'), auth_backend='native')
|
||||
except User.DoesNotExist:
|
||||
return self.invalid('unknownuser')
|
||||
if not default_token_generator.check_token(user, self.request.GET.get('token')):
|
||||
return self.invalid('invalid')
|
||||
user.set_password(self.form.cleaned_data['password'])
|
||||
user.needs_password_change = False
|
||||
user.save()
|
||||
messages.success(request, _('You can now login using your new password.'))
|
||||
user.log_action('pretix.control.auth.user.forgot_password.recovered')
|
||||
|
||||
|
||||
@@ -1646,7 +1646,8 @@ class OrderCheckVATID(OrderView):
|
||||
return redirect(self.get_order_url())
|
||||
|
||||
try:
|
||||
normalized_id = validate_vat_id(ia.vat_id, str(ia.country))
|
||||
requester_id = self.request.event.settings.invoice_address_from_vat_id
|
||||
normalized_id = validate_vat_id(ia.vat_id, str(ia.country), requester_id)
|
||||
with transaction.atomic():
|
||||
ia.vat_id_validated = True
|
||||
ia.vat_id = normalized_id
|
||||
|
||||
@@ -27,6 +27,7 @@ from decimal import Decimal
|
||||
from io import BytesIO
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.files import File
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.files.storage import default_storage
|
||||
@@ -193,6 +194,7 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
|
||||
c.expires = now() + timedelta(days=7)
|
||||
c.date = now()
|
||||
c.filename = 'background_preview.pdf'
|
||||
c.bind_to_session(request, "ticketoutput-pdf-background")
|
||||
c.type = 'application/pdf'
|
||||
c.save()
|
||||
c.file.save('empty.pdf', ContentFile(buffer.read()))
|
||||
@@ -218,6 +220,7 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
|
||||
c.expires = now() + timedelta(days=7)
|
||||
c.date = now()
|
||||
c.filename = 'background_preview.pdf'
|
||||
c.bind_to_session(request, "ticketoutput-pdf-background")
|
||||
c.type = 'application/pdf'
|
||||
c.file = fileobj
|
||||
c.save()
|
||||
@@ -303,5 +306,7 @@ class FontsCSSView(TemplateView):
|
||||
class PdfView(TemplateView):
|
||||
def get(self, request, *args, **kwargs):
|
||||
cf = get_object_or_404(CachedFile, id=kwargs.get("filename"), filename="background_preview.pdf")
|
||||
if not cf.allowed_for_session(request, "ticketoutput-pdf-background"):
|
||||
raise PermissionDenied()
|
||||
resp = FileResponse(cf.file, filename=cf.filename, content_type='application/pdf')
|
||||
return resp
|
||||
|
||||
@@ -29,7 +29,7 @@ from django.urls import reverse
|
||||
def build_absolute_uri(urlname, args=None, kwargs=None):
|
||||
warnings.warn(
|
||||
'Usage of build_absolute_uri is confusing since there are many functions with that name. '
|
||||
'Replace this usage with ',
|
||||
'Replace this usage with mainreverse_absolute.',
|
||||
DeprecationWarning
|
||||
)
|
||||
return mainreverse_absolute(urlname, args, kwargs)
|
||||
|
||||
@@ -8,16 +8,16 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-08-24 15:35+0000\n"
|
||||
"PO-Revision-Date: 2026-08-24 14:30+0000\n"
|
||||
"PO-Revision-Date: 2026-09-04 14:29+0000\n"
|
||||
"Last-Translator: Albizuri <oier@puntu.eus>\n"
|
||||
"Language-Team: Basque <https://translate.pretix.eu/projects/pretix/pretix/eu/"
|
||||
">\n"
|
||||
"Language-Team: Basque <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"eu/>\n"
|
||||
"Language: eu\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 2026.8.1\n"
|
||||
"X-Generator: Weblate 2026.9\n"
|
||||
|
||||
#: pretix/_base_settings.py
|
||||
msgid "English"
|
||||
@@ -891,7 +891,7 @@ msgstr "Produktuaren data"
|
||||
#: pretix/control/templates/pretixcontrol/order/index.html
|
||||
#: pretix/presale/templates/pretixpresale/event/order.html
|
||||
msgid "Order details"
|
||||
msgstr ""
|
||||
msgstr "Eskaeraren xehetasunak"
|
||||
|
||||
#: pretix/base/datasync/sourcefields.py pretix/base/modelimport_orders.py
|
||||
#: pretix/control/forms/filter.py
|
||||
@@ -17296,7 +17296,7 @@ msgstr ""
|
||||
|
||||
#: pretix/control/logdisplay.py
|
||||
msgid "The order details have been changed."
|
||||
msgstr ""
|
||||
msgstr "Eskaeraren xehetasunak aldatu dira."
|
||||
|
||||
#: pretix/control/logdisplay.py
|
||||
msgid "The order has been marked as unpaid."
|
||||
@@ -23106,7 +23106,7 @@ msgstr ""
|
||||
#: pretix/control/templates/pretixcontrol/order/index.html
|
||||
#, python-format
|
||||
msgid "Order details: %(code)s"
|
||||
msgstr ""
|
||||
msgstr "Eskaeraren xehetasunak: %(code)s"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/index.html
|
||||
#: pretix/control/templates/pretixcontrol/orders/index.html
|
||||
|
||||
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-08-24 15:35+0000\n"
|
||||
"PO-Revision-Date: 2026-08-16 22:00+0000\n"
|
||||
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
|
||||
"PO-Revision-Date: 2026-09-07 18:00+0000\n"
|
||||
"Last-Translator: Yasunobu YesNo Kawaguchi <kawaguti@gmail.com>\n"
|
||||
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"ja/>\n"
|
||||
"Language: ja\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Weblate 2026.8.1\n"
|
||||
"X-Generator: Weblate 2026.9\n"
|
||||
|
||||
#: pretix/_base_settings.py
|
||||
msgid "English"
|
||||
@@ -93,7 +93,7 @@ msgstr "ヘブライ語"
|
||||
|
||||
#: pretix/_base_settings.py
|
||||
msgid "Hungarian"
|
||||
msgstr ""
|
||||
msgstr "ハンガリー語"
|
||||
|
||||
#: pretix/_base_settings.py
|
||||
msgid "Indonesian"
|
||||
@@ -220,7 +220,7 @@ msgstr "ターゲットURL"
|
||||
#: pretix/api/models.py pretix/base/models/devices.py
|
||||
#: pretix/base/models/organizer.py
|
||||
msgid "All events (including newly created ones)"
|
||||
msgstr "すべてのイベント(最近作成されたイベントを含む)"
|
||||
msgstr "すべてのイベント(今後作成されるものを含む)"
|
||||
|
||||
#: pretix/api/models.py pretix/base/models/devices.py
|
||||
#: pretix/base/models/organizer.py
|
||||
@@ -428,10 +428,8 @@ msgid "You cannot exchange a medium for a medium."
|
||||
msgstr "メディアを別のメディアに変更することはできません。"
|
||||
|
||||
#: pretix/api/views/checkin.py
|
||||
#, fuzzy
|
||||
#| msgid "Product does not support medium exchange."
|
||||
msgid "You cannot simulate a medium exchange."
|
||||
msgstr "本製品は中程度の交換に対応していません。"
|
||||
msgstr "メディアの交換をシミュレートすることはできません。"
|
||||
|
||||
#: pretix/api/views/oauth.py pretix/control/logdisplay.py
|
||||
#, python-brace-format
|
||||
@@ -5691,7 +5689,7 @@ msgstr "国コード(ISO 3166-1 alpha-2)"
|
||||
|
||||
#: pretix/base/models/items.py
|
||||
msgid "Asked on"
|
||||
msgstr ""
|
||||
msgstr "質問日"
|
||||
|
||||
#: pretix/base/models/items.py pretix/base/models/organizer.py
|
||||
msgid ""
|
||||
@@ -7340,12 +7338,9 @@ msgid "The payment for this invoice has already been received."
|
||||
msgstr "この請求書の支払いはすでに受領済みです。"
|
||||
|
||||
#: pretix/base/payment.py
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "This payment is already being processed and can not be canceled any more."
|
||||
msgid ""
|
||||
"This payment is already being processed and cannot be canceled any more."
|
||||
msgstr "この支払いは処理中のため、キャンセルできません。"
|
||||
msgstr "この支払いはすでに処理中のため、キャンセルできません。"
|
||||
|
||||
#: pretix/base/payment.py
|
||||
msgid "Automatic refunds are not supported by this payment provider."
|
||||
@@ -8105,16 +8100,12 @@ msgid "Presale end"
|
||||
msgstr "前売り終了"
|
||||
|
||||
#: pretix/base/reldate.py
|
||||
#, fuzzy
|
||||
#| msgid "Order email"
|
||||
msgid "Order creation"
|
||||
msgstr "注文者メール"
|
||||
msgstr "注文の作成"
|
||||
|
||||
#: pretix/base/reldate.py
|
||||
#, fuzzy
|
||||
#| msgid "Order expired"
|
||||
msgid "Order expiry"
|
||||
msgstr "注文の有効期限が切れました"
|
||||
msgstr "注文の失効"
|
||||
|
||||
#: pretix/base/reldate.py
|
||||
msgid "before"
|
||||
@@ -8143,22 +8134,22 @@ msgstr "未設定"
|
||||
#: pretix/base/reldate.py
|
||||
#, python-brace-format
|
||||
msgid "A relative date cannot be expressed as \"before\" for \"{}\""
|
||||
msgstr ""
|
||||
msgstr "相対的な日付は、「{}」に対して「before」で表すことはできません"
|
||||
|
||||
#: pretix/base/reldate.py
|
||||
#, python-brace-format
|
||||
msgid "A relative date cannot be expressed as \"after\" for \"{}\""
|
||||
msgstr ""
|
||||
msgstr "相対的な日付は「{}」に対して「after」として表すことはできません"
|
||||
|
||||
#: pretix/base/reldate.py
|
||||
#, python-brace-format
|
||||
msgid "A relative time cannot be expressed as \"before\" for \"{}\""
|
||||
msgstr ""
|
||||
msgstr "相対時間は「{}」に対して「before」で表すことはできません"
|
||||
|
||||
#: pretix/base/reldate.py
|
||||
#, python-brace-format
|
||||
msgid "A relative time cannot be expressed as \"after\" for \"{}\""
|
||||
msgstr ""
|
||||
msgstr "相対時間は「{}」に対して「after」で表すことはできません"
|
||||
|
||||
#: pretix/base/secrets.py
|
||||
msgid "Random (default, works with all pretix apps)"
|
||||
@@ -10067,12 +10058,12 @@ msgstr ""
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "No dates match your criteria."
|
||||
msgstr ""
|
||||
msgstr "条件に合致する日付はありません。"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgctxt "subevents"
|
||||
msgid "Text for empty date results"
|
||||
msgstr ""
|
||||
msgstr "空の日付結果のテキスト"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgctxt "subevents"
|
||||
@@ -10083,6 +10074,10 @@ msgid ""
|
||||
"touch with you to arrange further dates. We do not recommend more than one "
|
||||
"or two sentences."
|
||||
msgstr ""
|
||||
"このテキストは、カレンダーまたは日付リストが空の場合に表示されます。たとえば"
|
||||
"、月に日付が含まれていない場合や、ユーザーが選択したフィルターに結果が見つか"
|
||||
"らない場合です。これをご利用いただくことで、今後の日程調整のために連絡する方"
|
||||
"法を宣伝できます。1文または2文以上は推奨いたしません。"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Guidance text"
|
||||
@@ -13902,26 +13897,20 @@ msgid ""
|
||||
msgstr "ギフトカードの有効期限を発行年を含めた{}年に設定しました。"
|
||||
|
||||
#: pretix/control/forms/event.py
|
||||
#, fuzzy
|
||||
#| msgid "Payment date"
|
||||
msgid "Payment term"
|
||||
msgstr "支払い日"
|
||||
msgstr "支払条件"
|
||||
|
||||
#: pretix/control/forms/event.py
|
||||
msgid "same as above"
|
||||
msgstr ""
|
||||
msgstr "上記と同じ"
|
||||
|
||||
#: pretix/control/forms/event.py
|
||||
#, fuzzy
|
||||
#| msgid "Payment term in days"
|
||||
msgid "different payment term in days"
|
||||
msgstr "支払い条件(日数)"
|
||||
msgstr "日数で表す異なる支払条件"
|
||||
|
||||
#: pretix/control/forms/event.py
|
||||
#, fuzzy
|
||||
#| msgid "Payment term in minutes"
|
||||
msgid "different payment term in minutes"
|
||||
msgstr "支払い期限(分単位)"
|
||||
msgstr "分で表す異なる支払条件"
|
||||
|
||||
#: pretix/control/forms/event.py
|
||||
msgid "Prices including tax"
|
||||
@@ -22533,28 +22522,24 @@ msgstr ""
|
||||
"食事を提供する場合、ユーザーに食事制限について尋ねることができる一例です。"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/items/questions.html
|
||||
#, fuzzy
|
||||
#| msgid "Create a new question"
|
||||
msgid "Create a new per-ticket question"
|
||||
msgstr "新しい質問を作成"
|
||||
msgstr "チケットごとに新しい質問を作成"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/items/questions.html
|
||||
#, fuzzy
|
||||
#| msgid "Create a new question"
|
||||
msgid "Create a new order-level question"
|
||||
msgstr "新しい質問を作成"
|
||||
msgstr "注文ごとに新しい質問を作成"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/items/questions.html
|
||||
#, fuzzy
|
||||
#| msgid "Delete question"
|
||||
msgid "Per-ticket questions"
|
||||
msgstr "質問を削除"
|
||||
msgstr "チケットごとの質問"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/items/questions.html
|
||||
msgid ""
|
||||
"These questions are asked for every ticket, so possibly multiple times in "
|
||||
"the same order."
|
||||
msgstr ""
|
||||
"これらの質問はすべてのチケットに対して尋ねられるため、同じ順序で複数回になる"
|
||||
"可能性があります。"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/items/questions.html
|
||||
msgid "Create a new question"
|
||||
@@ -22573,28 +22558,24 @@ msgid "All personalized products"
|
||||
msgstr "すべてのパーソナライズされた製品"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/items/questions.html
|
||||
#, fuzzy
|
||||
#| msgid "Include questions"
|
||||
msgid "Per-order questions"
|
||||
msgstr "質問を含む"
|
||||
msgstr "注文ごとの質問"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/items/questions.html
|
||||
msgid ""
|
||||
"This functionality is in active development and expected to change "
|
||||
"significantly over the coming months."
|
||||
msgstr ""
|
||||
msgstr "この機能は現在開発が活発で、今後数か月で大幅に変化すると予想されています。"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/items/questions.html
|
||||
msgid ""
|
||||
"Per-order questions are currently not supported and will not be displayed in "
|
||||
"pretixPOS."
|
||||
msgstr ""
|
||||
msgstr "注文ごとの質問は現在サポートされておらず、pretixPOSでは表示されません。"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/items/questions.html
|
||||
#, fuzzy
|
||||
#| msgid "The question has been reordered."
|
||||
msgid "These questions are asked once per order."
|
||||
msgstr "質問の並び順が変更されました。"
|
||||
msgstr "これらの質問は、注文ごとに1回ずつ尋ねられます。"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/items/quota.html
|
||||
#: pretix/control/templates/pretixcontrol/items/quota_edit.html
|
||||
@@ -23186,10 +23167,8 @@ msgstr "(任意)"
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_questions.html
|
||||
#: pretix/presale/templates/pretixpresale/event/order_modify.html
|
||||
#, fuzzy
|
||||
#| msgid "Additional information"
|
||||
msgid "Additional order information"
|
||||
msgstr "追加情報"
|
||||
msgstr "追加の注文情報"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/delete.html
|
||||
msgid "Delete order"
|
||||
@@ -24856,14 +24835,13 @@ msgid "Hardware model"
|
||||
msgstr "ハードウェアの機種"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/organizers/devices.html
|
||||
#, fuzzy, python-format
|
||||
#| msgid "Begin: %(time)s"
|
||||
#, python-format
|
||||
msgid "Last seen: %(time)s"
|
||||
msgstr "開始: %(time)s"
|
||||
msgstr "最後の閲覧: %(time)s"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/organizers/devices.html
|
||||
msgid "No recent contact"
|
||||
msgstr ""
|
||||
msgstr "最近の連絡なし"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/organizers/devices.html
|
||||
msgid "Not yet initialized"
|
||||
@@ -28220,19 +28198,14 @@ msgstr ""
|
||||
"に高いことを意味します。ドメインのDNS設定を更新すべきです。"
|
||||
|
||||
#: pretix/control/views/mailsetup.py
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "We did not find DMARC record for your domain. This means that there is a "
|
||||
#| "very high chance most of the emails will be rejected or marked as spam. "
|
||||
#| "You should update the DNS settings of your domain."
|
||||
msgid ""
|
||||
"We did not find a DMARC record for your domain. This means that there is a "
|
||||
"very high chance most of the emails will be rejected or marked as spam. You "
|
||||
"should update the DNS settings of your domain."
|
||||
msgstr ""
|
||||
"お客様のドメインのDMARCレコードが見つかりませんでした。これは、ほとんどのメー"
|
||||
"ルが拒否されたりスパムとしてマークされたりする可能性が非常に高いことを意味し"
|
||||
"ます。ドメインのDNS設定を更新すべきです。"
|
||||
"あなたのドメインのDMARCレコードが見つかりませんでした。これは、ほとんどの"
|
||||
"メールが拒否されたかスパムとしてマークされた可能性が非常に高いことを意味しま"
|
||||
"す。ドメインのDNS設定を更新すべきです。"
|
||||
|
||||
#: pretix/control/views/mailsetup.py
|
||||
msgid "The verification code was incorrect, please try again."
|
||||
@@ -30906,7 +30879,7 @@ msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/payment.py
|
||||
msgid "Allow further payments during compliance hold"
|
||||
msgstr ""
|
||||
msgstr "コンプライアンス保留の間、追加の支払いを許可する"
|
||||
|
||||
#: pretix/plugins/paypal2/payment.py
|
||||
msgid ""
|
||||
@@ -30916,16 +30889,20 @@ msgid ""
|
||||
"attempts during that window. This might result in them being charged twice "
|
||||
"if the original payment is approved."
|
||||
msgstr ""
|
||||
"PayPalの不正防止は、個々の支払いの処理をかなりの期間ブロックする可能性があり"
|
||||
"ます。この期間中、支払いは「保留中」とマークされています。顧客がその期間中に"
|
||||
"別の支払い試行を開始できるように許可することができます。元の支払いが承認され"
|
||||
"た場合、二重に請求される可能性があります。"
|
||||
|
||||
#: pretix/plugins/paypal2/payment.py
|
||||
msgid "Timeout further payment attempts"
|
||||
msgstr ""
|
||||
msgstr "追加の支払い試行がタイムアウト"
|
||||
|
||||
#: pretix/plugins/paypal2/payment.py
|
||||
msgid ""
|
||||
"Time duration in minutes after which another payment attempt is possible, "
|
||||
"while the last payment is still under investigation."
|
||||
msgstr ""
|
||||
msgstr "最後の支払いがまだ調査中である間、別の支払い試行が可能となる時間(分)。"
|
||||
|
||||
#: pretix/plugins/paypal2/payment.py
|
||||
msgid "-- Automatic --"
|
||||
@@ -31190,6 +31167,10 @@ msgid ""
|
||||
"twice in case PayPal allows your initial payment attempt. Please contact us "
|
||||
"to resolve this case."
|
||||
msgstr ""
|
||||
"支払いはPayPalで処理されています。通常より時間がかかります。PayPalが支払いを"
|
||||
"確認するまでお待ちいただくか、こちらまたは別の支払い方法で再度お支払いをお試"
|
||||
"しください。PayPalが最初の支払い試行を許可した場合、二重に請求される可能性が"
|
||||
"あります。この件を解決するために、弊社までご連絡ください。"
|
||||
|
||||
#: pretix/plugins/paypal2/views.py
|
||||
msgid ""
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-08-24 15:35+0000\n"
|
||||
"PO-Revision-Date: 2026-03-30 21:00+0000\n"
|
||||
"PO-Revision-Date: 2026-09-02 08:35+0000\n"
|
||||
"Last-Translator: Renne Rocha <renne@rocha.dev.br>\n"
|
||||
"Language-Team: Portuguese (Brazil) <https://translate.pretix.eu/projects/"
|
||||
"pretix/pretix/pt_BR/>\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||
"X-Generator: Weblate 5.16.2\n"
|
||||
"X-Generator: Weblate 2026.8.1\n"
|
||||
|
||||
#: pretix/_base_settings.py
|
||||
msgid "English"
|
||||
@@ -93,7 +93,7 @@ msgstr "Hebraico"
|
||||
|
||||
#: pretix/_base_settings.py
|
||||
msgid "Hungarian"
|
||||
msgstr ""
|
||||
msgstr "Húngaro"
|
||||
|
||||
#: pretix/_base_settings.py
|
||||
msgid "Indonesian"
|
||||
@@ -153,7 +153,7 @@ msgstr "Espanhol (América latina)"
|
||||
|
||||
#: pretix/_base_settings.py
|
||||
msgid "Thai"
|
||||
msgstr ""
|
||||
msgstr "Tailandês"
|
||||
|
||||
#: pretix/_base_settings.py
|
||||
msgid "Turkish"
|
||||
@@ -3022,11 +3022,12 @@ msgid ""
|
||||
"The field \"%(label)s\" may not contain special characters such as "
|
||||
"\"%(chars)s\"."
|
||||
msgstr ""
|
||||
"O campo \"%(label)s\" não pode conter caracteres especiais como %(chars)s\"."
|
||||
|
||||
#: pretix/base/forms/questions.py
|
||||
#, python-format
|
||||
msgid "The field \"%(label)s\" may not contain an URL (%(url)s)."
|
||||
msgstr ""
|
||||
msgstr "O campo \"%(label)s\" não pode conter uma URL (%(url)s)."
|
||||
|
||||
#: pretix/base/forms/questions.py
|
||||
msgctxt "phonenumber"
|
||||
@@ -8173,7 +8174,7 @@ msgstr "Permitida"
|
||||
#: pretix/base/permissions.py
|
||||
msgctxt "permission_level"
|
||||
msgid "Access existing events"
|
||||
msgstr ""
|
||||
msgstr "Acessar eventos existentes"
|
||||
|
||||
#: pretix/base/permissions.py
|
||||
msgctxt "permission_level"
|
||||
@@ -13607,11 +13608,15 @@ msgstr "Por favor, continue em uma nova aba"
|
||||
#: pretix/base/templates/pretixbase/framebreak.html
|
||||
msgid "For security reasons, the following step is only possible in a new tab."
|
||||
msgstr ""
|
||||
"Por razões de segurança, o passo seguinte só é possível de ser feito em uma "
|
||||
"nova aba."
|
||||
|
||||
#: pretix/base/templates/pretixbase/framebreak.html
|
||||
msgid ""
|
||||
"If the new tab did not open automatically, please click the following button:"
|
||||
msgstr ""
|
||||
"Se uma nova aba não abrir automaticamente, por favor, clique no botão a "
|
||||
"seguir:"
|
||||
|
||||
#: pretix/base/templates/pretixbase/framebreak.html
|
||||
#: pretix/presale/templates/pretixpresale/event/cookies.html
|
||||
|
||||
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-08-24 15:35+0000\n"
|
||||
"PO-Revision-Date: 2026-08-09 06:00+0000\n"
|
||||
"Last-Translator: Julien <julien@circusiloveyou.com>\n"
|
||||
"PO-Revision-Date: 2026-09-04 14:29+0000\n"
|
||||
"Last-Translator: Linnea Thelander <linnea@coeo.events>\n"
|
||||
"Language-Team: Swedish <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"sv/>\n"
|
||||
"Language: sv\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 2026.8.1\n"
|
||||
"X-Generator: Weblate 2026.9\n"
|
||||
|
||||
#: pretix/_base_settings.py
|
||||
msgid "English"
|
||||
@@ -19170,8 +19170,9 @@ msgstr ""
|
||||
#: pretix/presale/templates/pretixpresale/event/order_pay.html
|
||||
#: pretix/presale/templates/pretixpresale/event/order_pay_change.html
|
||||
#: pretix/presale/templates/pretixpresale/event/position_change.html
|
||||
#, fuzzy
|
||||
msgid "Continue"
|
||||
msgstr "Fortsätta"
|
||||
msgstr "Fortsätt"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/auth/oauth_authorization.html
|
||||
msgid "Authorize an application"
|
||||
|
||||
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-06 15:52+0000\n"
|
||||
"PO-Revision-Date: 2026-08-09 06:00+0000\n"
|
||||
"Last-Translator: Julien <julien@circusiloveyou.com>\n"
|
||||
"PO-Revision-Date: 2026-09-04 14:29+0000\n"
|
||||
"Last-Translator: Linnea Thelander <linnea@coeo.events>\n"
|
||||
"Language-Team: Swedish <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
"js/sv/>\n"
|
||||
"Language: sv\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 2026.8.1\n"
|
||||
"X-Generator: Weblate 2026.9\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js
|
||||
msgid "Marked as paid"
|
||||
@@ -133,8 +133,9 @@ msgstr "Mercado Pago"
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/i18n.ts
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js
|
||||
#, fuzzy
|
||||
msgid "Continue"
|
||||
msgstr "Fortsätta"
|
||||
msgstr "Fortsätt"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js
|
||||
|
||||
@@ -1,183 +1,183 @@
|
||||
/*global $, gettext*/
|
||||
/* global gettext */
|
||||
var bankimport_transactionlist = {
|
||||
|
||||
_btn_click: function (e) {
|
||||
console.log(e.delegateTarget);
|
||||
var trans_id = parseInt($(e.delegateTarget).attr("name").split("_")[1]);
|
||||
var value = $(e.delegateTarget).val();
|
||||
if (value === "discard") {
|
||||
bankimport_transactionlist.discard(trans_id);
|
||||
} else if (value === "accept") {
|
||||
bankimport_transactionlist.accept(trans_id);
|
||||
} else if (value === "retry") {
|
||||
bankimport_transactionlist.retry(trans_id);
|
||||
} else if (value === "assign") {
|
||||
bankimport_transactionlist.assign(trans_id);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
_btn_click: function (e) {
|
||||
console.log(e.delegateTarget)
|
||||
let trans_id = parseInt($(e.delegateTarget).attr('name').split('_')[1])
|
||||
let value = $(e.delegateTarget).val()
|
||||
if (value === 'discard') {
|
||||
bankimport_transactionlist.discard(trans_id)
|
||||
} else if (value === 'accept') {
|
||||
bankimport_transactionlist.accept(trans_id)
|
||||
} else if (value === 'retry') {
|
||||
bankimport_transactionlist.retry(trans_id)
|
||||
} else if (value === 'assign') {
|
||||
bankimport_transactionlist.assign(trans_id)
|
||||
}
|
||||
return false
|
||||
},
|
||||
|
||||
_action: function (id, action, success) {
|
||||
$("tr[data-id=" + id + "] button").prop("disabled", true);
|
||||
var data = {
|
||||
"csrfmiddlewaretoken": $("[name=csrfmiddlewaretoken]").val()
|
||||
};
|
||||
data["action_" + id] = action;
|
||||
$.ajax({
|
||||
"method": "POST",
|
||||
"url": $(".transaction-list").attr("data-url"),
|
||||
"data": data,
|
||||
"dataType": "json",
|
||||
"success": function (data) {
|
||||
if (data.status == "ok") {
|
||||
$("tr[data-id=" + id + "]").removeClass("has-error");
|
||||
if (data.comment) {
|
||||
bankimport_transactionlist.comment_reset_to_text(id, data.comment, data.plain);
|
||||
}
|
||||
success();
|
||||
} else {
|
||||
$("tr[data-id=" + id + "] button").prop("disabled", false);
|
||||
$("tr[data-id=" + id + "] .help-block").remove();
|
||||
$("tr[data-id=" + id + "]").addClass("has-error");
|
||||
$("<p>").addClass("help-block").text(data.message).appendTo($("tr[data-id=" + id + "] td.actions"));
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
_action: function (id, action, success) {
|
||||
$('tr[data-id=' + id + '] button').prop('disabled', true)
|
||||
let data = {
|
||||
csrfmiddlewaretoken: $('[name=csrfmiddlewaretoken]').val()
|
||||
}
|
||||
data['action_' + id] = action
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: $('.transaction-list').attr('data-url'),
|
||||
data: data,
|
||||
dataType: 'json',
|
||||
success: function (data) {
|
||||
if (data.status == 'ok') {
|
||||
$('tr[data-id=' + id + ']').removeClass('has-error')
|
||||
if (data.comment) {
|
||||
bankimport_transactionlist.comment_reset_to_text(id, data.comment, data.plain)
|
||||
}
|
||||
success()
|
||||
} else {
|
||||
$('tr[data-id=' + id + '] button').prop('disabled', false)
|
||||
$('tr[data-id=' + id + '] .help-block').remove()
|
||||
$('tr[data-id=' + id + ']').addClass('has-error')
|
||||
$('<p>').addClass('help-block').text(data.message).appendTo($('tr[data-id=' + id + '] td.actions'))
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
discard: function (id) {
|
||||
bankimport_transactionlist._action(id, "discard", function () {
|
||||
$("tr[data-id=" + id + "] td").remove();
|
||||
});
|
||||
},
|
||||
discard: function (id) {
|
||||
bankimport_transactionlist._action(id, 'discard', function () {
|
||||
$('tr[data-id=' + id + '] td').remove()
|
||||
})
|
||||
},
|
||||
|
||||
retry: function (id) {
|
||||
bankimport_transactionlist._action(id, "retry", function () {
|
||||
$("tr[data-id=" + id + "] td.actions").html('').text(gettext("Marked as paid"));
|
||||
});
|
||||
},
|
||||
retry: function (id) {
|
||||
bankimport_transactionlist._action(id, 'retry', function () {
|
||||
$('tr[data-id=' + id + '] td.actions').html('').text(gettext('Marked as paid'))
|
||||
})
|
||||
},
|
||||
|
||||
accept: function (id) {
|
||||
bankimport_transactionlist._action(id, "accept", function () {
|
||||
$("tr[data-id=" + id + "] td.actions").html('').text(gettext("Marked as paid"));
|
||||
});
|
||||
},
|
||||
accept: function (id) {
|
||||
bankimport_transactionlist._action(id, 'accept', function () {
|
||||
$('tr[data-id=' + id + '] td.actions').html('').text(gettext('Marked as paid'))
|
||||
})
|
||||
},
|
||||
|
||||
assign: function (id) {
|
||||
bankimport_transactionlist._action(id, "assign:" + $("tr[data-id=" + id + "] input.form-control:not(.tt-hint)").val(), function () {
|
||||
$("tr[data-id=" + id + "] td.actions").html('').text(gettext("Marked as paid"));
|
||||
});
|
||||
},
|
||||
assign: function (id) {
|
||||
bankimport_transactionlist._action(id, 'assign:' + $('tr[data-id=' + id + '] input.form-control:not(.tt-hint)').val(), function () {
|
||||
$('tr[data-id=' + id + '] td.actions').html('').text(gettext('Marked as paid'))
|
||||
})
|
||||
},
|
||||
|
||||
comment_reset_to_text: function (id, text, plain) {
|
||||
var $box = $("tr[data-id=" + id + "] .comment-box");
|
||||
$box[0].dataset["plain"] = plain;
|
||||
$box.html("")
|
||||
.append($("<strong>").text(gettext("Comment:")))
|
||||
.append(" ")
|
||||
.append($("<span>").addClass("comment").append(" ").append(text))
|
||||
.append(" ")
|
||||
.append($("<a>").addClass("comment-modify btn btn-default btn-xs")
|
||||
.append("<span class='fa fa-edit'></span>"));
|
||||
},
|
||||
comment_reset_to_text: function (id, text, plain) {
|
||||
let $box = $('tr[data-id=' + id + '] .comment-box')
|
||||
$box[0].dataset['plain'] = plain
|
||||
$box.html('')
|
||||
.append($('<strong>').text(gettext('Comment:')))
|
||||
.append(' ')
|
||||
.append($('<span>').addClass('comment').append(' ').append(text))
|
||||
.append(' ')
|
||||
.append($('<a>').addClass('comment-modify btn btn-default btn-xs')
|
||||
.append('<span class=\'fa fa-edit\'></span>'))
|
||||
},
|
||||
|
||||
comment_start_edit: function (e) {
|
||||
var $box = $(e.target).closest("div");
|
||||
var id = $box.closest("tr").attr("data-id");
|
||||
var $inp = $("<textarea>").addClass("form-control");
|
||||
var orig_rendered = $box.find(".comment");
|
||||
var orig_text = $box[0].dataset.plain;
|
||||
$inp.val(orig_text);
|
||||
comment_start_edit: function (e) {
|
||||
let $box = $(e.target).closest('div')
|
||||
let id = $box.closest('tr').attr('data-id')
|
||||
let $inp = $('<textarea>').addClass('form-control')
|
||||
let orig_rendered = $box.find('.comment')
|
||||
let orig_text = $box[0].dataset.plain
|
||||
$inp.val(orig_text)
|
||||
|
||||
var $btngrp = $("<div>");
|
||||
$btngrp.addClass("btn-group");
|
||||
var $btn1 = $("<button>");
|
||||
$btn1.attr("type", "button").addClass("btn btn-default");
|
||||
$btn1.append("<span class='fa fa-check'></span>");
|
||||
$btngrp.append($btn1);
|
||||
var $btn2 = $("<button>");
|
||||
$btn2.attr("type", "button").addClass("btn btn-default");
|
||||
$btn2.append("<span class='fa fa-close'></span>");
|
||||
$btngrp.append($btn2);
|
||||
$box.html("").append($inp).append($btngrp);
|
||||
$btn1.click(function () {
|
||||
var text = $box.find("textarea").val();
|
||||
$box.find("input, textarea, button").prop("disabled", true);
|
||||
bankimport_transactionlist._action(id, "comment:" + text, function () {
|
||||
$("tr[data-id=" + id + "] button").prop("disabled", false);
|
||||
});
|
||||
});
|
||||
$btn2.click(function () {
|
||||
bankimport_transactionlist.comment_reset_to_text(id, orig_rendered, orig_text);
|
||||
});
|
||||
let $btngrp = $('<div>')
|
||||
$btngrp.addClass('btn-group')
|
||||
let $btn1 = $('<button>')
|
||||
$btn1.attr('type', 'button').addClass('btn btn-default')
|
||||
$btn1.append('<span class=\'fa fa-check\'></span>')
|
||||
$btngrp.append($btn1)
|
||||
let $btn2 = $('<button>')
|
||||
$btn2.attr('type', 'button').addClass('btn btn-default')
|
||||
$btn2.append('<span class=\'fa fa-close\'></span>')
|
||||
$btngrp.append($btn2)
|
||||
$box.html('').append($inp).append($btngrp)
|
||||
$btn1.click(function () {
|
||||
let text = $box.find('textarea').val()
|
||||
$box.find('input, textarea, button').prop('disabled', true)
|
||||
bankimport_transactionlist._action(id, 'comment:' + text, function () {
|
||||
$('tr[data-id=' + id + '] button').prop('disabled', false)
|
||||
})
|
||||
})
|
||||
$btn2.click(function () {
|
||||
bankimport_transactionlist.comment_reset_to_text(id, orig_rendered, orig_text)
|
||||
})
|
||||
|
||||
e.preventDefault();
|
||||
},
|
||||
e.preventDefault()
|
||||
},
|
||||
|
||||
typeahead_source: function () {
|
||||
return new Bloodhound({
|
||||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
|
||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||
remote: {
|
||||
url: $(".transaction-list").attr("data-url"),
|
||||
prepare: function (query, settings) {
|
||||
settings.url = settings.url + '?query=' + encodeURIComponent(query);
|
||||
return settings;
|
||||
},
|
||||
transform: function (object) {
|
||||
var results = object.results;
|
||||
var suggs = [];
|
||||
var reslen = results.length;
|
||||
for (var i = 0; i < reslen; i++) {
|
||||
suggs.push(results[i]);
|
||||
}
|
||||
return suggs;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
typeahead_source: function () {
|
||||
return new Bloodhound({
|
||||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
|
||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||
remote: {
|
||||
url: $('.transaction-list').attr('data-url'),
|
||||
prepare: function (query, settings) {
|
||||
settings.url = settings.url + '?query=' + encodeURIComponent(query)
|
||||
return settings
|
||||
},
|
||||
transform: function (object) {
|
||||
let results = object.results
|
||||
let suggs = []
|
||||
let reslen = results.length
|
||||
for (let i = 0; i < reslen; i++) {
|
||||
suggs.push(results[i])
|
||||
}
|
||||
return suggs
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
init: function () {
|
||||
if ($(".transaction-list").length) {
|
||||
$(".transaction-list button").click(bankimport_transactionlist._btn_click);
|
||||
init: function () {
|
||||
if ($('.transaction-list').length) {
|
||||
$('.transaction-list button').click(bankimport_transactionlist._btn_click)
|
||||
|
||||
$(".transaction-list").on("click", ".comment-modify", bankimport_transactionlist.comment_start_edit);
|
||||
$('.transaction-list').on('click', '.comment-modify', bankimport_transactionlist.comment_start_edit)
|
||||
|
||||
$(".transaction-list .form-control").typeahead(null, {
|
||||
minLength: 2,
|
||||
name: 'order-dataset',
|
||||
source: bankimport_transactionlist.typeahead_source(),
|
||||
display: function (obj) {
|
||||
return obj.code;
|
||||
},
|
||||
templates: {
|
||||
suggestion: function (obj) {
|
||||
return '<div>' + obj.code + ' (' + obj.total + ', ' + obj.status + ')</div>';
|
||||
}
|
||||
}
|
||||
}).keypress(function (e) {
|
||||
if (e.keyCode === 13) {
|
||||
$(this).parent().parent().find("button[value=assign]").click();
|
||||
}
|
||||
});
|
||||
}
|
||||
$('.transaction-list .form-control').typeahead(null, {
|
||||
minLength: 2,
|
||||
name: 'order-dataset',
|
||||
source: bankimport_transactionlist.typeahead_source(),
|
||||
display: function (obj) {
|
||||
return obj.code
|
||||
},
|
||||
templates: {
|
||||
suggestion: function (obj) {
|
||||
return '<div>' + obj.code + ' (' + obj.total + ', ' + obj.status + ')</div>'
|
||||
}
|
||||
}
|
||||
}).keypress(function (e) {
|
||||
if (e.keyCode === 13) {
|
||||
$(this).parent().parent().find('button[value=assign]').click()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if ($("[data-job-waiting]").length) {
|
||||
window.setTimeout(bankimport_transactionlist.check_state, 750);
|
||||
}
|
||||
},
|
||||
if ($('[data-job-waiting]').length) {
|
||||
window.setTimeout(bankimport_transactionlist.check_state, 750)
|
||||
}
|
||||
},
|
||||
|
||||
check_state: function () {
|
||||
$.getJSON($("[data-job-waiting-url]").attr("data-job-waiting-url"), function (data) {
|
||||
if (data.state == 'running' || data.state == 'pending') {
|
||||
window.setTimeout(bankimport_transactionlist.check_state, 750);
|
||||
} else {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
check_state: function () {
|
||||
$.getJSON($('[data-job-waiting-url]').attr('data-job-waiting-url'), function (data) {
|
||||
if (data.state == 'running' || data.state == 'pending') {
|
||||
window.setTimeout(bankimport_transactionlist.check_state, 750)
|
||||
} else {
|
||||
location.reload()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
$(function () {
|
||||
bankimport_transactionlist.init();
|
||||
});
|
||||
bankimport_transactionlist.init()
|
||||
})
|
||||
|
||||
@@ -1,350 +1,350 @@
|
||||
/*global $, paypal_client_id, paypal_loadingmessage, gettext */
|
||||
'use strict';
|
||||
/* global paypal_client_id, paypal_loadingmessage, gettext */
|
||||
'use strict'
|
||||
|
||||
var pretixpaypal = {
|
||||
paypal: null,
|
||||
client_id: null,
|
||||
order_id: null,
|
||||
payer_id: null,
|
||||
merchant_id: null,
|
||||
currency: null,
|
||||
method: null,
|
||||
additional_disabled_funding: null,
|
||||
additional_enabled_funding: null,
|
||||
debug_buyer_country: null,
|
||||
continue_button: null,
|
||||
paypage: false,
|
||||
method_map: {
|
||||
wallet: {
|
||||
method: 'wallet',
|
||||
funding_source: 'paypal',
|
||||
//disable_funding: null,
|
||||
//enable_funding: 'paylater',
|
||||
early_auth: true,
|
||||
},
|
||||
apm: {
|
||||
method: 'apm',
|
||||
funding_source: null,
|
||||
//disable_funding: null,
|
||||
//enable_funding: null,
|
||||
early_auth: false,
|
||||
}
|
||||
},
|
||||
apm_map: {
|
||||
paypal: gettext('PayPal'),
|
||||
venmo: gettext('Venmo'),
|
||||
applepay: gettext('Apple Pay'),
|
||||
itau: gettext('Itaú'),
|
||||
credit: gettext('PayPal Credit'),
|
||||
card: gettext('Credit Card'),
|
||||
paylater: gettext('PayPal Pay Later'),
|
||||
ideal: gettext('iDEAL | Wero'),
|
||||
sepa: gettext('SEPA Direct Debit'),
|
||||
bancontact: gettext('Bancontact'),
|
||||
giropay: gettext('giropay'),
|
||||
sofort: gettext('SOFORT'),
|
||||
eps: gettext('eps'),
|
||||
mybank: gettext('MyBank'),
|
||||
p24: gettext('Przelewy24'),
|
||||
verkkopankki: gettext('Verkkopankki'),
|
||||
payu: gettext('PayU'),
|
||||
blik: gettext('BLIK'),
|
||||
trustly: gettext('Trustly'),
|
||||
zimpler: gettext('Zimpler'),
|
||||
maxima: gettext('Maxima'),
|
||||
oxxo: gettext('OXXO'),
|
||||
boleto: gettext('Boleto'),
|
||||
wechatpay: gettext('WeChat Pay'),
|
||||
mercadopago: gettext('Mercado Pago')
|
||||
},
|
||||
readyToSubmitApproval: false,
|
||||
paypal: null,
|
||||
client_id: null,
|
||||
order_id: null,
|
||||
payer_id: null,
|
||||
merchant_id: null,
|
||||
currency: null,
|
||||
method: null,
|
||||
additional_disabled_funding: null,
|
||||
additional_enabled_funding: null,
|
||||
debug_buyer_country: null,
|
||||
continue_button: null,
|
||||
paypage: false,
|
||||
method_map: {
|
||||
wallet: {
|
||||
method: 'wallet',
|
||||
funding_source: 'paypal',
|
||||
// disable_funding: null,
|
||||
// enable_funding: 'paylater',
|
||||
early_auth: true,
|
||||
},
|
||||
apm: {
|
||||
method: 'apm',
|
||||
funding_source: null,
|
||||
// disable_funding: null,
|
||||
// enable_funding: null,
|
||||
early_auth: false,
|
||||
}
|
||||
},
|
||||
apm_map: {
|
||||
paypal: gettext('PayPal'),
|
||||
venmo: gettext('Venmo'),
|
||||
applepay: gettext('Apple Pay'),
|
||||
itau: gettext('Itaú'),
|
||||
credit: gettext('PayPal Credit'),
|
||||
card: gettext('Credit Card'),
|
||||
paylater: gettext('PayPal Pay Later'),
|
||||
ideal: gettext('iDEAL | Wero'),
|
||||
sepa: gettext('SEPA Direct Debit'),
|
||||
bancontact: gettext('Bancontact'),
|
||||
giropay: gettext('giropay'),
|
||||
sofort: gettext('SOFORT'),
|
||||
eps: gettext('eps'),
|
||||
mybank: gettext('MyBank'),
|
||||
p24: gettext('Przelewy24'),
|
||||
verkkopankki: gettext('Verkkopankki'),
|
||||
payu: gettext('PayU'),
|
||||
blik: gettext('BLIK'),
|
||||
trustly: gettext('Trustly'),
|
||||
zimpler: gettext('Zimpler'),
|
||||
maxima: gettext('Maxima'),
|
||||
oxxo: gettext('OXXO'),
|
||||
boleto: gettext('Boleto'),
|
||||
wechatpay: gettext('WeChat Pay'),
|
||||
mercadopago: gettext('Mercado Pago')
|
||||
},
|
||||
readyToSubmitApproval: false,
|
||||
|
||||
load: function () {
|
||||
if (pretixpaypal.paypal === null) {
|
||||
pretixpaypal.client_id = $.trim($("#paypal_client_id").html());
|
||||
pretixpaypal.merchant_id = $.trim($("#paypal_merchant_id").html());
|
||||
pretixpaypal.debug_buyer_country = $.trim($("#paypal_buyer_country").html());
|
||||
pretixpaypal.continue_button = $('.checkout-button-row').closest("form").find(".checkout-button-row .btn-primary");
|
||||
pretixpaypal.continue_button.closest('div').append('<div id="paypal-button-container"></div>');
|
||||
pretixpaypal.additional_disabled_funding = $.trim($("#paypal_disable_funding").html());
|
||||
pretixpaypal.additional_enabled_funding = $.trim($("#paypal_enable_funding").html());
|
||||
pretixpaypal.paypage = Boolean($('#paypal-button-container').data('paypage'));
|
||||
pretixpaypal.order_id = $.trim($("#paypal_oid").html());
|
||||
pretixpaypal.currency = $("body").attr("data-currency");
|
||||
pretixpaypal.locale = this.guessLocale();
|
||||
}
|
||||
load: function () {
|
||||
if (pretixpaypal.paypal === null) {
|
||||
pretixpaypal.client_id = $.trim($('#paypal_client_id').html())
|
||||
pretixpaypal.merchant_id = $.trim($('#paypal_merchant_id').html())
|
||||
pretixpaypal.debug_buyer_country = $.trim($('#paypal_buyer_country').html())
|
||||
pretixpaypal.continue_button = $('.checkout-button-row').closest('form').find('.checkout-button-row .btn-primary')
|
||||
pretixpaypal.continue_button.closest('div').append('<div id="paypal-button-container"></div>')
|
||||
pretixpaypal.additional_disabled_funding = $.trim($('#paypal_disable_funding').html())
|
||||
pretixpaypal.additional_enabled_funding = $.trim($('#paypal_enable_funding').html())
|
||||
pretixpaypal.paypage = Boolean($('#paypal-button-container').data('paypage'))
|
||||
pretixpaypal.order_id = $.trim($('#paypal_oid').html())
|
||||
pretixpaypal.currency = $('body').attr('data-currency')
|
||||
pretixpaypal.locale = this.guessLocale()
|
||||
}
|
||||
|
||||
$("input[name=payment][value^='paypal']").change(function () {
|
||||
if (pretixpaypal.paypal !== null) {
|
||||
pretixpaypal.renderButton($(this).val());
|
||||
} else {
|
||||
pretixpaypal.continue_button.prop("disabled", true);
|
||||
}
|
||||
});
|
||||
$('input[name=payment][value^=\'paypal\']').change(function () {
|
||||
if (pretixpaypal.paypal !== null) {
|
||||
pretixpaypal.renderButton($(this).val())
|
||||
} else {
|
||||
pretixpaypal.continue_button.prop('disabled', true)
|
||||
}
|
||||
})
|
||||
|
||||
$("input[name=payment]").not("[value^='paypal']").change(function () {
|
||||
pretixpaypal.restore();
|
||||
});
|
||||
$('input[name=payment]').not('[value^=\'paypal\']').change(function () {
|
||||
pretixpaypal.restore()
|
||||
})
|
||||
|
||||
// If paypal is pre-selected, we must disable the continue button and handle it after SDK is loaded
|
||||
if ($("input[name=payment][value^='paypal']").is(':checked')) {
|
||||
pretixpaypal.continue_button.prop("disabled", true);
|
||||
}
|
||||
// If paypal is pre-selected, we must disable the continue button and handle it after SDK is loaded
|
||||
if ($('input[name=payment][value^=\'paypal\']').is(':checked')) {
|
||||
pretixpaypal.continue_button.prop('disabled', true)
|
||||
}
|
||||
|
||||
// We are setting the cogwheel already here, as the renderAPM() method might take some time to get loaded.
|
||||
const apmtextselector = $("input[name=payment][value=paypal_apm]").closest("label").find(".accordion-label-text");
|
||||
apmtextselector.append(' <span aria-hidden="true" class="fa fa-cog fa-spin"></span>');
|
||||
// We are setting the cogwheel already here, as the renderAPM() method might take some time to get loaded.
|
||||
const apmtextselector = $('input[name=payment][value=paypal_apm]').closest('label').find('.accordion-label-text')
|
||||
apmtextselector.append(' <span aria-hidden="true" class="fa fa-cog fa-spin"></span>')
|
||||
|
||||
let sdk_url = 'https://www.paypal.com/sdk/js' +
|
||||
'?client-id=' + pretixpaypal.client_id +
|
||||
'&components=buttons,funding-eligibility' +
|
||||
'¤cy=' + pretixpaypal.currency;
|
||||
let sdk_url = 'https://www.paypal.com/sdk/js'
|
||||
+ '?client-id=' + pretixpaypal.client_id
|
||||
+ '&components=buttons,funding-eligibility'
|
||||
+ '¤cy=' + pretixpaypal.currency
|
||||
|
||||
if (pretixpaypal.locale) {
|
||||
sdk_url += '&locale=' + pretixpaypal.locale;
|
||||
}
|
||||
if (pretixpaypal.locale) {
|
||||
sdk_url += '&locale=' + pretixpaypal.locale
|
||||
}
|
||||
|
||||
if (pretixpaypal.merchant_id) {
|
||||
sdk_url += '&merchant-id=' + pretixpaypal.merchant_id;
|
||||
}
|
||||
if (pretixpaypal.merchant_id) {
|
||||
sdk_url += '&merchant-id=' + pretixpaypal.merchant_id
|
||||
}
|
||||
|
||||
if (pretixpaypal.additional_disabled_funding) {
|
||||
sdk_url += '&disable-funding=' + [pretixpaypal.additional_disabled_funding].filter(Boolean).join(',');
|
||||
}
|
||||
if (pretixpaypal.additional_disabled_funding) {
|
||||
sdk_url += '&disable-funding=' + [pretixpaypal.additional_disabled_funding].filter(Boolean).join(',')
|
||||
}
|
||||
|
||||
if (pretixpaypal.additional_enabled_funding) {
|
||||
sdk_url += '&enable-funding=' + [pretixpaypal.additional_enabled_funding].filter(Boolean).join(',');
|
||||
}
|
||||
if (pretixpaypal.additional_enabled_funding) {
|
||||
sdk_url += '&enable-funding=' + [pretixpaypal.additional_enabled_funding].filter(Boolean).join(',')
|
||||
}
|
||||
|
||||
if (pretixpaypal.debug_buyer_country) {
|
||||
sdk_url += '&buyer-country=' + pretixpaypal.debug_buyer_country;
|
||||
}
|
||||
if (pretixpaypal.debug_buyer_country) {
|
||||
sdk_url += '&buyer-country=' + pretixpaypal.debug_buyer_country
|
||||
}
|
||||
|
||||
let ppscript = document.createElement('script');
|
||||
let ready = false;
|
||||
let head = document.getElementsByTagName("head")[0];
|
||||
ppscript.setAttribute('src', sdk_url);
|
||||
ppscript.setAttribute('data-csp-nonce', $.trim($("#csp_nonce").html()));
|
||||
ppscript.setAttribute('data-page-type', 'checkout');
|
||||
ppscript.setAttribute('data-partner-attribution-id', 'ramiioGmbH_Cart_PPCP');
|
||||
document.head.appendChild(ppscript);
|
||||
let ppscript = document.createElement('script')
|
||||
let ready = false
|
||||
let head = document.getElementsByTagName('head')[0]
|
||||
ppscript.setAttribute('src', sdk_url)
|
||||
ppscript.setAttribute('data-csp-nonce', $.trim($('#csp_nonce').html()))
|
||||
ppscript.setAttribute('data-page-type', 'checkout')
|
||||
ppscript.setAttribute('data-partner-attribution-id', 'ramiioGmbH_Cart_PPCP')
|
||||
document.head.appendChild(ppscript)
|
||||
|
||||
ppscript.onload = ppscript.onreadystatechange = function () {
|
||||
if (!ready && (!this.readyState || this.readyState === "loaded" || this.readyState === "complete")) {
|
||||
ready = true;
|
||||
ppscript.onload = ppscript.onreadystatechange = function () {
|
||||
if (!ready && (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete')) {
|
||||
ready = true
|
||||
|
||||
pretixpaypal.paypal = paypal;
|
||||
pretixpaypal.paypal = paypal
|
||||
|
||||
// Handle memory leak in IE
|
||||
ppscript.onload = ppscript.onreadystatechange = null;
|
||||
if (head && ppscript.parentNode) {
|
||||
head.removeChild(ppscript);
|
||||
}
|
||||
}
|
||||
};
|
||||
// Handle memory leak in IE
|
||||
ppscript.onload = ppscript.onreadystatechange = null
|
||||
if (head && ppscript.parentNode) {
|
||||
head.removeChild(ppscript)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("visibilitychange", this.onApproveSubmit);
|
||||
},
|
||||
document.addEventListener('visibilitychange', this.onApproveSubmit)
|
||||
},
|
||||
|
||||
ready: function () {
|
||||
if ($("input[name=payment][value=paypal_apm]").length > 0) {
|
||||
pretixpaypal.renderAPMs();
|
||||
}
|
||||
ready: function () {
|
||||
if ($('input[name=payment][value=paypal_apm]').length > 0) {
|
||||
pretixpaypal.renderAPMs()
|
||||
}
|
||||
|
||||
if ($("input[name=payment][value^='paypal']").is(':checked')) {
|
||||
pretixpaypal.renderButton($("input[name=payment][value^='paypal']:checked").val());
|
||||
} else if ($(".payment-redo-form").length) {
|
||||
pretixpaypal.renderButton($("input[name=payment][value^='paypal']").val());
|
||||
} else if ($('#paypal-button-container').data('paypage')) {
|
||||
pretixpaypal.renderButton('paypal_apm');
|
||||
}
|
||||
},
|
||||
if ($('input[name=payment][value^=\'paypal\']').is(':checked')) {
|
||||
pretixpaypal.renderButton($('input[name=payment][value^=\'paypal\']:checked').val())
|
||||
} else if ($('.payment-redo-form').length) {
|
||||
pretixpaypal.renderButton($('input[name=payment][value^=\'paypal\']').val())
|
||||
} else if ($('#paypal-button-container').data('paypage')) {
|
||||
pretixpaypal.renderButton('paypal_apm')
|
||||
}
|
||||
},
|
||||
|
||||
restore: function () {
|
||||
// if PayPal has not been initialized, there shouldn't be anything to cleanup
|
||||
if (pretixpaypal.paypal !== null) {
|
||||
$('#paypal-button-container').empty()
|
||||
pretixpaypal.continue_button.text(gettext('Continue'));
|
||||
pretixpaypal.continue_button.show();
|
||||
}
|
||||
pretixpaypal.continue_button.prop("disabled", false);
|
||||
},
|
||||
restore: function () {
|
||||
// if PayPal has not been initialized, there shouldn't be anything to cleanup
|
||||
if (pretixpaypal.paypal !== null) {
|
||||
$('#paypal-button-container').empty()
|
||||
pretixpaypal.continue_button.text(gettext('Continue'))
|
||||
pretixpaypal.continue_button.show()
|
||||
}
|
||||
pretixpaypal.continue_button.prop('disabled', false)
|
||||
},
|
||||
|
||||
renderButton: function (method) {
|
||||
if (method === 'paypal') {
|
||||
method = "wallet"
|
||||
} else {
|
||||
method = method.split('paypal_').at(-1)
|
||||
}
|
||||
pretixpaypal.method = pretixpaypal.method_map[method];
|
||||
renderButton: function (method) {
|
||||
if (method === 'paypal') {
|
||||
method = 'wallet'
|
||||
} else {
|
||||
method = method.split('paypal_').at(-1)
|
||||
}
|
||||
pretixpaypal.method = pretixpaypal.method_map[method]
|
||||
|
||||
if (pretixpaypal.method.method === 'apm' && !pretixpaypal.paypage) {
|
||||
pretixpaypal.restore();
|
||||
return;
|
||||
}
|
||||
if (pretixpaypal.method.method === 'apm' && !pretixpaypal.paypage) {
|
||||
pretixpaypal.restore()
|
||||
return
|
||||
}
|
||||
|
||||
$('#paypal-button-container').empty()
|
||||
$('#paypal-card-container').empty()
|
||||
$('#paypal-button-container').empty()
|
||||
$('#paypal-card-container').empty()
|
||||
|
||||
let button = pretixpaypal.paypal.Buttons({
|
||||
fundingSource: pretixpaypal.method.funding_source,
|
||||
style: {
|
||||
layout: pretixpaypal.method.early_auth ? 'horizontal' : 'vertical',
|
||||
//color: 'white',
|
||||
shape: 'rect',
|
||||
label: 'pay',
|
||||
tagline: false
|
||||
},
|
||||
createOrder: function (data, actions) {
|
||||
if (pretixpaypal.order_id) {
|
||||
return pretixpaypal.order_id;
|
||||
}
|
||||
let button = pretixpaypal.paypal.Buttons({
|
||||
fundingSource: pretixpaypal.method.funding_source,
|
||||
style: {
|
||||
layout: pretixpaypal.method.early_auth ? 'horizontal' : 'vertical',
|
||||
// color: 'white',
|
||||
shape: 'rect',
|
||||
label: 'pay',
|
||||
tagline: false
|
||||
},
|
||||
createOrder: function (data, actions) {
|
||||
if (pretixpaypal.order_id) {
|
||||
return pretixpaypal.order_id
|
||||
}
|
||||
|
||||
// On the paypal:pay view, we already pregenerated the OID.
|
||||
// Since this view is also only used for APMs, we only need the XHR-calls for the Smart Payment Buttons.
|
||||
if (pretixpaypal.paypage) {
|
||||
return $("#payment_paypal_" + pretixpaypal.method.method + "_oid");
|
||||
} else {
|
||||
var xhrurl = $("#payment_paypal_" + pretixpaypal.method.method + "_xhr").val();
|
||||
}
|
||||
// On the paypal:pay view, we already pregenerated the OID.
|
||||
// Since this view is also only used for APMs, we only need the XHR-calls for the Smart Payment Buttons.
|
||||
if (pretixpaypal.paypage) {
|
||||
return $('#payment_paypal_' + pretixpaypal.method.method + '_oid')
|
||||
} else {
|
||||
var xhrurl = $('#payment_paypal_' + pretixpaypal.method.method + '_xhr').val()
|
||||
}
|
||||
|
||||
return fetch(xhrurl, {
|
||||
method: 'POST'
|
||||
}).then(function (res) {
|
||||
return res.json();
|
||||
}).then(function (data) {
|
||||
if ('id' in data) {
|
||||
return data.id;
|
||||
} else {
|
||||
// Refreshing the page to surface the request-error message
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
},
|
||||
onApprove: function (data, actions) {
|
||||
waitingDialog.show(gettext("Confirming your payment …"));
|
||||
pretixpaypal.order_id = data.orderID;
|
||||
pretixpaypal.payer_id = data.payerID;
|
||||
return fetch(xhrurl, {
|
||||
method: 'POST'
|
||||
}).then(function (res) {
|
||||
return res.json()
|
||||
}).then(function (data) {
|
||||
if ('id' in data) {
|
||||
return data.id
|
||||
} else {
|
||||
// Refreshing the page to surface the request-error message
|
||||
location.reload()
|
||||
}
|
||||
})
|
||||
},
|
||||
onApprove: function (data, actions) {
|
||||
waitingDialog.show(gettext('Confirming your payment …'))
|
||||
pretixpaypal.order_id = data.orderID
|
||||
pretixpaypal.payer_id = data.payerID
|
||||
|
||||
let method = pretixpaypal.paypage ? "wallet" : pretixpaypal.method.method;
|
||||
let selectorstub = "#payment_paypal_" + method;
|
||||
// Insert the tokens into the form, so it gets submitted to the server
|
||||
$(selectorstub + "_oid").val(pretixpaypal.order_id);
|
||||
$(selectorstub + "_payer").val(pretixpaypal.payer_id);
|
||||
let method = pretixpaypal.paypage ? 'wallet' : pretixpaypal.method.method
|
||||
let selectorstub = '#payment_paypal_' + method
|
||||
// Insert the tokens into the form, so it gets submitted to the server
|
||||
$(selectorstub + '_oid').val(pretixpaypal.order_id)
|
||||
$(selectorstub + '_payer').val(pretixpaypal.payer_id)
|
||||
|
||||
// We are moving the submission to a separate function, which is also an EventListener, since
|
||||
// SFSafariView refuses to submit a form that is not visible. Unfortunately, that is exactly the case
|
||||
// when the ticket shop is used on iOS within an SFSafariView and the PayPal payment popup has not
|
||||
// closed itself quickly enough.
|
||||
pretixpaypal.readyToSubmitApproval = true;
|
||||
pretixpaypal.onApproveSubmit();
|
||||
// We are moving the submission to a separate function, which is also an EventListener, since
|
||||
// SFSafariView refuses to submit a form that is not visible. Unfortunately, that is exactly the case
|
||||
// when the ticket shop is used on iOS within an SFSafariView and the PayPal payment popup has not
|
||||
// closed itself quickly enough.
|
||||
pretixpaypal.readyToSubmitApproval = true
|
||||
pretixpaypal.onApproveSubmit()
|
||||
|
||||
// billingToken: null
|
||||
// facilitatorAccessToken: "A21AAL_fEu0gDD-sIXyOy65a6MjgSJJrhmxuPcxxUGnL5gW2DzTxiiAksfoC4x8hD-BjeY1LsFVKl7ceuO7UR1a9pQr8Q_AVw"
|
||||
// orderID: "7RF70259NY7589848"
|
||||
// payerID: "8M3BU92Z97VXA"
|
||||
// paymentID: null
|
||||
},
|
||||
});
|
||||
// billingToken: null
|
||||
// facilitatorAccessToken: "A21AAL_fEu0gDD-sIXyOy65a6MjgSJJrhmxuPcxxUGnL5gW2DzTxiiAksfoC4x8hD-BjeY1LsFVKl7ceuO7UR1a9pQr8Q_AVw"
|
||||
// orderID: "7RF70259NY7589848"
|
||||
// payerID: "8M3BU92Z97VXA"
|
||||
// paymentID: null
|
||||
},
|
||||
})
|
||||
|
||||
if (button.isEligible()) {
|
||||
button.render('#paypal-button-container');
|
||||
pretixpaypal.continue_button.hide();
|
||||
} else {
|
||||
pretixpaypal.continue_button.text(gettext('Payment method unavailable'));
|
||||
pretixpaypal.continue_button.show();
|
||||
}
|
||||
},
|
||||
if (button.isEligible()) {
|
||||
button.render('#paypal-button-container')
|
||||
pretixpaypal.continue_button.hide()
|
||||
} else {
|
||||
pretixpaypal.continue_button.text(gettext('Payment method unavailable'))
|
||||
pretixpaypal.continue_button.show()
|
||||
}
|
||||
},
|
||||
|
||||
onApproveSubmit: function() {
|
||||
if (document.visibilityState === "visible" && pretixpaypal.readyToSubmitApproval === true) {
|
||||
let method = pretixpaypal.paypage ? "wallet" : pretixpaypal.method.method;
|
||||
let selectorstub = "#payment_paypal_" + method;
|
||||
var $form = $(selectorstub + "_oid").closest("form");
|
||||
onApproveSubmit: function () {
|
||||
if (document.visibilityState === 'visible' && pretixpaypal.readyToSubmitApproval === true) {
|
||||
let method = pretixpaypal.paypage ? 'wallet' : pretixpaypal.method.method
|
||||
let selectorstub = '#payment_paypal_' + method
|
||||
let $form = $(selectorstub + '_oid').closest('form')
|
||||
|
||||
$form.get(0).submit();
|
||||
}
|
||||
},
|
||||
$form.get(0).submit()
|
||||
}
|
||||
},
|
||||
|
||||
renderAPMs: function () {
|
||||
pretixpaypal.restore();
|
||||
let inputselector = $("input[name=payment][value=paypal_apm]");
|
||||
let textselector = inputselector.closest("label").find('.accordion-label-text');
|
||||
let eligibles = [];
|
||||
renderAPMs: function () {
|
||||
pretixpaypal.restore()
|
||||
let inputselector = $('input[name=payment][value=paypal_apm]')
|
||||
let textselector = inputselector.closest('label').find('.accordion-label-text')
|
||||
let eligibles = []
|
||||
|
||||
pretixpaypal.paypal.getFundingSources().forEach(function (fundingSource) {
|
||||
// Let's always skip PayPal, since it's always a dedicated funding source
|
||||
if (fundingSource === 'paypal') {
|
||||
return;
|
||||
}
|
||||
pretixpaypal.paypal.getFundingSources().forEach(function (fundingSource) {
|
||||
// Let's always skip PayPal, since it's always a dedicated funding source
|
||||
if (fundingSource === 'paypal') {
|
||||
return
|
||||
}
|
||||
|
||||
// This could also be paypal.Marks() - but they only expose images instead of cleartext...
|
||||
let button = pretixpaypal.paypal.Buttons({
|
||||
fundingSource: fundingSource
|
||||
});
|
||||
// This could also be paypal.Marks() - but they only expose images instead of cleartext...
|
||||
let button = pretixpaypal.paypal.Buttons({
|
||||
fundingSource: fundingSource
|
||||
})
|
||||
|
||||
if (button.isEligible()) {
|
||||
eligibles.push(gettext(pretixpaypal.apm_map[fundingSource] || fundingSource));
|
||||
}
|
||||
});
|
||||
if (button.isEligible()) {
|
||||
eligibles.push(gettext(pretixpaypal.apm_map[fundingSource] || fundingSource))
|
||||
}
|
||||
})
|
||||
|
||||
inputselector.attr('title', eligibles.join(', '));
|
||||
textselector.fadeOut(300, function () {
|
||||
textselector.text(eligibles.join(', '));
|
||||
textselector.fadeIn(300);
|
||||
});
|
||||
},
|
||||
inputselector.attr('title', eligibles.join(', '))
|
||||
textselector.fadeOut(300, function () {
|
||||
textselector.text(eligibles.join(', '))
|
||||
textselector.fadeIn(300)
|
||||
})
|
||||
},
|
||||
|
||||
guessLocale: function() {
|
||||
// This is a horrible hackjob and does not at all take into consideration the actual locale.
|
||||
// Instead, we only look at the language that the shop is currently being displayed in and make
|
||||
// that into a locale.
|
||||
let allowed_locales = [
|
||||
'en_US',
|
||||
'ar_DZ',
|
||||
'fr_FR',
|
||||
'es_ES',
|
||||
'zh_CN',
|
||||
'de_DE',
|
||||
'nl_NL',
|
||||
'pt_PT',
|
||||
'cs_CZ',
|
||||
'da_DK',
|
||||
'fi_FI',
|
||||
'el_GR',
|
||||
'hu_HU',
|
||||
'id_ID',
|
||||
'he_IL',
|
||||
'it_IT',
|
||||
'ja_JP',
|
||||
'ru_RU',
|
||||
'no_NO',
|
||||
'pl_PL',
|
||||
'sk_SK',
|
||||
'sv_SE',
|
||||
'th_TH',
|
||||
'tr_TR',
|
||||
]
|
||||
let lang = $("body").attr("data-locale").split('-')[0];
|
||||
return allowed_locales.find(element => element.startsWith(lang));
|
||||
}
|
||||
};
|
||||
guessLocale: function () {
|
||||
// This is a horrible hackjob and does not at all take into consideration the actual locale.
|
||||
// Instead, we only look at the language that the shop is currently being displayed in and make
|
||||
// that into a locale.
|
||||
let allowed_locales = [
|
||||
'en_US',
|
||||
'ar_DZ',
|
||||
'fr_FR',
|
||||
'es_ES',
|
||||
'zh_CN',
|
||||
'de_DE',
|
||||
'nl_NL',
|
||||
'pt_PT',
|
||||
'cs_CZ',
|
||||
'da_DK',
|
||||
'fi_FI',
|
||||
'el_GR',
|
||||
'hu_HU',
|
||||
'id_ID',
|
||||
'he_IL',
|
||||
'it_IT',
|
||||
'ja_JP',
|
||||
'ru_RU',
|
||||
'no_NO',
|
||||
'pl_PL',
|
||||
'sk_SK',
|
||||
'sv_SE',
|
||||
'th_TH',
|
||||
'tr_TR',
|
||||
]
|
||||
let lang = $('body').attr('data-locale').split('-')[0]
|
||||
return allowed_locales.find(element => element.startsWith(lang))
|
||||
}
|
||||
}
|
||||
|
||||
$(function () {
|
||||
// This script is always loaded if paypal is enabled as a payment method, regardless of
|
||||
// whether it is available (it could e.g. be hidden or limited to certain countries).
|
||||
// We do not want to unnecessarily load the sdk.
|
||||
// If no paypal/paypal_apm payment option is present and we are not on
|
||||
// the (APM) PayView, then we do not need the SDK.
|
||||
if (!$("input[name=payment][value^='paypal']").length && !$('#paypal-button-container').data('paypage')) {
|
||||
return
|
||||
}
|
||||
// This script is always loaded if paypal is enabled as a payment method, regardless of
|
||||
// whether it is available (it could e.g. be hidden or limited to certain countries).
|
||||
// We do not want to unnecessarily load the sdk.
|
||||
// If no paypal/paypal_apm payment option is present and we are not on
|
||||
// the (APM) PayView, then we do not need the SDK.
|
||||
if (!$('input[name=payment][value^=\'paypal\']').length && !$('#paypal-button-container').data('paypage')) {
|
||||
return
|
||||
}
|
||||
|
||||
pretixpaypal.load();
|
||||
pretixpaypal.load();
|
||||
|
||||
(async() => {
|
||||
while(!pretixpaypal.paypal)
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
pretixpaypal.ready();
|
||||
})();
|
||||
});
|
||||
(async () => {
|
||||
while (!pretixpaypal.paypal)
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
pretixpaypal.ready()
|
||||
})()
|
||||
})
|
||||
|
||||
@@ -494,6 +494,7 @@ def webhook(request, *args, **kwargs):
|
||||
elif payment.state in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED,
|
||||
OrderPayment.PAYMENT_STATE_CANCELED, OrderPayment.PAYMENT_STATE_FAILED):
|
||||
if sale['status'] == 'COMPLETED':
|
||||
any_captures = False
|
||||
all_captures_completed = True
|
||||
any_pending_review = False
|
||||
any_failed = None
|
||||
@@ -505,6 +506,7 @@ def webhook(request, *args, **kwargs):
|
||||
except ReferencedPayPalObject.MultipleObjectsReturned:
|
||||
pass
|
||||
|
||||
any_captures = True
|
||||
if capture['status'] in ('COMPLETED', 'REFUNDED', 'PARTIALLY_REFUNDED'):
|
||||
pass
|
||||
elif capture['status'] in ("DECLINED", "FAILED"):
|
||||
@@ -516,7 +518,7 @@ def webhook(request, *args, **kwargs):
|
||||
any_pending_review = True
|
||||
else:
|
||||
raise ValueError("Unknown paypal capture state: {}".format(capture['status']))
|
||||
if all_captures_completed:
|
||||
if any_captures and all_captures_completed:
|
||||
try:
|
||||
payment.confirm()
|
||||
prov.log_payment_duration(payment)
|
||||
|
||||
@@ -56,18 +56,11 @@ from pretix.base.services.placeholders import FormPlaceholderMixin # noqa
|
||||
class BaseMailForm(FormPlaceholderMixin, forms.Form):
|
||||
subject = forms.CharField(label=_("Subject"))
|
||||
message = forms.CharField(label=_("Message"))
|
||||
attachment = CachedFileField(
|
||||
label=_("Attachment"),
|
||||
required=False,
|
||||
ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_EMAIL_ATTACHMENT,
|
||||
help_text=_('Sending an attachment increases the chance of your email not arriving or being sorted into spam folders. We recommend only using PDFs '
|
||||
'of no more than 2 MB in size.'),
|
||||
max_size=settings.FILE_UPLOAD_MAX_SIZE_EMAIL_ATTACHMENT
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
event = self.event = kwargs.pop('event')
|
||||
context_parameters = kwargs.pop('context_parameters')
|
||||
request = kwargs.pop('request')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['subject'] = I18nFormField(
|
||||
label=_('Subject'),
|
||||
@@ -79,6 +72,16 @@ class BaseMailForm(FormPlaceholderMixin, forms.Form):
|
||||
widget=I18nMarkdownTextarea, required=True,
|
||||
locales=event.settings.get('locales'),
|
||||
)
|
||||
self.fields['attachment'] = CachedFileField(
|
||||
label=_("Attachment"),
|
||||
required=False,
|
||||
ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_EMAIL_ATTACHMENT,
|
||||
help_text=_(
|
||||
'Sending an attachment increases the chance of your email not arriving or being sorted into spam folders. We recommend only using PDFs '
|
||||
'of no more than 2 MB in size.'),
|
||||
max_size=settings.FILE_UPLOAD_MAX_SIZE_EMAIL_ATTACHMENT,
|
||||
request=request,
|
||||
)
|
||||
self._set_field_placeholders('subject', context_parameters, rich=False)
|
||||
self._set_field_placeholders('message', context_parameters, rich=True)
|
||||
|
||||
|
||||
@@ -157,6 +157,7 @@ class BaseSenderView(EventPermissionRequiredMixin, FormView):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs['event'] = self.request.event
|
||||
kwargs['context_parameters'] = self.context_parameters
|
||||
kwargs['request'] = self.request
|
||||
if 'from_log' in self.request.GET:
|
||||
try:
|
||||
from_log_id = self.request.GET.get('from_log')
|
||||
@@ -366,36 +367,43 @@ class OrderSendView(BaseSenderView):
|
||||
)
|
||||
|
||||
if form.cleaned_data.get('filter_checkins'):
|
||||
ql = []
|
||||
ci_filter = Q(pk__in=[]) # return nothing
|
||||
|
||||
if form.cleaned_data.get('not_checked_in'):
|
||||
consider_tickets_used_lists = list(self.request.event.checkin_lists.filter(consider_tickets_used=True).values_list("id", flat=True))
|
||||
|
||||
opq = opq.alias(
|
||||
any_checkins=Exists(
|
||||
Checkin.all.filter(
|
||||
Q(position_id=OuterRef('pk')) | Q(position__addon_to_id=OuterRef('pk')),
|
||||
successful=True,
|
||||
list__consider_tickets_used=True,
|
||||
Checkin.objects.with_scopes_disabled().filter(
|
||||
position_id=OuterRef('pk'),
|
||||
list_id__in=consider_tickets_used_lists,
|
||||
)
|
||||
) | Exists(
|
||||
Checkin.objects.with_scopes_disabled().filter(
|
||||
position__addon_to_id=OuterRef('pk'),
|
||||
list_id__in=consider_tickets_used_lists,
|
||||
)
|
||||
)
|
||||
)
|
||||
ql.append(Q(any_checkins=False))
|
||||
ci_filter |= Q(any_checkins=False)
|
||||
|
||||
if form.cleaned_data.get('checkin_lists'):
|
||||
opq = opq.alias(
|
||||
matching_checkins=Exists(
|
||||
Checkin.all.filter(
|
||||
Q(position_id=OuterRef('pk')) | Q(position__addon_to_id=OuterRef('pk')),
|
||||
Checkin.objects.with_scopes_disabled().filter(
|
||||
position_id=OuterRef('pk'),
|
||||
list_id__in=[i.pk for i in form.cleaned_data.get('checkin_lists', [])],
|
||||
)
|
||||
) | Exists(
|
||||
Checkin.objects.with_scopes_disabled().filter(
|
||||
position__addon_to_id=OuterRef('pk'),
|
||||
list_id__in=[i.pk for i in form.cleaned_data.get('checkin_lists', [])],
|
||||
successful=True
|
||||
)
|
||||
)
|
||||
)
|
||||
ql.append(Q(matching_checkins=True))
|
||||
if len(ql) == 2:
|
||||
opq = opq.filter(ql[0] | ql[1])
|
||||
elif ql:
|
||||
opq = opq.filter(ql[0])
|
||||
else:
|
||||
opq = opq.none()
|
||||
ci_filter |= Q(matching_checkins=True)
|
||||
|
||||
opq = opq.filter(ci_filter)
|
||||
|
||||
if form.cleaned_data.get('subevent'):
|
||||
opq = opq.filter(subevent=form.cleaned_data.get('subevent'))
|
||||
|
||||
@@ -1,81 +1,81 @@
|
||||
/*globals $, Morris, gettext, django*/
|
||||
function gettext(msgid) {
|
||||
if (typeof django !== 'undefined' && typeof django.gettext !== 'undefined') {
|
||||
return django.gettext(msgid);
|
||||
}
|
||||
return msgid;
|
||||
/* globals Morris, django */
|
||||
function gettext (msgid) {
|
||||
if (typeof django !== 'undefined' && typeof django.gettext !== 'undefined') {
|
||||
return django.gettext(msgid)
|
||||
}
|
||||
return msgid
|
||||
}
|
||||
$(function () {
|
||||
$(".chart").css("height", "250px");
|
||||
new Morris.Area({
|
||||
element: 'obd_chart',
|
||||
data: JSON.parse($("#obd-data").html()),
|
||||
xkey: 'date',
|
||||
ykeys: ['ordered', 'paid'],
|
||||
labels: [gettext('Placed orders'), gettext('Paid orders')],
|
||||
lineColors: ['#3b1c4a', '#50a167'],
|
||||
smooth: false,
|
||||
resize: true,
|
||||
fillOpacity: 0.3,
|
||||
behaveLikeLine: true
|
||||
});
|
||||
new Morris.Area({
|
||||
element: 'abd_chart',
|
||||
data: JSON.parse($("#abd-data").html()),
|
||||
xkey: 'date',
|
||||
ykeys: ['ordered', 'paid'],
|
||||
labels: [gettext('Attendees (ordered)'), gettext('Attendees (paid)')],
|
||||
lineColors: ['#3b1c4a', '#50a167'],
|
||||
smooth: false,
|
||||
resize: true,
|
||||
fillOpacity: 0.3,
|
||||
behaveLikeLine: true
|
||||
});
|
||||
new Morris.Area({
|
||||
element: 'abt_chart',
|
||||
data: JSON.parse($("#abt-data").html()),
|
||||
xkey: 'date',
|
||||
ykeys: ['ordered', 'paid'],
|
||||
labels: [gettext('Attendees (ordered)'), gettext('Attendees (paid)')],
|
||||
lineColors: ['#3b1c4a', '#50a167'],
|
||||
smooth: false,
|
||||
resize: true,
|
||||
fillOpacity: 0.3,
|
||||
behaveLikeLine: true
|
||||
});
|
||||
new Morris.Area({
|
||||
element: 'rev_chart',
|
||||
data: JSON.parse($("#rev-data").html()),
|
||||
xkey: 'date',
|
||||
ykeys: ['revenue'],
|
||||
labels: [gettext('Total revenue')],
|
||||
smooth: false,
|
||||
resize: true,
|
||||
lineColors: ['#3b1c4a'],
|
||||
fillOpacity: 0.3,
|
||||
preUnits: $.trim($("#currency").html()) + ' '
|
||||
});
|
||||
new Morris.Bar({
|
||||
element: 'obp_chart',
|
||||
data: JSON.parse($("#obp-data").html()),
|
||||
xkey: 'item_short',
|
||||
ykeys: ['ordered', 'paid'],
|
||||
labels: [gettext('Placed orders'), gettext('Paid orders')],
|
||||
barColors: ['#3b1c4a', '#50a167'],
|
||||
hoverCallback: function (index, options, content, row) {
|
||||
console.log(content);
|
||||
var $c = $("<div>" + content + "</div>");
|
||||
var $label = $c.find(".morris-hover-row-label");
|
||||
$label.text(row.item);
|
||||
var newc = $label.get(0).outerHTML;
|
||||
$c.find('.morris-hover-point').each(function (i, r) {
|
||||
if ($.trim($(r).text().split("\n")[2]) !== "0") {
|
||||
newc += r.outerHTML;
|
||||
}
|
||||
});
|
||||
return newc;
|
||||
},
|
||||
resize: true,
|
||||
xLabelAngle: 30
|
||||
});
|
||||
});
|
||||
$('.chart').css('height', '250px')
|
||||
new Morris.Area({
|
||||
element: 'obd_chart',
|
||||
data: JSON.parse($('#obd-data').html()),
|
||||
xkey: 'date',
|
||||
ykeys: ['ordered', 'paid'],
|
||||
labels: [gettext('Placed orders'), gettext('Paid orders')],
|
||||
lineColors: ['#3b1c4a', '#50a167'],
|
||||
smooth: false,
|
||||
resize: true,
|
||||
fillOpacity: 0.3,
|
||||
behaveLikeLine: true
|
||||
})
|
||||
new Morris.Area({
|
||||
element: 'abd_chart',
|
||||
data: JSON.parse($('#abd-data').html()),
|
||||
xkey: 'date',
|
||||
ykeys: ['ordered', 'paid'],
|
||||
labels: [gettext('Attendees (ordered)'), gettext('Attendees (paid)')],
|
||||
lineColors: ['#3b1c4a', '#50a167'],
|
||||
smooth: false,
|
||||
resize: true,
|
||||
fillOpacity: 0.3,
|
||||
behaveLikeLine: true
|
||||
})
|
||||
new Morris.Area({
|
||||
element: 'abt_chart',
|
||||
data: JSON.parse($('#abt-data').html()),
|
||||
xkey: 'date',
|
||||
ykeys: ['ordered', 'paid'],
|
||||
labels: [gettext('Attendees (ordered)'), gettext('Attendees (paid)')],
|
||||
lineColors: ['#3b1c4a', '#50a167'],
|
||||
smooth: false,
|
||||
resize: true,
|
||||
fillOpacity: 0.3,
|
||||
behaveLikeLine: true
|
||||
})
|
||||
new Morris.Area({
|
||||
element: 'rev_chart',
|
||||
data: JSON.parse($('#rev-data').html()),
|
||||
xkey: 'date',
|
||||
ykeys: ['revenue'],
|
||||
labels: [gettext('Total revenue')],
|
||||
smooth: false,
|
||||
resize: true,
|
||||
lineColors: ['#3b1c4a'],
|
||||
fillOpacity: 0.3,
|
||||
preUnits: $.trim($('#currency').html()) + ' '
|
||||
})
|
||||
new Morris.Bar({
|
||||
element: 'obp_chart',
|
||||
data: JSON.parse($('#obp-data').html()),
|
||||
xkey: 'item_short',
|
||||
ykeys: ['ordered', 'paid'],
|
||||
labels: [gettext('Placed orders'), gettext('Paid orders')],
|
||||
barColors: ['#3b1c4a', '#50a167'],
|
||||
hoverCallback: function (index, options, content, row) {
|
||||
console.log(content)
|
||||
let $c = $('<div>' + content + '</div>')
|
||||
let $label = $c.find('.morris-hover-row-label')
|
||||
$label.text(row.item)
|
||||
let newc = $label.get(0).outerHTML
|
||||
$c.find('.morris-hover-point').each(function (i, r) {
|
||||
if ($.trim($(r).text().split('\n')[2]) !== '0') {
|
||||
newc += r.outerHTML
|
||||
}
|
||||
})
|
||||
return newc
|
||||
},
|
||||
resize: true,
|
||||
xLabelAngle: 30
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,435 +1,435 @@
|
||||
/*global $, stripe_pubkey, stripe_loadingmessage, gettext */
|
||||
'use strict';
|
||||
/* global stripe_pubkey, stripe_loadingmessage, gettext */
|
||||
'use strict'
|
||||
|
||||
var pretixstripe = {
|
||||
stripe: null,
|
||||
elements: null,
|
||||
card: null,
|
||||
sepa: null,
|
||||
affirm: null,
|
||||
klarna: null,
|
||||
paymentRequest: null,
|
||||
paymentRequestButton: null,
|
||||
stripe: null,
|
||||
elements: null,
|
||||
card: null,
|
||||
sepa: null,
|
||||
affirm: null,
|
||||
klarna: null,
|
||||
paymentRequest: null,
|
||||
paymentRequestButton: null,
|
||||
|
||||
'pm_request': function (method, element, kwargs = {}) {
|
||||
waitingDialog.show(gettext("Contacting Stripe …"));
|
||||
$(".stripe-errors").hide();
|
||||
pm_request: function (method, element, kwargs = {}) {
|
||||
waitingDialog.show(gettext('Contacting Stripe …'))
|
||||
$('.stripe-errors').hide()
|
||||
|
||||
pretixstripe.stripe.createPaymentMethod(method, element, kwargs).then(function (result) {
|
||||
waitingDialog.hide();
|
||||
if (result.error) {
|
||||
$(".stripe-errors").stop().hide().removeClass("sr-only");
|
||||
$(".stripe-errors").html("<div class='alert alert-danger'>" + result.error.message + "</div>");
|
||||
$(".stripe-errors").slideDown();
|
||||
} else {
|
||||
var $form = $("#stripe_" + method + "_payment_method_id").closest("form");
|
||||
// Insert the token into the form so it gets submitted to the server
|
||||
$("#stripe_" + method + "_payment_method_id").val(result.paymentMethod.id);
|
||||
if (method === 'card') {
|
||||
$("#stripe_card_brand").val(result.paymentMethod.card.brand);
|
||||
$("#stripe_card_last4").val(result.paymentMethod.card.last4);
|
||||
}
|
||||
if (method === 'sepa_debit') {
|
||||
$("#stripe_sepa_debit_last4").val(result.paymentMethod.sepa_debit.last4);
|
||||
}
|
||||
// and submit
|
||||
$form.get(0).submit();
|
||||
}
|
||||
}).catch((e) => {
|
||||
waitingDialog.hide();
|
||||
$(".stripe-errors").stop().hide().removeClass("sr-only");
|
||||
$(".stripe-errors").html("<div class='alert alert-danger'>Technical error, please contact support: " + e + "</div>");
|
||||
$(".stripe-errors").slideDown();
|
||||
});
|
||||
},
|
||||
'load': function () {
|
||||
if (pretixstripe.stripe !== null) {
|
||||
return;
|
||||
}
|
||||
$('.stripe-container').closest("form").find(".checkout-button-row .btn-primary").prop("disabled", true);
|
||||
$.ajax(
|
||||
{
|
||||
url: 'https://js.stripe.com/v3/',
|
||||
dataType: 'script',
|
||||
success: function () {
|
||||
if ($.trim($("#stripe_connectedAccountId").html())) {
|
||||
pretixstripe.stripe = Stripe($.trim($("#stripe_pubkey").html()), {
|
||||
stripeAccount: $.trim($("#stripe_connectedAccountId").html()),
|
||||
locale: $.trim($("body").attr("data-locale"))
|
||||
});
|
||||
} else {
|
||||
pretixstripe.stripe = Stripe($.trim($("#stripe_pubkey").html()), {
|
||||
locale: $.trim($("body").attr("data-locale"))
|
||||
});
|
||||
}
|
||||
pretixstripe.elements = pretixstripe.stripe.elements();
|
||||
if ($.trim($("#stripe_merchantcountry").html()) !== "") {
|
||||
try {
|
||||
pretixstripe.paymentRequest = pretixstripe.stripe.paymentRequest({
|
||||
country: $("#stripe_merchantcountry").html(),
|
||||
currency: $("#stripe_card_currency").val().toLowerCase(),
|
||||
total: {
|
||||
label: gettext('Total'),
|
||||
amount: parseInt($("#stripe_card_total").val())
|
||||
},
|
||||
displayItems: [],
|
||||
requestPayerName: false,
|
||||
requestPayerEmail: false,
|
||||
requestPayerPhone: false,
|
||||
requestShipping: false,
|
||||
});
|
||||
pretixstripe.stripe.createPaymentMethod(method, element, kwargs).then(function (result) {
|
||||
waitingDialog.hide()
|
||||
if (result.error) {
|
||||
$('.stripe-errors').stop().hide().removeClass('sr-only')
|
||||
$('.stripe-errors').html('<div class=\'alert alert-danger\'>' + result.error.message + '</div>')
|
||||
$('.stripe-errors').slideDown()
|
||||
} else {
|
||||
let $form = $('#stripe_' + method + '_payment_method_id').closest('form')
|
||||
// Insert the token into the form so it gets submitted to the server
|
||||
$('#stripe_' + method + '_payment_method_id').val(result.paymentMethod.id)
|
||||
if (method === 'card') {
|
||||
$('#stripe_card_brand').val(result.paymentMethod.card.brand)
|
||||
$('#stripe_card_last4').val(result.paymentMethod.card.last4)
|
||||
}
|
||||
if (method === 'sepa_debit') {
|
||||
$('#stripe_sepa_debit_last4').val(result.paymentMethod.sepa_debit.last4)
|
||||
}
|
||||
// and submit
|
||||
$form.get(0).submit()
|
||||
}
|
||||
}).catch((e) => {
|
||||
waitingDialog.hide()
|
||||
$('.stripe-errors').stop().hide().removeClass('sr-only')
|
||||
$('.stripe-errors').html('<div class=\'alert alert-danger\'>Technical error, please contact support: ' + e + '</div>')
|
||||
$('.stripe-errors').slideDown()
|
||||
})
|
||||
},
|
||||
load: function () {
|
||||
if (pretixstripe.stripe !== null) {
|
||||
return
|
||||
}
|
||||
$('.stripe-container').closest('form').find('.checkout-button-row .btn-primary').prop('disabled', true)
|
||||
$.ajax(
|
||||
{
|
||||
url: 'https://js.stripe.com/v3/',
|
||||
dataType: 'script',
|
||||
success: function () {
|
||||
if ($.trim($('#stripe_connectedAccountId').html())) {
|
||||
pretixstripe.stripe = Stripe($.trim($('#stripe_pubkey').html()), {
|
||||
stripeAccount: $.trim($('#stripe_connectedAccountId').html()),
|
||||
locale: $.trim($('body').attr('data-locale'))
|
||||
})
|
||||
} else {
|
||||
pretixstripe.stripe = Stripe($.trim($('#stripe_pubkey').html()), {
|
||||
locale: $.trim($('body').attr('data-locale'))
|
||||
})
|
||||
}
|
||||
pretixstripe.elements = pretixstripe.stripe.elements()
|
||||
if ($.trim($('#stripe_merchantcountry').html()) !== '') {
|
||||
try {
|
||||
pretixstripe.paymentRequest = pretixstripe.stripe.paymentRequest({
|
||||
country: $('#stripe_merchantcountry').html(),
|
||||
currency: $('#stripe_card_currency').val().toLowerCase(),
|
||||
total: {
|
||||
label: gettext('Total'),
|
||||
amount: parseInt($('#stripe_card_total').val())
|
||||
},
|
||||
displayItems: [],
|
||||
requestPayerName: false,
|
||||
requestPayerEmail: false,
|
||||
requestPayerPhone: false,
|
||||
requestShipping: false,
|
||||
})
|
||||
|
||||
pretixstripe.paymentRequest.on('paymentmethod', function (ev) {
|
||||
ev.complete('success');
|
||||
pretixstripe.paymentRequest.on('paymentmethod', function (ev) {
|
||||
ev.complete('success')
|
||||
|
||||
var $form = $("#stripe_card_payment_method_id").closest("form");
|
||||
// Insert the token into the form so it gets submitted to the server
|
||||
$("#stripe_card_payment_method_id").val(ev.paymentMethod.id);
|
||||
$("#stripe_card_brand").val(ev.paymentMethod.card.brand);
|
||||
$("#stripe_card_last4").val(ev.paymentMethod.card.last4);
|
||||
// and submit
|
||||
$form.get(0).submit();
|
||||
});
|
||||
} catch (e) {
|
||||
pretixstripe.paymentRequest = null;
|
||||
}
|
||||
} else {
|
||||
pretixstripe.paymentRequest = null;
|
||||
}
|
||||
if ($("#stripe-card").length) {
|
||||
pretixstripe.card = pretixstripe.elements.create('card', {
|
||||
'style': {
|
||||
'base': {
|
||||
'fontFamily': '"Open Sans","OpenSans","Helvetica Neue",Helvetica,Arial,sans-serif',
|
||||
'fontSize': '14px',
|
||||
'color': '#555555',
|
||||
'lineHeight': '1.42857',
|
||||
'border': '1px solid #ccc',
|
||||
'::placeholder': {
|
||||
color: 'rgba(0,0,0,0.4)',
|
||||
},
|
||||
},
|
||||
'invalid': {
|
||||
'color': 'red',
|
||||
},
|
||||
},
|
||||
classes: {
|
||||
focus: 'is-focused',
|
||||
invalid: 'has-error',
|
||||
}
|
||||
});
|
||||
pretixstripe.card.mount("#stripe-card");
|
||||
pretixstripe.card.on('ready', function () {
|
||||
$('.stripe-container').closest("form").find(".checkout-button-row .btn-primary").prop("disabled", false);
|
||||
});
|
||||
}
|
||||
if ($("#stripe-sepa").length) {
|
||||
pretixstripe.sepa = pretixstripe.elements.create('iban', {
|
||||
'style': {
|
||||
'base': {
|
||||
'fontFamily': '"Open Sans","OpenSans","Helvetica Neue",Helvetica,Arial,sans-serif',
|
||||
'fontSize': '14px',
|
||||
'color': '#555555',
|
||||
'lineHeight': '1.42857',
|
||||
'border': '1px solid #ccc',
|
||||
'::placeholder': {
|
||||
color: 'rgba(0,0,0,0.4)',
|
||||
},
|
||||
},
|
||||
'invalid': {
|
||||
'color': 'red',
|
||||
},
|
||||
},
|
||||
supportedCountries: ['SEPA'],
|
||||
classes: {
|
||||
focus: 'is-focused',
|
||||
invalid: 'has-error',
|
||||
}
|
||||
});
|
||||
pretixstripe.sepa.on('change', function (event) {
|
||||
// List of IBAN-countries, that require the country as well as line1-property according to
|
||||
// https://stripe.com/docs/payments/sepa-debit/accept-a-payment?platform=web&ui=element#web-submit-payment
|
||||
if (['AD', 'PF', 'TF', 'GI', 'GB', 'GG', 'VA', 'IM', 'JE', 'MC', 'NC', 'BL', 'PM', 'SM', 'CH', 'WF'].indexOf(event.country) > 0) {
|
||||
$("#stripe_sepa_debit_country").prop('checked', true);
|
||||
$("#stripe_sepa_debit_country").change();
|
||||
} else {
|
||||
$("#stripe_sepa_debit_country").prop('checked', false);
|
||||
$("#stripe_sepa_debit_country").change();
|
||||
}
|
||||
if (event.bankName) {
|
||||
$("#stripe_sepa_debit_bank").val(event.bankName);
|
||||
}
|
||||
});
|
||||
pretixstripe.sepa.mount("#stripe-sepa");
|
||||
pretixstripe.sepa.on('ready', function () {
|
||||
$('.stripe-container').closest("form").find(".checkout-button-row .btn-primary").prop("disabled", false);
|
||||
});
|
||||
}
|
||||
if ($("#stripe-affirm").length) {
|
||||
pretixstripe.affirm = pretixstripe.elements.create('affirmMessage', {
|
||||
'amount': parseInt($("#stripe_affirm_total").val()),
|
||||
'currency': $("#stripe_affirm_currency").val(),
|
||||
});
|
||||
let $form = $('#stripe_card_payment_method_id').closest('form')
|
||||
// Insert the token into the form so it gets submitted to the server
|
||||
$('#stripe_card_payment_method_id').val(ev.paymentMethod.id)
|
||||
$('#stripe_card_brand').val(ev.paymentMethod.card.brand)
|
||||
$('#stripe_card_last4').val(ev.paymentMethod.card.last4)
|
||||
// and submit
|
||||
$form.get(0).submit()
|
||||
})
|
||||
} catch (e) {
|
||||
pretixstripe.paymentRequest = null
|
||||
}
|
||||
} else {
|
||||
pretixstripe.paymentRequest = null
|
||||
}
|
||||
if ($('#stripe-card').length) {
|
||||
pretixstripe.card = pretixstripe.elements.create('card', {
|
||||
style: {
|
||||
base: {
|
||||
fontFamily: '"Open Sans","OpenSans","Helvetica Neue",Helvetica,Arial,sans-serif',
|
||||
fontSize: '14px',
|
||||
color: '#555555',
|
||||
lineHeight: '1.42857',
|
||||
border: '1px solid #ccc',
|
||||
'::placeholder': {
|
||||
color: 'rgba(0,0,0,0.4)',
|
||||
},
|
||||
},
|
||||
invalid: {
|
||||
color: 'red',
|
||||
},
|
||||
},
|
||||
classes: {
|
||||
focus: 'is-focused',
|
||||
invalid: 'has-error',
|
||||
}
|
||||
})
|
||||
pretixstripe.card.mount('#stripe-card')
|
||||
pretixstripe.card.on('ready', function () {
|
||||
$('.stripe-container').closest('form').find('.checkout-button-row .btn-primary').prop('disabled', false)
|
||||
})
|
||||
}
|
||||
if ($('#stripe-sepa').length) {
|
||||
pretixstripe.sepa = pretixstripe.elements.create('iban', {
|
||||
style: {
|
||||
base: {
|
||||
fontFamily: '"Open Sans","OpenSans","Helvetica Neue",Helvetica,Arial,sans-serif',
|
||||
fontSize: '14px',
|
||||
color: '#555555',
|
||||
lineHeight: '1.42857',
|
||||
border: '1px solid #ccc',
|
||||
'::placeholder': {
|
||||
color: 'rgba(0,0,0,0.4)',
|
||||
},
|
||||
},
|
||||
invalid: {
|
||||
color: 'red',
|
||||
},
|
||||
},
|
||||
supportedCountries: ['SEPA'],
|
||||
classes: {
|
||||
focus: 'is-focused',
|
||||
invalid: 'has-error',
|
||||
}
|
||||
})
|
||||
pretixstripe.sepa.on('change', function (event) {
|
||||
// List of IBAN-countries, that require the country as well as line1-property according to
|
||||
// https://stripe.com/docs/payments/sepa-debit/accept-a-payment?platform=web&ui=element#web-submit-payment
|
||||
if (['AD', 'PF', 'TF', 'GI', 'GB', 'GG', 'VA', 'IM', 'JE', 'MC', 'NC', 'BL', 'PM', 'SM', 'CH', 'WF'].indexOf(event.country) > 0) {
|
||||
$('#stripe_sepa_debit_country').prop('checked', true)
|
||||
$('#stripe_sepa_debit_country').change()
|
||||
} else {
|
||||
$('#stripe_sepa_debit_country').prop('checked', false)
|
||||
$('#stripe_sepa_debit_country').change()
|
||||
}
|
||||
if (event.bankName) {
|
||||
$('#stripe_sepa_debit_bank').val(event.bankName)
|
||||
}
|
||||
})
|
||||
pretixstripe.sepa.mount('#stripe-sepa')
|
||||
pretixstripe.sepa.on('ready', function () {
|
||||
$('.stripe-container').closest('form').find('.checkout-button-row .btn-primary').prop('disabled', false)
|
||||
})
|
||||
}
|
||||
if ($('#stripe-affirm').length) {
|
||||
pretixstripe.affirm = pretixstripe.elements.create('affirmMessage', {
|
||||
amount: parseInt($('#stripe_affirm_total').val()),
|
||||
currency: $('#stripe_affirm_currency').val(),
|
||||
})
|
||||
|
||||
pretixstripe.affirm.mount('#stripe-affirm');
|
||||
}
|
||||
if ($("#stripe-klarna").length) {
|
||||
try {
|
||||
pretixstripe.klarna = pretixstripe.elements.create('paymentMethodMessaging', {
|
||||
'amount': parseInt($("#stripe_klarna_total").val()),
|
||||
'currency': $("#stripe_klarna_currency").val(),
|
||||
'countryCode': $("#stripe_klarna_country").val(),
|
||||
'paymentMethodTypes': ['klarna'],
|
||||
});
|
||||
pretixstripe.affirm.mount('#stripe-affirm')
|
||||
}
|
||||
if ($('#stripe-klarna').length) {
|
||||
try {
|
||||
pretixstripe.klarna = pretixstripe.elements.create('paymentMethodMessaging', {
|
||||
amount: parseInt($('#stripe_klarna_total').val()),
|
||||
currency: $('#stripe_klarna_currency').val(),
|
||||
countryCode: $('#stripe_klarna_country').val(),
|
||||
paymentMethodTypes: ['klarna'],
|
||||
})
|
||||
|
||||
pretixstripe.klarna.mount('#stripe-klarna');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
$("#stripe-klarna").html("<div class='alert alert-danger'>Technical error, please contact support: " + e + "</div>");
|
||||
}
|
||||
}
|
||||
if ($("#stripe-payment-request-button").length && pretixstripe.paymentRequest != null) {
|
||||
pretixstripe.paymentRequestButton = pretixstripe.elements.create('paymentRequestButton', {
|
||||
paymentRequest: pretixstripe.paymentRequest,
|
||||
});
|
||||
pretixstripe.klarna.mount('#stripe-klarna')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
$('#stripe-klarna').html('<div class=\'alert alert-danger\'>Technical error, please contact support: ' + e + '</div>')
|
||||
}
|
||||
}
|
||||
if ($('#stripe-payment-request-button').length && pretixstripe.paymentRequest != null) {
|
||||
pretixstripe.paymentRequestButton = pretixstripe.elements.create('paymentRequestButton', {
|
||||
paymentRequest: pretixstripe.paymentRequest,
|
||||
})
|
||||
|
||||
pretixstripe.paymentRequest.canMakePayment().then(function (result) {
|
||||
if (result) {
|
||||
pretixstripe.paymentRequestButton.mount('#stripe-payment-request-button');
|
||||
$('#stripe-card-elements .stripe-or').removeClass("hidden");
|
||||
$('#stripe-payment-request-button').parent().removeClass("hidden");
|
||||
} else {
|
||||
$('#stripe-payment-request-button').hide();
|
||||
document.getElementById('stripe-payment-request-button').style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
'withStripe': function (callback) {
|
||||
$.ajax({
|
||||
url: 'https://js.stripe.com/v3/',
|
||||
dataType: 'script',
|
||||
success: function () {
|
||||
if ($.trim($("#stripe_connectedAccountId").html())) {
|
||||
pretixstripe.stripe = Stripe($.trim($("#stripe_pubkey").html()), {
|
||||
stripeAccount: $.trim($("#stripe_connectedAccountId").html()),
|
||||
locale: $.trim($("body").attr("data-locale"))
|
||||
});
|
||||
} else {
|
||||
pretixstripe.stripe = Stripe($.trim($("#stripe_pubkey").html()), {
|
||||
locale: $.trim($("body").attr("data-locale"))
|
||||
});
|
||||
}
|
||||
callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
'handleAlipayAction': function (payment_intent_client_secret) {
|
||||
pretixstripe.withStripe(function () {
|
||||
pretixstripe.stripe.confirmAlipayPayment(
|
||||
payment_intent_client_secret,
|
||||
{
|
||||
return_url: window.location.href
|
||||
}
|
||||
).then(function (result) {
|
||||
if (result.error) {
|
||||
waitingDialog.hide();
|
||||
$(".stripe-errors").stop().hide().removeClass("sr-only");
|
||||
$(".stripe-errors").html("<div class='alert alert-danger'>Technical error, please contact support: " + result.error.message + "</div>");
|
||||
$(".stripe-errors").slideDown();
|
||||
} else {
|
||||
waitingDialog.show(gettext("Confirming your payment …"));
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
'handleWechatAction': function (payment_intent_client_secret) {
|
||||
pretixstripe.withStripe(function () {
|
||||
pretixstripe.stripe.confirmWechatPayPayment(
|
||||
payment_intent_client_secret,
|
||||
{
|
||||
payment_method_options: {
|
||||
wechat_pay: {
|
||||
client: 'web',
|
||||
},
|
||||
},
|
||||
}
|
||||
).then(function (result) {
|
||||
if (result.error) {
|
||||
waitingDialog.hide();
|
||||
$(".stripe-errors").stop().hide().removeClass("sr-only");
|
||||
$(".stripe-errors").html("<div class='alert alert-danger'>Technical error, please contact support: " + result.error.message + "</div>");
|
||||
$(".stripe-errors").slideDown();
|
||||
} else {
|
||||
waitingDialog.show(gettext("Confirming your payment …"));
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
'handleCardAction': function (payment_intent_client_secret) {
|
||||
pretixstripe.withStripe(function () {
|
||||
pretixstripe.stripe.handleCardAction(
|
||||
payment_intent_client_secret
|
||||
).then(function (result) {
|
||||
if (result.error) {
|
||||
waitingDialog.hide();
|
||||
$(".stripe-errors").stop().hide().removeClass("sr-only");
|
||||
$(".stripe-errors").html("<div class='alert alert-danger'>Technical error, please contact support: " + result.error.message + "</div>");
|
||||
$(".stripe-errors").slideDown();
|
||||
} else {
|
||||
waitingDialog.show(gettext("Confirming your payment …"));
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
'handlePaymentRedirectAction': function (payment_intent_next_action_redirect_url) {
|
||||
waitingDialog.show(gettext("Contacting your bank …"));
|
||||
pretixstripe.paymentRequest.canMakePayment().then(function (result) {
|
||||
if (result) {
|
||||
pretixstripe.paymentRequestButton.mount('#stripe-payment-request-button')
|
||||
$('#stripe-card-elements .stripe-or').removeClass('hidden')
|
||||
$('#stripe-payment-request-button').parent().removeClass('hidden')
|
||||
} else {
|
||||
$('#stripe-payment-request-button').hide()
|
||||
document.getElementById('stripe-payment-request-button').style.display = 'none'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
withStripe: function (callback) {
|
||||
$.ajax({
|
||||
url: 'https://js.stripe.com/v3/',
|
||||
dataType: 'script',
|
||||
success: function () {
|
||||
if ($.trim($('#stripe_connectedAccountId').html())) {
|
||||
pretixstripe.stripe = Stripe($.trim($('#stripe_pubkey').html()), {
|
||||
stripeAccount: $.trim($('#stripe_connectedAccountId').html()),
|
||||
locale: $.trim($('body').attr('data-locale'))
|
||||
})
|
||||
} else {
|
||||
pretixstripe.stripe = Stripe($.trim($('#stripe_pubkey').html()), {
|
||||
locale: $.trim($('body').attr('data-locale'))
|
||||
})
|
||||
}
|
||||
callback()
|
||||
}
|
||||
})
|
||||
},
|
||||
handleAlipayAction: function (payment_intent_client_secret) {
|
||||
pretixstripe.withStripe(function () {
|
||||
pretixstripe.stripe.confirmAlipayPayment(
|
||||
payment_intent_client_secret,
|
||||
{
|
||||
return_url: window.location.href
|
||||
}
|
||||
).then(function (result) {
|
||||
if (result.error) {
|
||||
waitingDialog.hide()
|
||||
$('.stripe-errors').stop().hide().removeClass('sr-only')
|
||||
$('.stripe-errors').html('<div class=\'alert alert-danger\'>Technical error, please contact support: ' + result.error.message + '</div>')
|
||||
$('.stripe-errors').slideDown()
|
||||
} else {
|
||||
waitingDialog.show(gettext('Confirming your payment …'))
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
handleWechatAction: function (payment_intent_client_secret) {
|
||||
pretixstripe.withStripe(function () {
|
||||
pretixstripe.stripe.confirmWechatPayPayment(
|
||||
payment_intent_client_secret,
|
||||
{
|
||||
payment_method_options: {
|
||||
wechat_pay: {
|
||||
client: 'web',
|
||||
},
|
||||
},
|
||||
}
|
||||
).then(function (result) {
|
||||
if (result.error) {
|
||||
waitingDialog.hide()
|
||||
$('.stripe-errors').stop().hide().removeClass('sr-only')
|
||||
$('.stripe-errors').html('<div class=\'alert alert-danger\'>Technical error, please contact support: ' + result.error.message + '</div>')
|
||||
$('.stripe-errors').slideDown()
|
||||
} else {
|
||||
waitingDialog.show(gettext('Confirming your payment …'))
|
||||
location.reload()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
handleCardAction: function (payment_intent_client_secret) {
|
||||
pretixstripe.withStripe(function () {
|
||||
pretixstripe.stripe.handleCardAction(
|
||||
payment_intent_client_secret
|
||||
).then(function (result) {
|
||||
if (result.error) {
|
||||
waitingDialog.hide()
|
||||
$('.stripe-errors').stop().hide().removeClass('sr-only')
|
||||
$('.stripe-errors').html('<div class=\'alert alert-danger\'>Technical error, please contact support: ' + result.error.message + '</div>')
|
||||
$('.stripe-errors').slideDown()
|
||||
} else {
|
||||
waitingDialog.show(gettext('Confirming your payment …'))
|
||||
location.reload()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
handlePaymentRedirectAction: function (payment_intent_next_action_redirect_url) {
|
||||
waitingDialog.show(gettext('Contacting your bank …'))
|
||||
|
||||
let payment_intent_redirect_action_handling = $.trim($("#stripe_payment_intent_redirect_action_handling").html());
|
||||
if (payment_intent_redirect_action_handling === 'iframe') {
|
||||
let iframe = document.createElement('iframe');
|
||||
iframe.src = payment_intent_next_action_redirect_url;
|
||||
iframe.className = 'embed-responsive-item';
|
||||
$('#scacontainer').append(iframe);
|
||||
$('#scacontainer iframe').on("load", function () {
|
||||
waitingDialog.hide();
|
||||
});
|
||||
} else if (payment_intent_redirect_action_handling === 'redirect') {
|
||||
window.location.href = payment_intent_next_action_redirect_url;
|
||||
}
|
||||
}
|
||||
};
|
||||
let payment_intent_redirect_action_handling = $.trim($('#stripe_payment_intent_redirect_action_handling').html())
|
||||
if (payment_intent_redirect_action_handling === 'iframe') {
|
||||
let iframe = document.createElement('iframe')
|
||||
iframe.src = payment_intent_next_action_redirect_url
|
||||
iframe.className = 'embed-responsive-item'
|
||||
$('#scacontainer').append(iframe)
|
||||
$('#scacontainer iframe').on('load', function () {
|
||||
waitingDialog.hide()
|
||||
})
|
||||
} else if (payment_intent_redirect_action_handling === 'redirect') {
|
||||
window.location.href = payment_intent_next_action_redirect_url
|
||||
}
|
||||
}
|
||||
}
|
||||
$(function () {
|
||||
if ($("#stripe_payment_intent_SCA_status").length) {
|
||||
let payment_intent_redirect_action_handling = $.trim($("#stripe_payment_intent_redirect_action_handling").html());
|
||||
let order_status = $.trim($("#order_status").html());
|
||||
let order_url = $.trim($("#order_url").html())
|
||||
if ($('#stripe_payment_intent_SCA_status').length) {
|
||||
let payment_intent_redirect_action_handling = $.trim($('#stripe_payment_intent_redirect_action_handling').html())
|
||||
let order_status = $.trim($('#order_status').html())
|
||||
let order_url = $.trim($('#order_url').html())
|
||||
|
||||
if (payment_intent_redirect_action_handling === 'iframe') {
|
||||
window.parent.postMessage('3DS-authentication-complete.' + order_status, '*');
|
||||
return;
|
||||
} else if (payment_intent_redirect_action_handling === 'redirect') {
|
||||
waitingDialog.show(gettext("Confirming your payment …"));
|
||||
if (payment_intent_redirect_action_handling === 'iframe') {
|
||||
window.parent.postMessage('3DS-authentication-complete.' + order_status, '*')
|
||||
return
|
||||
} else if (payment_intent_redirect_action_handling === 'redirect') {
|
||||
waitingDialog.show(gettext('Confirming your payment …'))
|
||||
|
||||
if (order_status === 'p') {
|
||||
window.location.href = order_url + '?paid=yes';
|
||||
} else {
|
||||
window.location.href = order_url;
|
||||
}
|
||||
}
|
||||
} else if ($("#stripe_payment_intent_next_action_redirect_url").length) {
|
||||
let payment_intent_next_action_redirect_url = JSON.parse($("#stripe_payment_intent_next_action_redirect_url").html());
|
||||
pretixstripe.handlePaymentRedirectAction(payment_intent_next_action_redirect_url);
|
||||
} else if ($.trim($("#stripe_payment_intent_action_type").html()) === "promptpay_display_qr_code") {
|
||||
waitingDialog.hide();
|
||||
} else if ($.trim($("#stripe_payment_intent_action_type").html()) === "wechat_pay_display_qr_code") {
|
||||
let payment_intent_client_secret = $.trim($("#stripe_payment_intent_client_secret").html());
|
||||
pretixstripe.handleWechatAction(payment_intent_client_secret);
|
||||
} else if ($.trim($("#stripe_payment_intent_action_type").html()) === "alipay_handle_redirect") {
|
||||
let payment_intent_client_secret = $.trim($("#stripe_payment_intent_client_secret").html());
|
||||
pretixstripe.handleAlipayAction(payment_intent_client_secret);
|
||||
} else if ($("#stripe_payment_intent_client_secret").length) {
|
||||
let payment_intent_client_secret = $.trim($("#stripe_payment_intent_client_secret").html());
|
||||
pretixstripe.handleCardAction(payment_intent_client_secret);
|
||||
}
|
||||
if (order_status === 'p') {
|
||||
window.location.href = order_url + '?paid=yes'
|
||||
} else {
|
||||
window.location.href = order_url
|
||||
}
|
||||
}
|
||||
} else if ($('#stripe_payment_intent_next_action_redirect_url').length) {
|
||||
let payment_intent_next_action_redirect_url = JSON.parse($('#stripe_payment_intent_next_action_redirect_url').html())
|
||||
pretixstripe.handlePaymentRedirectAction(payment_intent_next_action_redirect_url)
|
||||
} else if ($.trim($('#stripe_payment_intent_action_type').html()) === 'promptpay_display_qr_code') {
|
||||
waitingDialog.hide()
|
||||
} else if ($.trim($('#stripe_payment_intent_action_type').html()) === 'wechat_pay_display_qr_code') {
|
||||
let payment_intent_client_secret = $.trim($('#stripe_payment_intent_client_secret').html())
|
||||
pretixstripe.handleWechatAction(payment_intent_client_secret)
|
||||
} else if ($.trim($('#stripe_payment_intent_action_type').html()) === 'alipay_handle_redirect') {
|
||||
let payment_intent_client_secret = $.trim($('#stripe_payment_intent_client_secret').html())
|
||||
pretixstripe.handleAlipayAction(payment_intent_client_secret)
|
||||
} else if ($('#stripe_payment_intent_client_secret').length) {
|
||||
let payment_intent_client_secret = $.trim($('#stripe_payment_intent_client_secret').html())
|
||||
pretixstripe.handleCardAction(payment_intent_client_secret)
|
||||
}
|
||||
|
||||
$(window).on("message onmessage", function (e) {
|
||||
if (typeof e.originalEvent.data === "string" && e.originalEvent.data.startsWith('3DS-authentication-complete.')) {
|
||||
waitingDialog.show(gettext("Confirming your payment …"));
|
||||
$('#scacontainer').hide();
|
||||
$('#continuebutton').removeClass('hidden');
|
||||
$(window).on('message onmessage', function (e) {
|
||||
if (typeof e.originalEvent.data === 'string' && e.originalEvent.data.startsWith('3DS-authentication-complete.')) {
|
||||
waitingDialog.show(gettext('Confirming your payment …'))
|
||||
$('#scacontainer').hide()
|
||||
$('#continuebutton').removeClass('hidden')
|
||||
|
||||
if (e.originalEvent.data.split('.')[1] == 'p') {
|
||||
window.location.href = $('#continuebutton').attr('href') + '?paid=yes';
|
||||
} else {
|
||||
window.location.href = $('#continuebutton').attr('href');
|
||||
}
|
||||
}
|
||||
});
|
||||
if (e.originalEvent.data.split('.')[1] == 'p') {
|
||||
window.location.href = $('#continuebutton').attr('href') + '?paid=yes'
|
||||
} else {
|
||||
window.location.href = $('#continuebutton').attr('href')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!$(".stripe-container").length)
|
||||
return;
|
||||
if (!$('.stripe-container').length)
|
||||
return
|
||||
|
||||
if (
|
||||
$("input[name=payment][value=stripe]").is(':checked')
|
||||
|| $("input[name=payment][value=stripe_sepa_debit]").is(':checked')
|
||||
|| $("input[name=payment][value=stripe_affirm]").is(':checked')
|
||||
|| $("input[name=payment][value=stripe_klarna]").is(':checked')
|
||||
|| $(".payment-redo-form").length) {
|
||||
pretixstripe.load();
|
||||
} else {
|
||||
$("input[name=payment]").change(function () {
|
||||
if (['stripe', 'stripe_sepa_debit', 'stripe_affirm', 'stripe_klarna'].indexOf($(this).val()) > -1) {
|
||||
pretixstripe.load();
|
||||
}
|
||||
})
|
||||
}
|
||||
if (
|
||||
$('input[name=payment][value=stripe]').is(':checked')
|
||||
|| $('input[name=payment][value=stripe_sepa_debit]').is(':checked')
|
||||
|| $('input[name=payment][value=stripe_affirm]').is(':checked')
|
||||
|| $('input[name=payment][value=stripe_klarna]').is(':checked')
|
||||
|| $('.payment-redo-form').length) {
|
||||
pretixstripe.load()
|
||||
} else {
|
||||
$('input[name=payment]').change(function () {
|
||||
if (['stripe', 'stripe_sepa_debit', 'stripe_affirm', 'stripe_klarna'].indexOf($(this).val()) > -1) {
|
||||
pretixstripe.load()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
$("#stripe_other_card").click(
|
||||
function (e) {
|
||||
$("#stripe_card_payment_method_id").val("");
|
||||
$("#stripe-current-card").slideUp();
|
||||
$("#stripe-card-elements").slideDown();
|
||||
$('#stripe_other_card').click(
|
||||
function (e) {
|
||||
$('#stripe_card_payment_method_id').val('')
|
||||
$('#stripe-current-card').slideUp()
|
||||
$('#stripe-card-elements').slideDown()
|
||||
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
);
|
||||
e.preventDefault()
|
||||
return false
|
||||
}
|
||||
)
|
||||
|
||||
if ($("#stripe-current-card").length) {
|
||||
$("#stripe-card-elements").hide();
|
||||
}
|
||||
if ($('#stripe-current-card').length) {
|
||||
$('#stripe-card-elements').hide()
|
||||
}
|
||||
|
||||
$("#stripe_other_account").click(
|
||||
function (e) {
|
||||
$("#stripe_sepa_debit_payment_method_id").val("");
|
||||
$("#stripe-current-account").slideUp();
|
||||
// We're using a css-selector here instead of the id-selector,
|
||||
// as we're hiding Stripe Elements *and* Django form fields
|
||||
$('.stripe-sepa_debit-form').slideDown();
|
||||
$('#stripe_other_account').click(
|
||||
function (e) {
|
||||
$('#stripe_sepa_debit_payment_method_id').val('')
|
||||
$('#stripe-current-account').slideUp()
|
||||
// We're using a css-selector here instead of the id-selector,
|
||||
// as we're hiding Stripe Elements *and* Django form fields
|
||||
$('.stripe-sepa_debit-form').slideDown()
|
||||
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
);
|
||||
e.preventDefault()
|
||||
return false
|
||||
}
|
||||
)
|
||||
|
||||
if ($("#stripe-current-account").length) {
|
||||
// We're using a css-selector here instead of the id-selector,
|
||||
// as we're hiding Stripe Elements *and* Django form fields
|
||||
$('.stripe-sepa_debit-form').hide();
|
||||
}
|
||||
if ($('#stripe-current-account').length) {
|
||||
// We're using a css-selector here instead of the id-selector,
|
||||
// as we're hiding Stripe Elements *and* Django form fields
|
||||
$('.stripe-sepa_debit-form').hide()
|
||||
}
|
||||
|
||||
$('.stripe-container').closest("form").submit(
|
||||
function () {
|
||||
if ($("input[name=card_new]").length && !$("input[name=card_new]").prop('checked')) {
|
||||
return null;
|
||||
}
|
||||
if (($("input[name=payment][value=stripe]").prop('checked') || $("input[name=payment][type=radio]").length === 0)
|
||||
&& $("#stripe_card_payment_method_id").val() == "") {
|
||||
pretixstripe.pm_request('card', pretixstripe.card);
|
||||
return false;
|
||||
}
|
||||
$('.stripe-container').closest('form').submit(
|
||||
function () {
|
||||
if ($('input[name=card_new]').length && !$('input[name=card_new]').prop('checked')) {
|
||||
return null
|
||||
}
|
||||
if (($('input[name=payment][value=stripe]').prop('checked') || $('input[name=payment][type=radio]').length === 0)
|
||||
&& $('#stripe_card_payment_method_id').val() == '') {
|
||||
pretixstripe.pm_request('card', pretixstripe.card)
|
||||
return false
|
||||
}
|
||||
|
||||
if (($("input[name=payment][value=stripe_sepa_debit]").prop('checked')) && $("#stripe_sepa_debit_payment_method_id").val() == "") {
|
||||
pretixstripe.pm_request('sepa_debit', pretixstripe.sepa, {
|
||||
billing_details: {
|
||||
name: $("#id_payment_stripe_sepa_debit-accountname").val(),
|
||||
email: $("#stripe_sepa_debit_email").val(),
|
||||
address: {
|
||||
line1: $("#id_payment_stripe_sepa_debit-line1").val(),
|
||||
postal_code: $("#id_payment_stripe_sepa_debit-postal_code").val(),
|
||||
city: $("#id_payment_stripe_sepa_debit-city").val(),
|
||||
country: $("#id_payment_stripe_sepa_debit-country").val(),
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
if (($('input[name=payment][value=stripe_sepa_debit]').prop('checked')) && $('#stripe_sepa_debit_payment_method_id').val() == '') {
|
||||
pretixstripe.pm_request('sepa_debit', pretixstripe.sepa, {
|
||||
billing_details: {
|
||||
name: $('#id_payment_stripe_sepa_debit-accountname').val(),
|
||||
email: $('#stripe_sepa_debit_email').val(),
|
||||
address: {
|
||||
line1: $('#id_payment_stripe_sepa_debit-line1').val(),
|
||||
postal_code: $('#id_payment_stripe_sepa_debit-postal_code').val(),
|
||||
city: $('#id_payment_stripe_sepa_debit-city').val(),
|
||||
country: $('#id_payment_stripe_sepa_debit-country').val(),
|
||||
}
|
||||
}
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -35,6 +35,7 @@ import copy
|
||||
import inspect
|
||||
import uuid
|
||||
from collections import defaultdict
|
||||
from datetime import time
|
||||
from decimal import Decimal
|
||||
|
||||
from django import forms
|
||||
@@ -52,6 +53,7 @@ from django.shortcuts import redirect
|
||||
from django.utils import translation
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.html import conditional_escape
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import (
|
||||
get_language, gettext_lazy as _, pgettext_lazy,
|
||||
)
|
||||
@@ -71,6 +73,7 @@ from pretix.base.services.cart import (
|
||||
from pretix.base.services.cross_selling import CrossSellingService
|
||||
from pretix.base.services.memberships import validate_memberships_in_order
|
||||
from pretix.base.services.orders import perform_order
|
||||
from pretix.base.services.payment import compute_payment_deadline
|
||||
from pretix.base.services.pricing import get_price
|
||||
from pretix.base.services.tasks import EventTask
|
||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||
@@ -840,6 +843,8 @@ class QuestionsStep(CartQuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
f.fields[fname].disabled = val['disabled']
|
||||
if 'validators' in val and fname in f.fields:
|
||||
f.fields[fname].validators += val['validators']
|
||||
if 'label' in val and fname in f.fields:
|
||||
f.fields[fname].label = val['label']
|
||||
|
||||
return f
|
||||
|
||||
@@ -868,6 +873,28 @@ class QuestionsStep(CartQuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
'attendee_name_parts': d
|
||||
})
|
||||
|
||||
wd = self.cart_session.get('widget_data', {})
|
||||
if wd.get('attendee-fix', '') == 'true':
|
||||
for k, v in wd.items():
|
||||
if v and k.startswith('attendee-name'):
|
||||
o.append({
|
||||
'attendee_name_parts': {
|
||||
'disabled': True,
|
||||
}
|
||||
})
|
||||
elif v and k.startswith('email'):
|
||||
o.append({
|
||||
'attendee_email': {
|
||||
'disabled': True,
|
||||
}
|
||||
})
|
||||
elif v and k.startswith('question-'):
|
||||
o.append({
|
||||
k[9:].upper(): {
|
||||
'disabled': True,
|
||||
}
|
||||
})
|
||||
|
||||
return o
|
||||
|
||||
@cached_property
|
||||
@@ -944,6 +971,8 @@ class QuestionsStep(CartQuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
f.fields[fname].disabled = val['disabled']
|
||||
if 'validators' in val and fname in f.fields:
|
||||
f.fields[fname].validators += val['validators']
|
||||
if 'label' in val and fname in f.fields:
|
||||
f.fields[fname].label = val['label']
|
||||
|
||||
return f
|
||||
|
||||
@@ -1340,6 +1369,11 @@ class PaymentStep(CartMixin, TemplateFlowStep):
|
||||
self.request = request
|
||||
self.request.pci_dss_payment_page = True
|
||||
|
||||
if "postpone" in request.POST and self._allow_postpone:
|
||||
self.cart_session['payments_postpone'] = True
|
||||
self.cart_session['payments'] = []
|
||||
return redirect_to_url(self.get_next_url(request))
|
||||
|
||||
if "remove_payment" in request.POST:
|
||||
self._remove_payment(request.POST["remove_payment"])
|
||||
return redirect_to_url(self.get_step_url(request))
|
||||
@@ -1428,20 +1462,41 @@ class PaymentStep(CartMixin, TemplateFlowStep):
|
||||
ctx['providers'] = self.provider_forms
|
||||
ctx['show_fees'] = any(p['fee'] for p in self.provider_forms)
|
||||
|
||||
if len(self.provider_forms) == 1:
|
||||
ctx['selected'] = self.provider_forms[0]['provider'].identifier
|
||||
elif 'payment' in self.request.POST:
|
||||
if 'payment' in self.request.POST:
|
||||
ctx['selected'] = self.request.POST['payment']
|
||||
elif self.cart_session.get('payments_postpone') and self._allow_postpone:
|
||||
ctx['selected'] = ''
|
||||
elif len(self.provider_forms) == 1:
|
||||
ctx['selected'] = self.provider_forms[0]['provider'].identifier
|
||||
elif self.single_use_payment:
|
||||
ctx['selected'] = self.single_use_payment['provider']
|
||||
else:
|
||||
ctx['selected'] = ''
|
||||
|
||||
ctx['allow_postpone'] = self._allow_postpone
|
||||
if self._allow_postpone:
|
||||
now_dt = now()
|
||||
ctx['payment_deadline'] = compute_payment_deadline(
|
||||
event=self.request.event,
|
||||
sales_channel=self.request.sales_channel,
|
||||
subevents={p.subevent for p in ctx['cart']['raw']},
|
||||
now_dt=now_dt,
|
||||
)
|
||||
if ctx['payment_deadline'].time() != time(hour=23, minute=59, second=59):
|
||||
ctx['payment_deadline_minutes'] = int((ctx['payment_deadline'] - now_dt).total_seconds() // 60)
|
||||
return ctx
|
||||
|
||||
@cached_property
|
||||
def _allow_postpone(self):
|
||||
return self.request.sales_channel.identifier in self.request.event.settings.payment_choice_postpone_allowed_channels
|
||||
|
||||
def _is_allowed(self, prov, request):
|
||||
return prov.is_allowed(request, total=self._total_order_value)
|
||||
|
||||
def is_completed(self, request, warn=False):
|
||||
if self.cart_session.get('payments_postpone') and self._allow_postpone:
|
||||
return True
|
||||
|
||||
if not self.cart_session.get('payments'):
|
||||
if warn:
|
||||
messages.error(request, _('Please select a payment method to proceed.'))
|
||||
|
||||
@@ -233,7 +233,7 @@ Arguments: ``request``, ``order``
|
||||
This signal allows you to override fields of the contact form that is presented during checkout
|
||||
and by default only asks for the email address. It is also being used for the invoice address
|
||||
form. You are supposed to return a dictionary of dictionaries with globally unique keys. The
|
||||
value-dictionary should contain one or more of the following keys: ``initial``, ``disabled``,
|
||||
value-dictionary should contain one or more of the following keys: ``label``, ``initial``, ``disabled``,
|
||||
``validators``. The key of the dictionary should be the name of the form field.
|
||||
|
||||
As with all event plugin signals, the ``sender`` keyword argument will contain the event. A ``request``
|
||||
@@ -264,9 +264,9 @@ Arguments: ``position``, ``request``
|
||||
This signal allows you to override fields of the questions form that is presented during checkout
|
||||
and by default only asks for the questions configured in the backend. You are supposed to return a
|
||||
dictionary of dictionaries with globally unique keys. The value-dictionary should contain one or
|
||||
more of the following keys: ``initial``, ``disabled``, ``validators``. The key of the dictionary
|
||||
should be the form field name for system fields (e.g. ``company``), or the question's ``identifier``
|
||||
for user-defined questions.
|
||||
more of the following keys: ``label``, ``initial``, ``disabled``, ``validators``. The key of the
|
||||
dictionary should be the form field name for system fields (e.g. ``company``), or the question's
|
||||
``identifier`` for user-defined questions.
|
||||
|
||||
The ``position`` keyword argument will contain a ``CartPosition`` or ``OrderPosition`` object.
|
||||
|
||||
|
||||
@@ -128,6 +128,35 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if allow_postpone %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body row">
|
||||
<div class="col-md-9 col-xs-12">
|
||||
{% trans "Not sure yet? You can complete your order first and then select a payment method later." %}
|
||||
<br>
|
||||
<span class="text-muted">
|
||||
{% if current_payments %}
|
||||
{% trans "To do so, please first remove the payment methods you already selected above." %}
|
||||
{% elif payment_deadline_minutes %}
|
||||
{% blocktrans trimmed with minutes=payment_deadline_minutes %}
|
||||
Your payment needs to be completed within {{ minutes }} minutes.
|
||||
{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans trimmed with deadline=payment_deadline|date:"SHORT_DATE_FORMAT" %}
|
||||
Your payment needs to be completed by {{ deadline }}.
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-md-3 col-xs-12 text-right flip">
|
||||
<button name="postpone" value="on" class="btn btn-primary"
|
||||
{% if current_payments %}disabled{% endif %}>
|
||||
{% trans "Proceed without selection" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="row checkout-button-row">
|
||||
<div class="col-md-4 col-sm-6">
|
||||
<a class="btn btn-block btn-default btn-lg"
|
||||
|
||||
@@ -23,7 +23,11 @@
|
||||
{% endblocktrans %} ::
|
||||
{% endif %}
|
||||
{% elif subevent %}
|
||||
{{ subevent.get_date_range_display }} ::
|
||||
{% if subevent.name|upper != event.name|upper %}
|
||||
{# The |upper is a trick to force LazyI18nString→str conversion before comparison #}
|
||||
{{ subevent.name }} ::
|
||||
{% endif %}
|
||||
{{ subevent.get_date_range_display_with_times }} ::
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ from pretix.base.models import Customer, InvoiceAddress, Order, OrderPosition
|
||||
from pretix.base.services.mail import mail
|
||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||
from pretix.base.signals import customer_created, customer_signed_in
|
||||
from pretix.helpers import OF_SELF
|
||||
from pretix.helpers.compat import CompatDeleteView
|
||||
from pretix.helpers.http import redirect_to_url
|
||||
from pretix.multidomain.models import KnownDomain
|
||||
@@ -280,6 +281,11 @@ class SetPasswordView(FormView):
|
||||
|
||||
def form_valid(self, form):
|
||||
with transaction.atomic():
|
||||
# Re-check token in transaction to prevent race condition
|
||||
self.customer = Customer.objects.select_for_update(of=OF_SELF).get(pk=self.customer.pk)
|
||||
if not TokenGenerator().check_token(self.customer, self.request.GET.get('token', '')):
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
self.customer.set_password(form.cleaned_data['password'])
|
||||
self.customer.is_verified = True
|
||||
self.customer.save()
|
||||
|
||||
@@ -79,7 +79,7 @@ from pretix.presale.signals import seatingframe_html_head
|
||||
from pretix.presale.views.organizer import (
|
||||
EventListMixin, add_subevents_for_days, days_for_template,
|
||||
filter_qs_by_attr, filter_subevents_with_plugins, has_before_after,
|
||||
weeks_for_template,
|
||||
should_hide_subevent, weeks_for_template,
|
||||
)
|
||||
|
||||
from . import (
|
||||
@@ -443,12 +443,10 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
|
||||
)
|
||||
)
|
||||
subevents = filter_subevents_with_plugins(list(subevents), self.request.sales_channel)
|
||||
context['subevent_list'] = subevents
|
||||
if self.request.event.settings.event_list_available_only and not voucher:
|
||||
context['subevent_list'] = [
|
||||
se for se in subevents
|
||||
if not se.presale_has_ended and (se.best_availability_state is None or se.best_availability_state >= Quota.AVAILABILITY_RESERVED)
|
||||
]
|
||||
context['subevent_list'] = [
|
||||
se for se in subevents
|
||||
if not should_hide_subevent(self.request.event.settings, se, voucher)
|
||||
]
|
||||
context['visible_events'] = len(subevents) > 0
|
||||
return context
|
||||
|
||||
|
||||
@@ -601,6 +601,32 @@ def filter_subevents_with_plugins(subevents, sales_channel=None):
|
||||
return subevents
|
||||
|
||||
|
||||
def should_hide_subevent(settings, subevent, voucher=None):
|
||||
hide = False
|
||||
if settings.event_list_available_only:
|
||||
hide = (
|
||||
# Presale is over → the subevent is not available → hide
|
||||
subevent.presale_has_ended or
|
||||
# Not a single product is available on this sales channel → hide
|
||||
# Note that means there could be products which are ignored for calendar availability (Quota.ignore_for_event_availability)
|
||||
# or products only visible with a voucher. However, for customers with these scenarios, the event_list_available_only
|
||||
# makes only very little sense as it would never do anything, so the flag can just be removed -- or the products should
|
||||
# be made visible so people know why there are no products. In case a voucher is already entered on the calendar view,
|
||||
# this is already respected and subevents are shown correctly.
|
||||
subevent.best_availability_state is None or
|
||||
(
|
||||
# Sold out → hide, unless we have a voucher active that can bypass all quotas
|
||||
(not voucher or not voucher.allow_ignore_quota) and
|
||||
subevent.best_availability_state < Quota.AVAILABILITY_RESERVED
|
||||
)
|
||||
)
|
||||
|
||||
if settings.event_calendar_future_only:
|
||||
if (subevent.date_to or subevent.date_from) < time_machine_now():
|
||||
hide = True
|
||||
return hide
|
||||
|
||||
|
||||
def add_subevents_for_days(qs, before, after, ebd, timezones, sales_channel, event=None, cart_namespace=None,
|
||||
voucher=None):
|
||||
qs = qs.filter(active=True, is_public=True).filter(
|
||||
@@ -640,19 +666,8 @@ def add_subevents_for_days(qs, before, after, ebd, timezones, sales_channel, eve
|
||||
kwargs['cart_namespace'] = cart_namespace
|
||||
|
||||
s = event.settings if event else se.event.settings
|
||||
|
||||
if s.event_list_available_only:
|
||||
hide = se.presale_has_ended or (
|
||||
(not voucher or not voucher.allow_ignore_quota) and
|
||||
se.best_availability_state is not None and
|
||||
se.best_availability_state < Quota.AVAILABILITY_RESERVED
|
||||
)
|
||||
if hide:
|
||||
continue
|
||||
|
||||
if s.event_calendar_future_only:
|
||||
if (se.date_to or se.date_from) < time_machine_now():
|
||||
continue
|
||||
if should_hide_subevent(s, se, voucher):
|
||||
continue
|
||||
|
||||
timezones.add(s.timezone)
|
||||
tz = ZoneInfo(s.timezone)
|
||||
|
||||
@@ -75,7 +75,7 @@ from pretix.presale.views.cart import get_or_create_cart_id
|
||||
from pretix.presale.views.organizer import (
|
||||
EventListMixin, add_events_for_days, add_subevents_for_days,
|
||||
days_for_template, filter_qs_by_attr, filter_subevents_with_plugins,
|
||||
weeks_for_template,
|
||||
should_hide_subevent, weeks_for_template,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -122,19 +122,20 @@ def widget_css_etag(request, version, **kwargs):
|
||||
return f'{_get_source_cache_key(version)}-{request.organizer.cache.get_or_set("css_version", default=lambda: int(time.time()))}'
|
||||
|
||||
|
||||
# use vite by default, serve old vue2-based widget only for widget_vue2_origins
|
||||
def _use_vite(request):
|
||||
if getattr(settings, 'PRETIX_WIDGET_VITE', False) or "beta" in request.GET:
|
||||
return True
|
||||
if getattr(settings, 'PRETIX_WIDGET_VUE', False) or "legacy" in request.GET:
|
||||
return False
|
||||
origin = request.META.get('HTTP_ORIGIN', '')
|
||||
gs = GlobalSettingsObject()
|
||||
vite_origins = gs.settings.get('widget_vite_origins', as_type=str, default='')
|
||||
if vite_origins and not origin:
|
||||
vue_origins = gs.settings.get('widget_vue2_origins', as_type=str, default='')
|
||||
if vue_origins and not origin:
|
||||
referer = request.META.get('HTTP_REFERER', '')
|
||||
origin = '/'.join(referer.split('/', 3)[:3])
|
||||
if origin and vite_origins:
|
||||
origins_list = [o.strip() for o in vite_origins.strip().splitlines() if o.strip()]
|
||||
return origin in origins_list
|
||||
return False
|
||||
if origin and vue_origins:
|
||||
origins_list = [o.strip() for o in vue_origins.strip().splitlines() if o.strip()]
|
||||
return origin not in origins_list
|
||||
return True
|
||||
|
||||
|
||||
def widget_js_etag(request, version, lang, **kwargs):
|
||||
@@ -757,14 +758,10 @@ class WidgetAPIProductList(EventListMixin, View):
|
||||
evs = evs[:limit]
|
||||
|
||||
tz = request.event.timezone
|
||||
if self.request.event.settings.event_list_available_only:
|
||||
evs = [
|
||||
se for se in evs
|
||||
if not se.presale_has_ended and (
|
||||
se.best_availability_state is not None and
|
||||
se.best_availability_state >= Quota.AVAILABILITY_RESERVED
|
||||
)
|
||||
]
|
||||
evs = [
|
||||
se for se in evs
|
||||
if not should_hide_subevent(self.request.event.settings, se)
|
||||
]
|
||||
|
||||
data['events'] = [
|
||||
{
|
||||
|
||||
@@ -913,7 +913,7 @@ VITE_DEV_SERVER_PORT = 5173
|
||||
VITE_DEV_SERVER = f"http://localhost:{VITE_DEV_SERVER_PORT}"
|
||||
VITE_DEV_MODE = DEBUG
|
||||
VITE_IGNORE = False # Used to ignore `collectstatic`/`rebuild`
|
||||
PRETIX_WIDGET_VITE = os.environ.get('PRETIX_WIDGET_VITE', '') not in ('', '0')
|
||||
PRETIX_WIDGET_VUE = os.environ.get('PRETIX_WIDGET_VUE', '') not in ('', '0')
|
||||
|
||||
if DEBUG:
|
||||
# Reload if settings file changes
|
||||
|
||||
@@ -1,195 +1,193 @@
|
||||
$(function () {
|
||||
"use strict";
|
||||
'use strict'
|
||||
|
||||
// Responses are expected to only depend on the GET parameters passed, so we can have a little client-side cache
|
||||
// to prevent fetching the same thing many times.
|
||||
var responseCache = {};
|
||||
// Responses are expected to only depend on the GET parameters passed, so we can have a little client-side cache
|
||||
// to prevent fetching the same thing many times.
|
||||
let responseCache = {}
|
||||
|
||||
const cleanName = (name) => {
|
||||
// Remove form prefix
|
||||
name = name.split("-").pop();
|
||||
// Remove settings prefix
|
||||
name = name.replace(/^invoice_address_from_/, "");
|
||||
return name
|
||||
}
|
||||
const cleanName = (name) => {
|
||||
// Remove form prefix
|
||||
name = name.split('-').pop()
|
||||
// Remove settings prefix
|
||||
name = name.replace(/^invoice_address_from_/, '')
|
||||
return name
|
||||
}
|
||||
|
||||
$("[data-address-information-url]").each(function () {
|
||||
let xhr;
|
||||
const form = $(this);
|
||||
const dependencies = $(this).find("[data-trigger-address-info]");
|
||||
const loader = $("<span class='fa fa-cog fa-spin'></span>").hide().prependTo(dependencies.closest(".form-group").find("label").first())
|
||||
const baseUrl = this.getAttribute('data-address-information-url')
|
||||
const isAnyRequired = dependencies.toArray().some(function (e) { return $(e).closest(".form-group").is(".required") });
|
||||
$('[data-address-information-url]').each(function () {
|
||||
let xhr
|
||||
const form = $(this)
|
||||
const dependencies = $(this).find('[data-trigger-address-info]')
|
||||
const loader = $('<span class=\'fa fa-cog fa-spin\'></span>').hide().prependTo(dependencies.closest('.form-group').find('label').first())
|
||||
const baseUrl = this.getAttribute('data-address-information-url')
|
||||
const isAnyRequired = dependencies.toArray().some(function (e) { return $(e).closest('.form-group').is('.required') })
|
||||
|
||||
const dependents = {
|
||||
'city': form.find("input[name$=city]"),
|
||||
'zipcode': form.find("input[name$=zipcode]"),
|
||||
'street': form.find("textarea[name$=street]"),
|
||||
'state': form.find("select[name$=state]"),
|
||||
'vat_id': form.find("input[name$=vat_id]"),
|
||||
};
|
||||
const dependents = {
|
||||
city: form.find('input[name$=city]'),
|
||||
zipcode: form.find('input[name$=zipcode]'),
|
||||
street: form.find('textarea[name$=street]'),
|
||||
state: form.find('select[name$=state]'),
|
||||
vat_id: form.find('input[name$=vat_id]'),
|
||||
}
|
||||
|
||||
form.find("select[name*=transmission_], textarea[name*=transmission_], input[name*=transmission_]").each(function () {
|
||||
dependents[cleanName($(this).attr("name"))] = $(this)
|
||||
})
|
||||
form.find('select[name*=transmission_], textarea[name*=transmission_], input[name*=transmission_]').each(function () {
|
||||
dependents[cleanName($(this).attr('name'))] = $(this)
|
||||
})
|
||||
|
||||
const dependentsDisabled = [];
|
||||
for (var k in dependents) {
|
||||
if (dependents[k].prop("disabled")) {
|
||||
dependentsDisabled.push(k);
|
||||
}
|
||||
}
|
||||
const dependentsDisabled = []
|
||||
for (let k in dependents) {
|
||||
if (dependents[k].prop('disabled')) {
|
||||
dependentsDisabled.push(k)
|
||||
}
|
||||
}
|
||||
|
||||
if (!Object.values(dependents).some((el) => el.length)) {
|
||||
// No address fields found, do not create request
|
||||
return;
|
||||
}
|
||||
if (!Object.values(dependents).some((el) => el.length)) {
|
||||
// No address fields found, do not create request
|
||||
return
|
||||
}
|
||||
|
||||
const update_form = function (data) {
|
||||
var selected_state = dependents.state.prop("data-selected-value");
|
||||
if (selected_state) dependents.state.prop("data-selected-value", "");
|
||||
dependents.state.find("option:not([value=''])").remove();
|
||||
$.each(data.data, function (k, s) {
|
||||
var o = $("<option>").attr("value", s.code).text(s.name);
|
||||
if (selected_state === s.code) o.prop("selected", true);
|
||||
dependents.state.append(o);
|
||||
});
|
||||
const update_form = function (data) {
|
||||
let selected_state = dependents.state.prop('data-selected-value')
|
||||
if (selected_state) dependents.state.prop('data-selected-value', '')
|
||||
dependents.state.find('option:not([value=\'\'])').remove()
|
||||
$.each(data.data, function (k, s) {
|
||||
let o = $('<option>').attr('value', s.code).text(s.name)
|
||||
if (selected_state === s.code) o.prop('selected', true)
|
||||
dependents.state.append(o)
|
||||
})
|
||||
|
||||
if (dependents.transmission_type) {
|
||||
var selected_transmission_type = dependents.transmission_type.prop("data-selected-value");
|
||||
if (selected_transmission_type) dependents.transmission_type.prop("data-selected-value", "");
|
||||
dependents.transmission_type.find("option:not([value='']):not([value='-'])").remove();
|
||||
if (dependents.transmission_type) {
|
||||
let selected_transmission_type = dependents.transmission_type.prop('data-selected-value')
|
||||
if (selected_transmission_type) dependents.transmission_type.prop('data-selected-value', '')
|
||||
dependents.transmission_type.find('option:not([value=\'\']):not([value=\'-\'])').remove()
|
||||
|
||||
if (!data.transmission_type.visible) {
|
||||
selected_transmission_type = "email";
|
||||
}
|
||||
if (!data.transmission_type.visible) {
|
||||
selected_transmission_type = 'email'
|
||||
}
|
||||
|
||||
$.each(data.transmission_types, function (k, s) {
|
||||
var o = $("<option>").attr("value", s.code).text(s.name);
|
||||
if (selected_transmission_type === s.code) {
|
||||
o.prop("selected", true);
|
||||
}
|
||||
dependents.transmission_type.append(o);
|
||||
});
|
||||
$.each(data.transmission_types, function (k, s) {
|
||||
let o = $('<option>').attr('value', s.code).text(s.name)
|
||||
if (selected_transmission_type === s.code) {
|
||||
o.prop('selected', true)
|
||||
}
|
||||
dependents.transmission_type.append(o)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
for (var k in dependents) {
|
||||
const options = data[k],
|
||||
dependent = dependents[k]
|
||||
let visible = 'visible' in options ? options.visible : true
|
||||
|
||||
for (var k in dependents) {
|
||||
const options = data[k],
|
||||
dependent = dependents[k];
|
||||
let visible = 'visible' in options ? options.visible : true;
|
||||
if (dependent.is('[data-display-dependency]')) {
|
||||
const dependency = $(dependent.attr('data-display-dependency'))
|
||||
visible = visible && (
|
||||
(dependency.attr('type') === 'checkbox' || dependency.attr('type') === 'radio') ? dependency.prop('checked') : !!dependency.val()
|
||||
)
|
||||
}
|
||||
|
||||
if (dependent.is("[data-display-dependency]")) {
|
||||
const dependency = $(dependent.attr("data-display-dependency"));
|
||||
visible = visible && (
|
||||
(dependency.attr("type") === 'checkbox' || dependency.attr("type") === 'radio') ? dependency.prop('checked') : !!dependency.val()
|
||||
);
|
||||
}
|
||||
if ('label' in options) {
|
||||
dependent.closest('.form-group').find('.control-label').text(options.label)
|
||||
}
|
||||
if ('helptext_visible' in options) {
|
||||
dependent.closest('.form-group').find('.help-block').toggle(options.helptext_visible)
|
||||
}
|
||||
|
||||
if ('label' in options) {
|
||||
dependent.closest(".form-group").find(".control-label").text(options.label);
|
||||
}
|
||||
if ('helptext_visible' in options) {
|
||||
dependent.closest(".form-group").find(".help-block").toggle(options.helptext_visible);
|
||||
}
|
||||
const required = 'required' in options && visible && (
|
||||
(options.required === 'if_any' && isAnyRequired)
|
||||
|| (options.required === true)
|
||||
)
|
||||
dependent.closest('.form-group').toggle(visible).toggleClass('required', required)
|
||||
dependent.prop('required', required)
|
||||
|
||||
const required = 'required' in options && visible && (
|
||||
(options.required === 'if_any' && isAnyRequired) ||
|
||||
(options.required === true)
|
||||
);
|
||||
dependent.closest(".form-group").toggle(visible).toggleClass('required', required);
|
||||
dependent.prop("required", required);
|
||||
const label = dependent.closest('.form-group').find('label')
|
||||
const labelRequired = label.find('.label-required')
|
||||
if (!required) {
|
||||
labelRequired.remove()
|
||||
} else if (!labelRequired.length) {
|
||||
label.append('<i class="label-required">' + gettext('required') + '</i>')
|
||||
}
|
||||
}
|
||||
for (var k in dependents) dependents[k].prop('disabled', dependentsDisabled.includes(k))
|
||||
loader.hide()
|
||||
}
|
||||
|
||||
const label = dependent.closest(".form-group").find("label");
|
||||
const labelRequired = label.find(".label-required");
|
||||
if (!required) {
|
||||
labelRequired.remove();
|
||||
} else if (!labelRequired.length) {
|
||||
label.append('<i class="label-required">' + gettext('required') + '</i>')
|
||||
}
|
||||
}
|
||||
for (var k in dependents) dependents[k].prop("disabled", dependentsDisabled.includes(k));
|
||||
loader.hide();
|
||||
}
|
||||
const update = function (ev) {
|
||||
dependents.state.prop('data-selected-value', dependents.state.val())
|
||||
if (dependents.transmission_type) {
|
||||
dependents.transmission_type.prop('data-selected-value', dependents.transmission_type.val())
|
||||
}
|
||||
|
||||
const update = function (ev) {
|
||||
dependents.state.prop("data-selected-value", dependents.state.val());
|
||||
if (dependents.transmission_type) {
|
||||
dependents.transmission_type.prop("data-selected-value", dependents.transmission_type.val());
|
||||
}
|
||||
for (let k in dependents) dependents[k].prop('disabled', true)
|
||||
loader.show()
|
||||
let url = new URL(baseUrl, location.href)
|
||||
// Address depends on all annotated fields
|
||||
form.find('[data-trigger-address-info]').each(function () {
|
||||
// Remove prefix of the form to get actual field name
|
||||
if (($(this).attr('type') === 'radio' || $(this).attr('type') === 'checkbox') && !$(this).prop('checked')) {
|
||||
return
|
||||
}
|
||||
url.searchParams.append(cleanName($(this).attr('name')), $(this).val())
|
||||
})
|
||||
if (dependents.transmission_type) {
|
||||
url.searchParams.append('transmission_type_required', !dependents.transmission_type.find('option[value=\'-\']').length)
|
||||
}
|
||||
|
||||
for (var k in dependents) dependents[k].prop("disabled", true);
|
||||
loader.show();
|
||||
var url = new URL(baseUrl, location.href);
|
||||
// Address depends on all annotated fields
|
||||
form.find("[data-trigger-address-info]").each(function () {
|
||||
// Remove prefix of the form to get actual field name
|
||||
if (($(this).attr("type") === "radio" || $(this).attr("type") === "checkbox") && !$(this).prop("checked")) {
|
||||
return
|
||||
}
|
||||
url.searchParams.append(cleanName($(this).attr("name")), $(this).val());
|
||||
})
|
||||
if (dependents.transmission_type) {
|
||||
url.searchParams.append("transmission_type_required", !dependents.transmission_type.find("option[value='-']").length);
|
||||
}
|
||||
if (xhr && url in responseCache) {
|
||||
if (responseCache[url] == xhr) {
|
||||
// already requested this, but XHR is still running and will resolve promise
|
||||
// only re-resolve promise for JSON-data in responseCache[url]
|
||||
return
|
||||
} else {
|
||||
// abort current xhr as it is not the one we want
|
||||
// aborting deletes responseCache[url] but async
|
||||
xhr.abort()
|
||||
}
|
||||
}
|
||||
|
||||
if (xhr && url in responseCache) {
|
||||
if (responseCache[url] == xhr) {
|
||||
// already requested this, but XHR is still running and will resolve promise
|
||||
// only re-resolve promise for JSON-data in responseCache[url]
|
||||
return;
|
||||
} else {
|
||||
// abort current xhr as it is not the one we want
|
||||
// aborting deletes responseCache[url] but async
|
||||
xhr.abort();
|
||||
}
|
||||
}
|
||||
if (!(url in responseCache)) {
|
||||
responseCache[url] = xhr = $.ajax({
|
||||
dataType: 'json',
|
||||
url: url,
|
||||
timeout: 3000,
|
||||
})
|
||||
}
|
||||
|
||||
if (!(url in responseCache)) {
|
||||
responseCache[url] = xhr = $.ajax({
|
||||
dataType: "json",
|
||||
url: url,
|
||||
timeout: 3000,
|
||||
});
|
||||
}
|
||||
Promise.resolve(responseCache[url]).then(function (data) {
|
||||
responseCache[url] = data
|
||||
update_form(data)
|
||||
}).catch(function () {
|
||||
delete responseCache[url]
|
||||
// In case of errors, show everything and require nothing, we can still handle errors in backend
|
||||
for (let k in dependents) {
|
||||
const dependent = dependents[k],
|
||||
visible = true,
|
||||
required = false
|
||||
|
||||
Promise.resolve(responseCache[url]).then(function (data) {
|
||||
responseCache[url] = data;
|
||||
update_form(data);
|
||||
}).catch(function () {
|
||||
delete responseCache[url];
|
||||
// In case of errors, show everything and require nothing, we can still handle errors in backend
|
||||
for (var k in dependents) {
|
||||
const dependent = dependents[k],
|
||||
visible = true,
|
||||
required = false;
|
||||
dependent.closest('.form-group').toggle(visible).toggleClass('required', required)
|
||||
dependent.prop('required', required).prop('disabled', dependentsDisabled.includes(k))
|
||||
}
|
||||
}).finally(function () {
|
||||
loader.hide()
|
||||
})
|
||||
}
|
||||
update()
|
||||
dependencies.on('change', update)
|
||||
|
||||
dependent.closest(".form-group").toggle(visible).toggleClass('required', required);
|
||||
dependent.prop("required", required).prop("disabled", dependentsDisabled.includes(k));
|
||||
}
|
||||
}).finally(function () {
|
||||
loader.hide();
|
||||
});
|
||||
};
|
||||
update();
|
||||
dependencies.on("change", update);
|
||||
|
||||
if (dependents.vat_id && dependents.transmission_type && dependents.transmission_peppol_participant_id) {
|
||||
// In Belgium, the VAT ID is built from "BE" + the company ID. The Peppol ID also needs to be built
|
||||
// from the company ID with ID scheme 0208. We can save users some knowing and typing by filling this in!
|
||||
if (!dependents.transmission_peppol_participant_id.val()) {
|
||||
const fill_peppol_id = function () {
|
||||
const vatId = dependents.vat_id.val();
|
||||
if (vatId && vatId.startsWith("BE") && dependents.transmission_type.val() === "peppol") {
|
||||
dependents.transmission_peppol_participant_id.val("0208:" + vatId.substring(2).replaceAll(".", ""))
|
||||
}
|
||||
}
|
||||
dependents.vat_id.add(dependents.transmission_type).on("change", fill_peppol_id);
|
||||
dependents.transmission_peppol_participant_id.one("change", () => {
|
||||
dependents.vat_id.add(dependents.transmission_type).unbind("change", fill_peppol_id)
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
if (dependents.vat_id && dependents.transmission_type && dependents.transmission_peppol_participant_id) {
|
||||
// In Belgium, the VAT ID is built from "BE" + the company ID. The Peppol ID also needs to be built
|
||||
// from the company ID with ID scheme 0208. We can save users some knowing and typing by filling this in!
|
||||
if (!dependents.transmission_peppol_participant_id.val()) {
|
||||
const fill_peppol_id = function () {
|
||||
const vatId = dependents.vat_id.val()
|
||||
if (vatId && vatId.startsWith('BE') && dependents.transmission_type.val() === 'peppol') {
|
||||
dependents.transmission_peppol_participant_id.val('0208:' + vatId.substring(2).replaceAll('.', ''))
|
||||
}
|
||||
}
|
||||
dependents.vat_id.add(dependents.transmission_type).on('change', fill_peppol_id)
|
||||
dependents.transmission_peppol_participant_id.one('change', () => {
|
||||
dependents.vat_id.add(dependents.transmission_type).unbind('change', fill_peppol_id)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
var check = function () {
|
||||
$.getJSON(location.href + '&ajax=1', function (data, status) {
|
||||
if (data.redirect) {
|
||||
location.href = data.redirect;
|
||||
} else {
|
||||
window.setTimeout(check, 500);
|
||||
}
|
||||
});
|
||||
let check = function () {
|
||||
$.getJSON(location.href + '&ajax=1', function (data, _status) {
|
||||
if (data.redirect) {
|
||||
location.href = data.redirect
|
||||
} else {
|
||||
window.setTimeout(check, 500)
|
||||
}
|
||||
})
|
||||
}
|
||||
window.setTimeout(check, 500);
|
||||
window.setTimeout(check, 500)
|
||||
|
||||
@@ -1,359 +1,357 @@
|
||||
/*global $, gettext */
|
||||
var async_task_id = null;
|
||||
var async_task_timeout = null;
|
||||
var async_task_check_url = null;
|
||||
var async_task_old_url = null;
|
||||
var async_task_is_download = false;
|
||||
var async_task_is_long = false;
|
||||
var async_task_dont_redirect = false;
|
||||
/* global gettext */
|
||||
let async_task_id = null
|
||||
let async_task_timeout = null
|
||||
let async_task_check_url = null
|
||||
let async_task_old_url = null
|
||||
let async_task_is_download = false
|
||||
let async_task_is_long = false
|
||||
let async_task_dont_redirect = false
|
||||
|
||||
var async_task_status_messages = {
|
||||
// These are functions in order to be lazily evaluated after the gettext file is loaded
|
||||
long_task_started: () => gettext(
|
||||
'Your request is currently being processed. Depending on the size of your event, this might take up to ' +
|
||||
'a few minutes.'
|
||||
),
|
||||
long_task_pending: () => gettext(
|
||||
'Your request has been queued on the server and will soon be ' +
|
||||
'processed.'
|
||||
),
|
||||
short_task: () => gettext(
|
||||
'Your request arrived on the server but we still wait for it to be ' +
|
||||
'processed. If this takes longer than two minutes, please contact us or go ' +
|
||||
'back in your browser and try again.'
|
||||
)
|
||||
};
|
||||
|
||||
function async_task_schedule_check(context, timeout) {
|
||||
"use strict";
|
||||
async_task_timeout = window.setTimeout(function() {
|
||||
$.ajax(
|
||||
{
|
||||
'type': 'GET',
|
||||
'url': async_task_check_url,
|
||||
'success': async_task_check_callback,
|
||||
'error': async_task_check_error,
|
||||
'context': context,
|
||||
'dataType': 'json'
|
||||
}
|
||||
);
|
||||
}, timeout);
|
||||
let async_task_status_messages = {
|
||||
// These are functions in order to be lazily evaluated after the gettext file is loaded
|
||||
long_task_started: () => gettext(
|
||||
'Your request is currently being processed. Depending on the size of your event, this might take up to '
|
||||
+ 'a few minutes.'
|
||||
),
|
||||
long_task_pending: () => gettext(
|
||||
'Your request has been queued on the server and will soon be '
|
||||
+ 'processed.'
|
||||
),
|
||||
short_task: () => gettext(
|
||||
'Your request arrived on the server but we still wait for it to be '
|
||||
+ 'processed. If this takes longer than two minutes, please contact us or go '
|
||||
+ 'back in your browser and try again.'
|
||||
)
|
||||
}
|
||||
|
||||
function async_task_on_success(data) {
|
||||
"use strict";
|
||||
if ((async_task_is_download && data.success) || async_task_dont_redirect) {
|
||||
waitingDialog.hide();
|
||||
if (location.href.indexOf("async_id") !== -1) {
|
||||
history.replaceState({}, "pretix", async_task_old_url);
|
||||
}
|
||||
}
|
||||
if (!async_task_dont_redirect) {
|
||||
$(window).one("pageshow", function (e) {
|
||||
// hide waitingDialog when using browser's history back
|
||||
waitingDialog.hide();
|
||||
});
|
||||
if (async_task_is_download && window.self !== window.top) {
|
||||
// if in an iframe, force to download an async_task_is_download
|
||||
// e.g. pretix-reseller embeds order-page in iframe, which would cause ticket-PDFs to be displayed inline
|
||||
var a = document.createElement("a");
|
||||
a.href = data.redirect;
|
||||
a.download = "";
|
||||
a.target = "_blank";
|
||||
a.click();
|
||||
} else {
|
||||
location.href = data.redirect;
|
||||
}
|
||||
}
|
||||
$(this).trigger('pretix:async-task-success', data);
|
||||
function async_task_schedule_check (context, timeout) {
|
||||
'use strict'
|
||||
async_task_timeout = window.setTimeout(function () {
|
||||
$.ajax(
|
||||
{
|
||||
type: 'GET',
|
||||
url: async_task_check_url,
|
||||
success: async_task_check_callback,
|
||||
error: async_task_check_error,
|
||||
context: context,
|
||||
dataType: 'json'
|
||||
}
|
||||
)
|
||||
}, timeout)
|
||||
}
|
||||
|
||||
function async_task_check_callback(data, textStatus, jqXHR) {
|
||||
"use strict";
|
||||
if (data.ready && data.redirect) {
|
||||
async_task_on_success.call(this, data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof data.percentage === "number") {
|
||||
waitingDialog.setProgress(data.percentage);
|
||||
}
|
||||
if (typeof data.steps === "object" && Array.isArray(data.steps)) {
|
||||
waitingDialog.setSteps(data.steps);
|
||||
}
|
||||
async_task_schedule_check(this, 250);
|
||||
|
||||
async_task_update_status(data);
|
||||
function async_task_on_success (data) {
|
||||
'use strict'
|
||||
if ((async_task_is_download && data.success) || async_task_dont_redirect) {
|
||||
waitingDialog.hide()
|
||||
if (location.href.indexOf('async_id') !== -1) {
|
||||
history.replaceState({}, 'pretix', async_task_old_url)
|
||||
}
|
||||
}
|
||||
if (!async_task_dont_redirect) {
|
||||
$(window).one('pageshow', function (e) {
|
||||
// hide waitingDialog when using browser's history back
|
||||
waitingDialog.hide()
|
||||
})
|
||||
if (async_task_is_download && window.self !== window.top) {
|
||||
// if in an iframe, force to download an async_task_is_download
|
||||
// e.g. pretix-reseller embeds order-page in iframe, which would cause ticket-PDFs to be displayed inline
|
||||
let a = document.createElement('a')
|
||||
a.href = data.redirect
|
||||
a.download = ''
|
||||
a.target = '_blank'
|
||||
a.click()
|
||||
} else {
|
||||
location.href = data.redirect
|
||||
}
|
||||
}
|
||||
$(this).trigger('pretix:async-task-success', data)
|
||||
}
|
||||
|
||||
function async_task_update_status(data) {
|
||||
if (async_task_is_long) {
|
||||
if (data.started) {
|
||||
waitingDialog.setStatus(async_task_status_messages.long_task_started());
|
||||
} else {
|
||||
waitingDialog.setStatus(async_task_status_messages.long_task_pending());
|
||||
}
|
||||
} else {
|
||||
waitingDialog.setStatus(async_task_status_messages.short_task());
|
||||
}
|
||||
function async_task_check_callback (data, textStatus, jqXHR) {
|
||||
'use strict'
|
||||
if (data.ready && data.redirect) {
|
||||
async_task_on_success.call(this, data)
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof data.percentage === 'number') {
|
||||
waitingDialog.setProgress(data.percentage)
|
||||
}
|
||||
if (typeof data.steps === 'object' && Array.isArray(data.steps)) {
|
||||
waitingDialog.setSteps(data.steps)
|
||||
}
|
||||
async_task_schedule_check(this, 250)
|
||||
|
||||
async_task_update_status(data)
|
||||
}
|
||||
|
||||
function async_task_replace_page(target, new_html) {
|
||||
"use strict";
|
||||
waitingDialog.hide();
|
||||
$(target).html(new_html);
|
||||
setup_basics($(target));
|
||||
form_handlers($(target));
|
||||
setup_collapsible_details($(target));
|
||||
window.setTimeout(function () { $(window).scrollTop(0) }, 200)
|
||||
$(document).trigger("pretix:bind-forms");
|
||||
function async_task_update_status (data) {
|
||||
if (async_task_is_long) {
|
||||
if (data.started) {
|
||||
waitingDialog.setStatus(async_task_status_messages.long_task_started())
|
||||
} else {
|
||||
waitingDialog.setStatus(async_task_status_messages.long_task_pending())
|
||||
}
|
||||
} else {
|
||||
waitingDialog.setStatus(async_task_status_messages.short_task())
|
||||
}
|
||||
}
|
||||
|
||||
function async_task_check_error(jqXHR, textStatus, errorThrown) {
|
||||
"use strict";
|
||||
var respdom = $(jqXHR.responseText);
|
||||
var c = respdom.filter('.container');
|
||||
if (jqXHR.status === 401 && jqXHR.getResponseHeader("X-Login-Url")) {
|
||||
window.location = jqXHR.getResponseHeader("X-Login-Url") + "?next=" + encodeURIComponent(location.pathname + location.search + location.hash);
|
||||
return;
|
||||
}
|
||||
if (respdom.filter('form') && (respdom.filter('.has-error') || respdom.filter('.alert-danger'))) {
|
||||
// This is a failed form validation, let's just use it
|
||||
$("body").data('ajaxing', false);
|
||||
async_task_replace_page("body", jqXHR.responseText.substring(
|
||||
jqXHR.responseText.indexOf("<body"),
|
||||
jqXHR.responseText.indexOf("</body")
|
||||
));
|
||||
} else if (c.length > 0) {
|
||||
// This is some kind of 500/404/403 page, show it in an overlay
|
||||
$("body").data('ajaxing', false);
|
||||
waitingDialog.hide();
|
||||
if (location.href.indexOf("async_id") !== -1) {
|
||||
history.replaceState({}, "pretix", async_task_old_url);
|
||||
}
|
||||
ajaxErrDialog.show(c.first().html());
|
||||
} else {
|
||||
if (jqXHR.status >= 400 && jqXHR.status < 500) {
|
||||
$("body").data('ajaxing', false);
|
||||
waitingDialog.hide();
|
||||
alert(gettext('An error of type {code} occurred.').replace(/\{code\}/, jqXHR.status));
|
||||
} else {
|
||||
// 500 can be an application error or overload in some cases :(
|
||||
waitingDialog.setStatus(gettext('We currently cannot reach the server, but we keep trying.' +
|
||||
' Last error code: {code}').replace(/\{code\}/, jqXHR.status));
|
||||
async_task_schedule_check(this, 5000);
|
||||
}
|
||||
}
|
||||
function async_task_replace_page (target, new_html) {
|
||||
'use strict'
|
||||
waitingDialog.hide()
|
||||
$(target).html(new_html)
|
||||
setup_basics($(target))
|
||||
form_handlers($(target))
|
||||
setup_collapsible_details($(target))
|
||||
window.setTimeout(function () { $(window).scrollTop(0) }, 200)
|
||||
$(document).trigger('pretix:bind-forms')
|
||||
}
|
||||
|
||||
function async_task_callback(data, jqXHR, status) {
|
||||
"use strict";
|
||||
$("body").data('ajaxing', false);
|
||||
if (data.redirect) {
|
||||
async_task_on_success.call(this, data);
|
||||
return;
|
||||
}
|
||||
var check_url = new URL(data.check_url, window.location);
|
||||
if (async_task_dont_redirect) {
|
||||
check_url.searchParams.set('ajax_dont_redirect', '1');
|
||||
}
|
||||
async_task_id = data.async_id;
|
||||
async_task_check_url = check_url.toString();
|
||||
async_task_schedule_check(this, 100);
|
||||
|
||||
async_task_update_status(data);
|
||||
|
||||
if (location.href.indexOf("async_id") === -1) {
|
||||
history.pushState({}, "Waiting", async_task_check_url.replace(/ajax=1/, ''));
|
||||
}
|
||||
function async_task_check_error (jqXHR, textStatus, errorThrown) {
|
||||
'use strict'
|
||||
let respdom = $(jqXHR.responseText)
|
||||
let c = respdom.filter('.container')
|
||||
if (jqXHR.status === 401 && jqXHR.getResponseHeader('X-Login-Url')) {
|
||||
window.location = jqXHR.getResponseHeader('X-Login-Url') + '?next=' + encodeURIComponent(location.pathname + location.search + location.hash)
|
||||
return
|
||||
}
|
||||
if (respdom.filter('form') && (respdom.filter('.has-error') || respdom.filter('.alert-danger'))) {
|
||||
// This is a failed form validation, let's just use it
|
||||
$('body').data('ajaxing', false)
|
||||
async_task_replace_page('body', jqXHR.responseText.substring(
|
||||
jqXHR.responseText.indexOf('<body'),
|
||||
jqXHR.responseText.indexOf('</body')
|
||||
))
|
||||
} else if (c.length > 0) {
|
||||
// This is some kind of 500/404/403 page, show it in an overlay
|
||||
$('body').data('ajaxing', false)
|
||||
waitingDialog.hide()
|
||||
if (location.href.indexOf('async_id') !== -1) {
|
||||
history.replaceState({}, 'pretix', async_task_old_url)
|
||||
}
|
||||
ajaxErrDialog.show(c.first().html())
|
||||
} else {
|
||||
if (jqXHR.status >= 400 && jqXHR.status < 500) {
|
||||
$('body').data('ajaxing', false)
|
||||
waitingDialog.hide()
|
||||
alert(gettext('An error of type {code} occurred.').replace(/\{code\}/, jqXHR.status))
|
||||
} else {
|
||||
// 500 can be an application error or overload in some cases :(
|
||||
waitingDialog.setStatus(gettext('We currently cannot reach the server, but we keep trying.'
|
||||
+ ' Last error code: {code}').replace(/\{code\}/, jqXHR.status))
|
||||
async_task_schedule_check(this, 5000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function async_task_error(jqXHR, textStatus, errorThrown) {
|
||||
"use strict";
|
||||
$("body").data('ajaxing', false);
|
||||
if (jqXHR.status === 401 && jqXHR.getResponseHeader("X-Login-Url")) {
|
||||
window.location = jqXHR.getResponseHeader("X-Login-Url") + "?next=" + encodeURIComponent(location.pathname + location.search + location.hash);
|
||||
return;
|
||||
}
|
||||
waitingDialog.hide();
|
||||
if (textStatus === "timeout") {
|
||||
alert(gettext("The request took too long. Please try again."));
|
||||
} else if (jqXHR.responseText.indexOf('<html') > 0) {
|
||||
var respdom = $(jqXHR.responseText);
|
||||
var c = respdom.filter('.container');
|
||||
if (respdom.filter('form') && (respdom.filter('.has-error') || respdom.filter('.alert-danger'))) {
|
||||
if (respdom.filter('#page-wrapper') && $('#page-wrapper').length) {
|
||||
// This is a failed form validation, let's just use it
|
||||
async_task_replace_page("#page-wrapper", respdom.find("#page-wrapper").html());
|
||||
} else {
|
||||
async_task_replace_page("body", jqXHR.responseText.substring(
|
||||
jqXHR.responseText.indexOf("<body"),
|
||||
jqXHR.responseText.indexOf("</body")
|
||||
));
|
||||
document.dispatchEvent(new Event("pretix:async-task-error"))
|
||||
function async_task_callback (data, jqXHR, status) {
|
||||
'use strict'
|
||||
$('body').data('ajaxing', false)
|
||||
if (data.redirect) {
|
||||
async_task_on_success.call(this, data)
|
||||
return
|
||||
}
|
||||
let check_url = new URL(data.check_url, window.location)
|
||||
if (async_task_dont_redirect) {
|
||||
check_url.searchParams.set('ajax_dont_redirect', '1')
|
||||
}
|
||||
async_task_id = data.async_id
|
||||
async_task_check_url = check_url.toString()
|
||||
async_task_schedule_check(this, 100)
|
||||
|
||||
}
|
||||
async_task_update_status(data)
|
||||
|
||||
} else if (c.length > 0) {
|
||||
// This is some kind of 500/404/403 page, show it in an overlay
|
||||
ajaxErrDialog.show(c.first().html());
|
||||
} else {
|
||||
alert(gettext('An error of type {code} occurred.').replace(/\{code\}/, jqXHR.status));
|
||||
}
|
||||
} else {
|
||||
if (jqXHR.status >= 400 && jqXHR.status < 500) {
|
||||
alert(gettext('An error of type {code} occurred.').replace(/\{code\}/, jqXHR.status));
|
||||
} else {
|
||||
alert(gettext('We currently cannot reach the server. Please try again. ' +
|
||||
'Error code: {code}').replace(/\{code\}/, jqXHR.status));
|
||||
}
|
||||
}
|
||||
if (location.href.indexOf('async_id') === -1) {
|
||||
history.pushState({}, 'Waiting', async_task_check_url.replace(/ajax=1/, ''))
|
||||
}
|
||||
}
|
||||
|
||||
function async_task_error (jqXHR, textStatus, errorThrown) {
|
||||
'use strict'
|
||||
$('body').data('ajaxing', false)
|
||||
if (jqXHR.status === 401 && jqXHR.getResponseHeader('X-Login-Url')) {
|
||||
window.location = jqXHR.getResponseHeader('X-Login-Url') + '?next=' + encodeURIComponent(location.pathname + location.search + location.hash)
|
||||
return
|
||||
}
|
||||
waitingDialog.hide()
|
||||
if (textStatus === 'timeout') {
|
||||
alert(gettext('The request took too long. Please try again.'))
|
||||
} else if (jqXHR.responseText.indexOf('<html') > 0) {
|
||||
let respdom = $(jqXHR.responseText)
|
||||
let c = respdom.filter('.container')
|
||||
if (respdom.filter('form') && (respdom.filter('.has-error') || respdom.filter('.alert-danger'))) {
|
||||
if (respdom.filter('#page-wrapper') && $('#page-wrapper').length) {
|
||||
// This is a failed form validation, let's just use it
|
||||
async_task_replace_page('#page-wrapper', respdom.find('#page-wrapper').html())
|
||||
} else {
|
||||
async_task_replace_page('body', jqXHR.responseText.substring(
|
||||
jqXHR.responseText.indexOf('<body'),
|
||||
jqXHR.responseText.indexOf('</body')
|
||||
))
|
||||
document.dispatchEvent(new Event('pretix:async-task-error'))
|
||||
}
|
||||
} else if (c.length > 0) {
|
||||
// This is some kind of 500/404/403 page, show it in an overlay
|
||||
ajaxErrDialog.show(c.first().html())
|
||||
} else {
|
||||
alert(gettext('An error of type {code} occurred.').replace(/\{code\}/, jqXHR.status))
|
||||
}
|
||||
} else {
|
||||
if (jqXHR.status >= 400 && jqXHR.status < 500) {
|
||||
alert(gettext('An error of type {code} occurred.').replace(/\{code\}/, jqXHR.status))
|
||||
} else {
|
||||
alert(gettext('We currently cannot reach the server. Please try again. '
|
||||
+ 'Error code: {code}').replace(/\{code\}/, jqXHR.status))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$(function () {
|
||||
"use strict";
|
||||
$("body").on('submit', 'form[data-asynctask]', function (e) {
|
||||
// Not supported on IE, may lead to wrong results, but we don't support IE in the backend anymore
|
||||
var submitter = e.originalEvent ? e.originalEvent.submitter : null;
|
||||
'use strict'
|
||||
$('body').on('submit', 'form[data-asynctask]', function (e) {
|
||||
// Not supported on IE, may lead to wrong results, but we don't support IE in the backend anymore
|
||||
let submitter = e.originalEvent ? e.originalEvent.submitter : null
|
||||
|
||||
if (submitter && submitter.hasAttribute("data-no-asynctask")) {
|
||||
return;
|
||||
}
|
||||
if (submitter && submitter.hasAttribute('data-no-asynctask')) {
|
||||
return
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
$(this).removeClass("dirty"); // Avoid problems with are-you-sure.js
|
||||
if ($("body").data('ajaxing')) {
|
||||
return;
|
||||
}
|
||||
async_task_id = null;
|
||||
async_task_is_download = $(this).is("[data-asynctask-download]");
|
||||
async_task_dont_redirect = $(this).is("[data-asynctask-no-redirect]");
|
||||
async_task_is_long = $(this).is("[data-asynctask-long]");
|
||||
async_task_old_url = location.href;
|
||||
$("body").data('ajaxing', true);
|
||||
waitingDialog.show(
|
||||
$(this).attr("data-asynctask-headline") || gettext('We are processing your request …'),
|
||||
$(this).attr("data-asynctask-text") || '',
|
||||
gettext(
|
||||
'We are currently sending your request to the server. If this takes longer ' +
|
||||
'than one minute, please check your internet connection and then reload ' +
|
||||
'this page and try again.'
|
||||
)
|
||||
);
|
||||
e.preventDefault()
|
||||
$(this).removeClass('dirty') // Avoid problems with are-you-sure.js
|
||||
if ($('body').data('ajaxing')) {
|
||||
return
|
||||
}
|
||||
async_task_id = null
|
||||
async_task_is_download = $(this).is('[data-asynctask-download]')
|
||||
async_task_dont_redirect = $(this).is('[data-asynctask-no-redirect]')
|
||||
async_task_is_long = $(this).is('[data-asynctask-long]')
|
||||
async_task_old_url = location.href
|
||||
$('body').data('ajaxing', true)
|
||||
waitingDialog.show(
|
||||
$(this).attr('data-asynctask-headline') || gettext('We are processing your request …'),
|
||||
$(this).attr('data-asynctask-text') || '',
|
||||
gettext(
|
||||
'We are currently sending your request to the server. If this takes longer '
|
||||
+ 'than one minute, please check your internet connection and then reload '
|
||||
+ 'this page and try again.'
|
||||
)
|
||||
)
|
||||
|
||||
var action = this.action;
|
||||
var formData = new FormData(this);
|
||||
formData.append('ajax', '1');
|
||||
if (async_task_dont_redirect) {
|
||||
formData.append('ajax_dont_redirect', '1');
|
||||
}
|
||||
if (submitter && submitter.name) {
|
||||
formData.append(submitter.name, submitter.value);
|
||||
}
|
||||
if (submitter && submitter.getAttribute("formaction")) {
|
||||
action = submitter.getAttribute("formaction");
|
||||
}
|
||||
$.ajax(
|
||||
{
|
||||
'type': 'POST',
|
||||
'url': action,
|
||||
'data': formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
'success': async_task_callback,
|
||||
'error': async_task_error,
|
||||
'context': this,
|
||||
'dataType': 'json',
|
||||
'timeout': 60000,
|
||||
}
|
||||
);
|
||||
});
|
||||
let action = this.action
|
||||
let formData = new FormData(this)
|
||||
formData.append('ajax', '1')
|
||||
if (async_task_dont_redirect) {
|
||||
formData.append('ajax_dont_redirect', '1')
|
||||
}
|
||||
if (submitter && submitter.name) {
|
||||
formData.append(submitter.name, submitter.value)
|
||||
}
|
||||
if (submitter && submitter.getAttribute('formaction')) {
|
||||
action = submitter.getAttribute('formaction')
|
||||
}
|
||||
$.ajax(
|
||||
{
|
||||
type: 'POST',
|
||||
url: action,
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: async_task_callback,
|
||||
error: async_task_error,
|
||||
context: this,
|
||||
dataType: 'json',
|
||||
timeout: 60000,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
window.addEventListener("pageshow", function (evt) {
|
||||
// In Safari, if you submit an async task, then get redirected, then go back,
|
||||
// Safari won't reload the HTML from disk cache but instead reuse the DOM of the
|
||||
// previous request, thus not clearing the "loading" state.
|
||||
if (evt.persisted && $("body").hasClass("loading")) {
|
||||
setTimeout(function () {
|
||||
window.location.reload();
|
||||
}, 10);
|
||||
}
|
||||
}, false);
|
||||
window.addEventListener('pageshow', function (evt) {
|
||||
// In Safari, if you submit an async task, then get redirected, then go back,
|
||||
// Safari won't reload the HTML from disk cache but instead reuse the DOM of the
|
||||
// previous request, thus not clearing the "loading" state.
|
||||
if (evt.persisted && $('body').hasClass('loading')) {
|
||||
setTimeout(function () {
|
||||
window.location.reload()
|
||||
}, 10)
|
||||
}
|
||||
}, false)
|
||||
|
||||
$("#ajaxerr").on("click", ".ajaxerr-close", ajaxErrDialog.hide);
|
||||
$("#loadingmodal").on("cancel", function() {
|
||||
return false;
|
||||
});
|
||||
$("#loadingmodal").prop("closedBy", "none");
|
||||
});
|
||||
$('#ajaxerr').on('click', '.ajaxerr-close', ajaxErrDialog.hide)
|
||||
$('#loadingmodal').on('cancel', function () {
|
||||
return false
|
||||
})
|
||||
$('#loadingmodal').prop('closedBy', 'none')
|
||||
})
|
||||
|
||||
var waitingDialog = {
|
||||
show: function (title, text, status) {
|
||||
"use strict";
|
||||
this.setTitle(title);
|
||||
this.setText(text);
|
||||
this.setStatus(status || gettext('If this takes longer than a few minutes, please contact us.'));
|
||||
this.setProgress(null);
|
||||
this.setSteps(null);
|
||||
document.getElementById("loadingmodal").showModal();
|
||||
},
|
||||
hide: function () {
|
||||
"use strict";
|
||||
document.getElementById("loadingmodal").close();
|
||||
},
|
||||
setTitle: function(title) {
|
||||
$("#loadingmodal .modal-card-title").text(title);
|
||||
},
|
||||
setStatus: function(statusText) {
|
||||
$("#loadingmodal p.status").text(statusText);
|
||||
},
|
||||
setText: function(text) {
|
||||
if (text)
|
||||
$("#loadingmodal .modal-card-description").text(text).show();
|
||||
else
|
||||
$("#loadingmodal .modal-card-description").hide();
|
||||
},
|
||||
setProgress: function(percentage) {
|
||||
if (typeof percentage === 'number') {
|
||||
$("#loadingmodal .progress").show();
|
||||
$("#loadingmodal .progress .progress-bar").css("width", percentage + "%");
|
||||
} else {
|
||||
$("#loadingmodal .progress").hide();
|
||||
}
|
||||
},
|
||||
setSteps: function(steps) {
|
||||
var $steps = $("#loadingmodal .steps");
|
||||
if (steps) {
|
||||
$steps.html("").show()
|
||||
for (var step of steps) {
|
||||
$steps.append(
|
||||
$("<span>").addClass("fa fa-fw")
|
||||
.toggleClass("fa-check text-success", step.done)
|
||||
.toggleClass("fa-cog fa-spin text-muted", !step.done)
|
||||
).append(
|
||||
$("<span>").text(step.label)
|
||||
).append(
|
||||
$("<br>")
|
||||
)
|
||||
}
|
||||
} else {
|
||||
$steps.hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
show: function (title, text, status) {
|
||||
'use strict'
|
||||
this.setTitle(title)
|
||||
this.setText(text)
|
||||
this.setStatus(status || gettext('If this takes longer than a few minutes, please contact us.'))
|
||||
this.setProgress(null)
|
||||
this.setSteps(null)
|
||||
document.getElementById('loadingmodal').showModal()
|
||||
},
|
||||
hide: function () {
|
||||
'use strict'
|
||||
document.getElementById('loadingmodal').close()
|
||||
},
|
||||
setTitle: function (title) {
|
||||
$('#loadingmodal .modal-card-title').text(title)
|
||||
},
|
||||
setStatus: function (statusText) {
|
||||
$('#loadingmodal p.status').text(statusText)
|
||||
},
|
||||
setText: function (text) {
|
||||
if (text)
|
||||
$('#loadingmodal .modal-card-description').text(text).show()
|
||||
else
|
||||
$('#loadingmodal .modal-card-description').hide()
|
||||
},
|
||||
setProgress: function (percentage) {
|
||||
if (typeof percentage === 'number') {
|
||||
$('#loadingmodal .progress').show()
|
||||
$('#loadingmodal .progress .progress-bar').css('width', percentage + '%')
|
||||
} else {
|
||||
$('#loadingmodal .progress').hide()
|
||||
}
|
||||
},
|
||||
setSteps: function (steps) {
|
||||
let $steps = $('#loadingmodal .steps')
|
||||
if (steps) {
|
||||
$steps.html('').show()
|
||||
for (let step of steps) {
|
||||
$steps.append(
|
||||
$('<span>').addClass('fa fa-fw')
|
||||
.toggleClass('fa-check text-success', step.done)
|
||||
.toggleClass('fa-cog fa-spin text-muted', !step.done)
|
||||
).append(
|
||||
$('<span>').text(step.label)
|
||||
).append(
|
||||
$('<br>')
|
||||
)
|
||||
}
|
||||
} else {
|
||||
$steps.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var ajaxErrDialog = {
|
||||
show: function (c) {
|
||||
"use strict";
|
||||
$("#ajaxerr").html(c);
|
||||
$("#ajaxerr .links").html("<a class='btn btn-default ajaxerr-close'>"
|
||||
+ gettext("Close message") + "</a>");
|
||||
$("body").addClass("ajaxerr has-modal-dialog");
|
||||
$("#ajaxerr").prop("hidden", false);
|
||||
},
|
||||
hide: function () {
|
||||
"use strict";
|
||||
$("body").removeClass("ajaxerr has-modal-dialog");
|
||||
$("#ajaxerr").prop("hidden", true);
|
||||
},
|
||||
};
|
||||
show: function (c) {
|
||||
'use strict'
|
||||
$('#ajaxerr').html(c)
|
||||
$('#ajaxerr .links').html('<a class=\'btn btn-default ajaxerr-close\'>'
|
||||
+ gettext('Close message') + '</a>')
|
||||
$('body').addClass('ajaxerr has-modal-dialog')
|
||||
$('#ajaxerr').prop('hidden', false)
|
||||
},
|
||||
hide: function () {
|
||||
'use strict'
|
||||
$('body').removeClass('ajaxerr has-modal-dialog')
|
||||
$('#ajaxerr').prop('hidden', true)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.querySelectorAll('a[href^="mailto:"]').forEach(function(link) {
|
||||
// Replace [at] with @ and the [dot] with . in both the href and the displayed text (if needed)
|
||||
link.href = link.href.replace('[at]', '@').replace('[dot]', '.');
|
||||
link.textContent = link.textContent.replace('[at]', '@').replace('[dot]', '.');
|
||||
});
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.querySelectorAll('a[href^="mailto:"]').forEach(function (link) {
|
||||
// Replace [at] with @ and the [dot] with . in both the href and the displayed text (if needed)
|
||||
link.href = link.href.replace('[at]', '@').replace('[dot]', '.')
|
||||
link.textContent = link.textContent.replace('[at]', '@').replace('[dot]', '.')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,152 +1,149 @@
|
||||
/*global $ */
|
||||
|
||||
setup_collapsible_details = function (el) {
|
||||
el.find('.sneak-peek-trigger').each(function () {
|
||||
let trigger = this
|
||||
let button = this.querySelector('button')
|
||||
let content = document.getElementById(button.getAttribute('aria-controls'))
|
||||
if (content.scrollHeight < 200) {
|
||||
trigger.remove()
|
||||
content.classList.remove('sneak-peek-content')
|
||||
return
|
||||
}
|
||||
content.setAttribute('aria-hidden', 'true')
|
||||
content.setAttribute('inert', true)
|
||||
button.setAttribute('aria-expanded', 'false')
|
||||
button.addEventListener('click', function (e) {
|
||||
button.setAttribute('aria-expanded', 'true')
|
||||
content.setAttribute('aria-hidden', 'false')
|
||||
content.removeAttribute('inert')
|
||||
|
||||
el.find('.sneak-peek-trigger').each(function() {
|
||||
var trigger = this;
|
||||
var button = this.querySelector('button');
|
||||
var content = document.getElementById(button.getAttribute('aria-controls'));
|
||||
if (content.scrollHeight < 200) {
|
||||
trigger.remove();
|
||||
content.classList.remove('sneak-peek-content');
|
||||
return;
|
||||
}
|
||||
content.setAttribute('aria-hidden', 'true');
|
||||
content.setAttribute('inert', true);
|
||||
button.setAttribute('aria-expanded', 'false');
|
||||
button.addEventListener('click', function (e) {
|
||||
button.setAttribute('aria-expanded', 'true');
|
||||
content.setAttribute('aria-hidden', 'false');
|
||||
content.removeAttribute('inert');
|
||||
content.addEventListener('transitionend', function () {
|
||||
content.classList.remove('sneak-peek-content')
|
||||
content.style.removeProperty('height')
|
||||
// we need to keep the trigger/button in the DOM to not irritate screenreaders toggling visibility
|
||||
trigger.classList.add('sr-only')
|
||||
}, { once: true })
|
||||
content.style.height = content.scrollHeight + 'px'
|
||||
|
||||
content.addEventListener('transitionend', function() {
|
||||
content.classList.remove('sneak-peek-content');
|
||||
content.style.removeProperty('height');
|
||||
// we need to keep the trigger/button in the DOM to not irritate screenreaders toggling visibility
|
||||
trigger.classList.add('sr-only');
|
||||
}, {once: true});
|
||||
content.style.height = content.scrollHeight + 'px';
|
||||
button.addEventListener('click', function (e) {
|
||||
// this will be called by screenreader users if they kept focus on the button after expanding
|
||||
// we need to keep the trigger/button in the DOM to not irritate screenreaders toggling visibility
|
||||
let expanded = button.getAttribute('aria-expanded') == 'true'
|
||||
button.setAttribute('aria-expanded', !expanded)
|
||||
content.setAttribute('aria-hidden', expanded)
|
||||
})
|
||||
button.addEventListener('blur', function (e) {
|
||||
// if content is visible and the user leaves the button, we can safely remove the trigger/button
|
||||
if (button.getAttribute('aria-expanded') == 'true') {
|
||||
trigger.remove()
|
||||
}
|
||||
})
|
||||
}, { once: true })
|
||||
|
||||
button.addEventListener('click', function (e) {
|
||||
// this will be called by screenreader users if they kept focus on the button after expanding
|
||||
// we need to keep the trigger/button in the DOM to not irritate screenreaders toggling visibility
|
||||
var expanded = button.getAttribute('aria-expanded') == 'true';
|
||||
button.setAttribute('aria-expanded', !expanded);
|
||||
content.setAttribute('aria-hidden', expanded);
|
||||
});
|
||||
button.addEventListener('blur', function (e) {
|
||||
// if content is visible and the user leaves the button, we can safely remove the trigger/button
|
||||
if (button.getAttribute('aria-expanded') == 'true') {
|
||||
trigger.remove();
|
||||
}
|
||||
});
|
||||
}, { once: true });
|
||||
let container = this.closest('details.sneak-peek-container')
|
||||
if (container) {
|
||||
function removeSneekPeakWhenClosed (e) {
|
||||
if (e.newState == 'closed') {
|
||||
container.removeEventListener('toggle', removeSneekPeakWhenClosed)
|
||||
trigger.remove()
|
||||
content.removeAttribute('aria-hidden')
|
||||
content.removeAttribute('inert')
|
||||
content.classList.remove('sneak-peek-content')
|
||||
}
|
||||
}
|
||||
container.addEventListener('toggle', removeSneekPeakWhenClosed)
|
||||
}
|
||||
})
|
||||
|
||||
var container = this.closest('details.sneak-peek-container');
|
||||
if (container) {
|
||||
function removeSneekPeakWhenClosed(e) {
|
||||
if (e.newState == "closed") {
|
||||
container.removeEventListener("toggle", removeSneekPeakWhenClosed);
|
||||
trigger.remove();
|
||||
content.removeAttribute('aria-hidden');
|
||||
content.removeAttribute('inert');
|
||||
content.classList.remove('sneak-peek-content');
|
||||
}
|
||||
}
|
||||
container.addEventListener("toggle", removeSneekPeakWhenClosed);
|
||||
}
|
||||
});
|
||||
let isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]'
|
||||
el.find('details summary').click(function (e) {
|
||||
if (this.tagName !== 'A' && $(e.target).closest('a').length > 0) {
|
||||
return true
|
||||
}
|
||||
let $details = $(this).closest('details')
|
||||
let isOpen = $details.prop('open')
|
||||
let $detailsNotSummary = $details.children(':not(summary)')
|
||||
if ($detailsNotSummary.is(':animated')) {
|
||||
e.preventDefault()
|
||||
return false
|
||||
}
|
||||
if (isOpen) {
|
||||
$details.removeClass('details-open')
|
||||
$detailsNotSummary.stop().show().slideUp(500, function () {
|
||||
$details.prop('open', false)
|
||||
})
|
||||
} else {
|
||||
$detailsNotSummary.stop().hide()
|
||||
$details.prop('open', true)
|
||||
$details.addClass('details-open')
|
||||
$detailsNotSummary.slideDown()
|
||||
}
|
||||
e.preventDefault()
|
||||
return false
|
||||
}).keyup(function (event) {
|
||||
if (32 == event.keyCode || (13 == event.keyCode && !isOpera)) {
|
||||
// Space or Enter is pressed — trigger the `click` event on the `summary` element
|
||||
// Opera already seems to trigger the `click` event when Enter is pressed
|
||||
event.preventDefault()
|
||||
$(this).click()
|
||||
}
|
||||
})
|
||||
|
||||
var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]';
|
||||
el.find("details summary").click(function (e) {
|
||||
if (this.tagName !== "A" && $(e.target).closest("a").length > 0) {
|
||||
return true;
|
||||
}
|
||||
var $details = $(this).closest("details");
|
||||
var isOpen = $details.prop("open");
|
||||
var $detailsNotSummary = $details.children(':not(summary)');
|
||||
if ($detailsNotSummary.is(':animated')) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
if (isOpen) {
|
||||
$details.removeClass("details-open");
|
||||
$detailsNotSummary.stop().show().slideUp(500, function () {
|
||||
$details.prop("open", false);
|
||||
});
|
||||
} else {
|
||||
$detailsNotSummary.stop().hide();
|
||||
$details.prop("open", true);
|
||||
$details.addClass("details-open");
|
||||
$detailsNotSummary.slideDown();
|
||||
}
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}).keyup(function (event) {
|
||||
if (32 == event.keyCode || (13 == event.keyCode && !isOpera)) {
|
||||
// Space or Enter is pressed — trigger the `click` event on the `summary` element
|
||||
// Opera already seems to trigger the `click` event when Enter is pressed
|
||||
event.preventDefault();
|
||||
$(this).click();
|
||||
}
|
||||
});
|
||||
$('details').each(function () {
|
||||
let $details = $(this),
|
||||
$detailsSummary = $('summary', $details).first(),
|
||||
$detailsNotSummary = $details.children(':not(summary)')
|
||||
$details.prop('open', typeof $details.attr('open') == 'string')
|
||||
if (!$details.prop('open')) {
|
||||
if ($details.find('.has-error, .alert-danger').length) {
|
||||
$details.addClass('details-open')
|
||||
$details.prop('open', true)
|
||||
} else {
|
||||
$detailsNotSummary.hide()
|
||||
}
|
||||
} else {
|
||||
$details.addClass('details-open')
|
||||
}
|
||||
$detailsSummary.attr({
|
||||
role: 'button',
|
||||
'aria-controls': $details.attr('id')
|
||||
}).prop('tabIndex', 0).bind('selectstart dragstart mousedown', function () {
|
||||
return false
|
||||
})
|
||||
})
|
||||
|
||||
$('details').each(function () {
|
||||
var $details = $(this),
|
||||
$detailsSummary = $('summary', $details).first(),
|
||||
$detailsNotSummary = $details.children(':not(summary)');
|
||||
$details.prop('open', typeof $details.attr('open') == 'string');
|
||||
if (!$details.prop('open')) {
|
||||
if ($details.find(".has-error, .alert-danger").length) {
|
||||
$details.addClass("details-open");
|
||||
$details.prop('open', true);
|
||||
} else {
|
||||
$detailsNotSummary.hide();
|
||||
}
|
||||
} else {
|
||||
$details.addClass("details-open");
|
||||
}
|
||||
$detailsSummary.attr({
|
||||
'role': 'button',
|
||||
'aria-controls': $details.attr('id')
|
||||
}).prop('tabIndex', 0).bind('selectstart dragstart mousedown', function () {
|
||||
return false;
|
||||
});
|
||||
});
|
||||
el.find('article button[data-toggle=variations]').click(function (e) {
|
||||
let $button = $(this)
|
||||
let $details = $button.closest('article')
|
||||
let $detailsNotSummary = $button.attr('aria-controls') ? $('#' + $button.attr('aria-controls')) : $('.variations', $details)
|
||||
let isOpen = !$detailsNotSummary.prop('hidden')
|
||||
if ($detailsNotSummary.is(':animated')) {
|
||||
e.preventDefault()
|
||||
return false
|
||||
}
|
||||
|
||||
el.find("article button[data-toggle=variations]").click(function (e) {
|
||||
var $button = $(this);
|
||||
var $details = $button.closest("article");
|
||||
var $detailsNotSummary = $button.attr("aria-controls") ? $('#' + $button.attr("aria-controls")) : $(".variations", $details);
|
||||
var isOpen = !$detailsNotSummary.prop("hidden");
|
||||
if ($detailsNotSummary.is(':animated')) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
let altLabel = $button.attr('data-label-alt')
|
||||
$button.attr('data-label-alt', $button.text().trim())
|
||||
$button.find('span').text(altLabel)
|
||||
$button.attr('aria-expanded', !isOpen)
|
||||
|
||||
var altLabel = $button.attr("data-label-alt");
|
||||
$button.attr("data-label-alt", $button.text().trim());
|
||||
$button.find("span").text(altLabel);
|
||||
$button.attr("aria-expanded", !isOpen);
|
||||
|
||||
if (isOpen) {
|
||||
$details.removeClass("details-open");
|
||||
$detailsNotSummary.stop().show().slideUp(500, function () {
|
||||
$detailsNotSummary.prop("hidden", true);
|
||||
});
|
||||
} else {
|
||||
$detailsNotSummary.prop("hidden", false).stop().hide();
|
||||
$details.addClass("details-open");
|
||||
$detailsNotSummary.slideDown();
|
||||
}
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
el.find(".variations-collapsed").prop("hidden", true);
|
||||
};
|
||||
if (isOpen) {
|
||||
$details.removeClass('details-open')
|
||||
$detailsNotSummary.stop().show().slideUp(500, function () {
|
||||
$detailsNotSummary.prop('hidden', true)
|
||||
})
|
||||
} else {
|
||||
$detailsNotSummary.prop('hidden', false).stop().hide()
|
||||
$details.addClass('details-open')
|
||||
$detailsNotSummary.slideDown()
|
||||
}
|
||||
e.preventDefault()
|
||||
return false
|
||||
})
|
||||
el.find('.variations-collapsed').prop('hidden', true)
|
||||
}
|
||||
|
||||
$(function () {
|
||||
"use strict";
|
||||
'use strict'
|
||||
|
||||
setup_collapsible_details($("body"));
|
||||
});
|
||||
setup_collapsible_details($('body'))
|
||||
})
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
['DOMContentLoaded', 'pretix:async-task-error'].forEach(function (ev) {
|
||||
document.addEventListener(ev, function () {
|
||||
document.querySelectorAll('#goback, #reload').forEach(function (element) {
|
||||
const regularLoad = ev === 'DOMContentLoaded' && element.id === 'goback';
|
||||
element.addEventListener('click', regularLoad
|
||||
? () => window.history.back()
|
||||
: () => window.location.reload()
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
document.addEventListener(ev, function () {
|
||||
document.querySelectorAll('#goback, #reload').forEach(function (element) {
|
||||
const regularLoad = ev === 'DOMContentLoaded' && element.id === 'goback'
|
||||
element.addEventListener('click', regularLoad
|
||||
? () => window.history.back()
|
||||
: () => window.location.reload()
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
// Attempt to auto-open page in new tab. Will be ignored by most browser's popup blockers anyways, though.
|
||||
var url = JSON.parse(document.getElementById('framebreak-url').innerText)
|
||||
let url = JSON.parse(document.getElementById('framebreak-url').innerText)
|
||||
window.open(url)
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
// The actual gettext implementation is loaded asynchronously with the translation
|
||||
function gettext(msgid) {
|
||||
if (typeof django !== 'undefined' && typeof django.gettext !== 'undefined') {
|
||||
return django.gettext(msgid);
|
||||
}
|
||||
return msgid;
|
||||
function gettext (msgid) {
|
||||
if (typeof django !== 'undefined' && typeof django.gettext !== 'undefined') {
|
||||
return django.gettext(msgid)
|
||||
}
|
||||
return msgid
|
||||
}
|
||||
|
||||
function ngettext(singular, plural, count) {
|
||||
if (typeof django !== 'undefined' && typeof django.ngettext !== 'undefined') {
|
||||
return django.ngettext(singular, plural, count);
|
||||
}
|
||||
return plural;
|
||||
function ngettext (singular, plural, count) {
|
||||
if (typeof django !== 'undefined' && typeof django.ngettext !== 'undefined') {
|
||||
return django.ngettext(singular, plural, count)
|
||||
}
|
||||
return plural
|
||||
}
|
||||
|
||||
function pgettext(context, msgid) {
|
||||
if (typeof django !== 'undefined' && typeof django.pgettext !== 'undefined') {
|
||||
return django.pgettext(context, msgid);
|
||||
}
|
||||
return msgid;
|
||||
function pgettext (context, msgid) {
|
||||
if (typeof django !== 'undefined' && typeof django.pgettext !== 'undefined') {
|
||||
return django.pgettext(context, msgid)
|
||||
}
|
||||
return msgid
|
||||
}
|
||||
|
||||
function interpolate(fmt, object, named) {
|
||||
if (named) {
|
||||
return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])});
|
||||
} else {
|
||||
return fmt.replace(/%s/g, function(match){return String(obj.shift())});
|
||||
}
|
||||
function interpolate (fmt, object, named) {
|
||||
if (named) {
|
||||
return fmt.replace(/%\(\w+\)s/g, function (match) { return String(obj[match.slice(2, -2)]) })
|
||||
} else {
|
||||
return fmt.replace(/%s/g, function (match) { return String(obj.shift()) })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
function i18nstring_localize(o) {
|
||||
var locale = document.body.attributes['data-pretixlocale'].value
|
||||
var short_locale = locale.split('-')[0]
|
||||
if (o[locale])
|
||||
return o[locale]
|
||||
function i18nstring_localize (o) {
|
||||
let locale = document.body.attributes['data-pretixlocale'].value
|
||||
let short_locale = locale.split('-')[0]
|
||||
if (o[locale])
|
||||
return o[locale]
|
||||
|
||||
if (o[short_locale])
|
||||
return o[short_locale]
|
||||
if (o[short_locale])
|
||||
return o[short_locale]
|
||||
|
||||
for (k of Object.keys(o)) {
|
||||
if (k.split('-')[0] === short_locale && o[k]) {
|
||||
return o[k]
|
||||
}
|
||||
}
|
||||
for (let k of Object.keys(o)) {
|
||||
if (k.split('-')[0] === short_locale && o[k]) {
|
||||
return o[k]
|
||||
}
|
||||
}
|
||||
|
||||
if (o['en'])
|
||||
return o['en']
|
||||
if (o['en'])
|
||||
return o['en']
|
||||
|
||||
for (k of Object.keys(o)) {
|
||||
if (o[k]) {
|
||||
return o[k]
|
||||
}
|
||||
}
|
||||
for (let k of Object.keys(o)) {
|
||||
if (o[k]) {
|
||||
return o[k]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ $(document).on('pretix:bind-forms', () => {
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
beforeAfterSelect.dispatchEvent(new Event('change', {bubbles: true}))
|
||||
beforeAfterSelect.dispatchEvent(new Event('change', { bubbles: true }))
|
||||
}
|
||||
}
|
||||
referenceSelect.addEventListener('change', updateBeforeOption)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
var intId = window.setInterval(function () {
|
||||
$.get(location.href + '?ajax=1', function (data, status) {
|
||||
if (data === "1") {
|
||||
window.clearInterval(intId);
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
const intId = window.setInterval(function () {
|
||||
$.get(location.href + '?ajax=1', function (data, _status) {
|
||||
if (data === '1') {
|
||||
window.clearInterval(intId)
|
||||
location.reload()
|
||||
}
|
||||
})
|
||||
}, 500)
|
||||
|
||||
@@ -170,13 +170,14 @@ body.has-modal-dialog .container, body.has-modal-dialog #wrapper {
|
||||
#lightbox-dialog {
|
||||
width: fit-content;
|
||||
max-width: 80%;
|
||||
min-width: 24em;
|
||||
min-width: calc(min(24em, 90%));
|
||||
.modal-card-content {
|
||||
padding: 2.5em;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: calc(100dvh - 60px - 5em - 5em);
|
||||
}
|
||||
|
||||
button {
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
var hiddenfield = document.querySelector("input[name=origin][type=hidden]");
|
||||
let hiddenfield = document.querySelector('input[name=origin][type=hidden]')
|
||||
if (hiddenfield) {
|
||||
hiddenfield.value = window.location.origin
|
||||
hiddenfield.value = window.location.origin
|
||||
}
|
||||
async function runCheck() {
|
||||
if (document.getElementById("good_origin")) {
|
||||
if (document.getElementById("good_origin").innerText.split('').reverse().join('') !== window.location.origin) {
|
||||
const response = await fetch(document.getElementById("bad_origin_report_url").innerText.split('').reverse().join(''), {
|
||||
method: "POST",
|
||||
mode: "cors",
|
||||
referrerPolicy: "unsafe-url",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: "origin=" + window.location.origin,
|
||||
});
|
||||
}
|
||||
}
|
||||
async function runCheck () {
|
||||
if (document.getElementById('good_origin')) {
|
||||
if (document.getElementById('good_origin').innerText.split('').reverse().join('') !== window.location.origin) {
|
||||
const _response = await fetch(document.getElementById('bad_origin_report_url').innerText.split('').reverse().join(''), {
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
referrerPolicy: 'unsafe-url',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: 'origin=' + window.location.origin,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runCheck();
|
||||
runCheck()
|
||||
|
||||
@@ -1,118 +1,118 @@
|
||||
var lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
let lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
|
||||
;(function (exports) {
|
||||
'use strict'
|
||||
'use strict'
|
||||
|
||||
var Arr = (typeof Uint8Array !== 'undefined')
|
||||
? Uint8Array
|
||||
: Array
|
||||
let Arr = (typeof Uint8Array !== 'undefined')
|
||||
? Uint8Array
|
||||
: Array
|
||||
|
||||
var PLUS = '+'.charCodeAt(0)
|
||||
var SLASH = '/'.charCodeAt(0)
|
||||
var NUMBER = '0'.charCodeAt(0)
|
||||
var LOWER = 'a'.charCodeAt(0)
|
||||
var UPPER = 'A'.charCodeAt(0)
|
||||
var PLUS_URL_SAFE = '-'.charCodeAt(0)
|
||||
var SLASH_URL_SAFE = '_'.charCodeAt(0)
|
||||
let PLUS = '+'.charCodeAt(0)
|
||||
let SLASH = '/'.charCodeAt(0)
|
||||
let NUMBER = '0'.charCodeAt(0)
|
||||
let LOWER = 'a'.charCodeAt(0)
|
||||
let UPPER = 'A'.charCodeAt(0)
|
||||
let PLUS_URL_SAFE = '-'.charCodeAt(0)
|
||||
let SLASH_URL_SAFE = '_'.charCodeAt(0)
|
||||
|
||||
function decode (elt) {
|
||||
var code = elt.charCodeAt(0)
|
||||
if (code === PLUS || code === PLUS_URL_SAFE) return 62 // '+'
|
||||
if (code === SLASH || code === SLASH_URL_SAFE) return 63 // '/'
|
||||
if (code < NUMBER) return -1 // no match
|
||||
if (code < NUMBER + 10) return code - NUMBER + 26 + 26
|
||||
if (code < UPPER + 26) return code - UPPER
|
||||
if (code < LOWER + 26) return code - LOWER + 26
|
||||
}
|
||||
function decode (elt) {
|
||||
let code = elt.charCodeAt(0)
|
||||
if (code === PLUS || code === PLUS_URL_SAFE) return 62 // '+'
|
||||
if (code === SLASH || code === SLASH_URL_SAFE) return 63 // '/'
|
||||
if (code < NUMBER) return -1 // no match
|
||||
if (code < NUMBER + 10) return code - NUMBER + 26 + 26
|
||||
if (code < UPPER + 26) return code - UPPER
|
||||
if (code < LOWER + 26) return code - LOWER + 26
|
||||
}
|
||||
|
||||
function b64ToByteArray (b64) {
|
||||
var i, j, l, tmp, placeHolders, arr
|
||||
function b64ToByteArray (b64) {
|
||||
let i, j, l, tmp, placeHolders, arr
|
||||
|
||||
if (b64.length % 4 > 0) {
|
||||
throw new Error('Invalid string. Length must be a multiple of 4')
|
||||
}
|
||||
if (b64.length % 4 > 0) {
|
||||
throw new Error('Invalid string. Length must be a multiple of 4')
|
||||
}
|
||||
|
||||
// the number of equal signs (place holders)
|
||||
// if there are two placeholders, than the two characters before it
|
||||
// represent one byte
|
||||
// if there is only one, then the three characters before it represent 2 bytes
|
||||
// this is just a cheap hack to not do indexOf twice
|
||||
var len = b64.length
|
||||
placeHolders = b64.charAt(len - 2) === '=' ? 2 : b64.charAt(len - 1) === '=' ? 1 : 0
|
||||
// the number of equal signs (place holders)
|
||||
// if there are two placeholders, than the two characters before it
|
||||
// represent one byte
|
||||
// if there is only one, then the three characters before it represent 2 bytes
|
||||
// this is just a cheap hack to not do indexOf twice
|
||||
let len = b64.length
|
||||
placeHolders = b64.charAt(len - 2) === '=' ? 2 : b64.charAt(len - 1) === '=' ? 1 : 0
|
||||
|
||||
// base64 is 4/3 + up to two characters of the original data
|
||||
arr = new Arr(b64.length * 3 / 4 - placeHolders)
|
||||
// base64 is 4/3 + up to two characters of the original data
|
||||
arr = new Arr(b64.length * 3 / 4 - placeHolders)
|
||||
|
||||
// if there are placeholders, only get up to the last complete 4 chars
|
||||
l = placeHolders > 0 ? b64.length - 4 : b64.length
|
||||
// if there are placeholders, only get up to the last complete 4 chars
|
||||
l = placeHolders > 0 ? b64.length - 4 : b64.length
|
||||
|
||||
var L = 0
|
||||
let L = 0
|
||||
|
||||
function push (v) {
|
||||
arr[L++] = v
|
||||
}
|
||||
function push (v) {
|
||||
arr[L++] = v
|
||||
}
|
||||
|
||||
for (i = 0, j = 0; i < l; i += 4, j += 3) {
|
||||
tmp = (decode(b64.charAt(i)) << 18) | (decode(b64.charAt(i + 1)) << 12) | (decode(b64.charAt(i + 2)) << 6) | decode(b64.charAt(i + 3))
|
||||
push((tmp & 0xFF0000) >> 16)
|
||||
push((tmp & 0xFF00) >> 8)
|
||||
push(tmp & 0xFF)
|
||||
}
|
||||
for (i = 0, j = 0; i < l; i += 4, j += 3) {
|
||||
tmp = (decode(b64.charAt(i)) << 18) | (decode(b64.charAt(i + 1)) << 12) | (decode(b64.charAt(i + 2)) << 6) | decode(b64.charAt(i + 3))
|
||||
push((tmp & 0xFF0000) >> 16)
|
||||
push((tmp & 0xFF00) >> 8)
|
||||
push(tmp & 0xFF)
|
||||
}
|
||||
|
||||
if (placeHolders === 2) {
|
||||
tmp = (decode(b64.charAt(i)) << 2) | (decode(b64.charAt(i + 1)) >> 4)
|
||||
push(tmp & 0xFF)
|
||||
} else if (placeHolders === 1) {
|
||||
tmp = (decode(b64.charAt(i)) << 10) | (decode(b64.charAt(i + 1)) << 4) | (decode(b64.charAt(i + 2)) >> 2)
|
||||
push((tmp >> 8) & 0xFF)
|
||||
push(tmp & 0xFF)
|
||||
}
|
||||
if (placeHolders === 2) {
|
||||
tmp = (decode(b64.charAt(i)) << 2) | (decode(b64.charAt(i + 1)) >> 4)
|
||||
push(tmp & 0xFF)
|
||||
} else if (placeHolders === 1) {
|
||||
tmp = (decode(b64.charAt(i)) << 10) | (decode(b64.charAt(i + 1)) << 4) | (decode(b64.charAt(i + 2)) >> 2)
|
||||
push((tmp >> 8) & 0xFF)
|
||||
push(tmp & 0xFF)
|
||||
}
|
||||
|
||||
return arr
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
function uint8ToBase64 (uint8) {
|
||||
var i
|
||||
var extraBytes = uint8.length % 3 // if we have 1 byte left, pad 2 bytes
|
||||
var output = ''
|
||||
var temp, length
|
||||
function uint8ToBase64 (uint8) {
|
||||
let i
|
||||
let extraBytes = uint8.length % 3 // if we have 1 byte left, pad 2 bytes
|
||||
let output = ''
|
||||
let temp, length
|
||||
|
||||
function encode (num) {
|
||||
return lookup.charAt(num)
|
||||
}
|
||||
function encode (num) {
|
||||
return lookup.charAt(num)
|
||||
}
|
||||
|
||||
function tripletToBase64 (num) {
|
||||
return encode(num >> 18 & 0x3F) + encode(num >> 12 & 0x3F) + encode(num >> 6 & 0x3F) + encode(num & 0x3F)
|
||||
}
|
||||
function tripletToBase64 (num) {
|
||||
return encode(num >> 18 & 0x3F) + encode(num >> 12 & 0x3F) + encode(num >> 6 & 0x3F) + encode(num & 0x3F)
|
||||
}
|
||||
|
||||
// go through the array every three bytes, we'll deal with trailing stuff later
|
||||
for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) {
|
||||
temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2])
|
||||
output += tripletToBase64(temp)
|
||||
}
|
||||
// go through the array every three bytes, we'll deal with trailing stuff later
|
||||
for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) {
|
||||
temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2])
|
||||
output += tripletToBase64(temp)
|
||||
}
|
||||
|
||||
// pad the end with zeros, but make sure to not forget the extra bytes
|
||||
switch (extraBytes) {
|
||||
case 1:
|
||||
temp = uint8[uint8.length - 1]
|
||||
output += encode(temp >> 2)
|
||||
output += encode((temp << 4) & 0x3F)
|
||||
output += '=='
|
||||
break
|
||||
case 2:
|
||||
temp = (uint8[uint8.length - 2] << 8) + (uint8[uint8.length - 1])
|
||||
output += encode(temp >> 10)
|
||||
output += encode((temp >> 4) & 0x3F)
|
||||
output += encode((temp << 2) & 0x3F)
|
||||
output += '='
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
// pad the end with zeros, but make sure to not forget the extra bytes
|
||||
switch (extraBytes) {
|
||||
case 1:
|
||||
temp = uint8[uint8.length - 1]
|
||||
output += encode(temp >> 2)
|
||||
output += encode((temp << 4) & 0x3F)
|
||||
output += '=='
|
||||
break
|
||||
case 2:
|
||||
temp = (uint8[uint8.length - 2] << 8) + (uint8[uint8.length - 1])
|
||||
output += encode(temp >> 10)
|
||||
output += encode((temp >> 4) & 0x3F)
|
||||
output += encode((temp << 2) & 0x3F)
|
||||
output += '='
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
exports.toByteArray = b64ToByteArray
|
||||
exports.fromByteArray = uint8ToBase64
|
||||
exports.toByteArray = b64ToByteArray
|
||||
exports.fromByteArray = uint8ToBase64
|
||||
}(typeof exports === 'undefined' ? (this.base64js = {}) : exports))
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
$(function() {
|
||||
$('.btn-clipboard').tooltip({
|
||||
trigger: 'click',
|
||||
placement: 'bottom'
|
||||
});
|
||||
/* global gettext */
|
||||
$(function () {
|
||||
$('.btn-clipboard').tooltip({
|
||||
trigger: 'click',
|
||||
placement: 'bottom'
|
||||
})
|
||||
|
||||
function setTooltip(btn, message) {
|
||||
$(btn).tooltip('hide')
|
||||
.attr('data-original-title', message)
|
||||
.tooltip('show');
|
||||
}
|
||||
function setTooltip (btn, message) {
|
||||
$(btn).tooltip('hide')
|
||||
.attr('data-original-title', message)
|
||||
.tooltip('show')
|
||||
}
|
||||
|
||||
function hideTooltip(btn) {
|
||||
setTimeout(function() {
|
||||
$(btn).tooltip('hide');
|
||||
}, 1000);
|
||||
}
|
||||
function hideTooltip (btn) {
|
||||
setTimeout(function () {
|
||||
$(btn).tooltip('hide')
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
var clipboard = new Clipboard('.btn-clipboard');
|
||||
let clipboard = new Clipboard('.btn-clipboard')
|
||||
|
||||
clipboard.on('success', function(e) {
|
||||
if (e.text.length > 0) {
|
||||
setTooltip(e.trigger, gettext('Copied!'));
|
||||
hideTooltip(e.trigger);
|
||||
}
|
||||
});
|
||||
|
||||
clipboard.on('error', function(e) {
|
||||
setTooltip(e.trigger, gettext('Press Ctrl-C to copy!'));
|
||||
hideTooltip(e.trigger);
|
||||
});
|
||||
});
|
||||
clipboard.on('success', function (e) {
|
||||
if (e.text.length > 0) {
|
||||
setTooltip(e.trigger, gettext('Copied!'))
|
||||
hideTooltip(e.trigger)
|
||||
}
|
||||
})
|
||||
|
||||
clipboard.on('error', function (e) {
|
||||
setTooltip(e.trigger, gettext('Press Ctrl-C to copy!'))
|
||||
hideTooltip(e.trigger)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -8,79 +8,75 @@
|
||||
* Under MIT License
|
||||
* Modified by Raphael Michel
|
||||
*/
|
||||
;(function($, window, document, undefined) {
|
||||
;(function ($, window, document, undefined) {
|
||||
let pluginName = 'metisMenu',
|
||||
defaults = {
|
||||
toggle: true,
|
||||
}
|
||||
|
||||
var pluginName = "metisMenu",
|
||||
defaults = {
|
||||
toggle: true,
|
||||
};
|
||||
function Plugin (element, options) {
|
||||
this.element = $(element)
|
||||
this.settings = $.extend({}, defaults, options)
|
||||
this._defaults = defaults
|
||||
this._name = pluginName
|
||||
this.init()
|
||||
}
|
||||
|
||||
function Plugin(element, options) {
|
||||
this.element = $(element);
|
||||
this.settings = $.extend({}, defaults, options);
|
||||
this._defaults = defaults;
|
||||
this._name = pluginName;
|
||||
this.init();
|
||||
}
|
||||
Plugin.prototype = {
|
||||
init: function () {
|
||||
let $this = this.element,
|
||||
$toggle = this.settings.toggle,
|
||||
obj = this
|
||||
|
||||
Plugin.prototype = {
|
||||
init: function() {
|
||||
if (this.isIE() <= 9) {
|
||||
$this.find('li.active').has('ul').children('ul').collapse('show')
|
||||
$this.find('li').not('.active').has('ul').children('ul').collapse('hide')
|
||||
} else {
|
||||
$this.find('li.active').has('ul').children('ul').addClass('collapse in')
|
||||
$this.find('li').not('.active').has('ul').children('ul').addClass('collapse')
|
||||
}
|
||||
|
||||
var $this = this.element,
|
||||
$toggle = this.settings.toggle,
|
||||
obj = this;
|
||||
$this.find('li').has('ul').children('a.arrow').on('click' + '.' + pluginName, function (e) {
|
||||
e.preventDefault()
|
||||
$(this).blur()
|
||||
|
||||
if (this.isIE() <= 9) {
|
||||
$this.find("li.active").has("ul").children("ul").collapse("show");
|
||||
$this.find("li").not(".active").has("ul").children("ul").collapse("hide");
|
||||
} else {
|
||||
$this.find("li.active").has("ul").children("ul").addClass("collapse in");
|
||||
$this.find("li").not(".active").has("ul").children("ul").addClass("collapse");
|
||||
}
|
||||
$(this).parent('li').toggleClass('active').children('ul').collapse('toggle')
|
||||
|
||||
$this.find("li").has("ul").children("a.arrow").on("click" + "." + pluginName, function(e) {
|
||||
e.preventDefault();
|
||||
$(this).blur();
|
||||
if ($toggle) {
|
||||
$(this).parent('li').siblings().removeClass('active').children('ul.in').collapse('hide')
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
$(this).parent("li").toggleClass("active").children("ul").collapse("toggle");
|
||||
isIE: function () { // https://gist.github.com/padolsey/527683
|
||||
let undef,
|
||||
v = 3,
|
||||
div = document.createElement('div'),
|
||||
all = div.getElementsByTagName('i')
|
||||
|
||||
if ($toggle) {
|
||||
$(this).parent("li").siblings().removeClass("active").children("ul.in").collapse("hide");
|
||||
}
|
||||
while (
|
||||
div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->',
|
||||
all[0]
|
||||
) {
|
||||
return v > 4 ? v : undef
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
},
|
||||
remove: function () {
|
||||
this.element.off('.' + pluginName)
|
||||
this.element.removeData(pluginName)
|
||||
}
|
||||
|
||||
isIE: function() { //https://gist.github.com/padolsey/527683
|
||||
var undef,
|
||||
v = 3,
|
||||
div = document.createElement("div"),
|
||||
all = div.getElementsByTagName("i");
|
||||
}
|
||||
|
||||
while (
|
||||
div.innerHTML = "<!--[if gt IE " + (++v) + "]><i></i><![endif]-->",
|
||||
all[0]
|
||||
) {
|
||||
return v > 4 ? v : undef;
|
||||
}
|
||||
},
|
||||
|
||||
remove: function() {
|
||||
this.element.off("." + pluginName);
|
||||
this.element.removeData(pluginName);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
$.fn[pluginName] = function(options) {
|
||||
this.each(function () {
|
||||
var el = $(this);
|
||||
if (el.data(pluginName)) {
|
||||
el.data(pluginName).remove();
|
||||
}
|
||||
el.data(pluginName, new Plugin(this, options));
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
})(jQuery, window, document);
|
||||
$.fn[pluginName] = function (options) {
|
||||
this.each(function () {
|
||||
let el = $(this)
|
||||
if (el.data(pluginName)) {
|
||||
el.data(pluginName).remove()
|
||||
}
|
||||
el.data(pluginName, new Plugin(this, options))
|
||||
})
|
||||
return this
|
||||
}
|
||||
})(jQuery, window, document)
|
||||
|
||||
@@ -1,35 +1,34 @@
|
||||
/*global $ */
|
||||
/*
|
||||
Based on https://github.com/BlackrockDigital/startbootstrap-sb-admin-2
|
||||
Copyright 2013-2016 Blackrock Digital LLC
|
||||
MIT License
|
||||
Modified by Raphael Michel
|
||||
*/
|
||||
//Loads the correct sidebar on window load,
|
||||
//collapses the sidebar on window resize.
|
||||
// Loads the correct sidebar on window load,
|
||||
// collapses the sidebar on window resize.
|
||||
// Sets the min-height of #page-wrapper to window size
|
||||
$(window).bind("load resize", function () {
|
||||
'use strict';
|
||||
var topOffset = 50,
|
||||
width = (this.window.innerWidth > 0) ? this.window.innerWidth : this.screen.width;
|
||||
if (width < 768) {
|
||||
$('div.navbar-collapse').addClass('collapse');
|
||||
topOffset = 100; // 2-row-menu
|
||||
} else {
|
||||
$('div.navbar-collapse').removeClass('collapse');
|
||||
}
|
||||
$(window).bind('load resize', function () {
|
||||
'use strict'
|
||||
let topOffset = 50,
|
||||
width = (this.window.innerWidth > 0) ? this.window.innerWidth : this.screen.width
|
||||
if (width < 768) {
|
||||
$('div.navbar-collapse').addClass('collapse')
|
||||
topOffset = 100 // 2-row-menu
|
||||
} else {
|
||||
$('div.navbar-collapse').removeClass('collapse')
|
||||
}
|
||||
|
||||
var height = ((this.window.innerHeight > 0) ? this.window.innerHeight : this.screen.height) - 1;
|
||||
height = height - topOffset;
|
||||
if (height < 1) height = 1;
|
||||
if (height > topOffset) {
|
||||
$("#page-wrapper").css("min-height", (height) + "px");
|
||||
}
|
||||
});
|
||||
let height = ((this.window.innerHeight > 0) ? this.window.innerHeight : this.screen.height) - 1
|
||||
height = height - topOffset
|
||||
if (height < 1) height = 1
|
||||
if (height > topOffset) {
|
||||
$('#page-wrapper').css('min-height', (height) + 'px')
|
||||
}
|
||||
})
|
||||
$(function () {
|
||||
'use strict';
|
||||
$('ul.nav ul.nav-second-level a.active').parent().parent().addClass('in').parent().addClass('active');
|
||||
$('#side-menu').metisMenu({
|
||||
'toggle': false,
|
||||
});
|
||||
});
|
||||
'use strict'
|
||||
$('ul.nav ul.nav-second-level a.active').parent().parent().addClass('in').parent().addClass('active')
|
||||
$('#side-menu').metisMenu({
|
||||
toggle: false,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1 +1 @@
|
||||
document.forms[0].submit();
|
||||
document.forms[0].submit()
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
/*global $,gettext*/
|
||||
|
||||
/* global add_log_expand_handlers */
|
||||
$(function () {
|
||||
if ($("div[data-lazy-id]").length == 0) {
|
||||
return;
|
||||
}
|
||||
$.getJSON("widgets.json" + ($("select[name='subevent']").val() ? "?subevent=" + $("select[name='subevent']").val() : ""), function (data) {
|
||||
$.each(data.widgets, function (k, v) {
|
||||
$("[data-lazy-id=" + v.lazy + "]").removeClass("widget-lazy-loading");
|
||||
$("[data-lazy-id=" + v.lazy + "] .widget").html(v.content);
|
||||
});
|
||||
});
|
||||
});
|
||||
if ($('div[data-lazy-id]').length == 0) {
|
||||
return
|
||||
}
|
||||
$.getJSON('widgets.json' + ($('select[name=\'subevent\']').val() ? '?subevent=' + $('select[name=\'subevent\']').val() : ''), function (data) {
|
||||
$.each(data.widgets, function (_k, v) {
|
||||
$('[data-lazy-id=' + v.lazy + ']').removeClass('widget-lazy-loading')
|
||||
$('[data-lazy-id=' + v.lazy + '] .widget').html(v.content)
|
||||
})
|
||||
})
|
||||
})
|
||||
$(function () {
|
||||
if ($("#logs_target").length == 0) {
|
||||
return;
|
||||
}
|
||||
$.get("dashboard/partials/logs", function (data) {
|
||||
$("#logs_target").html(data)
|
||||
add_log_expand_handlers($("#logs_target"))
|
||||
});
|
||||
$.get("dashboard/partials/warnings", function (data) {
|
||||
$("#warnings_loading").remove()
|
||||
$("#warnings_target").html(data)
|
||||
});
|
||||
});
|
||||
if ($('#logs_target').length == 0) {
|
||||
return
|
||||
}
|
||||
$.get('dashboard/partials/logs', function (data) {
|
||||
$('#logs_target').html(data)
|
||||
add_log_expand_handlers($('#logs_target'))
|
||||
})
|
||||
$.get('dashboard/partials/warnings', function (data) {
|
||||
$('#warnings_loading').remove()
|
||||
$('#warnings_target').html(data)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
/*globals $, Morris, gettext, RRule, RRuleSet*/
|
||||
|
||||
$(function () {
|
||||
var update = function () {
|
||||
$.getJSON(location.href + '?ajax=true', {}, function (data) {
|
||||
if (data.initialized) {
|
||||
location.reload();
|
||||
} else {
|
||||
window.setTimeout(update, 500);
|
||||
}
|
||||
});
|
||||
};
|
||||
window.setTimeout(update, 500);
|
||||
});
|
||||
let update = function () {
|
||||
$.getJSON(location.href + '?ajax=true', {}, function (data) {
|
||||
if (data.initialized) {
|
||||
location.reload()
|
||||
} else {
|
||||
window.setTimeout(update, 500)
|
||||
}
|
||||
})
|
||||
}
|
||||
window.setTimeout(update, 500)
|
||||
})
|
||||
|
||||
@@ -1,91 +1,95 @@
|
||||
/*global $, Sortable*/
|
||||
/* global Sortable */
|
||||
$(function () {
|
||||
const allContainers = $("[data-dnd-url]");
|
||||
function updateAllSortButtonStates() {
|
||||
allContainers.each(function() { updateSortButtonState($(this)); });
|
||||
}
|
||||
function updateSortButtonState(container) {
|
||||
var disabledUp = container.find(".sortable-up:disabled"),
|
||||
firstUp = container.find(">tr[data-dnd-id] .sortable-up").first();
|
||||
if (disabledUp.length && disabledUp.get(0) !== firstUp.get(0)) {
|
||||
disabledUp.prop("disabled", false);
|
||||
firstUp.prop("disabled", true);
|
||||
}
|
||||
const allContainers = $('[data-dnd-url]')
|
||||
function updateAllSortButtonStates () {
|
||||
allContainers.each(function () {
|
||||
updateSortButtonState($(this))
|
||||
})
|
||||
}
|
||||
function updateSortButtonState (container) {
|
||||
let disabledUp = container.find('.sortable-up:disabled'),
|
||||
firstUp = container.find('>tr[data-dnd-id] .sortable-up').first()
|
||||
if (disabledUp.length && disabledUp.get(0) !== firstUp.get(0)) {
|
||||
disabledUp.prop('disabled', false)
|
||||
firstUp.prop('disabled', true)
|
||||
}
|
||||
|
||||
var disabledDown = container.find(".sortable-down:disabled"),
|
||||
lastDown = container.find(">tr[data-dnd-id] .sortable-down").last();
|
||||
if (disabledDown.length && disabledDown.get(0) !== lastDown.get(0)) {
|
||||
disabledDown.prop("disabled", false);
|
||||
lastDown.prop("disabled", true);
|
||||
}
|
||||
}
|
||||
let disabledDown = container.find('.sortable-down:disabled'),
|
||||
lastDown = container.find('>tr[data-dnd-id] .sortable-down').last()
|
||||
if (disabledDown.length && disabledDown.get(0) !== lastDown.get(0)) {
|
||||
disabledDown.prop('disabled', false)
|
||||
lastDown.prop('disabled', true)
|
||||
}
|
||||
}
|
||||
|
||||
let didSort = false, lastClick = 0;
|
||||
allContainers.each(function(){
|
||||
const container = $(this),
|
||||
url = container.data("dnd-url"),
|
||||
handle = $('<span class="btn btn-default btn-sm dnd-sort-handle"><i class="fa fa-arrows"></i></span>');
|
||||
let didSort = false, lastClick = 0
|
||||
allContainers.each(function () {
|
||||
const container = $(this),
|
||||
url = container.data('dnd-url'),
|
||||
handle = $('<span class="btn btn-default btn-sm dnd-sort-handle"><i class="fa fa-arrows"></i></span>')
|
||||
|
||||
container.find(".dnd-container").append(handle);
|
||||
if (!sessionStorage.dndShowMoveButtons) {
|
||||
container.find(".sortable-up, .sortable-down").addClass("sr-only").on("click", function () {
|
||||
sessionStorage.dndShowMoveButtons = 'true';
|
||||
});
|
||||
}
|
||||
if (container.find("[data-dnd-id]").length < 2 && !container.data("dnd-group")) {
|
||||
handle.addClass("disabled");
|
||||
return;
|
||||
}
|
||||
function maybeShowSortButtons() {
|
||||
if (Date.now() - lastClick < 3000) {
|
||||
$("[data-dnd-url] .sortable-up, [data-dnd-url] .sortable-down").removeClass("sr-only");
|
||||
updateAllSortButtonStates();
|
||||
}
|
||||
lastClick = Date.now();
|
||||
}
|
||||
container.find(".dnd-sort-handle").on("mouseup", maybeShowSortButtons);
|
||||
const group = container.data("dnd-group");
|
||||
const containers = group ? container.parent().find('[data-dnd-group="' + group + '"]') : container;
|
||||
Sortable.create(container.get(0), {
|
||||
filter: ".sortable-disabled",
|
||||
handle: ".dnd-sort-handle",
|
||||
group: group,
|
||||
onMove: function (evt) {
|
||||
return evt.related.className.indexOf('sortable-disabled') === -1;
|
||||
},
|
||||
onStart: function (evt) {
|
||||
containers.addClass("sortable-dragarea");
|
||||
container.parent().addClass("sortable-sorting");
|
||||
didSort = false;
|
||||
},
|
||||
onEnd: function (evt) {
|
||||
containers.removeClass("sortable-dragarea");
|
||||
container.parent().removeClass("sortable-sorting");
|
||||
if (!didSort) {
|
||||
maybeShowSortButtons();
|
||||
} else {
|
||||
$("[data-dnd-url] .sortable-up, [data-dnd-url] .sortable-down").addClass("sr-only");
|
||||
delete sessionStorage.dndShowMoveButtons;
|
||||
}
|
||||
},
|
||||
onSort: function (evt){
|
||||
if (evt.target !== evt.to) return;
|
||||
didSort = true;
|
||||
container.find('.dnd-container').append(handle)
|
||||
if (!sessionStorage.dndShowMoveButtons) {
|
||||
container.find('.sortable-up, .sortable-down').addClass('sr-only').on('click', function () {
|
||||
sessionStorage.dndShowMoveButtons = 'true'
|
||||
})
|
||||
}
|
||||
if (container.find('[data-dnd-id]').length < 2 && !container.data('dnd-group')) {
|
||||
handle.addClass('disabled')
|
||||
return
|
||||
}
|
||||
function maybeShowSortButtons () {
|
||||
if (Date.now() - lastClick < 3000) {
|
||||
$('[data-dnd-url] .sortable-up, [data-dnd-url] .sortable-down').removeClass('sr-only')
|
||||
updateAllSortButtonStates()
|
||||
}
|
||||
lastClick = Date.now()
|
||||
}
|
||||
container.find('.dnd-sort-handle').on('mouseup', maybeShowSortButtons)
|
||||
const group = container.data('dnd-group')
|
||||
const containers = group ? container.parent().find('[data-dnd-group="' + group + '"]') : container
|
||||
Sortable.create(container.get(0), {
|
||||
filter: '.sortable-disabled',
|
||||
handle: '.dnd-sort-handle',
|
||||
group: group,
|
||||
onMove: function (evt) {
|
||||
return evt.related.className.indexOf('sortable-disabled') === -1
|
||||
},
|
||||
onStart: function () {
|
||||
containers.addClass('sortable-dragarea')
|
||||
container.parent().addClass('sortable-sorting')
|
||||
didSort = false
|
||||
},
|
||||
onEnd: function () {
|
||||
containers.removeClass('sortable-dragarea')
|
||||
container.parent().removeClass('sortable-sorting')
|
||||
if (!didSort) {
|
||||
maybeShowSortButtons()
|
||||
} else {
|
||||
$('[data-dnd-url] .sortable-up, [data-dnd-url] .sortable-down').addClass('sr-only')
|
||||
delete sessionStorage.dndShowMoveButtons
|
||||
}
|
||||
},
|
||||
onSort: function (evt) {
|
||||
if (evt.target !== evt.to) return
|
||||
didSort = true
|
||||
|
||||
const ids = container.find("[data-dnd-id]").toArray().map(function (e) { return e.dataset.dndId; });
|
||||
$.ajax(
|
||||
{
|
||||
'type': 'POST',
|
||||
'url': url,
|
||||
'headers': {'X-CSRFToken': $("input[name=csrfmiddlewaretoken]").val()},
|
||||
'data': JSON.stringify({
|
||||
ids: ids
|
||||
}),
|
||||
'contentType': "application/json",
|
||||
'timeout': 30000
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
const ids = container.find('[data-dnd-id]').toArray().map(function (e) {
|
||||
return e.dataset.dndId
|
||||
})
|
||||
$.ajax(
|
||||
{
|
||||
type: 'POST',
|
||||
url: url,
|
||||
headers: { 'X-CSRFToken': $('input[name=csrfmiddlewaretoken]').val() },
|
||||
data: JSON.stringify({
|
||||
ids: ids
|
||||
}),
|
||||
contentType: 'application/json',
|
||||
timeout: 30000
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,3 @@
|
||||
$(function () {
|
||||
$("input, select, textarea").not(":disabled").focus();
|
||||
});
|
||||
$('input, select, textarea').not(':disabled').focus()
|
||||
})
|
||||
|
||||
@@ -1,201 +1,198 @@
|
||||
/*globals $*/
|
||||
/* globals $ */
|
||||
|
||||
$(document).on("pretix:bind-forms", function () {
|
||||
function cleanup(l) {
|
||||
return $.trim(l.replace(/\n/g, ", "));
|
||||
}
|
||||
function combine($sel) {
|
||||
var parts = [
|
||||
$sel.filter("[name*=street]").val(),
|
||||
$sel.filter("[name*=zipcode]").val(),
|
||||
$sel.filter("[name*=city]").val(),
|
||||
$sel.filter("[name*=state]").val(),
|
||||
$sel.filter("[name*=country]").find("option:selected").text(),
|
||||
$sel.filter("[name*=location]").val(),
|
||||
]
|
||||
var res = "";
|
||||
for (var val of parts) {
|
||||
if (val) {
|
||||
if (res) {
|
||||
res += ", "
|
||||
}
|
||||
res += val
|
||||
}
|
||||
}
|
||||
return cleanup(res)
|
||||
}
|
||||
$(".geodata-section").each(function () {
|
||||
// Geocoding
|
||||
// detach notifications and append them to first label (should be from location)
|
||||
var $notifications = $(".geodata-autoupdate", this).detach().appendTo($("label", this).first());
|
||||
var $lat = $("input[name$=geo_lat]", this).first();
|
||||
var $lon = $("input[name$=geo_lon]", this).first();
|
||||
var lat;
|
||||
var lon;
|
||||
var $updateButton = $("[data-action=update]", this);
|
||||
$(document).on('pretix:bind-forms', function () {
|
||||
function cleanup (l) {
|
||||
return $.trim(l.replace(/\n/g, ', '))
|
||||
}
|
||||
function combine ($sel) {
|
||||
let parts = [
|
||||
$sel.filter('[name*=street]').val(),
|
||||
$sel.filter('[name*=zipcode]').val(),
|
||||
$sel.filter('[name*=city]').val(),
|
||||
$sel.filter('[name*=state]').val(),
|
||||
$sel.filter('[name*=country]').find('option:selected').text(),
|
||||
$sel.filter('[name*=location]').val(),
|
||||
]
|
||||
let res = ''
|
||||
for (let val of parts) {
|
||||
if (val) {
|
||||
if (res) {
|
||||
res += ', '
|
||||
}
|
||||
res += val
|
||||
}
|
||||
}
|
||||
return cleanup(res)
|
||||
}
|
||||
$('.geodata-section').each(function () {
|
||||
// Geocoding
|
||||
// detach notifications and append them to first label (should be from location)
|
||||
let $notifications = $('.geodata-autoupdate', this).detach().appendTo($('label', this).first())
|
||||
let $lat = $('input[name$=geo_lat]', this).first()
|
||||
let $lon = $('input[name$=geo_lon]', this).first()
|
||||
let lat
|
||||
let lon
|
||||
let $updateButton = $('[data-action=update]', this)
|
||||
|
||||
var $location;
|
||||
// The .geodata-section is expected to include either...
|
||||
// ... an English "location" field
|
||||
if ($("textarea[lang=en], input[lang=en]", this).length) {
|
||||
$location = $("textarea[lang=en], input[lang=en], select", this).not("[name*=geo_]");
|
||||
}
|
||||
let $location
|
||||
// The .geodata-section is expected to include either...
|
||||
// ... an English "location" field
|
||||
if ($('textarea[lang=en], input[lang=en]', this).length) {
|
||||
$location = $('textarea[lang=en], input[lang=en], select', this).not('[name*=geo_]')
|
||||
}
|
||||
|
||||
// ... a "location" field in any other language
|
||||
if (!$location || !$location.length) {
|
||||
var lang = $("textarea, input[type=text]", this).not("[name*=geo_]").first().attr("lang");
|
||||
if (lang) {
|
||||
$location = $("textarea[lang=" + lang + "], input[lang=" + lang + "], select", this);
|
||||
}
|
||||
}
|
||||
// ... a "location" field in any other language
|
||||
if (!$location || !$location.length) {
|
||||
let lang = $('textarea, input[type=text]', this).not('[name*=geo_]').first().attr('lang')
|
||||
if (lang) {
|
||||
$location = $('textarea[lang=' + lang + '], input[lang=' + lang + '], select', this)
|
||||
}
|
||||
}
|
||||
|
||||
// ... or a set of fields like a full address form
|
||||
if (!$location || !$location.length) {
|
||||
$location = $("textarea, input, select", this).not("[name*=geo_]");
|
||||
}
|
||||
// ... or a set of fields like a full address form
|
||||
if (!$location || !$location.length) {
|
||||
$location = $('textarea, input, select', this).not('[name*=geo_]')
|
||||
}
|
||||
|
||||
if (!$lat.length || !$lon.length || !$location.length) {
|
||||
return;
|
||||
}
|
||||
if (!$lat.length || !$lon.length || !$location.length) {
|
||||
return
|
||||
}
|
||||
|
||||
var debounceLoad, debounceLatLonChange, delayUpdateDismissal;
|
||||
var touched = $lat.val() !== "";
|
||||
var xhr;
|
||||
var lastLocation = combine($location);
|
||||
let debounceLoad, debounceLatLonChange, delayUpdateDismissal
|
||||
let touched = $lat.val() !== ''
|
||||
let xhr
|
||||
let lastLocation = combine($location)
|
||||
|
||||
function load() {
|
||||
window.clearTimeout(debounceLoad);
|
||||
if (xhr) {
|
||||
xhr.abort();
|
||||
xhr = null;
|
||||
}
|
||||
function load () {
|
||||
window.clearTimeout(debounceLoad)
|
||||
if (xhr) {
|
||||
xhr.abort()
|
||||
xhr = null
|
||||
}
|
||||
|
||||
var q = combine($location);
|
||||
if (q === "" || q === lastLocation) return;
|
||||
let q = combine($location)
|
||||
if (q === '' || q === lastLocation) return
|
||||
|
||||
lastLocation = q;
|
||||
$notifications.attr("data-notify", "loading");
|
||||
lastLocation = q
|
||||
$notifications.attr('data-notify', 'loading')
|
||||
|
||||
xhr = $.getJSON('/control/geocode/?q=' + encodeURIComponent(q), function (res) {
|
||||
if (!res.results || !res.results.length) {
|
||||
$notifications.attr("data-notify", "error");
|
||||
return;
|
||||
}
|
||||
xhr = $.getJSON('/control/geocode/?q=' + encodeURIComponent(q), function (res) {
|
||||
if (!res.results || !res.results.length) {
|
||||
$notifications.attr('data-notify', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
lat = res.results[0].lat;
|
||||
lon = res.results[0].lon;
|
||||
if ($lat.val() == lat && $lon.val() == lon) {
|
||||
$notifications.attr("data-notify", "");
|
||||
}
|
||||
else if (touched) {
|
||||
$notifications.attr("data-notify", "confirm");
|
||||
}
|
||||
else {
|
||||
$notifications.attr("data-notify", "");
|
||||
$lat.val(lat);
|
||||
$lon.val(lon);
|
||||
center(13);
|
||||
}
|
||||
})
|
||||
}
|
||||
lat = res.results[0].lat
|
||||
lon = res.results[0].lon
|
||||
if ($lat.val() == lat && $lon.val() == lon) {
|
||||
$notifications.attr('data-notify', '')
|
||||
} else if (touched) {
|
||||
$notifications.attr('data-notify', 'confirm')
|
||||
} else {
|
||||
$notifications.attr('data-notify', '')
|
||||
$lat.val(lat)
|
||||
$lon.val(lon)
|
||||
center(13)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
$lat.add($lon).change(function () {
|
||||
if (this.value !== "") touched = true;
|
||||
center(13);
|
||||
}).keyup(function () {
|
||||
window.clearTimeout(debounceLatLonChange);
|
||||
debounceLatLonChange = window.setTimeout(center, 300);
|
||||
});
|
||||
$lat.add($lon).change(function () {
|
||||
if (this.value !== '') touched = true
|
||||
center(13)
|
||||
}).keyup(function () {
|
||||
window.clearTimeout(debounceLatLonChange)
|
||||
debounceLatLonChange = window.setTimeout(center, 300)
|
||||
})
|
||||
|
||||
$location.change(load);
|
||||
$location.keyup(function () {
|
||||
window.clearTimeout(debounceLoad);
|
||||
debounceLoad = window.setTimeout(load, 1000);
|
||||
if ($notifications.attr("data-notify") == "confirm" && lastLocation !== cleanup(this.value)) $notifications.attr("data-notify", "");
|
||||
});
|
||||
$location.change(load)
|
||||
$location.keyup(function () {
|
||||
window.clearTimeout(debounceLoad)
|
||||
debounceLoad = window.setTimeout(load, 1000)
|
||||
if ($notifications.attr('data-notify') == 'confirm' && lastLocation !== cleanup(this.value)) $notifications.attr('data-notify', '')
|
||||
})
|
||||
|
||||
$updateButton.click(function() {
|
||||
$lat.val(lat);
|
||||
$lon.val(lon).trigger("change");// change-event is needed by bulk-edit
|
||||
touched = false;
|
||||
center(13);
|
||||
$notifications.attr("data-notify", "updated");
|
||||
delayUpdateDismissal = window.setTimeout(function() {
|
||||
if ($notifications.attr("data-notify") == "updated") $notifications.attr("data-notify", "");
|
||||
}, 2500);
|
||||
});
|
||||
$updateButton.click(function () {
|
||||
$lat.val(lat)
|
||||
$lon.val(lon).trigger('change')// change-event is needed by bulk-edit
|
||||
touched = false
|
||||
center(13)
|
||||
$notifications.attr('data-notify', 'updated')
|
||||
delayUpdateDismissal = window.setTimeout(function () {
|
||||
if ($notifications.attr('data-notify') == 'updated') $notifications.attr('data-notify', '')
|
||||
}, 2500)
|
||||
})
|
||||
|
||||
// Map
|
||||
var $grp = $(".geodata-group", this);
|
||||
var tiles = $grp.attr("data-tiles");
|
||||
var attrib = $grp.attr("data-attrib");
|
||||
if (tiles) {
|
||||
var $map = $("<div>");
|
||||
$grp.append($("<div>").addClass("col-md-9 col-md-offset-3").append($map));
|
||||
var map = L.map($map.get(0));
|
||||
L.tileLayer(tiles, {
|
||||
attribution: attrib,
|
||||
maxZoom: 18,
|
||||
}).addTo(map);
|
||||
// Map
|
||||
let $grp = $('.geodata-group', this)
|
||||
let tiles = $grp.attr('data-tiles')
|
||||
let attrib = $grp.attr('data-attrib')
|
||||
if (tiles) {
|
||||
let $map = $('<div>')
|
||||
$grp.append($('<div>').addClass('col-md-9 col-md-offset-3').append($map))
|
||||
let map = L.map($map.get(0))
|
||||
L.tileLayer(tiles, {
|
||||
attribution: attrib,
|
||||
maxZoom: 18,
|
||||
}).addTo(map)
|
||||
|
||||
function getpoint() {
|
||||
if ($lat.val() !== "" && $lon.val() !== "") {
|
||||
var p = [parseFloat($lat.val().replace(",", ".")), parseFloat($lon.val().replace(",", "."))];
|
||||
// Clip to valid ranges. Very invalid lon/lat values can even lead to browser crashes in leaflet apparently
|
||||
if (p[0] < -90) p[0] = -90
|
||||
if (p[0] > 90) p[0] = 90
|
||||
if (p[1] < -180) p[1] = -180
|
||||
if (p[1] > 180) p[1] = 180
|
||||
return p
|
||||
} else {
|
||||
return [0.0, 0.0];
|
||||
}
|
||||
}
|
||||
function getpoint () {
|
||||
if ($lat.val() !== '' && $lon.val() !== '') {
|
||||
let p = [parseFloat($lat.val().replace(',', '.')), parseFloat($lon.val().replace(',', '.'))]
|
||||
// Clip to valid ranges. Very invalid lon/lat values can even lead to browser crashes in leaflet apparently
|
||||
if (p[0] < -90) p[0] = -90
|
||||
if (p[0] > 90) p[0] = 90
|
||||
if (p[1] < -180) p[1] = -180
|
||||
if (p[1] > 180) p[1] = 180
|
||||
return p
|
||||
} else {
|
||||
return [0.0, 0.0]
|
||||
}
|
||||
}
|
||||
|
||||
var marker = L.marker(getpoint(), {
|
||||
draggable: 'true',
|
||||
icon: L.icon({
|
||||
iconUrl: $grp.attr("data-icon"),
|
||||
shadowUrl: $grp.attr("data-shadow"),
|
||||
iconSize: [25, 41],
|
||||
iconAnchor: [12, 41],
|
||||
popupAnchor: [1, -34],
|
||||
tooltipAnchor: [16, -28],
|
||||
shadowSize: [41, 41]
|
||||
})
|
||||
});
|
||||
marker.addTo(map);
|
||||
marker.on("dragend", function (event) {
|
||||
var position = marker.getLatLng();
|
||||
marker.setLatLng(position, {
|
||||
draggable: 'true'
|
||||
}).bindPopup(position).update();
|
||||
$lat.val(position.lat.toFixed(7));
|
||||
$lon.val(position.lng.toFixed(7));
|
||||
touched = true;
|
||||
center(null);
|
||||
});
|
||||
let marker = L.marker(getpoint(), {
|
||||
draggable: 'true',
|
||||
icon: L.icon({
|
||||
iconUrl: $grp.attr('data-icon'),
|
||||
shadowUrl: $grp.attr('data-shadow'),
|
||||
iconSize: [25, 41],
|
||||
iconAnchor: [12, 41],
|
||||
popupAnchor: [1, -34],
|
||||
tooltipAnchor: [16, -28],
|
||||
shadowSize: [41, 41]
|
||||
})
|
||||
})
|
||||
marker.addTo(map)
|
||||
marker.on('dragend', function (event) {
|
||||
let position = marker.getLatLng()
|
||||
marker.setLatLng(position, {
|
||||
draggable: 'true'
|
||||
}).bindPopup(position).update()
|
||||
$lat.val(position.lat.toFixed(7))
|
||||
$lon.val(position.lng.toFixed(7))
|
||||
touched = true
|
||||
center(null)
|
||||
})
|
||||
|
||||
function center(zoom) {
|
||||
if ($lat.val() !== "" && $lon.val() !== "") {
|
||||
if (zoom) {
|
||||
map.setView(getpoint(), zoom);
|
||||
} else {
|
||||
map.panTo(getpoint());
|
||||
}
|
||||
marker.setLatLng(getpoint(), {
|
||||
draggable: 'true'
|
||||
}).bindPopup(getpoint()).update();
|
||||
} else {
|
||||
map.fitWorld();
|
||||
}
|
||||
}
|
||||
function center (zoom) {
|
||||
if ($lat.val() !== '' && $lon.val() !== '') {
|
||||
if (zoom) {
|
||||
map.setView(getpoint(), zoom)
|
||||
} else {
|
||||
map.panTo(getpoint())
|
||||
}
|
||||
marker.setLatLng(getpoint(), {
|
||||
draggable: 'true'
|
||||
}).bindPopup(getpoint()).update()
|
||||
} else {
|
||||
map.fitWorld()
|
||||
}
|
||||
}
|
||||
|
||||
center(13);
|
||||
} else {
|
||||
function center(zoom) {
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
center(13)
|
||||
} else {
|
||||
function center (zoom) {
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
$(function () {
|
||||
hideDeselected(false);
|
||||
hideDeselected(false)
|
||||
|
||||
function hideDeselected(animate) {
|
||||
var v = $("input[name='quota_option']:checked").val(),
|
||||
fn = animate ? 'slideDown' : 'show';
|
||||
if (v === "existing") {
|
||||
hideAll(animate);
|
||||
$("#existing-quota-group").children()[fn]();
|
||||
} else if (v === "new") {
|
||||
hideAll(animate);
|
||||
if ($("#id_quota_add_new_name").val() === "") {
|
||||
$("#id_quota_add_new_name").val($("input[name^=name_]").first().val());
|
||||
}
|
||||
$("#new-quota-group").children()[fn]();
|
||||
} else {
|
||||
hideAll(animate);
|
||||
}
|
||||
}
|
||||
function hideDeselected (animate) {
|
||||
let v = $('input[name=\'quota_option\']:checked').val(),
|
||||
fn = animate ? 'slideDown' : 'show'
|
||||
if (v === 'existing') {
|
||||
hideAll(animate)
|
||||
$('#existing-quota-group').children()[fn]()
|
||||
} else if (v === 'new') {
|
||||
hideAll(animate)
|
||||
if ($('#id_quota_add_new_name').val() === '') {
|
||||
$('#id_quota_add_new_name').val($('input[name^=name_]').first().val())
|
||||
}
|
||||
$('#new-quota-group').children()[fn]()
|
||||
} else {
|
||||
hideAll(animate)
|
||||
}
|
||||
}
|
||||
|
||||
function hideAll(animate) {
|
||||
var fn = animate ? 'slideUp' : 'hide';
|
||||
$("#new-quota-group").children()[fn]();
|
||||
$("#existing-quota-group").children()[fn]();
|
||||
}
|
||||
function hideAll (animate) {
|
||||
let fn = animate ? 'slideUp' : 'hide'
|
||||
$('#new-quota-group').children()[fn]()
|
||||
$('#existing-quota-group').children()[fn]()
|
||||
}
|
||||
|
||||
$("input[name='quota_option']").on('change',
|
||||
function () {
|
||||
hideDeselected(true);
|
||||
}
|
||||
);
|
||||
$('input[name=\'quota_option\']').on('change',
|
||||
function () {
|
||||
hideDeselected(true)
|
||||
}
|
||||
)
|
||||
|
||||
function toggleblock() {
|
||||
$("#new-quota-group").closest('fieldset').toggle(!$("[name=has_variations][value=on]").prop('checked'));
|
||||
}
|
||||
function toggleblock () {
|
||||
$('#new-quota-group').closest('fieldset').toggle(!$('[name=has_variations][value=on]').prop('checked'))
|
||||
}
|
||||
|
||||
$("[name=has_variations]").change(toggleblock);
|
||||
toggleblock();
|
||||
});
|
||||
$('[name=has_variations]').change(toggleblock)
|
||||
toggleblock()
|
||||
})
|
||||
|
||||
@@ -1,75 +1,75 @@
|
||||
function preview_task_callback(data, jqXHR, status) {
|
||||
"use strict";
|
||||
if (data.item) {
|
||||
$('#' + data.item + '_panel').data('ajaxing', false);
|
||||
for (var m in data.msgs){
|
||||
var target = $('div[for=' + data.item + '][lang=' + m +']');
|
||||
if (target.length === 1){
|
||||
target.html(data.msgs[m]);
|
||||
target.find('.placeholder').tooltip();
|
||||
}
|
||||
}
|
||||
}
|
||||
/* global gettext */
|
||||
function preview_task_callback (data, _jqXHR, _status) {
|
||||
'use strict'
|
||||
if (data.item) {
|
||||
$('#' + data.item + '_panel').data('ajaxing', false)
|
||||
for (let m in data.msgs) {
|
||||
let target = $('div[for=' + data.item + '][lang=' + m + ']')
|
||||
if (target.length === 1) {
|
||||
target.html(data.msgs[m])
|
||||
target.find('.placeholder').tooltip()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function preview_task_error(item) {
|
||||
"use strict";
|
||||
return function(jqXHR, textStatus, errorThrown) {
|
||||
$('#' + item + '_panel').data('ajaxing', false);
|
||||
$('#' + item + '_preview div').text(gettext('An error has occurred.'));
|
||||
if (textStatus === "timeout") {
|
||||
alert(gettext("The request took too long. Please try again."));
|
||||
} else {
|
||||
if (jqXHR.status >= 400 && jqXHR.status < 500) {
|
||||
alert(gettext('An error of type {code} occurred.').replace(/\{code\}/, jqXHR.status));
|
||||
} else {
|
||||
alert(gettext('We currently cannot reach the server. Please try again. ' +
|
||||
'Error code: {code}').replace(/\{code\}/, jqXHR.status));
|
||||
}
|
||||
}
|
||||
}
|
||||
function preview_task_error (item) {
|
||||
'use strict'
|
||||
return function (jqXHR, textStatus, _errorThrown) {
|
||||
$('#' + item + '_panel').data('ajaxing', false)
|
||||
$('#' + item + '_preview div').text(gettext('An error has occurred.'))
|
||||
if (textStatus === 'timeout') {
|
||||
alert(gettext('The request took too long. Please try again.'))
|
||||
} else {
|
||||
if (jqXHR.status >= 400 && jqXHR.status < 500) {
|
||||
alert(gettext('An error of type {code} occurred.').replace(/\{code\}/, jqXHR.status))
|
||||
} else {
|
||||
alert(gettext('We currently cannot reach the server. Please try again. '
|
||||
+ 'Error code: {code}').replace(/\{code\}/, jqXHR.status))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mail_preview_setup($el) {
|
||||
$el.find('.mail-preview .placeholder').tooltip();
|
||||
$el.find('a[type=preview]').on('click', function () {
|
||||
var itemName = $(this).closest('.preview-panel').attr('for');
|
||||
if ($('#' + itemName + '_panel').data('ajaxing') || $(this).parent('.active').length !== 0) {
|
||||
return;
|
||||
}
|
||||
function mail_preview_setup ($el) {
|
||||
$el.find('.mail-preview .placeholder').tooltip()
|
||||
$el.find('a[type=preview]').on('click', function () {
|
||||
let itemName = $(this).closest('.preview-panel').attr('for')
|
||||
if ($('#' + itemName + '_panel').data('ajaxing') || $(this).parent('.active').length !== 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// gathering data
|
||||
var parentForm = $(this).closest('form');
|
||||
var previewUrl = $(parentForm).attr('mail-preview-url');
|
||||
var token = $(parentForm).find('input[name=csrfmiddlewaretoken]').val();
|
||||
var dataString = 'item=' + itemName + '&csrfmiddlewaretoken=' + token;
|
||||
$('#' + itemName + '_edit textarea, #' + itemName + '_edit input').each(function () {
|
||||
dataString += '&' + $(this).serialize();
|
||||
});
|
||||
// gathering data
|
||||
let parentForm = $(this).closest('form')
|
||||
let previewUrl = $(parentForm).attr('mail-preview-url')
|
||||
let token = $(parentForm).find('input[name=csrfmiddlewaretoken]').val()
|
||||
let dataString = 'item=' + itemName + '&csrfmiddlewaretoken=' + token
|
||||
$('#' + itemName + '_edit textarea, #' + itemName + '_edit input').each(function () {
|
||||
dataString += '&' + $(this).serialize()
|
||||
})
|
||||
|
||||
// prepare for ajax
|
||||
$('#' + itemName + '_panel').data('ajaxing', true);
|
||||
$('#' + itemName + '_preview div').text(gettext('Generating messages …'));
|
||||
// prepare for ajax
|
||||
$('#' + itemName + '_panel').data('ajaxing', true)
|
||||
$('#' + itemName + '_preview div').text(gettext('Generating messages …'))
|
||||
|
||||
$.ajax(
|
||||
{
|
||||
'type': 'POST',
|
||||
'url': previewUrl,
|
||||
'data': dataString,
|
||||
'success': preview_task_callback,
|
||||
'error': preview_task_error(itemName),
|
||||
'dataType': 'json',
|
||||
'timeout': 60000,
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
$.ajax(
|
||||
{
|
||||
type: 'POST',
|
||||
url: previewUrl,
|
||||
data: dataString,
|
||||
success: preview_task_callback,
|
||||
error: preview_task_error(itemName),
|
||||
dataType: 'json',
|
||||
timeout: 60000,
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
$(function () {
|
||||
"use strict";
|
||||
mail_preview_setup($("body"));
|
||||
$(document).on("pretix:bind-forms", function () {
|
||||
mail_preview_setup($("body"));
|
||||
});
|
||||
});
|
||||
'use strict'
|
||||
mail_preview_setup($('body'))
|
||||
$(document).on('pretix:bind-forms', function () {
|
||||
mail_preview_setup($('body'))
|
||||
})
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,59 +1,62 @@
|
||||
/*global $, gettext*/
|
||||
/* global gettext */
|
||||
$(function () {
|
||||
if (!$(".form-order-change").length) {
|
||||
return;
|
||||
}
|
||||
$(".form-order-change").each(function () {
|
||||
var url = $(this).attr("data-pricecalc-endpoint");
|
||||
var $itemvar = $(this).find("[name*=itemvar]");
|
||||
var $subevent = $(this).find("[name*=subevent]");
|
||||
var $tax_rule = $(this).find("[name*=tax_rule]");
|
||||
var $price = $(this).find("[name*=price]");
|
||||
var update_price = function () {
|
||||
console.log(url);
|
||||
var itemvar = $itemvar.val();
|
||||
var item = null;
|
||||
var variation = null;
|
||||
if (itemvar.indexOf("-")) {
|
||||
item = parseInt(itemvar.split("-")[0]);
|
||||
variation = parseInt(itemvar.split("-")[1]);
|
||||
} else {
|
||||
item = parseInt(itemvar);
|
||||
}
|
||||
$price.closest(".field-container").append("<small class=\"loading-indicator\"><span class=\"fa fa-cog fa-spin\"></span> " +
|
||||
gettext("Calculating default price…") + "</small>");
|
||||
$.ajax(
|
||||
{
|
||||
'type': 'POST',
|
||||
'url': url,
|
||||
'headers': {'X-CSRFToken': $("input[name=csrfmiddlewaretoken]").val()},
|
||||
'data': JSON.stringify({
|
||||
'item': item,
|
||||
'variation': variation,
|
||||
'subevent': $subevent.val(),
|
||||
'tax_rule': $tax_rule.val(),
|
||||
'locale': $("body").attr("data-pretixlocale"),
|
||||
}),
|
||||
'contentType': "application/json",
|
||||
'success': function (data) {
|
||||
$price.val(data.gross_formatted);
|
||||
$tax_rule.val(data.tax_rule);
|
||||
$price.closest(".field-container").find(".loading-indicator").remove();
|
||||
},
|
||||
// 'error': …
|
||||
'context': this,
|
||||
'dataType': 'json',
|
||||
'timeout': 30000
|
||||
}
|
||||
);
|
||||
};
|
||||
$itemvar.on("change", function () { $tax_rule.val(null); update_price() });
|
||||
$tax_rule.on("change", update_price);
|
||||
$subevent.on("change", update_price).on("change", function () {
|
||||
var seat = $(this).closest(".form-order-change").find("[id$=seat]");
|
||||
if (seat.length) {
|
||||
seat.prop("required", !!$subevent.val());
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
if (!$('.form-order-change').length) {
|
||||
return
|
||||
}
|
||||
$('.form-order-change').each(function () {
|
||||
let url = $(this).attr('data-pricecalc-endpoint')
|
||||
let $itemvar = $(this).find('[name*=itemvar]')
|
||||
let $subevent = $(this).find('[name*=subevent]')
|
||||
let $tax_rule = $(this).find('[name*=tax_rule]')
|
||||
let $price = $(this).find('[name*=price]')
|
||||
let update_price = function () {
|
||||
console.log(url)
|
||||
let itemvar = $itemvar.val()
|
||||
let item
|
||||
let variation = null
|
||||
if (itemvar.indexOf('-')) {
|
||||
item = parseInt(itemvar.split('-')[0])
|
||||
variation = parseInt(itemvar.split('-')[1])
|
||||
} else {
|
||||
item = parseInt(itemvar)
|
||||
}
|
||||
$price.closest('.field-container').append('<small class="loading-indicator"><span class="fa fa-cog fa-spin"></span> '
|
||||
+ gettext('Calculating default price…') + '</small>')
|
||||
$.ajax(
|
||||
{
|
||||
type: 'POST',
|
||||
url: url,
|
||||
headers: { 'X-CSRFToken': $('input[name=csrfmiddlewaretoken]').val() },
|
||||
data: JSON.stringify({
|
||||
item: item,
|
||||
variation: variation,
|
||||
subevent: $subevent.val(),
|
||||
tax_rule: $tax_rule.val(),
|
||||
locale: $('body').attr('data-pretixlocale'),
|
||||
}),
|
||||
contentType: 'application/json',
|
||||
success: function (data) {
|
||||
$price.val(data.gross_formatted)
|
||||
$tax_rule.val(data.tax_rule)
|
||||
$price.closest('.field-container').find('.loading-indicator').remove()
|
||||
},
|
||||
// 'error': …
|
||||
context: this,
|
||||
dataType: 'json',
|
||||
timeout: 30000
|
||||
}
|
||||
)
|
||||
}
|
||||
$itemvar.on('change', function () {
|
||||
$tax_rule.val(null)
|
||||
update_price()
|
||||
})
|
||||
$tax_rule.on('change', update_price)
|
||||
$subevent.on('change', update_price).on('change', function () {
|
||||
let seat = $(this).closest('.form-order-change').find('[id$=seat]')
|
||||
if (seat.length) {
|
||||
seat.prop('required', !!$subevent.val())
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
function is_sandbox_supported() {
|
||||
const iframe = document.createElement('iframe');
|
||||
return 'sandbox' in iframe;
|
||||
function is_sandbox_supported () {
|
||||
const iframe = document.createElement('iframe')
|
||||
return 'sandbox' in iframe
|
||||
}
|
||||
|
||||
function safe_render(url, parent) {
|
||||
// Estimate the height that prevents the user from having to scroll on two levels to see the full email
|
||||
const height = (
|
||||
Math.max(400, window.innerHeight - parent.parent().get(0).getBoundingClientRect().top - document.querySelector("footer").getBoundingClientRect().height - 20)
|
||||
) + "px";
|
||||
function safe_render (url, parent) {
|
||||
// Estimate the height that prevents the user from having to scroll on two levels to see the full email
|
||||
const height = (
|
||||
Math.max(400, window.innerHeight - parent.parent().get(0).getBoundingClientRect().top - document.querySelector('footer').getBoundingClientRect().height - 20)
|
||||
) + 'px'
|
||||
|
||||
const iframe = (
|
||||
// Per the HTML spec, a data: URL in an iframe is treated as its own origin:
|
||||
// https://github.com/whatwg/html/pull/1756
|
||||
// It is unclear, if Firefox complies, and the behaviour around data URLs is quite wild:
|
||||
// https://github.com/whatwg/html/issues/12091
|
||||
// Together with the sandbox attribute disallowing all JavaScript, and the fact
|
||||
// that we sanitize the HTML before we even save it to the database, this should
|
||||
// still be the safest way to render HTML in the context of our backend.
|
||||
$("<iframe>")
|
||||
.height(height)
|
||||
.attr("class", "html-email")
|
||||
.attr("src", url)
|
||||
.attr("sandbox", "allow-popups allow-popups-to-escape-sandbox")
|
||||
.attr("csp", "script-src 'none'; font-src 'none'; connect-src 'none'; form-action 'none'; style-src 'unsafe-inline'") // respected only by chrome
|
||||
.prop("credentialless", true) // respected only by chrome
|
||||
);
|
||||
const iframe = (
|
||||
// Per the HTML spec, a data: URL in an iframe is treated as its own origin:
|
||||
// https://github.com/whatwg/html/pull/1756
|
||||
// It is unclear, if Firefox complies, and the behaviour around data URLs is quite wild:
|
||||
// https://github.com/whatwg/html/issues/12091
|
||||
// Together with the sandbox attribute disallowing all JavaScript, and the fact
|
||||
// that we sanitize the HTML before we even save it to the database, this should
|
||||
// still be the safest way to render HTML in the context of our backend.
|
||||
$('<iframe>')
|
||||
.height(height)
|
||||
.attr('class', 'html-email')
|
||||
.attr('src', url)
|
||||
.attr('sandbox', 'allow-popups allow-popups-to-escape-sandbox')
|
||||
.attr('csp', 'script-src \'none\'; font-src \'none\'; connect-src \'none\'; form-action \'none\'; style-src \'unsafe-inline\'') // respected only by chrome
|
||||
.prop('credentialless', true) // respected only by chrome
|
||||
)
|
||||
|
||||
console.log(parent, iframe);
|
||||
parent.append(iframe);
|
||||
console.log(parent, iframe)
|
||||
parent.append(iframe)
|
||||
}
|
||||
|
||||
$(function () {
|
||||
const script_element = $("#mail_body_html");
|
||||
if (!script_element.length) return;
|
||||
if (!is_sandbox_supported()) {
|
||||
// Browser is too old for <iframe sandbox>
|
||||
$(script_element.parent()).text("Please switch to a modern browser to view HTML content safely.");
|
||||
return;
|
||||
}
|
||||
const script_element = $('#mail_body_html')
|
||||
if (!script_element.length) return
|
||||
if (!is_sandbox_supported()) {
|
||||
// Browser is too old for <iframe sandbox>
|
||||
$(script_element.parent()).text('Please switch to a modern browser to view HTML content safely.')
|
||||
return
|
||||
}
|
||||
|
||||
safe_render(JSON.parse(script_element.html()), script_element.parent());
|
||||
});
|
||||
safe_render(JSON.parse(script_element.html()), script_element.parent())
|
||||
})
|
||||
|
||||
@@ -1,84 +1,89 @@
|
||||
$(function() {
|
||||
var plugins = $(".plugin-container").toArray().map(function(el) {
|
||||
return {
|
||||
sortName: el.getAttribute('data-plugin-name').toLowerCase().replace(/pretix /g, ''),
|
||||
name: el.getAttribute('data-plugin-name').toLowerCase(),
|
||||
module: el.getAttribute('data-plugin-module').toLowerCase(),
|
||||
description: $(el).find('.plugin-description').text().toLowerCase(),
|
||||
html: el.outerHTML,
|
||||
category: $(el).closest('[data-plugin-category]').attr('data-plugin-category'),
|
||||
categoryLabel: $(el).closest('[data-plugin-category]').attr('data-plugin-category-label'),
|
||||
active: !!$(el).has('[data-is-active]').length,
|
||||
}
|
||||
});
|
||||
function SearchMatcher(term, fields) {
|
||||
this.searchFor = term.toLowerCase().split(/\s+/);
|
||||
this.fields = fields;
|
||||
}
|
||||
function inStringRanked(haystack, needle) {
|
||||
let pos = -1, rank = 0;
|
||||
do {
|
||||
pos = haystack.indexOf(needle, pos + 1);
|
||||
if (pos !== -1) rank = 10;
|
||||
if (pos === 0 || haystack.charCodeAt(pos - 1) <= 47)
|
||||
return 15; // string start or word start (=char before match is special char)
|
||||
} while (pos !== -1);
|
||||
return rank;
|
||||
}
|
||||
SearchMatcher.prototype.isMatch = function(obj) {
|
||||
let rank = 0;
|
||||
for(let j = 0; j < this.searchFor.length; j++) {
|
||||
var searchFor = this.searchFor[j];
|
||||
for(let i = this.fields.length - 1; i >= 0; i--) {
|
||||
var result = inStringRanked(obj[this.fields[i]], searchFor);
|
||||
if (result) {
|
||||
rank += (i + 1) * result;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return rank;
|
||||
}
|
||||
function strcmp(a, b) {
|
||||
return a > b ? 1 : a < b ? -1 : 0;
|
||||
}
|
||||
var $results_box = $("#plugin_search_results");
|
||||
var $plugin_tabs = $("#plugin_tabs");
|
||||
var $results = $("#plugin_search_results .plugin-list");
|
||||
function search() {
|
||||
$results.html("");
|
||||
var value = $("#plugin_search_input").val();
|
||||
var only_active = $("input[name=plugin_state_filter][value=active]").prop("checked");
|
||||
if (!value && !only_active) {
|
||||
$results_box.hide(); $plugin_tabs.show();
|
||||
return;
|
||||
}
|
||||
$results_box.show(); $plugin_tabs.hide();
|
||||
var matcher = new SearchMatcher(value, ["description", "module", "name"]);
|
||||
var matches = [];
|
||||
for(const plugin of plugins) {
|
||||
if (only_active && !plugin.active) continue;
|
||||
var rank = matcher.isMatch(plugin);
|
||||
if (!rank) continue;
|
||||
matches.push([rank, plugin]);
|
||||
}
|
||||
matches.sort(function (a,b) { return (b[0]-a[0]) || strcmp(a[1].sortName, b[1].sortName); })
|
||||
$results.append(matches.map(function(res) { return $(res[1].html).prepend('<span class="pull-right">' + res[1].categoryLabel + '</span>'); }))
|
||||
$results.find(".panel-body, .panel, .featured-plugin, .btn-lg").removeClass("panel-body panel featured-plugin btn-lg");
|
||||
if (matches.length === 0) {
|
||||
$results.append(gettext("No results"));
|
||||
}
|
||||
}
|
||||
$("#plugin_search_input").on("input", search);
|
||||
$("input[name=plugin_state_filter]").on("change", search);
|
||||
$results_box.find("button.close").on("click", function() {
|
||||
$("input[name=plugin_state_filter][value=all]").prop("checked", true).trigger("click");
|
||||
$("#plugin_search_input").val("").trigger("input");
|
||||
});
|
||||
if (location.search) {
|
||||
var search = new URLSearchParams(location.search);
|
||||
if (search.has('q')) {
|
||||
$("#plugin_search_input").val(search.get("q")).trigger("input");
|
||||
}
|
||||
}
|
||||
/* global gettext */
|
||||
$(function () {
|
||||
let plugins = $('.plugin-container').toArray().map(function (el) {
|
||||
return {
|
||||
sortName: el.getAttribute('data-plugin-name').toLowerCase().replace(/pretix /g, ''),
|
||||
name: el.getAttribute('data-plugin-name').toLowerCase(),
|
||||
module: el.getAttribute('data-plugin-module').toLowerCase(),
|
||||
description: $(el).find('.plugin-description').text().toLowerCase(),
|
||||
html: el.outerHTML,
|
||||
category: $(el).closest('[data-plugin-category]').attr('data-plugin-category'),
|
||||
categoryLabel: $(el).closest('[data-plugin-category]').attr('data-plugin-category-label'),
|
||||
active: !!$(el).has('[data-is-active]').length,
|
||||
}
|
||||
})
|
||||
function SearchMatcher (term, fields) {
|
||||
this.searchFor = term.toLowerCase().split(/\s+/)
|
||||
this.fields = fields
|
||||
}
|
||||
function inStringRanked (haystack, needle) {
|
||||
let pos = -1, rank = 0
|
||||
do {
|
||||
pos = haystack.indexOf(needle, pos + 1)
|
||||
if (pos !== -1) rank = 10
|
||||
if (pos === 0 || haystack.charCodeAt(pos - 1) <= 47)
|
||||
return 15 // string start or word start (=char before match is special char)
|
||||
} while (pos !== -1)
|
||||
return rank
|
||||
}
|
||||
SearchMatcher.prototype.isMatch = function (obj) {
|
||||
let rank = 0
|
||||
for (let j = 0; j < this.searchFor.length; j++) {
|
||||
let searchFor = this.searchFor[j]
|
||||
for (let i = this.fields.length - 1; i >= 0; i--) {
|
||||
let result = inStringRanked(obj[this.fields[i]], searchFor)
|
||||
if (result) {
|
||||
rank += (i + 1) * result
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return rank
|
||||
}
|
||||
function strcmp (a, b) {
|
||||
return a > b ? 1 : a < b ? -1 : 0
|
||||
}
|
||||
let $results_box = $('#plugin_search_results')
|
||||
let $plugin_tabs = $('#plugin_tabs')
|
||||
let $results = $('#plugin_search_results .plugin-list')
|
||||
function search () {
|
||||
$results.html('')
|
||||
let value = $('#plugin_search_input').val()
|
||||
let only_active = $('input[name=plugin_state_filter][value=active]').prop('checked')
|
||||
if (!value && !only_active) {
|
||||
$results_box.hide()
|
||||
$plugin_tabs.show()
|
||||
return
|
||||
}
|
||||
$results_box.show()
|
||||
$plugin_tabs.hide()
|
||||
let matcher = new SearchMatcher(value, ['description', 'module', 'name'])
|
||||
let matches = []
|
||||
for (const plugin of plugins) {
|
||||
if (only_active && !plugin.active)
|
||||
continue
|
||||
let rank = matcher.isMatch(plugin)
|
||||
if (!rank)
|
||||
continue
|
||||
matches.push([rank, plugin])
|
||||
}
|
||||
matches.sort(function (a, b) { return (b[0] - a[0]) || strcmp(a[1].sortName, b[1].sortName) })
|
||||
$results.append(matches.map(function (res) { return $(res[1].html).prepend('<span class="pull-right">' + res[1].categoryLabel + '</span>') }))
|
||||
$results.find('.panel-body, .panel, .featured-plugin, .btn-lg').removeClass('panel-body panel featured-plugin btn-lg')
|
||||
if (matches.length === 0) {
|
||||
$results.append(gettext('No results'))
|
||||
}
|
||||
}
|
||||
$('#plugin_search_input').on('input', search)
|
||||
$('input[name=plugin_state_filter]').on('change', search)
|
||||
$results_box.find('button.close').on('click', function () {
|
||||
$('input[name=plugin_state_filter][value=all]').prop('checked', true).trigger('click')
|
||||
$('#plugin_search_input').val('').trigger('input')
|
||||
})
|
||||
if (location.search) {
|
||||
var search = new URLSearchParams(location.search)
|
||||
if (search.has('q')) {
|
||||
$('#plugin_search_input').val(search.get('q')).trigger('input')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,156 +1,156 @@
|
||||
/*global $, Morris, gettext*/
|
||||
/* global Morris, gettext, apiGET, i18nToString */
|
||||
$(function () {
|
||||
// Question view
|
||||
if (!$("#question_chart").length) {
|
||||
return;
|
||||
}
|
||||
// Question view
|
||||
if (!$('#question_chart').length) {
|
||||
return
|
||||
}
|
||||
|
||||
$(".chart").css("height", "250px");
|
||||
var data_type = $("#question_chart").attr("data-type"),
|
||||
data = JSON.parse($("#question-chart-data").text() || "[]"),
|
||||
others_sum = 0,
|
||||
max_num = 8;
|
||||
$('.chart').css('height', '250px')
|
||||
let data_type = $('#question_chart').attr('data-type'),
|
||||
data = JSON.parse($('#question-chart-data').text() || '[]'),
|
||||
others_sum = 0,
|
||||
max_num = 8
|
||||
|
||||
data = data.map(function (d) {
|
||||
return {
|
||||
'value': d.count,
|
||||
'label': d.answer.length > 20 ? d.answer.substring(0, 20) + '…' : d.answer,
|
||||
}
|
||||
});
|
||||
data = data.map(function (d) {
|
||||
return {
|
||||
value: d.count,
|
||||
label: d.answer.length > 20 ? d.answer.substring(0, 20) + '…' : d.answer,
|
||||
}
|
||||
})
|
||||
|
||||
if (data_type == 'N') {
|
||||
// Sort
|
||||
data.sort(function (a, b) {
|
||||
if (parseFloat(a.label) > parseFloat(b.label)) {
|
||||
return 1;
|
||||
} else if (parseFloat(a.label) < parseFloat(b.label)) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
max_num = 20;
|
||||
}
|
||||
if (data_type == 'N') {
|
||||
// Sort
|
||||
data.sort(function (a, b) {
|
||||
if (parseFloat(a.label) > parseFloat(b.label)) {
|
||||
return 1
|
||||
} else if (parseFloat(a.label) < parseFloat(b.label)) {
|
||||
return -1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
})
|
||||
max_num = 20
|
||||
}
|
||||
|
||||
// Limit shown options
|
||||
if (data.length > max_num) {
|
||||
for (var i = max_num; i < data.length; i++) {
|
||||
others_sum += data[i].value;
|
||||
}
|
||||
data = data.slice(0, max_num);
|
||||
data.push({'value': others_sum, 'label': gettext('Others')});
|
||||
}
|
||||
// Limit shown options
|
||||
if (data.length > max_num) {
|
||||
for (let i = max_num; i < data.length; i++) {
|
||||
others_sum += data[i].value
|
||||
}
|
||||
data = data.slice(0, max_num)
|
||||
data.push({ value: others_sum, label: gettext('Others') })
|
||||
}
|
||||
|
||||
if (data_type === 'B') {
|
||||
var colors;
|
||||
if (data[0].answer_bool) {
|
||||
colors = ['#50A167', '#C44F4F'];
|
||||
} else {
|
||||
colors = ['#C44F4F', '#50A167'];
|
||||
}
|
||||
new Morris.Donut({
|
||||
element: 'question_chart',
|
||||
data: data,
|
||||
resize: true,
|
||||
colors: colors
|
||||
});
|
||||
} else if (data_type === 'C') {
|
||||
new Morris.Donut({
|
||||
element: 'question_chart',
|
||||
data: data,
|
||||
resize: true,
|
||||
colors: [
|
||||
'#7F4A91',
|
||||
'#50A167',
|
||||
'#FFB419',
|
||||
'#5F9CD4',
|
||||
'#C44F4F',
|
||||
'#83FFFA',
|
||||
'#FF6C38',
|
||||
'#1f5b8e',
|
||||
'#2d683c',
|
||||
]
|
||||
});
|
||||
} else { // M, N, S, T
|
||||
new Morris.Bar({
|
||||
element: 'question_chart',
|
||||
data: data,
|
||||
resize: true,
|
||||
xkey: 'label',
|
||||
ykeys: ['value'],
|
||||
labels: [gettext('Count')]
|
||||
});
|
||||
}
|
||||
if (data_type === 'B') {
|
||||
let colors
|
||||
if (data[0].answer_bool) {
|
||||
colors = ['#50A167', '#C44F4F']
|
||||
} else {
|
||||
colors = ['#C44F4F', '#50A167']
|
||||
}
|
||||
new Morris.Donut({
|
||||
element: 'question_chart',
|
||||
data: data,
|
||||
resize: true,
|
||||
colors: colors
|
||||
})
|
||||
} else if (data_type === 'C') {
|
||||
new Morris.Donut({
|
||||
element: 'question_chart',
|
||||
data: data,
|
||||
resize: true,
|
||||
colors: [
|
||||
'#7F4A91',
|
||||
'#50A167',
|
||||
'#FFB419',
|
||||
'#5F9CD4',
|
||||
'#C44F4F',
|
||||
'#83FFFA',
|
||||
'#FF6C38',
|
||||
'#1f5b8e',
|
||||
'#2d683c',
|
||||
]
|
||||
})
|
||||
} else { // M, N, S, T
|
||||
new Morris.Bar({
|
||||
element: 'question_chart',
|
||||
data: data,
|
||||
resize: true,
|
||||
xkey: 'label',
|
||||
ykeys: ['value'],
|
||||
labels: [gettext('Count')]
|
||||
})
|
||||
}
|
||||
|
||||
// N, S, T
|
||||
});
|
||||
// N, S, T
|
||||
})
|
||||
|
||||
$(function () {
|
||||
// Question editor
|
||||
// Question editor
|
||||
|
||||
if (!$("#answer-options").length) {
|
||||
return;
|
||||
}
|
||||
if (!$('#answer-options').length) {
|
||||
return
|
||||
}
|
||||
|
||||
// Question editor
|
||||
$("#id_type").change(question_page_toggle_view);
|
||||
$("#id_required").change(question_page_toggle_view);
|
||||
question_page_toggle_view();
|
||||
// Question editor
|
||||
$('#id_type').change(question_page_toggle_view)
|
||||
$('#id_required').change(question_page_toggle_view)
|
||||
question_page_toggle_view()
|
||||
|
||||
function question_page_toggle_view() {
|
||||
var show = $("#id_type").val() == "C" || $("#id_type").val() == "M";
|
||||
$("#answer-options").toggle(show);
|
||||
function question_page_toggle_view () {
|
||||
let show = $('#id_type').val() == 'C' || $('#id_type').val() == 'M'
|
||||
$('#answer-options').toggle(show)
|
||||
|
||||
$("#valid-date").toggle($("#id_type").val() == "D");
|
||||
$("#valid-datetime").toggle($("#id_type").val() == "W");
|
||||
$("#valid-string").toggle($("#id_type").val() == "T" || $("#id_type").val() == "S");
|
||||
$("#valid-number").toggle($("#id_type").val() == "N");
|
||||
$("#valid-file").toggle($("#id_type").val() == "F");
|
||||
$('#valid-date').toggle($('#id_type').val() == 'D')
|
||||
$('#valid-datetime').toggle($('#id_type').val() == 'W')
|
||||
$('#valid-string').toggle($('#id_type').val() == 'T' || $('#id_type').val() == 'S')
|
||||
$('#valid-number').toggle($('#id_type').val() == 'N')
|
||||
$('#valid-file').toggle($('#id_type').val() == 'F')
|
||||
|
||||
show = $("#id_type").val() == "B" && $("#id_required").prop("checked");
|
||||
$(".alert-required-boolean").toggle(show);
|
||||
}
|
||||
show = $('#id_type').val() == 'B' && $('#id_required').prop('checked')
|
||||
$('.alert-required-boolean').toggle(show)
|
||||
}
|
||||
|
||||
var $val = $("#id_dependency_values");
|
||||
var $dq = $("#id_dependency_question");
|
||||
var oldval = JSON.parse($("#dependency_value_val").text());
|
||||
function update_dependency_options() {
|
||||
$val.parent().find(".loading-indicator").remove();
|
||||
$("#id_dependency_values option").remove();
|
||||
$("#id_dependency_values").prop("required", false);
|
||||
let $val = $('#id_dependency_values')
|
||||
let $dq = $('#id_dependency_question')
|
||||
let oldval = JSON.parse($('#dependency_value_val').text())
|
||||
function update_dependency_options () {
|
||||
$val.parent().find('.loading-indicator').remove()
|
||||
$('#id_dependency_values option').remove()
|
||||
$('#id_dependency_values').prop('required', false)
|
||||
|
||||
var val = $dq.children("option:selected").val();
|
||||
if (!val) {
|
||||
$("#id_dependency_values").show();
|
||||
$val.show();
|
||||
return;
|
||||
}
|
||||
let val = $dq.children('option:selected').val()
|
||||
if (!val) {
|
||||
$('#id_dependency_values').show()
|
||||
$val.show()
|
||||
return
|
||||
}
|
||||
|
||||
$("#id_dependency_values").prop("required", true);
|
||||
$val.hide();
|
||||
$val.parent().append("<div class=\"help-block loading-indicator\"><span class=\"fa" +
|
||||
" fa-cog fa-spin\"></span></div>");
|
||||
$('#id_dependency_values').prop('required', true)
|
||||
$val.hide()
|
||||
$val.parent().append('<div class="help-block loading-indicator"><span class="fa'
|
||||
+ ' fa-cog fa-spin"></span></div>')
|
||||
|
||||
// the container_type parameter is undocumented. this API is going to change in a later release.
|
||||
apiGET('/api/v1/organizers/' + $("body").attr("data-organizer") + '/events/' + $("body").attr("data-event") + '/questions/' + val + '/?container_type=' + encodeURIComponent($dq.data('container-type')), function (data) {
|
||||
if (data.type === "B") {
|
||||
$val.append($("<option>").attr("value", "True").text(gettext("Yes")));
|
||||
$val.append($("<option>").attr("value", "False").text(gettext("No")));
|
||||
} else {
|
||||
for (var i = 0; i < data.options.length; i++) {
|
||||
var opt = data.options[i];
|
||||
var $opt = $("<option>").attr("value", opt.identifier).text(i18nToString(opt.answer));
|
||||
$val.append($opt);
|
||||
}
|
||||
}
|
||||
if (oldval) {
|
||||
$val.val(oldval);
|
||||
}
|
||||
$val.parent().find(".loading-indicator").remove();
|
||||
$val.show();
|
||||
});
|
||||
}
|
||||
// the container_type parameter is undocumented. this API is going to change in a later release.
|
||||
apiGET('/api/v1/organizers/' + $('body').attr('data-organizer') + '/events/' + $('body').attr('data-event') + '/questions/' + val + '/?container_type=' + encodeURIComponent($dq.data('container-type')), function (data) {
|
||||
if (data.type === 'B') {
|
||||
$val.append($('<option>').attr('value', 'True').text(gettext('Yes')))
|
||||
$val.append($('<option>').attr('value', 'False').text(gettext('No')))
|
||||
} else {
|
||||
for (let i = 0; i < data.options.length; i++) {
|
||||
let opt = data.options[i]
|
||||
let $opt = $('<option>').attr('value', opt.identifier).text(i18nToString(opt.answer))
|
||||
$val.append($opt)
|
||||
}
|
||||
}
|
||||
if (oldval) {
|
||||
$val.val(oldval)
|
||||
}
|
||||
$val.parent().find('.loading-indicator').remove()
|
||||
$val.show()
|
||||
})
|
||||
}
|
||||
|
||||
update_dependency_options();
|
||||
$dq.change(update_dependency_options);
|
||||
});
|
||||
update_dependency_options()
|
||||
$dq.change(update_dependency_options)
|
||||
})
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
$(function () {
|
||||
"use strict";
|
||||
'use strict'
|
||||
|
||||
var ticket_type_quota_calculation = function () {
|
||||
var sum = 0;
|
||||
$("#ticket-type-formset div[data-formset-form]").each(function () {
|
||||
if (!$(this).find("input[name$=DELETE]").prop("checked")) {
|
||||
var val = $(this).find("input[name$=quota]").val();
|
||||
if (val === "") {
|
||||
sum = "∞";
|
||||
} else if (sum !== "∞") {
|
||||
sum += parseInt(val);
|
||||
}
|
||||
}
|
||||
});
|
||||
$("#total-capacity").text(sum);
|
||||
};
|
||||
let ticket_type_quota_calculation = function () {
|
||||
let sum = 0
|
||||
$('#ticket-type-formset div[data-formset-form]').each(function () {
|
||||
if (!$(this).find('input[name$=DELETE]').prop('checked')) {
|
||||
let val = $(this).find('input[name$=quota]').val()
|
||||
if (val === '') {
|
||||
sum = '∞'
|
||||
} else if (sum !== '∞') {
|
||||
sum += parseInt(val)
|
||||
}
|
||||
}
|
||||
})
|
||||
$('#total-capacity').text(sum)
|
||||
}
|
||||
|
||||
var toggle_payment = function () {
|
||||
var any = false;
|
||||
$("#ticket-type-formset div[data-formset-form]").each(function () {
|
||||
if (!$(this).find("input[name$=DELETE]").prop("checked")) {
|
||||
var val = $(this).find("input[name$=default_price]").val();
|
||||
if (/.*[1-9].*/.test(val)) {
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
if ($("#quick-setup-step-payment:visible").length && !any) {
|
||||
$("#quick-setup-step-payment").stop().slideUp();
|
||||
} else if (!$("#quick-setup-step-payment:visible").length && any) {
|
||||
$("#quick-setup-step-payment").stop().slideDown();
|
||||
}
|
||||
};
|
||||
let toggle_payment = function () {
|
||||
let any = false
|
||||
$('#ticket-type-formset div[data-formset-form]').each(function () {
|
||||
if (!$(this).find('input[name$=DELETE]').prop('checked')) {
|
||||
let val = $(this).find('input[name$=default_price]').val()
|
||||
if (/.*[1-9].*/.test(val)) {
|
||||
any = true
|
||||
}
|
||||
}
|
||||
})
|
||||
if ($('#quick-setup-step-payment:visible').length && !any) {
|
||||
$('#quick-setup-step-payment').stop().slideUp()
|
||||
} else if (!$('#quick-setup-step-payment:visible').length && any) {
|
||||
$('#quick-setup-step-payment').stop().slideDown()
|
||||
}
|
||||
}
|
||||
|
||||
$("#ticket-type-formset").bind("formAdded", ticket_type_quota_calculation);
|
||||
$("#ticket-type-formset").on("change keyup keydown keypress", "input", function () {
|
||||
ticket_type_quota_calculation();
|
||||
toggle_payment();
|
||||
});
|
||||
ticket_type_quota_calculation();
|
||||
toggle_payment();
|
||||
$('#ticket-type-formset').bind('formAdded', ticket_type_quota_calculation)
|
||||
$('#ticket-type-formset').on('change keyup keydown keypress', 'input', function () {
|
||||
ticket_type_quota_calculation()
|
||||
toggle_payment()
|
||||
})
|
||||
ticket_type_quota_calculation()
|
||||
toggle_payment()
|
||||
|
||||
$("#total-capacity-edit").click(function () {
|
||||
$("#id_total_quota").val(parseInt($("#total-capacity").text()));
|
||||
$("#total-capacity").hide();
|
||||
$("#id_total_quota").closest("div").removeClass("sr-only");
|
||||
$("#total-capacity-edit").hide();
|
||||
});
|
||||
});
|
||||
$('#total-capacity-edit').click(function () {
|
||||
$('#id_total_quota').val(parseInt($('#total-capacity').text()))
|
||||
$('#total-capacity').hide()
|
||||
$('#id_total_quota').closest('div').removeClass('sr-only')
|
||||
$('#total-capacity-edit').hide()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
/*globals $, Morris, gettext*/
|
||||
/* globals Morris */
|
||||
$(function () {
|
||||
if (!$("#quota-stats").length) {
|
||||
return;
|
||||
}
|
||||
if (!$('#quota-stats').length) {
|
||||
return
|
||||
}
|
||||
|
||||
$(".chart").css("height", "250px");
|
||||
new Morris.Donut({
|
||||
element: 'quota_chart',
|
||||
data: JSON.parse($("#quota-chart-data").html()),
|
||||
resize: true,
|
||||
colors: [
|
||||
'#0044CC', // paid
|
||||
'#0088CC', // pending
|
||||
'#BD362F', // vouchers
|
||||
'#F89406', // carts
|
||||
'#51A351' // available
|
||||
]
|
||||
});
|
||||
});
|
||||
$('.chart').css('height', '250px')
|
||||
new Morris.Donut({
|
||||
element: 'quota_chart',
|
||||
data: JSON.parse($('#quota-chart-data').html()),
|
||||
resize: true,
|
||||
colors: [
|
||||
'#0044CC', // paid
|
||||
'#0088CC', // pending
|
||||
'#BD362F', // vouchers
|
||||
'#F89406', // carts
|
||||
'#51A351' // available
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
$(function () {
|
||||
if (!$("input[name=itemvars]").length) {
|
||||
return;
|
||||
}
|
||||
var autofill = ($("#id_name").val() === "");
|
||||
if (!$('input[name=itemvars]').length) {
|
||||
return
|
||||
}
|
||||
let autofill = ($('#id_name').val() === '')
|
||||
|
||||
$("#id_name").on("change keyup keydown keypress", function () {
|
||||
autofill = false;
|
||||
})
|
||||
$('#id_name').on('change keyup keydown keypress', function () {
|
||||
autofill = false
|
||||
})
|
||||
|
||||
function do_autofill() {
|
||||
if (autofill) {
|
||||
var names = [];
|
||||
$("input[name=itemvars]:checked").each(function () {
|
||||
names.push($.trim($(this).closest("label").text()))
|
||||
});
|
||||
$("#id_name").val(names.join(', '));
|
||||
}
|
||||
}
|
||||
$("input[name=itemvars]").change(do_autofill);
|
||||
do_autofill();
|
||||
});
|
||||
function do_autofill () {
|
||||
if (autofill) {
|
||||
let names = []
|
||||
$('input[name=itemvars]:checked').each(function () {
|
||||
names.push($.trim($(this).closest('label').text()))
|
||||
})
|
||||
$('#id_name').val(names.join(', '))
|
||||
}
|
||||
}
|
||||
$('input[name=itemvars]').change(do_autofill)
|
||||
do_autofill()
|
||||
})
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
/*globals $, Morris, gettext, RRule, RRuleSet*/
|
||||
function rrule_form_toggles($form) {
|
||||
var freq = $form.find("select[name*=freq]").val();
|
||||
$form.find(".repeat-yearly").toggle(freq === "yearly");
|
||||
$form.find(".repeat-monthly").toggle(freq === "monthly");
|
||||
$form.find(".repeat-weekly").toggle(freq === "weekly");
|
||||
function rrule_form_toggles ($form) {
|
||||
let freq = $form.find('select[name*=freq]').val()
|
||||
$form.find('.repeat-yearly').toggle(freq === 'yearly')
|
||||
$form.find('.repeat-monthly').toggle(freq === 'monthly')
|
||||
$form.find('.repeat-weekly').toggle(freq === 'weekly')
|
||||
}
|
||||
|
||||
function rrule_bind_form($form) {
|
||||
$form.find("select[name*=freq]").change(function () {
|
||||
rrule_form_toggles($form);
|
||||
});
|
||||
rrule_form_toggles($form);
|
||||
function rrule_bind_form ($form) {
|
||||
$form.find('select[name*=freq]').change(function () {
|
||||
rrule_form_toggles($form)
|
||||
})
|
||||
rrule_form_toggles($form)
|
||||
}
|
||||
|
||||
|
||||
$(document).on("pretix:bind-forms", function () {
|
||||
$(".rrule-form").each(function () {
|
||||
rrule_bind_form($(this));
|
||||
});
|
||||
});
|
||||
$(document).on('pretix:bind-forms', function () {
|
||||
$('.rrule-form').each(function () {
|
||||
rrule_bind_form($(this))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,226 +1,225 @@
|
||||
/*globals $, Morris, gettext, RRule, RRuleSet*/
|
||||
/* globals ngettext, rrule */
|
||||
|
||||
$(document).on("pretix:bind-forms", function () {
|
||||
if (!$("div[data-formset-prefix=checkinlist_set]").length) {
|
||||
return;
|
||||
}
|
||||
$(document).on('pretix:bind-forms', function () {
|
||||
if (!$('div[data-formset-prefix=checkinlist_set]').length) {
|
||||
return
|
||||
}
|
||||
|
||||
function parse_weekday(wd) {
|
||||
map = {
|
||||
'MO': 0,
|
||||
'TU': 1,
|
||||
'WE': 2,
|
||||
'TH': 3,
|
||||
'FR': 4,
|
||||
'SA': 5,
|
||||
'SU': 6
|
||||
}
|
||||
if (wd.indexOf(",") > 0) {
|
||||
var wds = [];
|
||||
$.each(wd.split(","), function (k, v) {
|
||||
wds.push(map[v]);
|
||||
});
|
||||
return wds;
|
||||
} else {
|
||||
return map[wd];
|
||||
}
|
||||
}
|
||||
function parse_weekday (wd) {
|
||||
map = {
|
||||
MO: 0,
|
||||
TU: 1,
|
||||
WE: 2,
|
||||
TH: 3,
|
||||
FR: 4,
|
||||
SA: 5,
|
||||
SU: 6
|
||||
}
|
||||
if (wd.indexOf(',') > 0) {
|
||||
let wds = []
|
||||
$.each(wd.split(','), function (k, v) {
|
||||
wds.push(map[v])
|
||||
})
|
||||
return wds
|
||||
} else {
|
||||
return map[wd]
|
||||
}
|
||||
}
|
||||
|
||||
// RRule editor
|
||||
function rrule_preview() {
|
||||
var ruleset = new rrule.RRuleSet();
|
||||
// RRule editor
|
||||
function rrule_preview () {
|
||||
let ruleset = new rrule.RRuleSet()
|
||||
|
||||
$(".rrule-form").each(function () {
|
||||
if ($(this).find("input[name$=DELETE]").prop("checked")) {
|
||||
return;
|
||||
}
|
||||
$('.rrule-form').each(function () {
|
||||
if ($(this).find('input[name$=DELETE]').prop('checked')) {
|
||||
return
|
||||
}
|
||||
|
||||
var rule_args = {};
|
||||
var $form = $(this);
|
||||
var freq = $form.find("select[name*=freq]").val();
|
||||
if (!$form.find("input[name*=dtstart]").data("DateTimePicker")) {
|
||||
// uninitialized
|
||||
return;
|
||||
}
|
||||
var dtstart = $form.find("input[name*=dtstart]").data("DateTimePicker").date();
|
||||
dtstart = dtstart.add(dtstart.utcOffset(), 'm').add(12, 'h').utcOffset(0);
|
||||
rule_args.dtstart = dtstart.toDate();
|
||||
rule_args.interval = parseInt($form.find("input[name*=interval]").val()) || 1;
|
||||
let rule_args = {}
|
||||
let $form = $(this)
|
||||
let freq = $form.find('select[name*=freq]').val()
|
||||
if (!$form.find('input[name*=dtstart]').data('DateTimePicker')) {
|
||||
// uninitialized
|
||||
return
|
||||
}
|
||||
let dtstart = $form.find('input[name*=dtstart]').data('DateTimePicker').date()
|
||||
dtstart = dtstart.add(dtstart.utcOffset(), 'm').add(12, 'h').utcOffset(0)
|
||||
rule_args.dtstart = dtstart.toDate()
|
||||
rule_args.interval = parseInt($form.find('input[name*=interval]').val()) || 1
|
||||
|
||||
if (freq === 'yearly') {
|
||||
rule_args.freq = rrule.RRule.YEARLY;
|
||||
if (freq === 'yearly') {
|
||||
rule_args.freq = rrule.RRule.YEARLY
|
||||
|
||||
var same = $form.find("input[name*=yearly_same]:checked").val();
|
||||
if (same === "off") {
|
||||
rule_args.bysetpos = parseInt($form.find("select[name*=yearly_bysetpos]").val());
|
||||
rule_args.byweekday = parse_weekday($form.find("select[name*=yearly_byweekday]").val());
|
||||
rule_args.bymonth = parseInt($form.find("select[name*=yearly_bymonth]").val());
|
||||
}
|
||||
} else if (freq === 'monthly') {
|
||||
rule_args.freq = rrule.RRule.MONTHLY;
|
||||
var same = $form.find('input[name*=yearly_same]:checked').val()
|
||||
if (same === 'off') {
|
||||
rule_args.bysetpos = parseInt($form.find('select[name*=yearly_bysetpos]').val())
|
||||
rule_args.byweekday = parse_weekday($form.find('select[name*=yearly_byweekday]').val())
|
||||
rule_args.bymonth = parseInt($form.find('select[name*=yearly_bymonth]').val())
|
||||
}
|
||||
} else if (freq === 'monthly') {
|
||||
rule_args.freq = rrule.RRule.MONTHLY
|
||||
|
||||
var same = $form.find("input[name*=monthly_same]:checked").val();
|
||||
if (same === "off") {
|
||||
rule_args.bysetpos = parseInt($form.find("select[name*=monthly_bysetpos]").val());
|
||||
rule_args.byweekday = parse_weekday($form.find("select[name*=monthly_byweekday]").val());
|
||||
}
|
||||
} else if (freq === 'weekly') {
|
||||
rule_args.freq = rrule.RRule.WEEKLY;
|
||||
var same = $form.find('input[name*=monthly_same]:checked').val()
|
||||
if (same === 'off') {
|
||||
rule_args.bysetpos = parseInt($form.find('select[name*=monthly_bysetpos]').val())
|
||||
rule_args.byweekday = parse_weekday($form.find('select[name*=monthly_byweekday]').val())
|
||||
}
|
||||
} else if (freq === 'weekly') {
|
||||
rule_args.freq = rrule.RRule.WEEKLY
|
||||
|
||||
var days = [];
|
||||
$form.find("input[name*=weekly_byweekday]:checked").each(function () {
|
||||
days.push(parse_weekday($(this).val()));
|
||||
});
|
||||
if (days.length !== 0) {
|
||||
rule_args.byweekday = days;
|
||||
}
|
||||
} else if (freq === 'daily') {
|
||||
rule_args.freq = rrule.RRule.DAILY;
|
||||
}
|
||||
let days = []
|
||||
$form.find('input[name*=weekly_byweekday]:checked').each(function () {
|
||||
days.push(parse_weekday($(this).val()))
|
||||
})
|
||||
if (days.length !== 0) {
|
||||
rule_args.byweekday = days
|
||||
}
|
||||
} else if (freq === 'daily') {
|
||||
rule_args.freq = rrule.RRule.DAILY
|
||||
}
|
||||
|
||||
var end = $form.find("input[name*=end]:checked").val();
|
||||
if (end === "count") {
|
||||
rule_args.count = Math.max(parseInt($form.find("input[name*=count]").val()) || 1, 1);
|
||||
} else {
|
||||
var date = $form.find("input[name*=until]").data("DateTimePicker").date();
|
||||
if (date !== null) {
|
||||
// rrule.until is non-inclusive, whereas in pretix-backend "until" is inclusive => add 1 day
|
||||
// date is a Moment-object. Moment.add() mutates, but is save to do here
|
||||
date.add(1, 'days');
|
||||
rule_args.until = date;
|
||||
}
|
||||
}
|
||||
let end = $form.find('input[name*=end]:checked').val()
|
||||
if (end === 'count') {
|
||||
rule_args.count = Math.max(parseInt($form.find('input[name*=count]').val()) || 1, 1)
|
||||
} else {
|
||||
let date = $form.find('input[name*=until]').data('DateTimePicker').date()
|
||||
if (date !== null) {
|
||||
// rrule.until is non-inclusive, whereas in pretix-backend "until" is inclusive => add 1 day
|
||||
// date is a Moment-object. Moment.add() mutates, but is save to do here
|
||||
date.add(1, 'days')
|
||||
rule_args.until = date
|
||||
}
|
||||
}
|
||||
|
||||
if ($form.find("input[name*=exclude]").prop("checked")) {
|
||||
ruleset.exrule(new rrule.RRule(rule_args));
|
||||
$form.closest(".panel").addClass("panel-danger").removeClass("panel-default");
|
||||
} else {
|
||||
ruleset.rrule(new rrule.RRule(rule_args));
|
||||
$form.closest(".panel").addClass("panel-default").removeClass("panel-danger");
|
||||
}
|
||||
});
|
||||
if ($form.find('input[name*=exclude]').prop('checked')) {
|
||||
ruleset.exrule(new rrule.RRule(rule_args))
|
||||
$form.closest('.panel').addClass('panel-danger').removeClass('panel-default')
|
||||
} else {
|
||||
ruleset.rrule(new rrule.RRule(rule_args))
|
||||
$form.closest('.panel').addClass('panel-default').removeClass('panel-danger')
|
||||
}
|
||||
})
|
||||
|
||||
var all_dates = ruleset.all();
|
||||
var format = $("body").attr("data-longdateformat") + " (dddd)";
|
||||
$("#rrule-preview").html("");
|
||||
if (all_dates.length > 20) {
|
||||
$("#rrule-preview").html("");
|
||||
all_dates.slice(0, 10).forEach(function(element) {
|
||||
$("#rrule-preview").append($("<li>").text(moment(element).utc().format(format)));
|
||||
});
|
||||
$("#rrule-preview").append($("<li>").text(ngettext(
|
||||
"(one more date)",
|
||||
"({num} more dates)",
|
||||
all_dates.length - 20
|
||||
).replace(/\{num\}/g, all_dates.length - 20)));
|
||||
all_dates.slice(-10).forEach(function(element) {
|
||||
$("#rrule-preview").append($("<li>").text(moment(element).utc().format(format)));
|
||||
});
|
||||
} else {
|
||||
all_dates.forEach(function(element) {
|
||||
$("#rrule-preview").append($("<li>").text(moment(element).utc().format(format)));
|
||||
});
|
||||
}
|
||||
}
|
||||
$("#rrule-formset").on("change keydown keyup keypress dp.change", "input, select", function () {
|
||||
rrule_preview();
|
||||
});
|
||||
rrule_preview();
|
||||
let all_dates = ruleset.all()
|
||||
let format = $('body').attr('data-longdateformat') + ' (dddd)'
|
||||
$('#rrule-preview').html('')
|
||||
if (all_dates.length > 20) {
|
||||
$('#rrule-preview').html('')
|
||||
all_dates.slice(0, 10).forEach(function (element) {
|
||||
$('#rrule-preview').append($('<li>').text(moment(element).utc().format(format)))
|
||||
})
|
||||
$('#rrule-preview').append($('<li>').text(ngettext(
|
||||
'(one more date)',
|
||||
'({num} more dates)',
|
||||
all_dates.length - 20
|
||||
).replace(/\{num\}/g, all_dates.length - 20)))
|
||||
all_dates.slice(-10).forEach(function (element) {
|
||||
$('#rrule-preview').append($('<li>').text(moment(element).utc().format(format)))
|
||||
})
|
||||
} else {
|
||||
all_dates.forEach(function (element) {
|
||||
$('#rrule-preview').append($('<li>').text(moment(element).utc().format(format)))
|
||||
})
|
||||
}
|
||||
}
|
||||
$('#rrule-formset').on('change keydown keyup keypress dp.change', 'input, select', function () {
|
||||
rrule_preview()
|
||||
})
|
||||
rrule_preview()
|
||||
|
||||
$("#rrule-formset").on("formAdded", "div", function (event) {rrule_bind_form($(event.target)); });
|
||||
$('#rrule-formset').on('formAdded', 'div', function (event) { rrule_bind_form($(event.target)) })
|
||||
|
||||
// Timeslot editor
|
||||
$("#subevent_add_many_slots_go").on("click", function () {
|
||||
$("#time-formset [data-formset-form]").each(function () {
|
||||
var tf = $(this).find("[name$=time_from]").val()
|
||||
if (!tf) {
|
||||
$(this).remove();
|
||||
}
|
||||
})
|
||||
// Timeslot editor
|
||||
$('#subevent_add_many_slots_go').on('click', function () {
|
||||
$('#time-formset [data-formset-form]').each(function () {
|
||||
let tf = $(this).find('[name$=time_from]').val()
|
||||
if (!tf) {
|
||||
$(this).remove()
|
||||
}
|
||||
})
|
||||
|
||||
var first = $("#subevent_add_many_slots_first").data('DateTimePicker').date();
|
||||
var end = $("#subevent_add_many_slots_end").data('DateTimePicker').date();
|
||||
var length_m = parseFloat($("#subevent_add_many_slots_length").val()) || 0;
|
||||
var break_m = parseFloat($("#subevent_add_many_slots_break").val()) || 0;
|
||||
if (!first || !end || !length_m) {
|
||||
console.log("invalid", first, end, length_m)
|
||||
return
|
||||
}
|
||||
let first = $('#subevent_add_many_slots_first').data('DateTimePicker').date()
|
||||
let end = $('#subevent_add_many_slots_end').data('DateTimePicker').date()
|
||||
let length_m = parseFloat($('#subevent_add_many_slots_length').val()) || 0
|
||||
let break_m = parseFloat($('#subevent_add_many_slots_break').val()) || 0
|
||||
if (!first || !end || !length_m) {
|
||||
console.log('invalid', first, end, length_m)
|
||||
return
|
||||
}
|
||||
|
||||
function closure($form, time) {
|
||||
return function () {
|
||||
console.log("setting value", time)
|
||||
$form.find("[name$=time_from]").data('DateTimePicker').date(time);
|
||||
time.add(length_m, 'minutes');
|
||||
$form.find("[name$=time_to]").data('DateTimePicker').date(time);
|
||||
}
|
||||
}
|
||||
function closure ($form, time) {
|
||||
return function () {
|
||||
console.log('setting value', time)
|
||||
$form.find('[name$=time_from]').data('DateTimePicker').date(time)
|
||||
time.add(length_m, 'minutes')
|
||||
$form.find('[name$=time_to]').data('DateTimePicker').date(time)
|
||||
}
|
||||
}
|
||||
|
||||
var pointer = first.clone();
|
||||
while (pointer.isBefore(end)) {
|
||||
var $form = $("#time-formset").formset("getOrCreate").addForm();
|
||||
$form.attr("data-formset-created-at-runtime", "false"); // prevents animation
|
||||
var time = pointer.clone();
|
||||
window.setTimeout(closure($form, time), 1);
|
||||
// jquery.formset.js only calls trigger("formAdded") after a setTimeout of 0,
|
||||
// but we need to run after that to make sure the date pickers are initialized
|
||||
pointer.add(break_m + length_m, 'minutes');
|
||||
}
|
||||
$("#subevent_add_many_slots").addClass("hidden");
|
||||
$("#subevent_add_many_slots_start").removeClass("hidden");
|
||||
let pointer = first.clone()
|
||||
while (pointer.isBefore(end)) {
|
||||
let $form = $('#time-formset').formset('getOrCreate').addForm()
|
||||
$form.attr('data-formset-created-at-runtime', 'false') // prevents animation
|
||||
let time = pointer.clone()
|
||||
window.setTimeout(closure($form, time), 1)
|
||||
// jquery.formset.js only calls trigger("formAdded") after a setTimeout of 0,
|
||||
// but we need to run after that to make sure the date pickers are initialized
|
||||
pointer.add(break_m + length_m, 'minutes')
|
||||
}
|
||||
$('#subevent_add_many_slots').addClass('hidden')
|
||||
$('#subevent_add_many_slots_start').removeClass('hidden')
|
||||
})
|
||||
$('#subevent_add_many_slots_start').on('click', function () {
|
||||
$('#subevent_add_many_slots').removeClass('hidden')
|
||||
$(this).addClass('hidden')
|
||||
})
|
||||
|
||||
});
|
||||
$("#subevent_add_many_slots_start").on("click", function () {
|
||||
$("#subevent_add_many_slots").removeClass("hidden");
|
||||
$(this).addClass("hidden");
|
||||
});
|
||||
// Hide config for products that are not for sale
|
||||
function quota_form_handlers (el) {
|
||||
// searchable_selection = True
|
||||
el.find('[id^="id_quotas-"]').on('select2:select select2:unselect', () => {
|
||||
update_item_visibility()
|
||||
})
|
||||
// searchable_selection = False
|
||||
el.find('input[id^="id_quotas-"][id*=itemvars_]').on('change', () => {
|
||||
update_item_visibility()
|
||||
})
|
||||
}
|
||||
function update_item_visibility () {
|
||||
const itemvars = []
|
||||
|
||||
// Hide config for products that are not for sale
|
||||
function quota_form_handlers(el) {
|
||||
// searchable_selection = True
|
||||
el.find('[id^="id_quotas-"]').on("select2:select select2:unselect", () => {
|
||||
update_item_visibility();
|
||||
});
|
||||
// searchable_selection = False
|
||||
el.find('input[id^="id_quotas-"][id*=itemvars_]').on("change", () => {
|
||||
update_item_visibility();
|
||||
});
|
||||
}
|
||||
function update_item_visibility() {
|
||||
const itemvars = [];
|
||||
// searchable_selection = True
|
||||
$('select[id^=id_quotas-][id$=-itemvars]').filter((idx, el) => {
|
||||
return !$(el).closest('[data-formset-form]').is('[data-formset-form-deleted]')
|
||||
}).each((_, e) => itemvars.push(...$(e).val()))
|
||||
// searchable_selection = False
|
||||
$('input[id^=id_quotas-][id*=itemvars_]:checked').filter((idx, el) => {
|
||||
return !$(el).closest('[data-formset-form]').is('[data-formset-form-deleted]')
|
||||
}).each((_, e) => itemvars.push($(e).val()))
|
||||
|
||||
// searchable_selection = True
|
||||
$("select[id^=id_quotas-][id$=-itemvars]").filter((idx, el) => {
|
||||
return !$(el).closest('[data-formset-form]').is('[data-formset-form-deleted]');
|
||||
}).each((_, e) => itemvars.push(...$(e).val()));
|
||||
// searchable_selection = False
|
||||
$("input[id^=id_quotas-][id*=itemvars_]:checked").filter((idx, el) => {
|
||||
return !$(el).closest('[data-formset-form]').is('[data-formset-form-deleted]');
|
||||
}).each((_, e) => itemvars.push($(e).val()));
|
||||
$('div[data-itemvar]').each(function (idx, e) {
|
||||
const el = $(e)
|
||||
el.prop('hidden', !itemvars.includes(el.attr('data-itemvar')) && !el.find('.has-error, .alert-danger').length)
|
||||
})
|
||||
}
|
||||
|
||||
$("div[data-itemvar]").each(function (idx, e) {
|
||||
const el = $(e);
|
||||
el.prop("hidden", !itemvars.includes(el.attr("data-itemvar")) && !el.find(".has-error, .alert-danger").length);
|
||||
});
|
||||
}
|
||||
$('[data-formset-prefix="quotas"]').on('formDeleted', 'div', () => {
|
||||
update_item_visibility()
|
||||
}).on('formAdded', 'div', (event) => {
|
||||
quota_form_handlers($(event.target))
|
||||
update_item_visibility()
|
||||
})
|
||||
quota_form_handlers($('body'))
|
||||
update_item_visibility()
|
||||
|
||||
$('[data-formset-prefix="quotas"]').on("formDeleted", "div", () => {
|
||||
update_item_visibility();
|
||||
}).on("formAdded", "div", (event) => {
|
||||
quota_form_handlers($(event.target));
|
||||
update_item_visibility();
|
||||
})
|
||||
quota_form_handlers($("body"));
|
||||
update_item_visibility();
|
||||
|
||||
// Auto-set name of check-in list
|
||||
var $namef = $("input[id^=id_name]").first();
|
||||
var lastValue = $namef.val();
|
||||
$namef.change(function () {
|
||||
var field = $("div[data-formset-prefix=checkinlist_set] input[id$=name]").first();
|
||||
if (field.val() === lastValue) {
|
||||
lastValue = $(this).val();
|
||||
field.val(lastValue);
|
||||
}
|
||||
});
|
||||
});
|
||||
// Auto-set name of check-in list
|
||||
let $namef = $('input[id^=id_name]').first()
|
||||
let lastValue = $namef.val()
|
||||
$namef.change(function () {
|
||||
let field = $('div[data-formset-prefix=checkinlist_set] input[id$=name]').first()
|
||||
if (field.val() === lastValue) {
|
||||
lastValue = $(this).val()
|
||||
field.val(lastValue)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,56 +1,54 @@
|
||||
/*globals $*/
|
||||
|
||||
$(function () {
|
||||
var j = 0;
|
||||
$(".tabbed-form").each(function () {
|
||||
var $form = $(this);
|
||||
var $tabs = $("<ul>").addClass("nav nav-tabs").insertBefore($form);
|
||||
$form.addClass("tab-content");
|
||||
let j = 0
|
||||
$('.tabbed-form').each(function () {
|
||||
let $form = $(this)
|
||||
let $tabs = $('<ul>').addClass('nav nav-tabs').insertBefore($form)
|
||||
$form.addClass('tab-content')
|
||||
|
||||
var i = 0;
|
||||
var preselect = null;
|
||||
var validity_error = false;
|
||||
$form.find("fieldset").each(function () {
|
||||
var $fieldset = $(this);
|
||||
var tid = $fieldset.attr("id");
|
||||
if (!tid) tid = "tab-" + j + "-" + i;
|
||||
var $tabli = $("<li>").appendTo($tabs);
|
||||
var $tablink = $("<a>").attr("role", "tab")
|
||||
.attr("data-toggle", "tab")
|
||||
.attr("href", "#" + tid)
|
||||
.text($fieldset.find("legend").text())
|
||||
.appendTo($tabli);
|
||||
if ($fieldset.find(".has-error, .alert-danger:not(.dynamic)").length > 0) {
|
||||
$tablink.append(" ");
|
||||
$tablink.append($("<span>").addClass("fa fa-warning text-danger"));
|
||||
if (preselect === null) {
|
||||
preselect = i;
|
||||
}
|
||||
}
|
||||
$fieldset.find("input, select, textarea").on("invalid", function () {
|
||||
if ($tablink.find(".fa-warning").length === 0) {
|
||||
$tablink.append(" ");
|
||||
$tablink.append($("<span>").addClass("fa fa-warning text-danger"));
|
||||
if (!validity_error) {
|
||||
validity_error = true;
|
||||
$tablink.click();
|
||||
}
|
||||
}
|
||||
});
|
||||
$fieldset.find("legend").remove();
|
||||
$fieldset.addClass("tab-pane").attr("id", tid);
|
||||
if (location.hash && ($fieldset.find(location.hash).length || location.hash === "#" + tid + "-open") && preselect === null) {
|
||||
preselect = i;
|
||||
}
|
||||
i++;
|
||||
});
|
||||
$tabs.find("a").get(preselect != null ? preselect : 0).click();
|
||||
$tabs.find("a").on('shown.bs.tab', function (e) {
|
||||
history.replaceState(null, null, e.target.getAttribute("href") + "-open");
|
||||
});
|
||||
$form.closest("form").on("submit", function () {
|
||||
validity_error = false;
|
||||
});
|
||||
j++;
|
||||
});
|
||||
});
|
||||
let i = 0
|
||||
let preselect = null
|
||||
let validity_error = false
|
||||
$form.find('fieldset').each(function () {
|
||||
let $fieldset = $(this)
|
||||
let tid = $fieldset.attr('id')
|
||||
if (!tid) tid = 'tab-' + j + '-' + i
|
||||
let $tabli = $('<li>').appendTo($tabs)
|
||||
let $tablink = $('<a>').attr('role', 'tab')
|
||||
.attr('data-toggle', 'tab')
|
||||
.attr('href', '#' + tid)
|
||||
.text($fieldset.find('legend').text())
|
||||
.appendTo($tabli)
|
||||
if ($fieldset.find('.has-error, .alert-danger:not(.dynamic)').length > 0) {
|
||||
$tablink.append(' ')
|
||||
$tablink.append($('<span>').addClass('fa fa-warning text-danger'))
|
||||
if (preselect === null) {
|
||||
preselect = i
|
||||
}
|
||||
}
|
||||
$fieldset.find('input, select, textarea').on('invalid', function () {
|
||||
if ($tablink.find('.fa-warning').length === 0) {
|
||||
$tablink.append(' ')
|
||||
$tablink.append($('<span>').addClass('fa fa-warning text-danger'))
|
||||
if (!validity_error) {
|
||||
validity_error = true
|
||||
$tablink.click()
|
||||
}
|
||||
}
|
||||
})
|
||||
$fieldset.find('legend').remove()
|
||||
$fieldset.addClass('tab-pane').attr('id', tid)
|
||||
if (location.hash && ($fieldset.find(location.hash).length || location.hash === '#' + tid + '-open') && preselect === null) {
|
||||
preselect = i
|
||||
}
|
||||
i++
|
||||
})
|
||||
$tabs.find('a').get(preselect != null ? preselect : 0).click()
|
||||
$tabs.find('a').on('shown.bs.tab', function (e) {
|
||||
history.replaceState(null, null, e.target.getAttribute('href') + '-open')
|
||||
})
|
||||
$form.closest('form').on('submit', function () {
|
||||
validity_error = false
|
||||
})
|
||||
j++
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,164 +1,163 @@
|
||||
/*global $,u2f */
|
||||
$(function () {
|
||||
$('.context-selector.dropdown').on('shown.bs.collapse shown.bs.dropdown', function () {
|
||||
$(this).parent().find("input").val("").trigger('forceRunQuery').focus();
|
||||
});
|
||||
$('.dropdown-menu .form-box input').click(function (e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
$('.context-selector.dropdown').on('shown.bs.collapse shown.bs.dropdown', function () {
|
||||
$(this).parent().find('input').val('').trigger('forceRunQuery').focus()
|
||||
})
|
||||
$('.dropdown-menu .form-box input').click(function (e) {
|
||||
e.stopPropagation()
|
||||
})
|
||||
|
||||
$("[data-event-typeahead]").each(function () {
|
||||
var $container = $(this);
|
||||
var $query = $(this).find('[data-typeahead-query]').length ? $(this).find('[data-typeahead-query]') : $($(this).attr("data-typeahead-field"));
|
||||
$container.find("li:not(.query-holder)").remove();
|
||||
var lastQuery = null;
|
||||
var runQueryTimeout = null;
|
||||
var loadIndicatorTimeout = null;
|
||||
var focusOutTimeout = null;
|
||||
function showLoadIndicator() {
|
||||
$container.find("li:not(.query-holder)").remove();
|
||||
$container.append("<li class='loading'><span class='fa fa-4x fa-cog fa-spin'></span></li>");
|
||||
$container.toggleClass('focused', $query.is(":focus") && $container.children().length > 0);
|
||||
}
|
||||
function runQuery() {
|
||||
var thisQuery = $query.val();
|
||||
if (thisQuery === lastQuery) return;
|
||||
lastQuery = $query.val();
|
||||
$('[data-event-typeahead]').each(function () {
|
||||
let $container = $(this)
|
||||
let $query = $(this).find('[data-typeahead-query]').length ? $(this).find('[data-typeahead-query]') : $($(this).attr('data-typeahead-field'))
|
||||
$container.find('li:not(.query-holder)').remove()
|
||||
let lastQuery = null
|
||||
let runQueryTimeout = null
|
||||
let loadIndicatorTimeout = null
|
||||
let focusOutTimeout = null
|
||||
function showLoadIndicator () {
|
||||
$container.find('li:not(.query-holder)').remove()
|
||||
$container.append('<li class=\'loading\'><span class=\'fa fa-4x fa-cog fa-spin\'></span></li>')
|
||||
$container.toggleClass('focused', $query.is(':focus') && $container.children().length > 0)
|
||||
}
|
||||
function runQuery () {
|
||||
let thisQuery = $query.val()
|
||||
if (thisQuery === lastQuery) return
|
||||
lastQuery = $query.val()
|
||||
|
||||
window.clearTimeout(loadIndicatorTimeout)
|
||||
loadIndicatorTimeout = window.setTimeout(showLoadIndicator, 80)
|
||||
window.clearTimeout(loadIndicatorTimeout)
|
||||
loadIndicatorTimeout = window.setTimeout(showLoadIndicator, 80)
|
||||
|
||||
$.getJSON(
|
||||
$container.attr("data-source") + "?query=" + encodeURIComponent($query.val()) + (typeof $container.attr("data-organizer") !== "undefined" ? "&organizer=" + $container.attr("data-organizer") : ""),
|
||||
function (data) {
|
||||
if (thisQuery !== lastQuery) {
|
||||
// Lost race condition
|
||||
return;
|
||||
}
|
||||
window.clearTimeout(loadIndicatorTimeout);
|
||||
$container.find("li:not(.query-holder)").remove();
|
||||
$.each(data.results, function (i, res) {
|
||||
let $linkContent = $("<div>");
|
||||
if (res.type === "organizer") {
|
||||
$linkContent.append(
|
||||
$("<span>").addClass("event-name-full").append(
|
||||
$("<span>").addClass("fa fa-users fa-fw")
|
||||
).append(" ").append($("<div>").text(res.name).html())
|
||||
)
|
||||
} else if (res.type === "order" || res.type === "voucher") {
|
||||
$linkContent.append(
|
||||
$("<span>").addClass("event-name-full").append($("<div>").text(res.title).html())
|
||||
).append(
|
||||
$("<span>").addClass("event-organizer").append(
|
||||
$("<span>").addClass("fa fa-calendar fa-fw")
|
||||
).append(" ").append($("<div>").text(res.event).html())
|
||||
)
|
||||
} else if (res.type === "user") {
|
||||
$linkContent.append(
|
||||
$("<span>").addClass("event-name-full").append(
|
||||
$("<span>").addClass("fa fa-user fa-fw")
|
||||
).append(" ").append($("<div>").text(res.name).html())
|
||||
)
|
||||
} else {
|
||||
$linkContent.append(
|
||||
$("<span>").addClass("event-name-full").append($("<div>").text(res.name).html())
|
||||
).append(
|
||||
$("<span>").addClass("event-organizer").append(
|
||||
$("<span>").addClass("fa fa-users fa-fw")
|
||||
).append(" ").append($("<div>").text(res.organizer).html())
|
||||
).append(
|
||||
$("<span>").addClass("event-daterange").append(
|
||||
$("<span>").addClass("fa fa-calendar fa-fw")
|
||||
).append(" ").append(res.date_range)
|
||||
)
|
||||
}
|
||||
$.getJSON(
|
||||
$container.attr('data-source') + '?query=' + encodeURIComponent($query.val()) + (typeof $container.attr('data-organizer') !== 'undefined' ? '&organizer=' + $container.attr('data-organizer') : ''),
|
||||
function (data) {
|
||||
if (thisQuery !== lastQuery) {
|
||||
// Lost race condition
|
||||
return
|
||||
}
|
||||
window.clearTimeout(loadIndicatorTimeout)
|
||||
$container.find('li:not(.query-holder)').remove()
|
||||
$.each(data.results, function (_i, res) {
|
||||
let $linkContent = $('<div>')
|
||||
if (res.type === 'organizer') {
|
||||
$linkContent.append(
|
||||
$('<span>').addClass('event-name-full').append(
|
||||
$('<span>').addClass('fa fa-users fa-fw')
|
||||
).append(' ').append($('<div>').text(res.name).html())
|
||||
)
|
||||
} else if (res.type === 'order' || res.type === 'voucher') {
|
||||
$linkContent.append(
|
||||
$('<span>').addClass('event-name-full').append($('<div>').text(res.title).html())
|
||||
).append(
|
||||
$('<span>').addClass('event-organizer').append(
|
||||
$('<span>').addClass('fa fa-calendar fa-fw')
|
||||
).append(' ').append($('<div>').text(res.event).html())
|
||||
)
|
||||
} else if (res.type === 'user') {
|
||||
$linkContent.append(
|
||||
$('<span>').addClass('event-name-full').append(
|
||||
$('<span>').addClass('fa fa-user fa-fw')
|
||||
).append(' ').append($('<div>').text(res.name).html())
|
||||
)
|
||||
} else {
|
||||
$linkContent.append(
|
||||
$('<span>').addClass('event-name-full').append($('<div>').text(res.name).html())
|
||||
).append(
|
||||
$('<span>').addClass('event-organizer').append(
|
||||
$('<span>').addClass('fa fa-users fa-fw')
|
||||
).append(' ').append($('<div>').text(res.organizer).html())
|
||||
).append(
|
||||
$('<span>').addClass('event-daterange').append(
|
||||
$('<span>').addClass('fa fa-calendar fa-fw')
|
||||
).append(' ').append(res.date_range)
|
||||
)
|
||||
}
|
||||
|
||||
$container.append(
|
||||
$("<li>").append(
|
||||
$("<a>").attr("href", res.url).append(
|
||||
$linkContent
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
$container.toggleClass('focused', $query.is(":focus") && $container.children().length > 0);
|
||||
}
|
||||
);
|
||||
}
|
||||
$query.on("forceRunQuery", function () {
|
||||
runQuery();
|
||||
});
|
||||
$query.on("input", function () {
|
||||
if ($container.attr("data-typeahead-field") && $query.val() === "") {
|
||||
$container.removeClass('focused');
|
||||
$container.find("li:not(.query-holder)").remove();
|
||||
lastQuery = null;
|
||||
return;
|
||||
}
|
||||
window.clearTimeout(runQueryTimeout)
|
||||
runQueryTimeout = window.setTimeout(runQuery, 250)
|
||||
});
|
||||
$query.on("keydown", function (event) {
|
||||
var $selected = $container.find(".active");
|
||||
if (event.which === 13) { // enter
|
||||
var $link = $selected.find("a");
|
||||
if ($link.length) {
|
||||
location.href = $link.attr("href");
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
});
|
||||
$container.add($query).on("keydown", function (event) {
|
||||
if (event.which === 27) { // escape
|
||||
$container.removeClass('focused');
|
||||
}
|
||||
}).on("focusin", function (event) {
|
||||
window.clearTimeout(focusOutTimeout);
|
||||
$(document.body).one("focusout", function (event) {
|
||||
focusOutTimeout = window.setTimeout(function () {
|
||||
$container.removeClass('focused');
|
||||
}, 100);
|
||||
})
|
||||
});
|
||||
$query.on("keyup", function (event) {
|
||||
var $first = $container.find("li:not(.query-holder)").first();
|
||||
var $last = $container.find("li:not(.query-holder)").last();
|
||||
var $selected = $container.find(".active");
|
||||
$container.append(
|
||||
$('<li>').append(
|
||||
$('<a>').attr('href', res.url).append(
|
||||
$linkContent
|
||||
)
|
||||
)
|
||||
)
|
||||
})
|
||||
$container.toggleClass('focused', $query.is(':focus') && $container.children().length > 0)
|
||||
}
|
||||
)
|
||||
}
|
||||
$query.on('forceRunQuery', function () {
|
||||
runQuery()
|
||||
})
|
||||
$query.on('input', function () {
|
||||
if ($container.attr('data-typeahead-field') && $query.val() === '') {
|
||||
$container.removeClass('focused')
|
||||
$container.find('li:not(.query-holder)').remove()
|
||||
lastQuery = null
|
||||
return
|
||||
}
|
||||
window.clearTimeout(runQueryTimeout)
|
||||
runQueryTimeout = window.setTimeout(runQuery, 250)
|
||||
})
|
||||
$query.on('keydown', function (event) {
|
||||
let $selected = $container.find('.active')
|
||||
if (event.which === 13) { // enter
|
||||
let $link = $selected.find('a')
|
||||
if ($link.length) {
|
||||
location.href = $link.attr('href')
|
||||
}
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
})
|
||||
$container.add($query).on('keydown', function (event) {
|
||||
if (event.which === 27) { // escape
|
||||
$container.removeClass('focused')
|
||||
}
|
||||
}).on('focusin', function () {
|
||||
window.clearTimeout(focusOutTimeout)
|
||||
$(document.body).one('focusout', function () {
|
||||
focusOutTimeout = window.setTimeout(function () {
|
||||
$container.removeClass('focused')
|
||||
}, 100)
|
||||
})
|
||||
})
|
||||
$query.on('keyup', function (event) {
|
||||
let $first = $container.find('li:not(.query-holder)').first()
|
||||
let $last = $container.find('li:not(.query-holder)').last()
|
||||
let $selected = $container.find('.active')
|
||||
|
||||
if (event.which === 13) { // enter
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return true;
|
||||
} else if (event.which === 40) { // down
|
||||
var $next;
|
||||
if ($selected.length === 0) {
|
||||
$next = $first;
|
||||
} else {
|
||||
$next = $selected.next();
|
||||
}
|
||||
if ($next.length === 0) {
|
||||
$next = $first;
|
||||
}
|
||||
$selected.removeClass("active");
|
||||
$next.addClass("active");
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return true;
|
||||
} else if (event.which === 38) { // up
|
||||
if ($selected.length === 0) {
|
||||
$selected = $first;
|
||||
}
|
||||
var $prev = $selected.prev();
|
||||
if ($prev.length === 0 || $prev.find("input").length > 0) {
|
||||
$prev = $last;
|
||||
}
|
||||
$selected.removeClass("active");
|
||||
$prev.addClass("active");
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
if (event.which === 13) { // enter
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return true
|
||||
} else if (event.which === 40) { // down
|
||||
let $next
|
||||
if ($selected.length === 0) {
|
||||
$next = $first
|
||||
} else {
|
||||
$next = $selected.next()
|
||||
}
|
||||
if ($next.length === 0) {
|
||||
$next = $first
|
||||
}
|
||||
$selected.removeClass('active')
|
||||
$next.addClass('active')
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return true
|
||||
} else if (event.which === 38) { // up
|
||||
if ($selected.length === 0) {
|
||||
$selected = $first
|
||||
}
|
||||
let $prev = $selected.prev()
|
||||
if ($prev.length === 0 || $prev.find('input').length > 0) {
|
||||
$prev = $last
|
||||
}
|
||||
$selected.removeClass('active')
|
||||
$prev.addClass('active')
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return true
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,78 +1,78 @@
|
||||
/*global $, Morris, gettext, formatPrice*/
|
||||
/* global i18nToString, formatPrice */
|
||||
$(function () {
|
||||
// Question view
|
||||
if (!$("#item_variations").length) {
|
||||
return;
|
||||
}
|
||||
// Question view
|
||||
if (!$('#item_variations').length) {
|
||||
return
|
||||
}
|
||||
|
||||
function update_variation_summary($el) {
|
||||
var var_names = Object.fromEntries(
|
||||
$el
|
||||
.find("input[name*=-value_]")
|
||||
.filter(function () {
|
||||
return !!this.value;
|
||||
})
|
||||
.map(function () {
|
||||
return [[this.getAttribute("lang"), this.value]];
|
||||
})
|
||||
.get()
|
||||
);
|
||||
var var_name = i18nToString(var_names);
|
||||
var price = $el.find("input[name*=-default_price]").val();
|
||||
if (price) {
|
||||
var currency = $el.find("[name*=-default_price] + .input-group-addon").text();
|
||||
price = formatPrice(price, currency);
|
||||
}
|
||||
function update_variation_summary ($el) {
|
||||
let var_names = Object.fromEntries(
|
||||
$el
|
||||
.find('input[name*=-value_]')
|
||||
.filter(function () {
|
||||
return !!this.value
|
||||
})
|
||||
.map(function () {
|
||||
return [[this.getAttribute('lang'), this.value]]
|
||||
})
|
||||
.get()
|
||||
)
|
||||
let var_name = i18nToString(var_names)
|
||||
let price = $el.find('input[name*=-default_price]').val()
|
||||
if (price) {
|
||||
let currency = $el.find('[name*=-default_price] + .input-group-addon').text()
|
||||
price = formatPrice(price, currency)
|
||||
}
|
||||
|
||||
$el.find(".variation-name").text(var_name);
|
||||
$el.find(".variation-price").text(price);
|
||||
$el.find(".variation-timeframe").toggleClass("variation-icon-hidden", !(
|
||||
!!$el.find("input[name$=-available_from_0]").val() ||
|
||||
!!$el.find("input[name$=-available_until_0]").val()
|
||||
));
|
||||
$el.find(".variation-name").toggleClass("variation-disabled", !(
|
||||
!!$el.find("input[name$=-active]").prop("checked")
|
||||
));
|
||||
$el.find(".variation-voucher").toggleClass("variation-icon-hidden", !(
|
||||
!!$el.find("input[name$=-hide_without_voucher]").prop("checked")
|
||||
));
|
||||
$el.find(".variation-membership").toggleClass("variation-icon-hidden", !(
|
||||
!!$el.find("input[name$=-require_membership]").prop("checked")
|
||||
));
|
||||
$el.find(".variation-warning").toggleClass("hidden", !(
|
||||
$el.find(".alert-warning").length
|
||||
));
|
||||
$el.find(".variation-error").toggleClass("hidden", !(
|
||||
$el.find(".alert-danger, .has-error").length
|
||||
));
|
||||
$el.find("input[name$=-limit_sales_channels]").each(function () {
|
||||
$el.find(".variation-channel-" + $(this).val()).toggleClass("variation-icon-hidden", !(
|
||||
(
|
||||
$(this).closest("[data-formset-form]").find("input[name$=-all_sales_channels]").prop("checked") ||
|
||||
$(this).prop("checked")
|
||||
) && (
|
||||
$("input[name=all_sales_channels]").prop("checked") ||
|
||||
$("input[name=limit_sales_channels][value=" + $(this).val() + "]").prop("checked")
|
||||
)
|
||||
));
|
||||
})
|
||||
}
|
||||
$el.find('.variation-name').text(var_name)
|
||||
$el.find('.variation-price').text(price)
|
||||
$el.find('.variation-timeframe').toggleClass('variation-icon-hidden', !(
|
||||
!!$el.find('input[name$=-available_from_0]').val()
|
||||
|| !!$el.find('input[name$=-available_until_0]').val()
|
||||
))
|
||||
$el.find('.variation-name').toggleClass('variation-disabled', !(
|
||||
$el.find('input[name$=-active]').prop('checked')
|
||||
))
|
||||
$el.find('.variation-voucher').toggleClass('variation-icon-hidden', !(
|
||||
$el.find('input[name$=-hide_without_voucher]').prop('checked')
|
||||
))
|
||||
$el.find('.variation-membership').toggleClass('variation-icon-hidden', !(
|
||||
$el.find('input[name$=-require_membership]').prop('checked')
|
||||
))
|
||||
$el.find('.variation-warning').toggleClass('hidden', !(
|
||||
$el.find('.alert-warning').length
|
||||
))
|
||||
$el.find('.variation-error').toggleClass('hidden', !(
|
||||
$el.find('.alert-danger, .has-error').length
|
||||
))
|
||||
$el.find('input[name$=-limit_sales_channels]').each(function () {
|
||||
$el.find('.variation-channel-' + $(this).val()).toggleClass('variation-icon-hidden', !(
|
||||
(
|
||||
$(this).closest('[data-formset-form]').find('input[name$=-all_sales_channels]').prop('checked')
|
||||
|| $(this).prop('checked')
|
||||
) && (
|
||||
$('input[name=all_sales_channels]').prop('checked')
|
||||
|| $('input[name=limit_sales_channels][value=' + $(this).val() + ']').prop('checked')
|
||||
)
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
$("#item_variations [data-formset-form]").each(function () {
|
||||
var $el = $(this);
|
||||
update_variation_summary($el);
|
||||
$(this).on("change dp.change", "input", function () {update_variation_summary($el)});
|
||||
});
|
||||
$("input[name=limit_sales_channels] input[name=all_sales_channels]").on("change", function() {
|
||||
$("#item_variations [data-formset-form]").each(function () {
|
||||
update_variation_summary($(this));
|
||||
});
|
||||
});
|
||||
$("#item_variations").on("formAdded", "details", function (event) {
|
||||
var $el = $(event.target);
|
||||
update_variation_summary($el);
|
||||
$(this).on("change dp.change", "input", function () {update_variation_summary($el)});
|
||||
setup_collapsible_details($("#item_variations"));
|
||||
form_handlers($(event.target));
|
||||
});
|
||||
});
|
||||
$('#item_variations [data-formset-form]').each(function () {
|
||||
let $el = $(this)
|
||||
update_variation_summary($el)
|
||||
$(this).on('change dp.change', 'input', function () { update_variation_summary($el) })
|
||||
})
|
||||
$('input[name=limit_sales_channels] input[name=all_sales_channels]').on('change', function () {
|
||||
$('#item_variations [data-formset-form]').each(function () {
|
||||
update_variation_summary($(this))
|
||||
})
|
||||
})
|
||||
$('#item_variations').on('formAdded', 'details', function (event) {
|
||||
let $el = $(event.target)
|
||||
update_variation_summary($el)
|
||||
$(this).on('change dp.change', 'input', function () { update_variation_summary($el) })
|
||||
setup_collapsible_details($('#item_variations'))
|
||||
form_handlers($(event.target))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
/*global $,u2f */
|
||||
/* global base64js */
|
||||
|
||||
function b64enc(buf) {
|
||||
return base64js.fromByteArray(buf)
|
||||
.replace(/\+/g, "-")
|
||||
.replace(/\//g, "_")
|
||||
.replace(/=/g, "");
|
||||
function b64enc (buf) {
|
||||
return base64js.fromByteArray(buf)
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_')
|
||||
.replace(/=/g, '')
|
||||
}
|
||||
|
||||
function b64RawEnc(buf) {
|
||||
return base64js.fromByteArray(buf)
|
||||
.replace(/\+/g, "-")
|
||||
.replace(/\//g, "_");
|
||||
function b64RawEnc (buf) {
|
||||
return base64js.fromByteArray(buf)
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_')
|
||||
}
|
||||
|
||||
function hexEncode(buf) {
|
||||
return Array.from(buf)
|
||||
.map(function(x) {
|
||||
return ("0" + x.toString(16)).substr(-2);
|
||||
})
|
||||
.join("");
|
||||
function hexEncode (buf) {
|
||||
return Array.from(buf)
|
||||
.map(function (x) {
|
||||
return ('0' + x.toString(16)).substr(-2)
|
||||
})
|
||||
.join('')
|
||||
}
|
||||
|
||||
async function fetch_json(url, options) {
|
||||
const response = await fetch(url, options);
|
||||
const body = await response.json();
|
||||
if (body.fail)
|
||||
throw body.fail;
|
||||
return body;
|
||||
async function fetch_json (url, options) {
|
||||
const response = await fetch(url, options)
|
||||
const body = await response.json()
|
||||
if (body.fail)
|
||||
throw body.fail
|
||||
return body
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,27 +35,26 @@ async function fetch_json(url, options) {
|
||||
* @param {Object} credentialCreateOptionsFromServer
|
||||
*/
|
||||
const transformCredentialCreateOptions = function (credentialCreateOptionsFromServer) {
|
||||
let {challenge, user, excludeCredentials} = credentialCreateOptionsFromServer;
|
||||
user.id = user.id.replace(/\_/g, "/").replace(/\-/g, "+");
|
||||
user.id = Uint8Array.from(atob(user.id), c => c.charCodeAt(0));
|
||||
let { challenge, user, excludeCredentials } = credentialCreateOptionsFromServer
|
||||
user.id = user.id.replace(/_/g, '/').replace(/-/g, '+')
|
||||
user.id = Uint8Array.from(atob(user.id), c => c.charCodeAt(0))
|
||||
|
||||
challenge = challenge.replace(/\_/g, "/").replace(/\-/g, "+");
|
||||
challenge = Uint8Array.from(atob(challenge), c => c.charCodeAt(0));
|
||||
challenge = challenge.replace(/_/g, '/').replace(/-/g, '+')
|
||||
challenge = Uint8Array.from(atob(challenge), c => c.charCodeAt(0))
|
||||
|
||||
excludeCredentials = excludeCredentials.map(credentialDescriptor => {
|
||||
let {id} = credentialDescriptor;
|
||||
id = id.replace(/\_/g, "/").replace(/\-/g, "+");
|
||||
id = Uint8Array.from(atob(id), c => c.charCodeAt(0));
|
||||
return Object.assign({}, credentialDescriptor, {id});
|
||||
});
|
||||
excludeCredentials = excludeCredentials.map(credentialDescriptor => {
|
||||
let { id } = credentialDescriptor
|
||||
id = id.replace(/_/g, '/').replace(/-/g, '+')
|
||||
id = Uint8Array.from(atob(id), c => c.charCodeAt(0))
|
||||
return Object.assign({}, credentialDescriptor, { id })
|
||||
})
|
||||
|
||||
const transformedCredentialCreateOptions = Object.assign(
|
||||
{}, credentialCreateOptionsFromServer,
|
||||
{challenge, user, excludeCredentials});
|
||||
|
||||
return transformedCredentialCreateOptions;
|
||||
};
|
||||
const transformedCredentialCreateOptions = Object.assign(
|
||||
{}, credentialCreateOptionsFromServer,
|
||||
{ challenge, user, excludeCredentials })
|
||||
|
||||
return transformedCredentialCreateOptions
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the binary data in the credential into base64 strings
|
||||
@@ -63,134 +62,132 @@ const transformCredentialCreateOptions = function (credentialCreateOptionsFromSe
|
||||
* @param {PublicKeyCredential} newAssertion
|
||||
*/
|
||||
const transformNewAssertionForServer = (newAssertion) => {
|
||||
const attObj = new Uint8Array(newAssertion.response.attestationObject);
|
||||
const clientDataJSON = new Uint8Array(newAssertion.response.clientDataJSON);
|
||||
const rawId = new Uint8Array(newAssertion.rawId);
|
||||
const transports = newAssertion.response.getTransports();
|
||||
const authenticatorAttachment = newAssertion.authenticatorAttachment;
|
||||
const attObj = new Uint8Array(newAssertion.response.attestationObject)
|
||||
const clientDataJSON = new Uint8Array(newAssertion.response.clientDataJSON)
|
||||
const rawId = new Uint8Array(newAssertion.rawId)
|
||||
const transports = newAssertion.response.getTransports()
|
||||
const authenticatorAttachment = newAssertion.authenticatorAttachment
|
||||
|
||||
const registrationClientExtensions = newAssertion.getClientExtensionResults();
|
||||
|
||||
return {
|
||||
id: newAssertion.id,
|
||||
rawId: b64enc(rawId),
|
||||
response: {
|
||||
attestationObject: b64enc(attObj),
|
||||
clientDataJSON: b64enc(clientDataJSON),
|
||||
transports: transports,
|
||||
},
|
||||
type: newAssertion.type,
|
||||
clientExtensionResults: JSON.stringify(registrationClientExtensions),
|
||||
authenticatorAttachment: authenticatorAttachment,
|
||||
};
|
||||
};
|
||||
const registrationClientExtensions = newAssertion.getClientExtensionResults()
|
||||
|
||||
return {
|
||||
id: newAssertion.id,
|
||||
rawId: b64enc(rawId),
|
||||
response: {
|
||||
attestationObject: b64enc(attObj),
|
||||
clientDataJSON: b64enc(clientDataJSON),
|
||||
transports: transports,
|
||||
},
|
||||
type: newAssertion.type,
|
||||
clientExtensionResults: JSON.stringify(registrationClientExtensions),
|
||||
authenticatorAttachment: authenticatorAttachment,
|
||||
}
|
||||
}
|
||||
|
||||
const transformCredentialRequestOptions = (credentialRequestOptionsFromServer) => {
|
||||
let {challenge, allowCredentials} = credentialRequestOptionsFromServer;
|
||||
let { challenge, allowCredentials } = credentialRequestOptionsFromServer
|
||||
|
||||
challenge = challenge.replace(/\_/g, "/").replace(/\-/g, "+");
|
||||
challenge = Uint8Array.from(atob(challenge), c => c.charCodeAt(0));
|
||||
challenge = challenge.replace(/_/g, '/').replace(/-/g, '+')
|
||||
challenge = Uint8Array.from(atob(challenge), c => c.charCodeAt(0))
|
||||
|
||||
allowCredentials = allowCredentials.map(credentialDescriptor => {
|
||||
let {id} = credentialDescriptor;
|
||||
id = id.replace(/\_/g, "/").replace(/\-/g, "+");
|
||||
id = Uint8Array.from(atob(id), c => c.charCodeAt(0));
|
||||
return Object.assign({}, credentialDescriptor, {id});
|
||||
});
|
||||
allowCredentials = allowCredentials.map(credentialDescriptor => {
|
||||
let { id } = credentialDescriptor
|
||||
id = id.replace(/_/g, '/').replace(/-/g, '+')
|
||||
id = Uint8Array.from(atob(id), c => c.charCodeAt(0))
|
||||
return Object.assign({}, credentialDescriptor, { id })
|
||||
})
|
||||
|
||||
const transformedCredentialRequestOptions = Object.assign(
|
||||
{},
|
||||
credentialRequestOptionsFromServer,
|
||||
{challenge, allowCredentials});
|
||||
const transformedCredentialRequestOptions = Object.assign(
|
||||
{},
|
||||
credentialRequestOptionsFromServer,
|
||||
{ challenge, allowCredentials })
|
||||
|
||||
return transformedCredentialRequestOptions;
|
||||
};
|
||||
return transformedCredentialRequestOptions
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the binary data in the assertion into strings for posting to the server.
|
||||
* @param {PublicKeyCredential} newAssertion
|
||||
*/
|
||||
const transformAssertionForServer = (newAssertion) => {
|
||||
const authData = new Uint8Array(newAssertion.response.authenticatorData);
|
||||
const clientDataJSON = new Uint8Array(newAssertion.response.clientDataJSON);
|
||||
const rawId = new Uint8Array(newAssertion.rawId);
|
||||
const sig = new Uint8Array(newAssertion.response.signature);
|
||||
const userHandle = new Uint8Array(newAssertion.response.userHandle);
|
||||
const assertionClientExtensions = newAssertion.getClientExtensionResults();
|
||||
const authenticatorAttachment = newAssertion.authenticatorAttachment;
|
||||
const authData = new Uint8Array(newAssertion.response.authenticatorData)
|
||||
const clientDataJSON = new Uint8Array(newAssertion.response.clientDataJSON)
|
||||
const rawId = new Uint8Array(newAssertion.rawId)
|
||||
const sig = new Uint8Array(newAssertion.response.signature)
|
||||
const userHandle = new Uint8Array(newAssertion.response.userHandle)
|
||||
const assertionClientExtensions = newAssertion.getClientExtensionResults()
|
||||
const authenticatorAttachment = newAssertion.authenticatorAttachment
|
||||
|
||||
return {
|
||||
id: newAssertion.id,
|
||||
rawId: b64enc(rawId),
|
||||
type: newAssertion.type,
|
||||
response: {
|
||||
authenticatorData: b64RawEnc(authData),
|
||||
clientDataJSON: b64RawEnc(clientDataJSON),
|
||||
signature: b64RawEnc(sig),
|
||||
userHandle: b64RawEnc(userHandle),
|
||||
},
|
||||
authenticatorAttachment: authenticatorAttachment,
|
||||
clientExtensionResults: JSON.stringify(assertionClientExtensions)
|
||||
};
|
||||
};
|
||||
return {
|
||||
id: newAssertion.id,
|
||||
rawId: b64enc(rawId),
|
||||
type: newAssertion.type,
|
||||
response: {
|
||||
authenticatorData: b64RawEnc(authData),
|
||||
clientDataJSON: b64RawEnc(clientDataJSON),
|
||||
signature: b64RawEnc(sig),
|
||||
userHandle: b64RawEnc(userHandle),
|
||||
},
|
||||
authenticatorAttachment: authenticatorAttachment,
|
||||
clientExtensionResults: JSON.stringify(assertionClientExtensions)
|
||||
}
|
||||
}
|
||||
|
||||
const startRegister = async (e) => {
|
||||
const publicKeyCredentialCreateOptions = transformCredentialCreateOptions(JSON.parse($("#webauthn-enroll").text()));
|
||||
const startRegister = async () => {
|
||||
const publicKeyCredentialCreateOptions = transformCredentialCreateOptions(JSON.parse($('#webauthn-enroll').text()))
|
||||
|
||||
// request the authenticator(s) to create a new credential keypair.
|
||||
let credential;
|
||||
try {
|
||||
credential = await navigator.credentials.create({
|
||||
publicKey: publicKeyCredentialCreateOptions
|
||||
});
|
||||
} catch (err) {
|
||||
$("#webauthn-error").removeClass("hidden");
|
||||
return console.error("Error creating credential:", err);
|
||||
}
|
||||
// request the authenticator(s) to create a new credential keypair.
|
||||
let credential
|
||||
try {
|
||||
credential = await navigator.credentials.create({
|
||||
publicKey: publicKeyCredentialCreateOptions
|
||||
})
|
||||
} catch (err) {
|
||||
$('#webauthn-error').removeClass('hidden')
|
||||
return console.error('Error creating credential:', err)
|
||||
}
|
||||
|
||||
// we now have a new credential! We now need to encode the byte arrays
|
||||
// in the credential into strings, for posting to our server.
|
||||
const newAssertionForServer = transformNewAssertionForServer(credential);
|
||||
// we now have a new credential! We now need to encode the byte arrays
|
||||
// in the credential into strings, for posting to our server.
|
||||
const newAssertionForServer = transformNewAssertionForServer(credential)
|
||||
|
||||
$("#webauthn-response").val(JSON.stringify(newAssertionForServer));
|
||||
$("#webauthn-form").submit();
|
||||
};
|
||||
$('#webauthn-response').val(JSON.stringify(newAssertionForServer))
|
||||
$('#webauthn-form').submit()
|
||||
}
|
||||
|
||||
const startLogin = async () => {
|
||||
const transformedCredentialRequestOptions = transformCredentialRequestOptions(JSON.parse($('#webauthn-login').text()))
|
||||
console.log(transformedCredentialRequestOptions)
|
||||
|
||||
const startLogin = async (e) => {
|
||||
const transformedCredentialRequestOptions = transformCredentialRequestOptions(JSON.parse($("#webauthn-login").text()));
|
||||
console.log(transformedCredentialRequestOptions);
|
||||
// request the authenticator to create an assertion signature using the
|
||||
// credential private key
|
||||
let assertion
|
||||
try {
|
||||
assertion = await navigator.credentials.get({
|
||||
publicKey: transformedCredentialRequestOptions,
|
||||
})
|
||||
} catch (err) {
|
||||
$('#webauthn-error').removeClass('hidden')
|
||||
return console.error('Error when creating credential:', err)
|
||||
}
|
||||
|
||||
// request the authenticator to create an assertion signature using the
|
||||
// credential private key
|
||||
let assertion;
|
||||
try {
|
||||
assertion = await navigator.credentials.get({
|
||||
publicKey: transformedCredentialRequestOptions,
|
||||
});
|
||||
} catch (err) {
|
||||
$("#webauthn-error").removeClass("hidden");
|
||||
return console.error("Error when creating credential:", err);
|
||||
}
|
||||
// we now have an authentication assertion! encode the byte arrays contained
|
||||
// in the assertion data as strings for posting to the server
|
||||
const transformedAssertionForServer = transformAssertionForServer(assertion)
|
||||
|
||||
// we now have an authentication assertion! encode the byte arrays contained
|
||||
// in the assertion data as strings for posting to the server
|
||||
const transformedAssertionForServer = transformAssertionForServer(assertion);
|
||||
|
||||
// post the assertion to the server for verification.
|
||||
$("input, select, textarea").prop("required", false);
|
||||
$("#webauthn-response, #id_password").val(JSON.stringify(transformedAssertionForServer));
|
||||
$("#webauthn-form").submit();
|
||||
};
|
||||
// post the assertion to the server for verification.
|
||||
$('input, select, textarea').prop('required', false)
|
||||
$('#webauthn-response, #id_password').val(JSON.stringify(transformedAssertionForServer))
|
||||
$('#webauthn-form').submit()
|
||||
}
|
||||
|
||||
$(function () {
|
||||
$("#webauthn-progress").hide();
|
||||
if ($("#webauthn-enroll").length) {
|
||||
$("#webauthn-progress").show();
|
||||
startRegister();
|
||||
} else if ($("#webauthn-login").length) {
|
||||
$("#webauthn-progress").show();
|
||||
startLogin();
|
||||
}
|
||||
});
|
||||
$('#webauthn-progress').hide()
|
||||
if ($('#webauthn-enroll').length) {
|
||||
$('#webauthn-progress').show()
|
||||
startRegister()
|
||||
} else if ($('#webauthn-login').length) {
|
||||
$('#webauthn-progress').show()
|
||||
startLogin()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const COOKIE_NAME = "__Host-pretix_csrftoken";
|
||||
const RELOAD_FLAG = "csrfReloadPerformed";
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const COOKIE_NAME = '__Host-pretix_csrftoken'
|
||||
const RELOAD_FLAG = 'csrfReloadPerformed'
|
||||
|
||||
const hasCookie = document.cookie
|
||||
.split("; ")
|
||||
.some((c) => c.startsWith(COOKIE_NAME + "="));
|
||||
const hasCookie = document.cookie
|
||||
.split('; ')
|
||||
.some((c) => c.startsWith(COOKIE_NAME + '='))
|
||||
|
||||
if (!hasCookie && !sessionStorage.getItem(RELOAD_FLAG)) {
|
||||
sessionStorage.setItem(RELOAD_FLAG, "1");
|
||||
location.reload();
|
||||
} else if (hasCookie && sessionStorage.getItem(RELOAD_FLAG)) {
|
||||
sessionStorage.removeItem(RELOAD_FLAG);
|
||||
}
|
||||
});
|
||||
if (!hasCookie && !sessionStorage.getItem(RELOAD_FLAG)) {
|
||||
sessionStorage.setItem(RELOAD_FLAG, '1')
|
||||
location.reload()
|
||||
} else if (hasCookie && sessionStorage.getItem(RELOAD_FLAG)) {
|
||||
sessionStorage.removeItem(RELOAD_FLAG)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
/*global $ */
|
||||
|
||||
$(function () {
|
||||
window.addEventListener("message", (event) => {
|
||||
if (event.data && event.data.__process === "popup_close") {
|
||||
window.close()
|
||||
}
|
||||
});
|
||||
window.opener.postMessage(JSON.parse($("#postmessage").text()), $("#origin").text())
|
||||
})
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.data && event.data.__process === 'popup_close') {
|
||||
window.close()
|
||||
}
|
||||
})
|
||||
window.opener.postMessage(JSON.parse($('#postmessage').text()), $('#origin').text())
|
||||
})
|
||||
|
||||
@@ -1,191 +1,191 @@
|
||||
/*global $,gettext,ngettext */
|
||||
/* global gettext,ngettext,moment,django */
|
||||
var cart = {
|
||||
_deadline: null,
|
||||
_deadline_timeout: null,
|
||||
_deadline_call: 0,
|
||||
_time_offset: 0,
|
||||
_prev_diff_minutes: 0,
|
||||
_deadline: null,
|
||||
_deadline_timeout: null,
|
||||
_deadline_call: 0,
|
||||
_time_offset: 0,
|
||||
_prev_diff_minutes: 0,
|
||||
|
||||
_get_now: function () {
|
||||
return moment().add(cart._time_offset, 'ms');
|
||||
},
|
||||
_get_now: function () {
|
||||
return moment().add(cart._time_offset, 'ms')
|
||||
},
|
||||
|
||||
_calc_offset: function () {
|
||||
if (typeof window.performance === "undefined") {
|
||||
return;
|
||||
}
|
||||
var perf = window.performance.timing;
|
||||
var server_time = Math.round(parseFloat($("body").attr("data-now")) * 1000);
|
||||
// We use requestStart as we don't know how latency is distributed and we rather want to err on the safe side
|
||||
var client_time = perf.requestStart;
|
||||
cart._time_offset = server_time - client_time;
|
||||
},
|
||||
_calc_offset: function () {
|
||||
if (typeof window.performance === 'undefined') {
|
||||
return
|
||||
}
|
||||
let perf = window.performance.timing
|
||||
let server_time = Math.round(parseFloat($('body').attr('data-now')) * 1000)
|
||||
// We use requestStart as we don't know how latency is distributed and we rather want to err on the safe side
|
||||
let client_time = perf.requestStart
|
||||
cart._time_offset = server_time - client_time
|
||||
},
|
||||
|
||||
show_expiry_notification: function () {
|
||||
document.getElementById("dialog-cart-extend").showModal();
|
||||
cart._expiry_notified = true;
|
||||
},
|
||||
show_expiry_notification: function () {
|
||||
document.getElementById('dialog-cart-extend').showModal()
|
||||
cart._expiry_notified = true
|
||||
},
|
||||
|
||||
draw_deadline: function () {
|
||||
function pad(n, width, z) {
|
||||
z = z || '0';
|
||||
n = n + '';
|
||||
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
|
||||
}
|
||||
draw_deadline: function () {
|
||||
function pad (n, width, z) {
|
||||
z = z || '0'
|
||||
n = n + ''
|
||||
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n
|
||||
}
|
||||
|
||||
cart._deadline_call++;
|
||||
if ((typeof django === 'undefined' || typeof django.gettext === 'undefined') && cart._deadline_call < 5) {
|
||||
// Language files are not loaded yet, don't run during the first seconds
|
||||
return;
|
||||
}
|
||||
var now = cart._get_now();
|
||||
var diff_total_seconds = cart._deadline.diff(now) / 1000;
|
||||
var diff_minutes = Math.floor(diff_total_seconds / 60);
|
||||
var diff_seconds = Math.floor(diff_total_seconds % 60);
|
||||
cart._deadline_call++
|
||||
if ((typeof django === 'undefined' || typeof django.gettext === 'undefined') && cart._deadline_call < 5) {
|
||||
// Language files are not loaded yet, don't run during the first seconds
|
||||
return
|
||||
}
|
||||
let now = cart._get_now()
|
||||
let diff_total_seconds = cart._deadline.diff(now) / 1000
|
||||
let diff_minutes = Math.floor(diff_total_seconds / 60)
|
||||
let diff_seconds = Math.floor(diff_total_seconds % 60)
|
||||
|
||||
if (diff_minutes < 0) {
|
||||
$("#cart-deadline").text(gettext("The items in your cart are no longer reserved for you. You can still complete your order as long as they’re available."));
|
||||
$("#cart-deadline-short").text(
|
||||
gettext("Cart expired")
|
||||
);
|
||||
if (!cart._deadline_timeout) {
|
||||
// no timeout => first time draw_deadline is invoked, but cart already expired => do not show dialog
|
||||
cart._expiry_notified = true;
|
||||
}
|
||||
} else {
|
||||
if (diff_minutes !== cart._prev_diff_minutes) {
|
||||
if (diff_minutes == 0) {
|
||||
$("#cart-deadline").text(gettext("Your cart is about to expire."))
|
||||
} else {
|
||||
$("#cart-deadline").text(
|
||||
ngettext(
|
||||
"The items in your cart are reserved for you for one minute.",
|
||||
"The items in your cart are reserved for you for {num} minutes.",
|
||||
diff_minutes
|
||||
).replace(/\{num\}/g, diff_minutes)
|
||||
);
|
||||
}
|
||||
cart._prev_diff_minutes = diff_minutes;
|
||||
}
|
||||
if (diff_minutes < 0) {
|
||||
$('#cart-deadline').text(gettext('The items in your cart are no longer reserved for you. You can still complete your order as long as they’re available.'))
|
||||
$('#cart-deadline-short').text(
|
||||
gettext('Cart expired')
|
||||
)
|
||||
if (!cart._deadline_timeout) {
|
||||
// no timeout => first time draw_deadline is invoked, but cart already expired => do not show dialog
|
||||
cart._expiry_notified = true
|
||||
}
|
||||
} else {
|
||||
if (diff_minutes !== cart._prev_diff_minutes) {
|
||||
if (diff_minutes == 0) {
|
||||
$('#cart-deadline').text(gettext('Your cart is about to expire.'))
|
||||
} else {
|
||||
$('#cart-deadline').text(
|
||||
ngettext(
|
||||
'The items in your cart are reserved for you for one minute.',
|
||||
'The items in your cart are reserved for you for {num} minutes.',
|
||||
diff_minutes
|
||||
).replace(/\{num\}/g, diff_minutes)
|
||||
)
|
||||
}
|
||||
cart._prev_diff_minutes = diff_minutes
|
||||
}
|
||||
|
||||
$("#cart-deadline-short").text(
|
||||
pad(diff_minutes.toString(), 2) + ':' + pad(diff_seconds.toString(), 2)
|
||||
);
|
||||
$('#cart-deadline-short').text(
|
||||
pad(diff_minutes.toString(), 2) + ':' + pad(diff_seconds.toString(), 2)
|
||||
)
|
||||
|
||||
cart._deadline_timeout = window.setTimeout(cart.draw_deadline, 500);
|
||||
}
|
||||
var already_expired = diff_total_seconds <= 0;
|
||||
var can_extend_cart = diff_minutes < 3 && (already_expired || cart._deadline < cart._max_extend);
|
||||
$("#cart-extend-button").toggle(can_extend_cart);
|
||||
if (can_extend_cart && diff_total_seconds < 45) {
|
||||
if (!cart._expiry_notified) cart.show_expiry_notification();
|
||||
$("#dialog-cart-extend-title").text(already_expired
|
||||
? gettext("Your cart has expired.")
|
||||
: gettext("Your cart is about to expire."));
|
||||
$("#dialog-cart-extend-description").text(already_expired
|
||||
? gettext("The items in your cart are no longer reserved for you. You can still complete your order as long as they're available.")
|
||||
: gettext("Do you want to renew the reservation period?"));
|
||||
$("#dialog-cart-extend .modal-card-confirm button").text(already_expired
|
||||
? gettext("Continue")
|
||||
: gettext("Renew reservation"));
|
||||
}
|
||||
},
|
||||
cart._deadline_timeout = window.setTimeout(cart.draw_deadline, 500)
|
||||
}
|
||||
let already_expired = diff_total_seconds <= 0
|
||||
let can_extend_cart = diff_minutes < 3 && (already_expired || cart._deadline < cart._max_extend)
|
||||
$('#cart-extend-button').toggle(can_extend_cart)
|
||||
if (can_extend_cart && diff_total_seconds < 45) {
|
||||
if (!cart._expiry_notified) cart.show_expiry_notification()
|
||||
$('#dialog-cart-extend-title').text(already_expired
|
||||
? gettext('Your cart has expired.')
|
||||
: gettext('Your cart is about to expire.'))
|
||||
$('#dialog-cart-extend-description').text(already_expired
|
||||
? gettext('The items in your cart are no longer reserved for you. You can still complete your order as long as they\'re available.')
|
||||
: gettext('Do you want to renew the reservation period?'))
|
||||
$('#dialog-cart-extend .modal-card-confirm button').text(already_expired
|
||||
? gettext('Continue')
|
||||
: gettext('Renew reservation'))
|
||||
}
|
||||
},
|
||||
|
||||
init: function () {
|
||||
"use strict";
|
||||
cart._calc_offset();
|
||||
cart.set_deadline(
|
||||
$("#cart-deadline").attr("data-expires"),
|
||||
$("#cart-deadline").attr("data-max-expiry-extend")
|
||||
);
|
||||
},
|
||||
init: function () {
|
||||
'use strict'
|
||||
cart._calc_offset()
|
||||
cart.set_deadline(
|
||||
$('#cart-deadline').attr('data-expires'),
|
||||
$('#cart-deadline').attr('data-max-expiry-extend')
|
||||
)
|
||||
},
|
||||
|
||||
set_deadline: function (expiry, max_extend, renewed_message) {
|
||||
"use strict";
|
||||
cart._expiry_notified = false;
|
||||
cart._deadline = moment(expiry);
|
||||
if (cart._deadline_timeout) {
|
||||
window.clearTimeout(cart._deadline_timeout);
|
||||
}
|
||||
cart._deadline_timeout = null;
|
||||
cart._max_extend = moment(max_extend);
|
||||
cart.draw_deadline();
|
||||
}
|
||||
};
|
||||
set_deadline: function (expiry, max_extend, renewed_message) {
|
||||
'use strict'
|
||||
cart._expiry_notified = false
|
||||
cart._deadline = moment(expiry)
|
||||
if (cart._deadline_timeout) {
|
||||
window.clearTimeout(cart._deadline_timeout)
|
||||
}
|
||||
cart._deadline_timeout = null
|
||||
cart._max_extend = moment(max_extend)
|
||||
cart.draw_deadline()
|
||||
}
|
||||
}
|
||||
|
||||
$(function () {
|
||||
"use strict";
|
||||
'use strict'
|
||||
|
||||
if ($("#cart-deadline").length) {
|
||||
cart.init();
|
||||
$("#cart-extend-confirmation-button").hide().on("blur", function() {
|
||||
$(this).hide();
|
||||
});
|
||||
}
|
||||
if ($('#cart-deadline').length) {
|
||||
cart.init()
|
||||
$('#cart-extend-confirmation-button').hide().on('blur', function () {
|
||||
$(this).hide()
|
||||
})
|
||||
}
|
||||
|
||||
$("#cart-extend-form").on("pretix:async-task-success", function(e, data) {
|
||||
if (data.success) {
|
||||
cart.set_deadline(data.expiry, data.max_expiry_extend);
|
||||
} else {
|
||||
alert(data.message);
|
||||
}
|
||||
});
|
||||
// renew-button in cart-panel is clicked, show inline dialog
|
||||
$("#cart-extend-button").on("click", function() {
|
||||
$("#cart-extend-form").one("pretix:async-task-success", function(e, data) {
|
||||
if (data.success) {
|
||||
document.getElementById("cart-extend-confirmation-dialog").show();
|
||||
}
|
||||
});
|
||||
});
|
||||
$("#cart-extend-confirmation-dialog").on("keydown", function (e) {
|
||||
if(e.key === "Escape") {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
$('#cart-extend-form').on('pretix:async-task-success', function (_e, data) {
|
||||
if (data.success) {
|
||||
cart.set_deadline(data.expiry, data.max_expiry_extend)
|
||||
} else {
|
||||
alert(data.message)
|
||||
}
|
||||
})
|
||||
// renew-button in cart-panel is clicked, show inline dialog
|
||||
$('#cart-extend-button').on('click', function () {
|
||||
$('#cart-extend-form').one('pretix:async-task-success', function (_e, data) {
|
||||
if (data.success) {
|
||||
document.getElementById('cart-extend-confirmation-dialog').show()
|
||||
}
|
||||
})
|
||||
})
|
||||
$('#cart-extend-confirmation-dialog').on('keydown', function (e) {
|
||||
if (e.key === 'Escape') {
|
||||
this.close()
|
||||
}
|
||||
})
|
||||
|
||||
// renew-button in modal dialog is clicked, show modal dialog
|
||||
$("#dialog-cart-extend form").submit(function() {
|
||||
$("#cart-extend-form").one("pretix:async-task-success", function(e, data) {
|
||||
if (data.success) {
|
||||
$("#dialog-cart-extended-title").text(data.message);
|
||||
$("#dialog-cart-extended-description").text($("#cart-deadline").text());
|
||||
document.getElementById("dialog-cart-extended").showModal();
|
||||
}
|
||||
}).submit();
|
||||
});
|
||||
// renew-button in modal dialog is clicked, show modal dialog
|
||||
$('#dialog-cart-extend form').submit(function () {
|
||||
$('#cart-extend-form').one('pretix:async-task-success', function (_e, data) {
|
||||
if (data.success) {
|
||||
$('#dialog-cart-extended-title').text(data.message)
|
||||
$('#dialog-cart-extended-description').text($('#cart-deadline').text())
|
||||
document.getElementById('dialog-cart-extended').showModal()
|
||||
}
|
||||
}).submit()
|
||||
})
|
||||
|
||||
$(".toggle-container").each(function() {
|
||||
var summary = $(".toggle-summary", this);
|
||||
var content = $("> :not(.toggle-summary)", this);
|
||||
var toggle = summary.find(".toggle").on("click", function(e) {
|
||||
this.ariaExpanded = !this.ariaExpanded;
|
||||
if (this.classList.contains("toggle-remove")) summary.attr("hidden", true);
|
||||
content.show().find(":input:visible").first().focus();
|
||||
});
|
||||
if (toggle.attr("aria-expanded")) {
|
||||
content.hide();
|
||||
}
|
||||
});
|
||||
$('.toggle-container').each(function () {
|
||||
let summary = $('.toggle-summary', this)
|
||||
let content = $('> :not(.toggle-summary)', this)
|
||||
let toggle = summary.find('.toggle').on('click', function () {
|
||||
this.ariaExpanded = !this.ariaExpanded
|
||||
if (this.classList.contains('toggle-remove')) summary.attr('hidden', true)
|
||||
content.show().find(':input:visible').first().focus()
|
||||
})
|
||||
if (toggle.attr('aria-expanded')) {
|
||||
content.hide()
|
||||
}
|
||||
})
|
||||
|
||||
$(".cart-icon-details.collapse-lines").each(function () {
|
||||
var $content = $(this).find(".content");
|
||||
var original_html = $content.html();
|
||||
var br_exp = /<br\s*\/?>/i;
|
||||
$content.text(original_html.split(br_exp).join(', '));
|
||||
if ($content.get(0).scrollWidth > $content.get(0).offsetWidth) {
|
||||
var $handler = $("<button>")
|
||||
.text($(this).attr("data-expand-text"))
|
||||
.addClass("btn btn-link collapse-handler")
|
||||
.attr("type", "button")
|
||||
.attr("aria-controls", $content.attr('id'))
|
||||
.attr("aria-expanded", "false");
|
||||
$handler.on("click", function (ev) {
|
||||
$content.html(original_html).removeClass("content");
|
||||
$handler.attr("aria-expanded", "true").attr("aria-hidden", "true");
|
||||
$handler.hide();
|
||||
});
|
||||
$(this).append($handler);
|
||||
}
|
||||
})
|
||||
});
|
||||
$('.cart-icon-details.collapse-lines').each(function () {
|
||||
let $content = $(this).find('.content')
|
||||
let original_html = $content.html()
|
||||
let br_exp = /<br\s*\/?>/i
|
||||
$content.text(original_html.split(br_exp).join(', '))
|
||||
if ($content.get(0).scrollWidth > $content.get(0).offsetWidth) {
|
||||
let $handler = $('<button>')
|
||||
.text($(this).attr('data-expand-text'))
|
||||
.addClass('btn btn-link collapse-handler')
|
||||
.attr('type', 'button')
|
||||
.attr('aria-controls', $content.attr('id'))
|
||||
.attr('aria-expanded', 'false')
|
||||
$handler.on('click', function () {
|
||||
$content.html(original_html).removeClass('content')
|
||||
$handler.attr('aria-expanded', 'true').attr('aria-hidden', 'true')
|
||||
$handler.hide()
|
||||
})
|
||||
$(this).append($handler)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,145 +1,143 @@
|
||||
/*global $ */
|
||||
|
||||
$(function () {
|
||||
window.pretix = window.pretix || {};
|
||||
window.pretix = window.pretix || {}
|
||||
|
||||
var storage_key = $("#cookie-consent-storage-key").text();
|
||||
var widget_consent = $("#cookie-consent-from-widget").text();
|
||||
var consent_checkboxes = $("#cookie-consent-details input[type=checkbox][name]");
|
||||
var consent_modal = document.getElementById("cookie-consent-modal");
|
||||
let storage_key = $('#cookie-consent-storage-key').text()
|
||||
let widget_consent = $('#cookie-consent-from-widget').text()
|
||||
let consent_checkboxes = $('#cookie-consent-details input[type=checkbox][name]')
|
||||
let consent_modal = document.getElementById('cookie-consent-modal')
|
||||
|
||||
function update_consent(consent, sessionOnly) {
|
||||
if (storage_key && window.sessionStorage && sessionOnly) {
|
||||
if (!window.localStorage[storage_key] || window.localStorage[storage_key] !== JSON.stringify(consent)) {
|
||||
// No need to write to sessionStorage if the value is identical to the one in localStorage
|
||||
window.sessionStorage[storage_key] = JSON.stringify(consent);
|
||||
}
|
||||
} else if (storage_key && window.localStorage) {
|
||||
window.localStorage[storage_key] = JSON.stringify(consent);
|
||||
// When saving permanent storage, clear session storage
|
||||
window.sessionStorage.removeItem(storage_key);
|
||||
}
|
||||
window.pretix.cookie_consent = consent;
|
||||
function update_consent (consent, sessionOnly) {
|
||||
if (storage_key && window.sessionStorage && sessionOnly) {
|
||||
if (!window.localStorage[storage_key] || window.localStorage[storage_key] !== JSON.stringify(consent)) {
|
||||
// No need to write to sessionStorage if the value is identical to the one in localStorage
|
||||
window.sessionStorage[storage_key] = JSON.stringify(consent)
|
||||
}
|
||||
} else if (storage_key && window.localStorage) {
|
||||
window.localStorage[storage_key] = JSON.stringify(consent)
|
||||
// When saving permanent storage, clear session storage
|
||||
window.sessionStorage.removeItem(storage_key)
|
||||
}
|
||||
window.pretix.cookie_consent = consent
|
||||
|
||||
// Event() is not supported by IE11, see ployfill here:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#polyfill
|
||||
var e = document.createEvent('CustomEvent');
|
||||
e.initCustomEvent('pretix:cookie-consent:change', true, true, consent);
|
||||
document.dispatchEvent(e)
|
||||
}
|
||||
// Event() is not supported by IE11, see ployfill here:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#polyfill
|
||||
let e = document.createEvent('CustomEvent')
|
||||
e.initCustomEvent('pretix:cookie-consent:change', true, true, consent)
|
||||
document.dispatchEvent(e)
|
||||
}
|
||||
|
||||
if (!storage_key) {
|
||||
// We are not on a page where the consent should run, fire the change event with empty consent but don't
|
||||
// actually store anything.
|
||||
update_consent(null, false);
|
||||
return;
|
||||
}
|
||||
if (!storage_key) {
|
||||
// We are not on a page where the consent should run, fire the change event with empty consent but don't
|
||||
// actually store anything.
|
||||
update_consent(null, false)
|
||||
return
|
||||
}
|
||||
|
||||
if (!window.localStorage) {
|
||||
// Consent not supported. Even IE8 supports it, so we're on a weird embedded device.
|
||||
// Let's just say we don't consent then.
|
||||
update_consent({}, false)
|
||||
return;
|
||||
}
|
||||
if (!window.localStorage) {
|
||||
// Consent not supported. Even IE8 supports it, so we're on a weird embedded device.
|
||||
// Let's just say we don't consent then.
|
||||
update_consent({}, false)
|
||||
return
|
||||
}
|
||||
|
||||
var storage_val, consent_source, save_for_session_only;
|
||||
if (window.sessionStorage[storage_key]) {
|
||||
// A manual input was given inside a widget. This is the user's last explicit choice and takes precedence –
|
||||
// as long as they are in the widget.
|
||||
storage_val = JSON.parse(window.sessionStorage[storage_key]);
|
||||
consent_source = 'sessionStorage';
|
||||
save_for_session_only = true;
|
||||
} else if (widget_consent) {
|
||||
// An input was given through the widget. This takes precedence over localStorage as we need to assume the
|
||||
// widget embedder is doing a correct job. If the user never visited the page without the widget, we also
|
||||
// use it to prefill local storage to save the user from seeing more cookie banners. (This will stop working
|
||||
// when browsers partition local storage of iframes, anyway.) If the user does have visited the page without
|
||||
// the widget before and has a consent setting in localStorage, we respect the widget consent *only* within
|
||||
// the widget -- hence, we save it into sessionStorage. We need to save it into sessionStorage because the
|
||||
// widget_data value itself will not "survive" the entire lifetime of the tab, i.e. it is no longer present
|
||||
// after the order was confirmed.
|
||||
widget_consent = JSON.parse(widget_consent);
|
||||
storage_val = {};
|
||||
consent_checkboxes.each(function () {
|
||||
this.checked = storage_val[this.name] = widget_consent.indexOf(this.name) > -1;
|
||||
});
|
||||
consent_source = 'widget';
|
||||
save_for_session_only = !!window.localStorage[storage_key];
|
||||
} else if (window.localStorage[storage_key]) {
|
||||
// The user made a specific selection, let's use that.
|
||||
storage_val = JSON.parse(window.localStorage[storage_key]) || {};
|
||||
consent_source = 'localStorage';
|
||||
save_for_session_only = false;
|
||||
} else {
|
||||
// No consent given, dialog will be shown.
|
||||
storage_val = {};
|
||||
consent_source = 'new';
|
||||
save_for_session_only = false;
|
||||
}
|
||||
let storage_val, consent_source, save_for_session_only
|
||||
if (window.sessionStorage[storage_key]) {
|
||||
// A manual input was given inside a widget. This is the user's last explicit choice and takes precedence –
|
||||
// as long as they are in the widget.
|
||||
storage_val = JSON.parse(window.sessionStorage[storage_key])
|
||||
consent_source = 'sessionStorage'
|
||||
save_for_session_only = true
|
||||
} else if (widget_consent) {
|
||||
// An input was given through the widget. This takes precedence over localStorage as we need to assume the
|
||||
// widget embedder is doing a correct job. If the user never visited the page without the widget, we also
|
||||
// use it to prefill local storage to save the user from seeing more cookie banners. (This will stop working
|
||||
// when browsers partition local storage of iframes, anyway.) If the user does have visited the page without
|
||||
// the widget before and has a consent setting in localStorage, we respect the widget consent *only* within
|
||||
// the widget -- hence, we save it into sessionStorage. We need to save it into sessionStorage because the
|
||||
// widget_data value itself will not "survive" the entire lifetime of the tab, i.e. it is no longer present
|
||||
// after the order was confirmed.
|
||||
widget_consent = JSON.parse(widget_consent)
|
||||
storage_val = {}
|
||||
consent_checkboxes.each(function () {
|
||||
this.checked = storage_val[this.name] = widget_consent.indexOf(this.name) > -1
|
||||
})
|
||||
consent_source = 'widget'
|
||||
save_for_session_only = !!window.localStorage[storage_key]
|
||||
} else if (window.localStorage[storage_key]) {
|
||||
// The user made a specific selection, let's use that.
|
||||
storage_val = JSON.parse(window.localStorage[storage_key]) || {}
|
||||
consent_source = 'localStorage'
|
||||
save_for_session_only = false
|
||||
} else {
|
||||
// No consent given, dialog will be shown.
|
||||
storage_val = {}
|
||||
consent_source = 'new'
|
||||
save_for_session_only = false
|
||||
}
|
||||
|
||||
var show_dialog = false;
|
||||
consent_checkboxes.each(function () {
|
||||
if (typeof storage_val[this.name] === "undefined") {
|
||||
// A new cookie type has been added that we haven't asked for yet
|
||||
if (consent_source === "widget") {
|
||||
// Trust the widget, keep it as "no consent"
|
||||
} else {
|
||||
show_dialog = true;
|
||||
}
|
||||
} else if (storage_val[this.name]) {
|
||||
this.checked = true;
|
||||
}
|
||||
})
|
||||
let show_dialog = false
|
||||
consent_checkboxes.each(function () {
|
||||
if (typeof storage_val[this.name] === 'undefined') {
|
||||
// A new cookie type has been added that we haven't asked for yet
|
||||
if (consent_source === 'widget') {
|
||||
// Trust the widget, keep it as "no consent"
|
||||
} else {
|
||||
show_dialog = true
|
||||
}
|
||||
} else if (storage_val[this.name]) {
|
||||
this.checked = true
|
||||
}
|
||||
})
|
||||
|
||||
update_consent(storage_val, save_for_session_only);
|
||||
update_consent(storage_val, save_for_session_only)
|
||||
|
||||
if (!consent_modal) {
|
||||
// Cookie consent is active, but no provider defined
|
||||
return;
|
||||
}
|
||||
if (!consent_modal) {
|
||||
// Cookie consent is active, but no provider defined
|
||||
return
|
||||
}
|
||||
|
||||
function _set_button_text () {
|
||||
var btn = $("#cookie-consent-button-no");
|
||||
btn.text(
|
||||
consent_checkboxes.filter(":checked").length ?
|
||||
btn.attr("data-detail-text") :
|
||||
btn.attr("data-summary-text")
|
||||
);
|
||||
}
|
||||
function _set_button_text () {
|
||||
let btn = $('#cookie-consent-button-no')
|
||||
btn.text(
|
||||
consent_checkboxes.filter(':checked').length
|
||||
? btn.attr('data-detail-text')
|
||||
: btn.attr('data-summary-text')
|
||||
)
|
||||
}
|
||||
|
||||
if (consent_checkboxes.filter(":checked").length) {
|
||||
$("#cookie-consent-details").prop("open", true).find("> *:not(summary)").show();
|
||||
}
|
||||
if (consent_checkboxes.filter(':checked').length) {
|
||||
$('#cookie-consent-details').prop('open', true).find('> *:not(summary)').show()
|
||||
}
|
||||
|
||||
_set_button_text();
|
||||
if (show_dialog) {
|
||||
consent_modal.showModal();
|
||||
consent_modal.addEventListener("cancel", function() {
|
||||
// Dialog was initially shown, interpret Escape as „do not consent to new providers“
|
||||
var consent = {};
|
||||
consent_checkboxes.each(function () {
|
||||
consent[this.name] = storage_val[this.name] || false;
|
||||
});
|
||||
update_consent(consent, false);
|
||||
}, {once : true});
|
||||
}
|
||||
_set_button_text()
|
||||
if (show_dialog) {
|
||||
consent_modal.showModal()
|
||||
consent_modal.addEventListener('cancel', function () {
|
||||
// Dialog was initially shown, interpret Escape as „do not consent to new providers“
|
||||
let consent = {}
|
||||
consent_checkboxes.each(function () {
|
||||
consent[this.name] = storage_val[this.name] || false
|
||||
})
|
||||
update_consent(consent, false)
|
||||
}, { once: true })
|
||||
}
|
||||
|
||||
consent_modal.addEventListener("close", function () {
|
||||
if (!consent_modal.returnValue) {// ESC, do not save
|
||||
return;
|
||||
}
|
||||
var consent = {};
|
||||
var consent_all = consent_modal.returnValue == "yes";
|
||||
consent_checkboxes.each(function () {
|
||||
consent[this.name] = this.checked = consent_all || this.checked;
|
||||
});
|
||||
if (consent_all) _set_button_text();
|
||||
update_consent(consent, false);
|
||||
});
|
||||
consent_checkboxes.on("change", _set_button_text);
|
||||
$("#cookie-consent-reopen").on("click", function (e) {
|
||||
consent_modal.showModal()
|
||||
e.preventDefault()
|
||||
return true
|
||||
})
|
||||
});
|
||||
consent_modal.addEventListener('close', function () {
|
||||
if (!consent_modal.returnValue) { // ESC, do not save
|
||||
return
|
||||
}
|
||||
let consent = {}
|
||||
let consent_all = consent_modal.returnValue == 'yes'
|
||||
consent_checkboxes.each(function () {
|
||||
consent[this.name] = this.checked = consent_all || this.checked
|
||||
})
|
||||
if (consent_all) _set_button_text()
|
||||
update_consent(consent, false)
|
||||
})
|
||||
consent_checkboxes.on('change', _set_button_text)
|
||||
$('#cookie-consent-reopen').on('click', function (e) {
|
||||
consent_modal.showModal()
|
||||
e.preventDefault()
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
var inIframe = function () {
|
||||
try {
|
||||
return window.self !== window.top;
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
let inIframe = function () {
|
||||
try {
|
||||
return window.self !== window.top
|
||||
} catch (_e) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if (inIframe()) {
|
||||
document.documentElement.classList.add('in-iframe');
|
||||
try {
|
||||
window.parent.postMessage({
|
||||
type: "pretix:widget:title",
|
||||
title: document.title,
|
||||
}, "*");
|
||||
} catch (e) {
|
||||
console.error("Could not post message to parent.", e);
|
||||
}
|
||||
document.documentElement.classList.add('in-iframe')
|
||||
try {
|
||||
window.parent.postMessage({
|
||||
type: 'pretix:widget:title',
|
||||
title: document.title,
|
||||
}, '*')
|
||||
} catch (e) {
|
||||
console.error('Could not post message to parent.', e)
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,134 +1,132 @@
|
||||
/*global $ */
|
||||
function questions_toggle_dependent (ev) {
|
||||
function q_should_be_shown ($el) {
|
||||
if (!$el.attr('data-question-dependency')) {
|
||||
return true
|
||||
}
|
||||
|
||||
function questions_toggle_dependent(ev) {
|
||||
function q_should_be_shown($el) {
|
||||
if (!$el.attr('data-question-dependency')) {
|
||||
return true;
|
||||
}
|
||||
let dependency_name = $el.attr('name').split('_')[0] + '_' + $el.attr('data-question-dependency')
|
||||
let dependency_values = JSON.parse($el.attr('data-question-dependency-values'))
|
||||
let $dependency_el
|
||||
|
||||
var dependency_name = $el.attr("name").split("_")[0] + "_" + $el.attr("data-question-dependency");
|
||||
var dependency_values = JSON.parse($el.attr("data-question-dependency-values"));
|
||||
var $dependency_el;
|
||||
if ($('select[name=' + dependency_name + ']').length) {
|
||||
// dependency is type C
|
||||
$dependency_el = $('select[name=' + dependency_name + ']')
|
||||
if (!$dependency_el.closest('.form-group').hasClass('dependency-hidden')) { // do not show things that depend on hidden things
|
||||
return q_should_be_shown($dependency_el) && $.inArray($dependency_el.val(), dependency_values) > -1
|
||||
}
|
||||
} else if ($('input[type=checkbox][name=' + dependency_name + ']').length) {
|
||||
// dependency type is B or M
|
||||
if ($.inArray('True', dependency_values) > -1 || $.inArray('False', dependency_values) > -1) {
|
||||
$dependency_el = $('input[name=' + dependency_name + ']')
|
||||
if (!$dependency_el.closest('.form-group').hasClass('dependency-hidden')) { // do not show things that depend on hidden things
|
||||
return q_should_be_shown($dependency_el) && (
|
||||
($.inArray('True', dependency_values) > -1 && $dependency_el.prop('checked'))
|
||||
|| ($.inArray('False', dependency_values) > -1 && !$dependency_el.prop('checked'))
|
||||
)
|
||||
}
|
||||
} else {
|
||||
let filter = ''
|
||||
for (let i = 0; i < dependency_values.length; i++) {
|
||||
if (filter) filter += ', '
|
||||
filter += 'input[value=' + dependency_values[i] + '][name=' + dependency_name + ']:checked'
|
||||
}
|
||||
$dependency_el = $('input[value=' + dependency_values[0] + '][name=' + dependency_name + ']')
|
||||
if (!$dependency_el.closest('.form-group').hasClass('dependency-hidden')) { // do not show things that depend on hidden things
|
||||
return q_should_be_shown($dependency_el) && $(filter).length
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($("select[name=" + dependency_name + "]").length) {
|
||||
// dependency is type C
|
||||
$dependency_el = $("select[name=" + dependency_name + "]");
|
||||
if (!$dependency_el.closest(".form-group").hasClass("dependency-hidden")) { // do not show things that depend on hidden things
|
||||
return q_should_be_shown($dependency_el) && $.inArray($dependency_el.val(), dependency_values) > -1;
|
||||
}
|
||||
} else if ($("input[type=checkbox][name=" + dependency_name + "]").length) {
|
||||
// dependency type is B or M
|
||||
if ($.inArray("True", dependency_values) > -1 || $.inArray("False", dependency_values) > -1) {
|
||||
$dependency_el = $("input[name=" + dependency_name + "]");
|
||||
if (!$dependency_el.closest(".form-group").hasClass("dependency-hidden")) { // do not show things that depend on hidden things
|
||||
return q_should_be_shown($dependency_el) && (
|
||||
($.inArray("True", dependency_values) > -1 && $dependency_el.prop('checked'))
|
||||
|| ($.inArray("False", dependency_values) > -1 && !$dependency_el.prop('checked'))
|
||||
);
|
||||
}
|
||||
} else {
|
||||
var filter = "";
|
||||
for (var i = 0; i < dependency_values.length; i++) {
|
||||
if (filter) filter += ", ";
|
||||
filter += "input[value=" + dependency_values[i] + "][name=" + dependency_name + "]:checked";
|
||||
}
|
||||
$dependency_el = $("input[value=" + dependency_values[0] + "][name=" + dependency_name + "]");
|
||||
if (!$dependency_el.closest(".form-group").hasClass("dependency-hidden")) { // do not show things that depend on hidden things
|
||||
return q_should_be_shown($dependency_el) && $(filter).length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$('[data-question-dependency]').each(function () {
|
||||
let $dependent = $(this).closest('.form-group')
|
||||
let is_shown = !$dependent.hasClass('dependency-hidden')
|
||||
let should_be_shown = q_should_be_shown($(this))
|
||||
|
||||
$("[data-question-dependency]").each(function () {
|
||||
var $dependent = $(this).closest(".form-group");
|
||||
var is_shown = !$dependent.hasClass("dependency-hidden");
|
||||
var should_be_shown = q_should_be_shown($(this));
|
||||
|
||||
if (should_be_shown && !is_shown) {
|
||||
$dependent.stop().removeClass("dependency-hidden");
|
||||
if (!ev) {
|
||||
$dependent.show();
|
||||
} else {
|
||||
$dependent.slideDown();
|
||||
}
|
||||
$dependent.find("input.required-hidden, select.required-hidden, textarea.required-hidden").each(function () {
|
||||
$(this).prop("required", true).removeClass("required-hidden");
|
||||
});
|
||||
} else if (!should_be_shown && is_shown) {
|
||||
if ($dependent.hasClass("has-error") || $dependent.find(".has-error").length) {
|
||||
// Do not hide things with invalid validation
|
||||
return;
|
||||
}
|
||||
$dependent.stop().addClass("dependency-hidden");
|
||||
if (!ev) {
|
||||
$dependent.hide();
|
||||
} else {
|
||||
$dependent.slideUp();
|
||||
}
|
||||
$dependent.find("input[required], select[required], textarea[required]").each(function () {
|
||||
$(this).prop("required", false).addClass("required-hidden");
|
||||
});
|
||||
}
|
||||
});
|
||||
if (should_be_shown && !is_shown) {
|
||||
$dependent.stop().removeClass('dependency-hidden')
|
||||
if (!ev) {
|
||||
$dependent.show()
|
||||
} else {
|
||||
$dependent.slideDown()
|
||||
}
|
||||
$dependent.find('input.required-hidden, select.required-hidden, textarea.required-hidden').each(function () {
|
||||
$(this).prop('required', true).removeClass('required-hidden')
|
||||
})
|
||||
} else if (!should_be_shown && is_shown) {
|
||||
if ($dependent.hasClass('has-error') || $dependent.find('.has-error').length) {
|
||||
// Do not hide things with invalid validation
|
||||
return
|
||||
}
|
||||
$dependent.stop().addClass('dependency-hidden')
|
||||
if (!ev) {
|
||||
$dependent.hide()
|
||||
} else {
|
||||
$dependent.slideUp()
|
||||
}
|
||||
$dependent.find('input[required], select[required], textarea[required]').each(function () {
|
||||
$(this).prop('required', false).addClass('required-hidden')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function questions_init_photos(el) {
|
||||
if (!FileReader) {
|
||||
// No browser support
|
||||
return
|
||||
}
|
||||
function questions_init_photos (el) {
|
||||
if (!FileReader) {
|
||||
// No browser support
|
||||
return
|
||||
}
|
||||
|
||||
el.find("input[data-portrait-photo]").each(function () {
|
||||
var $inp = $(this)
|
||||
var $container = $inp.parent().parent()
|
||||
el.find('input[data-portrait-photo]').each(function () {
|
||||
let $inp = $(this)
|
||||
let $container = $inp.parent().parent()
|
||||
|
||||
$inp.prop("required", false)
|
||||
$container.find(".photo-input").addClass("hidden")
|
||||
$container.find(".photo-buttons").removeClass("hidden")
|
||||
$inp.prop('required', false)
|
||||
$container.find('.photo-input').addClass('hidden')
|
||||
$container.find('.photo-buttons').removeClass('hidden')
|
||||
|
||||
$container.find("button[data-action=upload]").click(function () {
|
||||
$inp.click();
|
||||
})
|
||||
$container.find('button[data-action=upload]').click(function () {
|
||||
$inp.click()
|
||||
})
|
||||
|
||||
var cropper = new Cropper($container.find(".photo-preview img").get(0), {
|
||||
aspectRatio: 3 / 4,
|
||||
viewMode: 1,
|
||||
zoomable: false,
|
||||
crop: function (event) {
|
||||
$container.find("input[type=hidden]").val(JSON.stringify(cropper.getData(true)));
|
||||
},
|
||||
});
|
||||
/* This rule is very important, please don't ignore this */
|
||||
var cropper = new Cropper($container.find('.photo-preview img').get(0), {
|
||||
aspectRatio: 3 / 4,
|
||||
viewMode: 1,
|
||||
zoomable: false,
|
||||
crop: function (event) {
|
||||
$container.find('input[type=hidden]').val(JSON.stringify(cropper.getData(true)))
|
||||
},
|
||||
})
|
||||
/* This rule is very important, please don't ignore this */
|
||||
|
||||
$inp.on("change", function () {
|
||||
if (!$inp.get(0).files[0]) return
|
||||
$container.find("button[data-action=upload]").append("<span class='fa fa-spin fa-cog'></span>")
|
||||
var fr = new FileReader()
|
||||
fr.onload = function () {
|
||||
cropper.replace(fr.result)
|
||||
$container.find(".photo-preview").removeClass("hidden")
|
||||
$container.find("button[data-action=upload] .fa-spin").remove()
|
||||
}
|
||||
fr.readAsDataURL($inp.get(0).files[0])
|
||||
})
|
||||
});
|
||||
$inp.on('change', function () {
|
||||
if (!$inp.get(0).files[0]) return
|
||||
$container.find('button[data-action=upload]').append('<span class=\'fa fa-spin fa-cog\'></span>')
|
||||
let fr = new FileReader()
|
||||
fr.onload = function () {
|
||||
cropper.replace(fr.result)
|
||||
$container.find('.photo-preview').removeClass('hidden')
|
||||
$container.find('button[data-action=upload] .fa-spin').remove()
|
||||
}
|
||||
fr.readAsDataURL($inp.get(0).files[0])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function questions_init_profiles(el) {
|
||||
/*
|
||||
function questions_init_profiles (el) {
|
||||
/*
|
||||
Auto-fill answers with profiles and addresses from customer account.
|
||||
|
||||
There are two types of profiles:
|
||||
1. profiles for answers and
|
||||
1. profiles for answers and
|
||||
2. profiles for invoice addresses
|
||||
|
||||
Both are handled the same way.
|
||||
|
||||
Each form section/fieldset has its own auto-fill and save to profile
|
||||
inputs. Each fieldset can define its own profiles by providing the
|
||||
Each form section/fieldset has its own auto-fill and save to profile
|
||||
inputs. Each fieldset can define its own profiles by providing the
|
||||
HTML-attribute data-profiles-id, which defaults to "profiles_json".
|
||||
Currently only the invoice address fieldset uses this to load a
|
||||
Currently only the invoice address fieldset uses this to load a
|
||||
different set of profiles.
|
||||
|
||||
For each section each profile’s answers are matched to inputs inside
|
||||
@@ -141,349 +139,343 @@ function questions_init_profiles(el) {
|
||||
– in the original profile description, strikethrough which answer
|
||||
will be overwritten, followed by the new answer
|
||||
– add new answers with a + in front
|
||||
– change <select> to a list of radio-buttons for multiline-display
|
||||
– change <select> to a list of radio-buttons for multiline-display
|
||||
of profiles?
|
||||
*/
|
||||
var profilesById = {};
|
||||
function getProfilesById(id) {
|
||||
if (!(id in profilesById)) {
|
||||
var element = document.getElementById(id);
|
||||
profilesById[id] = (!element || !element.textContent) ? [] : JSON.parse(element.textContent);
|
||||
}
|
||||
return profilesById[id];
|
||||
}
|
||||
let profilesById = {}
|
||||
function getProfilesById (id) {
|
||||
if (!(id in profilesById)) {
|
||||
let element = document.getElementById(id)
|
||||
profilesById[id] = (!element || !element.textContent) ? [] : JSON.parse(element.textContent)
|
||||
}
|
||||
return profilesById[id]
|
||||
}
|
||||
|
||||
function matchProfilesToInputs(profiles, scope) {
|
||||
var filtered = [];
|
||||
var data;
|
||||
var matched_field;
|
||||
var addSpecialKey;
|
||||
// special fields are used for substition with human readable or pre-formatted values
|
||||
var addSpecialFieldMap = {
|
||||
"country": "_country_for_address",
|
||||
"state": "_state_for_address",
|
||||
"name_parts_0": "_name",
|
||||
"attendee_name_parts_0": "_attendee_name",
|
||||
}
|
||||
for (var p of profiles) {
|
||||
data = {};
|
||||
for (var key of Object.keys(p)) {
|
||||
if (key.startsWith("_") || p[key] === null) {
|
||||
continue;
|
||||
}
|
||||
matched_field = getMatchingInput(key, p[key], scope);
|
||||
if (matched_field) {
|
||||
// TODO: only add if no other answer matches same fields?
|
||||
data[key] = {
|
||||
"value": (typeof p[key] == "string") ? p[key] : p[key]["value"],
|
||||
"field": matched_field
|
||||
};
|
||||
if (p[key]["label"]) data[key]["label"] = p[key]["label"];
|
||||
if (p[key]["type"]) data[key]["type"] = p[key]["type"];
|
||||
if (addSpecialKey = addSpecialFieldMap[key]) {
|
||||
data[addSpecialKey] = p[addSpecialKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Object.keys(data).length) filtered.push(data);
|
||||
};
|
||||
return filtered;
|
||||
}
|
||||
// For auto-fill with few inputs it could happen that multiple profiles
|
||||
// only match with the same fields that have the same values. It makes
|
||||
// no sense to show multiple profiles if all fill the same value(s).
|
||||
// Therefore filter profiles to unique ones.
|
||||
function uniqueProfiles(profiles) {
|
||||
var uniques = [];
|
||||
var matchIndex;
|
||||
for (var p of profiles) {
|
||||
matchIndex = uniques.findIndex(function(element, index, array) {
|
||||
return _profilesAreEqual(element, p);
|
||||
});
|
||||
if (matchIndex === -1) uniques.push(p);
|
||||
}
|
||||
return uniques;
|
||||
}
|
||||
function _profilesAreEqual(a, b) {
|
||||
var keysA = Object.keys(a);
|
||||
var keysB = Object.keys(b);
|
||||
if (keysA.length !== keysB.length) return false;
|
||||
keysA.sort();
|
||||
keysB.sort();
|
||||
if (!keysA.every((val, index) => val === keysB[index])) return false;
|
||||
if (!keysA.every((key, index) => a[key].value === b[key].value)) return false;
|
||||
return true;
|
||||
}
|
||||
function matchProfilesToInputs (profiles, scope) {
|
||||
let filtered = []
|
||||
let data
|
||||
let matched_field
|
||||
let addSpecialKey
|
||||
// special fields are used for substition with human readable or pre-formatted values
|
||||
let addSpecialFieldMap = {
|
||||
country: '_country_for_address',
|
||||
state: '_state_for_address',
|
||||
name_parts_0: '_name',
|
||||
attendee_name_parts_0: '_attendee_name',
|
||||
}
|
||||
for (let p of profiles) {
|
||||
data = {}
|
||||
for (let key of Object.keys(p)) {
|
||||
if (key.startsWith('_') || p[key] === null) {
|
||||
continue
|
||||
}
|
||||
matched_field = getMatchingInput(key, p[key], scope)
|
||||
if (matched_field) {
|
||||
// TODO: only add if no other answer matches same fields?
|
||||
data[key] = {
|
||||
value: (typeof p[key] == 'string') ? p[key] : p[key]['value'],
|
||||
field: matched_field
|
||||
}
|
||||
if (p[key]['label']) data[key]['label'] = p[key]['label']
|
||||
if (p[key]['type']) data[key]['type'] = p[key]['type']
|
||||
if (addSpecialKey = addSpecialFieldMap[key]) {
|
||||
data[addSpecialKey] = p[addSpecialKey]
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Object.keys(data).length) filtered.push(data)
|
||||
};
|
||||
return filtered
|
||||
}
|
||||
// For auto-fill with few inputs it could happen that multiple profiles
|
||||
// only match with the same fields that have the same values. It makes
|
||||
// no sense to show multiple profiles if all fill the same value(s).
|
||||
// Therefore filter profiles to unique ones.
|
||||
function uniqueProfiles (profiles) {
|
||||
let uniques = []
|
||||
let matchIndex
|
||||
for (var p of profiles) {
|
||||
matchIndex = uniques.findIndex(function (element, index, array) {
|
||||
return _profilesAreEqual(element, p)
|
||||
})
|
||||
if (matchIndex === -1) uniques.push(p)
|
||||
}
|
||||
return uniques
|
||||
}
|
||||
function _profilesAreEqual (a, b) {
|
||||
let keysA = Object.keys(a)
|
||||
let keysB = Object.keys(b)
|
||||
if (keysA.length !== keysB.length) return false
|
||||
keysA.sort()
|
||||
keysB.sort()
|
||||
if (!keysA.every((val, index) => val === keysB[index])) return false
|
||||
if (!keysA.every((key, index) => a[key].value === b[key].value)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
function _getInputForLabel(label) {
|
||||
if (!label) return null;
|
||||
var input;
|
||||
if (label.getAttribute("for")) {
|
||||
input = document.getElementById(label.getAttribute("for"));
|
||||
if (input) return input;
|
||||
}
|
||||
// for grouped inputs like phone number the "label" is more a fieldset/legend
|
||||
return label.closest(".form-group").querySelectorAll("select, input, textarea");
|
||||
}
|
||||
function getMatchingInput(key, answer, scope) {
|
||||
var $label;
|
||||
// _0 and _1 are e.g. for phone-fields. name-fields have their parts/keys already split
|
||||
var $fields = $('[name$="' + key + '"], [name$="' + key + '_0"], [name$="' + key + '_1"]', scope).not(":disabled");
|
||||
if ($fields.length) return $fields;
|
||||
if (!answer) return null;
|
||||
function _getInputForLabel (label) {
|
||||
if (!label) return null
|
||||
let input
|
||||
if (label.getAttribute('for')) {
|
||||
input = document.getElementById(label.getAttribute('for'))
|
||||
if (input) return input
|
||||
}
|
||||
// for grouped inputs like phone number the "label" is more a fieldset/legend
|
||||
return label.closest('.form-group').querySelectorAll('select, input, textarea')
|
||||
}
|
||||
function getMatchingInput (key, answer, scope) {
|
||||
let $label
|
||||
// _0 and _1 are e.g. for phone-fields. name-fields have their parts/keys already split
|
||||
let $fields = $('[name$="' + key + '"], [name$="' + key + '_0"], [name$="' + key + '_1"]', scope).not(':disabled')
|
||||
if ($fields.length) return $fields
|
||||
if (!answer) return null
|
||||
|
||||
if (answer.identifier) {
|
||||
$label = $('[data-identifier="' + answer.identifier + '"]', scope);
|
||||
var input = _getInputForLabel($label.get(0));
|
||||
if (input) return $(input);
|
||||
}
|
||||
for (var label of scope.getElementsByTagName("label")) {
|
||||
if (label.textContent === answer.label) {
|
||||
var input = _getInputForLabel(label);
|
||||
if (input) return $(input);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (answer.identifier) {
|
||||
$label = $('[data-identifier="' + answer.identifier + '"]', scope)
|
||||
var input = _getInputForLabel($label.get(0))
|
||||
if (input) return $(input)
|
||||
}
|
||||
for (let label of scope.getElementsByTagName('label')) {
|
||||
if (label.textContent === answer.label) {
|
||||
var input = _getInputForLabel(label)
|
||||
if (input) return $(input)
|
||||
break
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function formatAnswerHumanReadable(answer) {
|
||||
if (typeof answer == "string") return answer;
|
||||
if (typeof answer == "number") return answer.toString();
|
||||
if (!answer && answer !== false) return "";
|
||||
var value = answer.value;
|
||||
if ("type" in answer) {
|
||||
if (answer.type === "TEL") {
|
||||
// TODO: format phone number with locale or use pre-formatted like with names?
|
||||
return value;
|
||||
}
|
||||
if (answer.type === "W") {
|
||||
return moment(value).format(document.body.getAttribute("data-datetimeformat"));
|
||||
}
|
||||
if (answer.type === "D") {
|
||||
return moment(value).format(document.body.getAttribute("data-dateformat"));
|
||||
}
|
||||
if (answer.type === "H") {
|
||||
var format = document.body.getAttribute("data-timeformat");
|
||||
return moment(value, "HH:mm:ss").format(format);
|
||||
}
|
||||
if (answer.type === "B") {
|
||||
return value ? gettext("Yes") : gettext("No");
|
||||
}
|
||||
}
|
||||
if (typeof value == "string") return value;
|
||||
if (!value) return "";
|
||||
return Object.values(value).join(", ");
|
||||
}
|
||||
function formatAnswerHumanReadable (answer) {
|
||||
if (typeof answer == 'string') return answer
|
||||
if (typeof answer == 'number') return answer.toString()
|
||||
if (!answer && answer !== false) return ''
|
||||
let value = answer.value
|
||||
if ('type' in answer) {
|
||||
if (answer.type === 'TEL') {
|
||||
// TODO: format phone number with locale or use pre-formatted like with names?
|
||||
return value
|
||||
}
|
||||
if (answer.type === 'W') {
|
||||
return moment(value).format(document.body.getAttribute('data-datetimeformat'))
|
||||
}
|
||||
if (answer.type === 'D') {
|
||||
return moment(value).format(document.body.getAttribute('data-dateformat'))
|
||||
}
|
||||
if (answer.type === 'H') {
|
||||
let format = document.body.getAttribute('data-timeformat')
|
||||
return moment(value, 'HH:mm:ss').format(format)
|
||||
}
|
||||
if (answer.type === 'B') {
|
||||
return value ? gettext('Yes') : gettext('No')
|
||||
}
|
||||
}
|
||||
if (typeof value == 'string') return value
|
||||
if (!value) return ''
|
||||
return Object.values(value).join(', ')
|
||||
}
|
||||
|
||||
// TODO: add as few info as possible to make a distinction between available profiles?
|
||||
function labelForProfile(p, profiles, scope = null) {
|
||||
var parts = describeProfile(p);
|
||||
var label = parts.join(", ");
|
||||
if (label.length > 74) {
|
||||
var len = label.lastIndexOf(' ', 74);
|
||||
label = label.substr(0, Math.max(len, 48)) + " …";
|
||||
}
|
||||
return label;
|
||||
}
|
||||
function getAnswer(a) {
|
||||
if (typeof a == "string") return a;
|
||||
return a && "value" in a ? a["value"] : "";
|
||||
}
|
||||
function describeProfile(p) {
|
||||
if (!p) return [];
|
||||
var lines = [
|
||||
getAnswer(p["company"]),
|
||||
p["_name"],
|
||||
[p["_attendee_name"], getAnswer(p["attendee_email"])].filter(v => v).join(", "),
|
||||
[
|
||||
getAnswer(p["street"]),
|
||||
[getAnswer(p["zipcode"]), getAnswer(p["city"]), p["_state_for_address"]].filter(v => v).join(" "),
|
||||
p["_country_for_address"]
|
||||
].filter(v => v).join(", ")
|
||||
];
|
||||
lines = lines.filter(line => line && line.trim());
|
||||
// TODO: add as few info as possible to make a distinction between available profiles?
|
||||
function labelForProfile (p, profiles, scope = null) {
|
||||
let parts = describeProfile(p)
|
||||
let label = parts.join(', ')
|
||||
if (label.length > 74) {
|
||||
let len = label.lastIndexOf(' ', 74)
|
||||
label = label.substr(0, Math.max(len, 48)) + ' …'
|
||||
}
|
||||
return label
|
||||
}
|
||||
function getAnswer (a) {
|
||||
if (typeof a == 'string') return a
|
||||
return a && 'value' in a ? a['value'] : ''
|
||||
}
|
||||
function describeProfile (p) {
|
||||
if (!p) return []
|
||||
let lines = [
|
||||
getAnswer(p['company']),
|
||||
p['_name'],
|
||||
[p['_attendee_name'], getAnswer(p['attendee_email'])].filter(v => v).join(', '),
|
||||
[
|
||||
getAnswer(p['street']),
|
||||
[getAnswer(p['zipcode']), getAnswer(p['city']), p['_state_for_address']].filter(v => v).join(' '),
|
||||
p['_country_for_address']
|
||||
].filter(v => v).join(', ')
|
||||
]
|
||||
lines = lines.filter(line => line && line.trim())
|
||||
|
||||
var answer;
|
||||
var label;
|
||||
for (var key of Object.keys(p)) {
|
||||
if (!key.startsWith("question_")) continue;
|
||||
answer = p[key];
|
||||
label = answer["label"] || "";
|
||||
lines.push(label + ("!?.:".split("").indexOf(label.slice(-1)) > -1 ? " " : ": ") + formatAnswerHumanReadable(answer))
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
function escapeHTML(t) {
|
||||
return $("<div>").text(t).get(0).innerHTML;
|
||||
}
|
||||
function describeProfileHTML(p) {
|
||||
return describeProfile(p).map(escapeHTML).join("<br>");
|
||||
}
|
||||
let answer
|
||||
let label
|
||||
for (let key of Object.keys(p)) {
|
||||
if (!key.startsWith('question_')) continue
|
||||
answer = p[key]
|
||||
label = answer['label'] || ''
|
||||
lines.push(label + ('!?.:'.split('').indexOf(label.slice(-1)) > -1 ? ' ' : ': ') + formatAnswerHumanReadable(answer))
|
||||
}
|
||||
return lines
|
||||
}
|
||||
function escapeHTML (t) {
|
||||
return $('<div>').text(t).get(0).innerHTML
|
||||
}
|
||||
function describeProfileHTML (p) {
|
||||
return describeProfile(p).map(escapeHTML).join('<br>')
|
||||
}
|
||||
|
||||
function _updateDescription (select, profile, $help) {
|
||||
// show additional description if different from option-text
|
||||
let label = select.options[select.selectedIndex].textContent
|
||||
let lines = describeProfile(profile).map(escapeHTML)
|
||||
if (!lines.length || label === lines.join(', ')) {
|
||||
$help.slideUp(function () {
|
||||
$help.html('')
|
||||
})
|
||||
} else {
|
||||
$help.html(lines.join('<br>')).slideDown()
|
||||
}
|
||||
}
|
||||
|
||||
function _updateDescription(select, profile, $help) {
|
||||
// show additional description if different from option-text
|
||||
var label = select.options[select.selectedIndex].textContent;
|
||||
var lines = describeProfile(profile).map(escapeHTML);
|
||||
if (!lines.length || label === lines.join(", ")) {
|
||||
$help.slideUp(function() {
|
||||
$help.html("");
|
||||
});
|
||||
}
|
||||
else {
|
||||
$help.html(lines.join("<br>")).slideDown();
|
||||
}
|
||||
}
|
||||
function setupSaveToProfile (scope, profiles) {
|
||||
let $select = $('[name$="saved_id"]', scope)
|
||||
let $selectContainer = $select.closest('.form-group').addClass('profile-save-id')
|
||||
let $checkbox = $('[name$="save"]', scope)
|
||||
let $checkboxContainer = $checkbox.closest('.form-group').addClass('profile-save')
|
||||
let $help = $selectContainer.find('.help-block')
|
||||
|
||||
function setupSaveToProfile(scope, profiles) {
|
||||
var $select = $('[name$="saved_id"]', scope);
|
||||
var $selectContainer = $select.closest(".form-group").addClass("profile-save-id");
|
||||
var $checkbox = $('[name$="save"]', scope);
|
||||
var $checkboxContainer = $checkbox.closest(".form-group").addClass("profile-save");
|
||||
var $help = $selectContainer.find(".help-block");
|
||||
let $container = $('<div class=\'profile-save-container js-do-not-copy-answers\'></div>')
|
||||
$selectContainer.after($container)
|
||||
$container.append($checkboxContainer)
|
||||
$container.append($selectContainer)
|
||||
|
||||
var $container = $("<div class='profile-save-container js-do-not-copy-answers'></div>");
|
||||
$selectContainer.after($container);
|
||||
$container.append($checkboxContainer);
|
||||
$container.append($selectContainer);
|
||||
|
||||
if (!profiles || !profiles.length) {
|
||||
$selectContainer.hide();
|
||||
return;
|
||||
}
|
||||
if (!profiles || !profiles.length) {
|
||||
$selectContainer.hide()
|
||||
return
|
||||
}
|
||||
|
||||
$checkbox.change(function() {
|
||||
if (this.checked) $selectContainer.slideDown();
|
||||
else $selectContainer.slideUp();
|
||||
});
|
||||
$checkbox.change(function () {
|
||||
if (this.checked) $selectContainer.slideDown()
|
||||
else $selectContainer.slideUp()
|
||||
})
|
||||
|
||||
for (var p of profiles) {
|
||||
$select.append($('<option>').attr('value', p._pk).text(labelForProfile(p, profiles)));
|
||||
}
|
||||
$select.append('<option value="" disabled>–</option>');
|
||||
$select.append($select.find("option").first());
|
||||
$select.get(0).selectedIndex = 0;
|
||||
$select.change(function() {
|
||||
_updateDescription(this, profiles[this.selectedIndex], $help);
|
||||
}).trigger("change");
|
||||
$checkbox.trigger("change");
|
||||
}
|
||||
for (let p of profiles) {
|
||||
$select.append($('<option>').attr('value', p._pk).text(labelForProfile(p, profiles)))
|
||||
}
|
||||
$select.append('<option value="" disabled>–</option>')
|
||||
$select.append($select.find('option').first())
|
||||
$select.get(0).selectedIndex = 0
|
||||
$select.change(function () {
|
||||
_updateDescription(this, profiles[this.selectedIndex], $help)
|
||||
}).trigger('change')
|
||||
$checkbox.trigger('change')
|
||||
}
|
||||
|
||||
// setup auto-fill for each scope/fieldset
|
||||
// match profile’s answers to inputs in scope
|
||||
// if none match, do not show auto-fill
|
||||
// if one matches, only show button to auto-fill
|
||||
// else show select with profiles and button to auto-fill
|
||||
function setupAutoFill(scope, profiles) {
|
||||
var matchedProfiles = uniqueProfiles(matchProfilesToInputs(profiles, scope));
|
||||
if (!matchedProfiles.length) {
|
||||
$(scope).addClass("profile-none-matched");
|
||||
return;
|
||||
}
|
||||
// setup auto-fill for each scope/fieldset
|
||||
// match profile’s answers to inputs in scope
|
||||
// if none match, do not show auto-fill
|
||||
// if one matches, only show button to auto-fill
|
||||
// else show select with profiles and button to auto-fill
|
||||
function setupAutoFill (scope, profiles) {
|
||||
let matchedProfiles = uniqueProfiles(matchProfilesToInputs(profiles, scope))
|
||||
if (!matchedProfiles.length) {
|
||||
$(scope).addClass('profile-none-matched')
|
||||
return
|
||||
}
|
||||
|
||||
var selectedProfile = matchedProfiles[0];
|
||||
var $select = $(".profile-select", scope);
|
||||
var $button = $(".profile-apply", scope);
|
||||
var $help = $(".profile-desc", scope);
|
||||
let selectedProfile = matchedProfiles[0]
|
||||
let $select = $('.profile-select', scope)
|
||||
let $button = $('.profile-apply', scope)
|
||||
let $help = $('.profile-desc', scope)
|
||||
|
||||
if (matchedProfiles.length === 1) {
|
||||
$(".profile-select-control", scope).hide().parent().addClass("form-control-text");
|
||||
$help.html(describeProfileHTML(selectedProfile)).addClass("single-profile-desc").after($button);
|
||||
}
|
||||
else {
|
||||
var i = 0;
|
||||
for (p of matchedProfiles) {
|
||||
$select.append($("<option>").text(labelForProfile(p, matchedProfiles, scope)).attr("value", i));
|
||||
i++;
|
||||
}
|
||||
$select.change(function() {
|
||||
selectedProfile = matchedProfiles[this.value];
|
||||
_updateDescription(this, selectedProfile, $help);
|
||||
}).trigger("change");
|
||||
}
|
||||
// Add-Ons sit on same level as their parent product scope
|
||||
// Therefore use .prevUntil("legend") as an Add-On is
|
||||
// offset by a <legend>
|
||||
// if no <legend> is present – e.g. on invoice-address – the
|
||||
// containing <summary> would be selected, which is not what we want
|
||||
$(scope).prevUntil("legend").not("summary").addClass("profile-pre-select");
|
||||
if (matchedProfiles.length === 1) {
|
||||
$('.profile-select-control', scope).hide().parent().addClass('form-control-text')
|
||||
$help.html(describeProfileHTML(selectedProfile)).addClass('single-profile-desc').after($button)
|
||||
} else {
|
||||
let i = 0
|
||||
for (p of matchedProfiles) {
|
||||
$select.append($('<option>').text(labelForProfile(p, matchedProfiles, scope)).attr('value', i))
|
||||
i++
|
||||
}
|
||||
$select.change(function () {
|
||||
selectedProfile = matchedProfiles[this.value]
|
||||
_updateDescription(this, selectedProfile, $help)
|
||||
}).trigger('change')
|
||||
}
|
||||
// Add-Ons sit on same level as their parent product scope
|
||||
// Therefore use .prevUntil("legend") as an Add-On is
|
||||
// offset by a <legend>
|
||||
// if no <legend> is present – e.g. on invoice-address – the
|
||||
// containing <summary> would be selected, which is not what we want
|
||||
$(scope).prevUntil('legend').not('summary').addClass('profile-pre-select')
|
||||
|
||||
$button.click(function() {
|
||||
Object.keys(selectedProfile).forEach(function(key) {
|
||||
var answer = selectedProfile[key].value;
|
||||
var $field = selectedProfile[key].field;
|
||||
if (!$field || !$field.length) return;
|
||||
|
||||
if ($field.attr("type") === "checkbox") {
|
||||
if (answer === true || answer === false) {
|
||||
// boolean
|
||||
$field.prop("checked", answer).trigger("change");
|
||||
}
|
||||
else if (typeof answer !== 'string') {
|
||||
answer = Object.keys(answer);
|
||||
$field.each(function() {
|
||||
var checked = answer.indexOf(this.value) > -1;
|
||||
if (checked !== this.checked) {
|
||||
this.checked = checked;
|
||||
$(this).trigger("change");
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if ($field.attr("type") === "radio") {
|
||||
$field.filter('[value="' + answer + '"]').prop("checked", true).trigger("change");
|
||||
} else if ($field.length > 1) {
|
||||
// multiple matching fields, could be phone number or datetime
|
||||
var $field_0 = $field.filter('[name$="_0"]');
|
||||
var $field_1 = $field.filter('[name$="_1"]');
|
||||
if (answer.substr(0, 1) === "+") {
|
||||
var prefix = "";
|
||||
var options = $field_0.get(0).options;
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
var v = options[i].value;
|
||||
if (v && answer.substr(0, v.length) === v) {
|
||||
prefix = v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
var number = answer.substr(prefix.length);
|
||||
$field_0.val(prefix).trigger("change");
|
||||
$field_1.val(number).trigger("change");
|
||||
}
|
||||
else if ($field_0.hasClass("datepickerfield")) {
|
||||
$field_0.data('DateTimePicker').date(moment(answer));
|
||||
$field_1.data('DateTimePicker').date(moment(answer));
|
||||
}
|
||||
} else if ($field.is("select")) {
|
||||
if (answer && typeof answer !== 'string') {
|
||||
answer = Object.keys(answer);
|
||||
}
|
||||
// save answer as data-attribute so if external event changes select-element/options it can select correct entries
|
||||
// currently used when country => state changes
|
||||
$field.prop("data-selected-value", answer);
|
||||
$field.find("option").each(function() {
|
||||
this.selected = this.value === answer || (answer && answer.indexOf && answer.indexOf(this.value) > -1);
|
||||
});
|
||||
$field.trigger("change");
|
||||
} else {
|
||||
if ($field.hasClass("datepickerfield")) {
|
||||
$field.data('DateTimePicker').date(moment(answer));
|
||||
}
|
||||
else {
|
||||
$field.val(answer).trigger("change");
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
$button.click(function () {
|
||||
Object.keys(selectedProfile).forEach(function (key) {
|
||||
let answer = selectedProfile[key].value
|
||||
let $field = selectedProfile[key].field
|
||||
if (!$field || !$field.length) return
|
||||
|
||||
// each fieldset is its own scope for auto-fill and save
|
||||
el.find(".profile-scope").each(function () {
|
||||
var profiles = getProfilesById(this.getAttribute("data-profiles-id") || "profiles_json");
|
||||
if ($field.attr('type') === 'checkbox') {
|
||||
if (answer === true || answer === false) {
|
||||
// boolean
|
||||
$field.prop('checked', answer).trigger('change')
|
||||
} else if (typeof answer !== 'string') {
|
||||
answer = Object.keys(answer)
|
||||
$field.each(function () {
|
||||
let checked = answer.indexOf(this.value) > -1
|
||||
if (checked !== this.checked) {
|
||||
this.checked = checked
|
||||
$(this).trigger('change')
|
||||
}
|
||||
})
|
||||
}
|
||||
} else if ($field.attr('type') === 'radio') {
|
||||
$field.filter('[value="' + answer + '"]').prop('checked', true).trigger('change')
|
||||
} else if ($field.length > 1) {
|
||||
// multiple matching fields, could be phone number or datetime
|
||||
let $field_0 = $field.filter('[name$="_0"]')
|
||||
let $field_1 = $field.filter('[name$="_1"]')
|
||||
if (answer.substr(0, 1) === '+') {
|
||||
let prefix = ''
|
||||
let options = $field_0.get(0).options
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
let v = options[i].value
|
||||
if (v && answer.substr(0, v.length) === v) {
|
||||
prefix = v
|
||||
break
|
||||
}
|
||||
}
|
||||
let number = answer.substr(prefix.length)
|
||||
$field_0.val(prefix).trigger('change')
|
||||
$field_1.val(number).trigger('change')
|
||||
} else if ($field_0.hasClass('datepickerfield')) {
|
||||
$field_0.data('DateTimePicker').date(moment(answer))
|
||||
$field_1.data('DateTimePicker').date(moment(answer))
|
||||
}
|
||||
} else if ($field.is('select')) {
|
||||
if (answer && typeof answer !== 'string') {
|
||||
answer = Object.keys(answer)
|
||||
}
|
||||
// save answer as data-attribute so if external event changes select-element/options it can select correct entries
|
||||
// currently used when country => state changes
|
||||
$field.prop('data-selected-value', answer)
|
||||
$field.find('option').each(function () {
|
||||
this.selected = this.value === answer || (answer && answer.indexOf && answer.indexOf(this.value) > -1)
|
||||
})
|
||||
$field.trigger('change')
|
||||
} else {
|
||||
if ($field.hasClass('datepickerfield')) {
|
||||
$field.data('DateTimePicker').date(moment(answer))
|
||||
} else {
|
||||
$field.val(answer).trigger('change')
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
setupSaveToProfile(this, profiles);
|
||||
setupAutoFill(this, profiles);
|
||||
// each fieldset is its own scope for auto-fill and save
|
||||
el.find('.profile-scope').each(function () {
|
||||
let profiles = getProfilesById(this.getAttribute('data-profiles-id') || 'profiles_json')
|
||||
|
||||
this.classList.add("profile-select-initialized");
|
||||
});
|
||||
setupSaveToProfile(this, profiles)
|
||||
setupAutoFill(this, profiles)
|
||||
|
||||
this.classList.add('profile-select-initialized')
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,53 +1,51 @@
|
||||
/*global $ */
|
||||
|
||||
$(function () {
|
||||
var popup_window = null
|
||||
var popup_check_interval = null
|
||||
let popup_window = null
|
||||
let popup_check_interval = null
|
||||
|
||||
$("a[data-open-in-popup-window]").on("click", function (e) {
|
||||
e.preventDefault()
|
||||
$('a[data-open-in-popup-window]').on('click', function (e) {
|
||||
e.preventDefault()
|
||||
|
||||
$("#popupmodal a").attr("href", this.href)
|
||||
$('#popupmodal a').attr('href', this.href)
|
||||
|
||||
var url = this.href
|
||||
if (url.includes("?")) {
|
||||
url += "&popup_origin=" + window.location.origin
|
||||
} else {
|
||||
url += "?popup_origin=" + window.location.origin
|
||||
}
|
||||
popup_window = window.open(
|
||||
url,
|
||||
"presale-popup",
|
||||
"scrollbars=yes,resizable=yes,status=yes,location=yes,toolbar=no,menubar=no,width=940,height=620,left=50,top=50"
|
||||
)
|
||||
$("body").addClass("has-popup has-modal-dialog")
|
||||
$("#popupmodal").removeAttr("hidden");
|
||||
let url = this.href
|
||||
if (url.includes('?')) {
|
||||
url += '&popup_origin=' + window.location.origin
|
||||
} else {
|
||||
url += '?popup_origin=' + window.location.origin
|
||||
}
|
||||
popup_window = window.open(
|
||||
url,
|
||||
'presale-popup',
|
||||
'scrollbars=yes,resizable=yes,status=yes,location=yes,toolbar=no,menubar=no,width=940,height=620,left=50,top=50'
|
||||
)
|
||||
$('body').addClass('has-popup has-modal-dialog')
|
||||
$('#popupmodal').removeAttr('hidden')
|
||||
|
||||
popup_check_interval = window.setInterval(function () {
|
||||
if (popup_window.closed) {
|
||||
$("body").removeClass("has-popup has-modal-dialog")
|
||||
$("#popupmodal").attr("hidden", true);
|
||||
window.clearInterval(popup_check_interval)
|
||||
}
|
||||
}, 250)
|
||||
popup_check_interval = window.setInterval(function () {
|
||||
if (popup_window.closed) {
|
||||
$('body').removeClass('has-popup has-modal-dialog')
|
||||
$('#popupmodal').attr('hidden', true)
|
||||
window.clearInterval(popup_check_interval)
|
||||
}
|
||||
}, 250)
|
||||
|
||||
return false
|
||||
});
|
||||
return false
|
||||
})
|
||||
|
||||
window.addEventListener("message", function (event) {
|
||||
if (event.source !== popup_window)
|
||||
return
|
||||
if (event.data && event.data.__process === "customer_sso_popup") {
|
||||
if (event.data.status === "ok") {
|
||||
$("#customer_login .alert.alert-danger").addClass("hidden");
|
||||
$("#login_sso_data").val(event.data.value)
|
||||
$("#login_sso_data").closest("form").get(0).submit()
|
||||
} else {
|
||||
$("#customer_login .alert.alert-danger").html(event.data.value);
|
||||
$("#customer_login .alert.alert-danger").removeClass("hidden");
|
||||
}
|
||||
event.source.postMessage({'__process': 'popup_close'}, "*")
|
||||
}
|
||||
console.log(event)
|
||||
}, false);
|
||||
window.addEventListener('message', function (event) {
|
||||
if (event.source !== popup_window)
|
||||
return
|
||||
if (event.data && event.data.__process === 'customer_sso_popup') {
|
||||
if (event.data.status === 'ok') {
|
||||
$('#customer_login .alert.alert-danger').addClass('hidden')
|
||||
$('#login_sso_data').val(event.data.value)
|
||||
$('#login_sso_data').closest('form').get(0).submit()
|
||||
} else {
|
||||
$('#customer_login .alert.alert-danger').html(event.data.value)
|
||||
$('#customer_login .alert.alert-danger').removeClass('hidden')
|
||||
}
|
||||
event.source.postMessage({ __process: 'popup_close' }, '*')
|
||||
}
|
||||
console.log(event)
|
||||
}, false)
|
||||
})
|
||||
|
||||
@@ -1,71 +1,72 @@
|
||||
'use strict';
|
||||
/* global google, gettext */
|
||||
'use strict'
|
||||
|
||||
var walletdetection = {
|
||||
applepay: async function () {
|
||||
// This is a weak check for Apple Pay - in order to do a proper check, we would need to also call
|
||||
// canMakePaymentsWithActiveCard(merchantIdentifier)
|
||||
let walletdetection = {
|
||||
applepay: async function () {
|
||||
// This is a weak check for Apple Pay - in order to do a proper check, we would need to also call
|
||||
// canMakePaymentsWithActiveCard(merchantIdentifier)
|
||||
|
||||
return !!(window.ApplePaySession && window.ApplePaySession.canMakePayments());
|
||||
},
|
||||
googlepay: async function () {
|
||||
// Checking for Google Pay is a little bit more involved, since it requires including the Google Pay JS SDK, and
|
||||
// providing a lot of information.
|
||||
// So for the time being, we only check if Google Pay is available in TEST-mode, which should hopefully give us a
|
||||
// good enough idea if Google Pay could be present on this device; even though there are still a lot of other
|
||||
// factors that could inhibit Google Pay from actually being offered to the customer.
|
||||
return !!(window.ApplePaySession && window.ApplePaySession.canMakePayments())
|
||||
},
|
||||
googlepay: async function () {
|
||||
// Checking for Google Pay is a little bit more involved, since it requires including the Google Pay JS SDK, and
|
||||
// providing a lot of information.
|
||||
// So for the time being, we only check if Google Pay is available in TEST-mode, which should hopefully give us a
|
||||
// good enough idea if Google Pay could be present on this device; even though there are still a lot of other
|
||||
// factors that could inhibit Google Pay from actually being offered to the customer.
|
||||
|
||||
return $.ajax({
|
||||
url: 'https://pay.google.com/gp/p/js/pay.js',
|
||||
dataType: 'script',
|
||||
}).then(function() {
|
||||
const paymentsClient = new google.payments.api.PaymentsClient({environment: 'TEST'});
|
||||
return paymentsClient.isReadyToPay({
|
||||
apiVersion: 2,
|
||||
apiVersionMinor: 0,
|
||||
allowedPaymentMethods: [{
|
||||
type: 'CARD',
|
||||
parameters: {
|
||||
allowedAuthMethods: ["PAN_ONLY", "CRYPTOGRAM_3DS"],
|
||||
allowedCardNetworks: ["AMEX", "DISCOVER", "INTERAC", "JCB", "MASTERCARD", "VISA"]
|
||||
}
|
||||
}],
|
||||
})
|
||||
}).then(function (response) {
|
||||
return !!response.result;
|
||||
});
|
||||
},
|
||||
name_map: {
|
||||
applepay: gettext('Apple Pay'),
|
||||
googlepay: gettext('Google Pay'),
|
||||
}
|
||||
return $.ajax({
|
||||
url: 'https://pay.google.com/gp/p/js/pay.js',
|
||||
dataType: 'script',
|
||||
}).then(function () {
|
||||
const paymentsClient = new google.payments.api.PaymentsClient({ environment: 'TEST' })
|
||||
return paymentsClient.isReadyToPay({
|
||||
apiVersion: 2,
|
||||
apiVersionMinor: 0,
|
||||
allowedPaymentMethods: [{
|
||||
type: 'CARD',
|
||||
parameters: {
|
||||
allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'],
|
||||
allowedCardNetworks: ['AMEX', 'DISCOVER', 'INTERAC', 'JCB', 'MASTERCARD', 'VISA']
|
||||
}
|
||||
}],
|
||||
})
|
||||
}).then(function (response) {
|
||||
return !!response.result
|
||||
})
|
||||
},
|
||||
name_map: {
|
||||
applepay: gettext('Apple Pay'),
|
||||
googlepay: gettext('Google Pay'),
|
||||
}
|
||||
}
|
||||
|
||||
$(function () {
|
||||
const wallets = $('[data-wallets]')
|
||||
.map(function(index, pm) {
|
||||
return pm.getAttribute("data-wallets").split("|");
|
||||
})
|
||||
.get()
|
||||
.flat()
|
||||
.filter(function(item, pos, self) {
|
||||
// filter out empty or duplicate values
|
||||
return item && self.indexOf(item) == pos;
|
||||
});
|
||||
const wallets = $('[data-wallets]')
|
||||
.map(function (_index, pm) {
|
||||
return pm.getAttribute('data-wallets').split('|')
|
||||
})
|
||||
.get()
|
||||
.flat()
|
||||
.filter(function (item, pos, self) {
|
||||
// filter out empty or duplicate values
|
||||
return item && self.indexOf(item) == pos
|
||||
})
|
||||
|
||||
wallets.forEach(function(wallet) {
|
||||
const labels = $('[data-wallets*='+wallet+'] + .accordion-label-text')
|
||||
.append('<span class="wallet wallet-loading" data-wallet="'+wallet+'"> <span aria-hidden="true" class="fa fa-cog fa-spin"></span></span>')
|
||||
walletdetection[wallet]()
|
||||
.then(function(result) {
|
||||
const spans = labels.find(".wallet-loading[data-wallet=" + wallet + "]");
|
||||
if (result) {
|
||||
spans.removeClass('wallet-loading').hide().text(', ' + walletdetection.name_map[wallet]).fadeIn(300);
|
||||
} else {
|
||||
spans.remove();
|
||||
}
|
||||
})
|
||||
.catch(function(result) {
|
||||
labels.find(".wallet-loading[data-wallet=" + wallet + "]").remove();
|
||||
})
|
||||
});
|
||||
});
|
||||
wallets.forEach(function (wallet) {
|
||||
const labels = $('[data-wallets*=' + wallet + '] + .accordion-label-text')
|
||||
.append('<span class="wallet wallet-loading" data-wallet="' + wallet + '"> <span aria-hidden="true" class="fa fa-cog fa-spin"></span></span>')
|
||||
walletdetection[wallet]()
|
||||
.then(function (result) {
|
||||
const spans = labels.find('.wallet-loading[data-wallet=' + wallet + ']')
|
||||
if (result) {
|
||||
spans.removeClass('wallet-loading').hide().text(', ' + walletdetection.name_map[wallet]).fadeIn(300)
|
||||
} else {
|
||||
spans.remove()
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
labels.find('.wallet-loading[data-wallet=' + wallet + ']').remove()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,80 +2,80 @@
|
||||
// by John Friend, https://github.com/jfriend00/docReady
|
||||
// MIT License
|
||||
|
||||
(function(funcName, baseObj) {
|
||||
"use strict";
|
||||
// The public function name defaults to window.docReady
|
||||
// but you can modify the last line of this function to pass in a different object or method name
|
||||
// if you want to put them in a different namespace and those will be used instead of
|
||||
// window.docReady(...)
|
||||
funcName = funcName || "docReady";
|
||||
baseObj = baseObj || window;
|
||||
var readyList = [];
|
||||
var readyFired = false;
|
||||
var readyEventHandlersInstalled = false;
|
||||
(function (funcName, baseObj) {
|
||||
'use strict'
|
||||
// The public function name defaults to window.docReady
|
||||
// but you can modify the last line of this function to pass in a different object or method name
|
||||
// if you want to put them in a different namespace and those will be used instead of
|
||||
// window.docReady(...)
|
||||
funcName = funcName || 'docReady'
|
||||
baseObj = baseObj || window
|
||||
let readyList = []
|
||||
let readyFired = false
|
||||
let readyEventHandlersInstalled = false
|
||||
|
||||
// call this when the document is ready
|
||||
// this function protects itself against being called more than once
|
||||
function ready() {
|
||||
if (!readyFired) {
|
||||
// this must be set to true before we start calling callbacks
|
||||
readyFired = true;
|
||||
for (var i = 0; i < readyList.length; i++) {
|
||||
// if a callback here happens to add new ready handlers,
|
||||
// the docReady() function will see that it already fired
|
||||
// and will schedule the callback to run right after
|
||||
// this event loop finishes so all handlers will still execute
|
||||
// in order and no new ones will be added to the readyList
|
||||
// while we are processing the list
|
||||
readyList[i].fn.call(window, readyList[i].ctx);
|
||||
}
|
||||
// allow any closures held by these functions to free
|
||||
readyList = [];
|
||||
}
|
||||
}
|
||||
// call this when the document is ready
|
||||
// this function protects itself against being called more than once
|
||||
function ready () {
|
||||
if (!readyFired) {
|
||||
// this must be set to true before we start calling callbacks
|
||||
readyFired = true
|
||||
for (let i = 0; i < readyList.length; i++) {
|
||||
// if a callback here happens to add new ready handlers,
|
||||
// the docReady() function will see that it already fired
|
||||
// and will schedule the callback to run right after
|
||||
// this event loop finishes so all handlers will still execute
|
||||
// in order and no new ones will be added to the readyList
|
||||
// while we are processing the list
|
||||
readyList[i].fn.call(window, readyList[i].ctx)
|
||||
}
|
||||
// allow any closures held by these functions to free
|
||||
readyList = []
|
||||
}
|
||||
}
|
||||
|
||||
function readyStateChange() {
|
||||
if ( document.readyState === "complete" ) {
|
||||
ready();
|
||||
}
|
||||
}
|
||||
function readyStateChange () {
|
||||
if (document.readyState === 'complete') {
|
||||
ready()
|
||||
}
|
||||
}
|
||||
|
||||
// This is the one public interface
|
||||
// docReady(fn, context);
|
||||
// the context argument is optional - if present, it will be passed
|
||||
// as an argument to the callback
|
||||
baseObj[funcName] = function(callback, context) {
|
||||
if (typeof callback !== "function") {
|
||||
throw new TypeError("callback for docReady(fn) must be a function");
|
||||
}
|
||||
// if ready has already fired, then just schedule the callback
|
||||
// to fire asynchronously, but right away
|
||||
if (readyFired) {
|
||||
setTimeout(function() {callback(context);}, 1);
|
||||
return;
|
||||
} else {
|
||||
// add the function and context to the list
|
||||
readyList.push({fn: callback, ctx: context});
|
||||
}
|
||||
// if document already ready to go, schedule the ready function to run
|
||||
// IE only safe when readyState is "complete", others safe when readyState is "interactive"
|
||||
if (document.readyState === "complete" || (!document.attachEvent && document.readyState === "interactive")) {
|
||||
setTimeout(ready, 1);
|
||||
} else if (!readyEventHandlersInstalled) {
|
||||
// otherwise if we don't have event handlers installed, install them
|
||||
if (document.addEventListener) {
|
||||
// first choice is DOMContentLoaded event
|
||||
document.addEventListener("DOMContentLoaded", ready, false);
|
||||
// backup is window load event
|
||||
window.addEventListener("load", ready, false);
|
||||
} else {
|
||||
// must be IE
|
||||
document.attachEvent("onreadystatechange", readyStateChange);
|
||||
window.attachEvent("onload", ready);
|
||||
}
|
||||
readyEventHandlersInstalled = true;
|
||||
}
|
||||
}
|
||||
})("docReady", window);
|
||||
// This is the one public interface
|
||||
// docReady(fn, context);
|
||||
// the context argument is optional - if present, it will be passed
|
||||
// as an argument to the callback
|
||||
baseObj[funcName] = function (callback, context) {
|
||||
if (typeof callback !== 'function') {
|
||||
throw new TypeError('callback for docReady(fn) must be a function')
|
||||
}
|
||||
// if ready has already fired, then just schedule the callback
|
||||
// to fire asynchronously, but right away
|
||||
if (readyFired) {
|
||||
setTimeout(function () { callback(context) }, 1)
|
||||
return
|
||||
} else {
|
||||
// add the function and context to the list
|
||||
readyList.push({ fn: callback, ctx: context })
|
||||
}
|
||||
// if document already ready to go, schedule the ready function to run
|
||||
// IE only safe when readyState is "complete", others safe when readyState is "interactive"
|
||||
if (document.readyState === 'complete' || (!document.attachEvent && document.readyState === 'interactive')) {
|
||||
setTimeout(ready, 1)
|
||||
} else if (!readyEventHandlersInstalled) {
|
||||
// otherwise if we don't have event handlers installed, install them
|
||||
if (document.addEventListener) {
|
||||
// first choice is DOMContentLoaded event
|
||||
document.addEventListener('DOMContentLoaded', ready, false)
|
||||
// backup is window load event
|
||||
window.addEventListener('load', ready, false)
|
||||
} else {
|
||||
// must be IE
|
||||
document.attachEvent('onreadystatechange', readyStateChange)
|
||||
window.attachEvent('onload', ready)
|
||||
}
|
||||
readyEventHandlersInstalled = true
|
||||
}
|
||||
}
|
||||
})('docReady', window)
|
||||
// modify this previous line to pass in your own method name
|
||||
// and object for the method to be attached to
|
||||
|
||||
@@ -1,36 +1,34 @@
|
||||
/*global django*/
|
||||
var roundTo = function (n, digits) {
|
||||
if (digits === undefined) {
|
||||
digits = 0;
|
||||
}
|
||||
/* global django */
|
||||
let roundTo = function (n, digits) {
|
||||
if (digits === undefined) {
|
||||
digits = 0
|
||||
}
|
||||
|
||||
var multiplicator = Math.pow(10, digits);
|
||||
n = parseFloat((n * multiplicator).toFixed(11));
|
||||
return Math.round(n) / multiplicator;
|
||||
};
|
||||
let multiplicator = Math.pow(10, digits)
|
||||
n = parseFloat((n * multiplicator).toFixed(11))
|
||||
return Math.round(n) / multiplicator
|
||||
}
|
||||
|
||||
let floatformat = function (val, places) {
|
||||
'use strict'
|
||||
if (places === undefined) {
|
||||
places = 2
|
||||
}
|
||||
if (typeof val === 'string') {
|
||||
val = parseFloat(val)
|
||||
}
|
||||
let parts = roundTo(val, places).toFixed(places).split('.')
|
||||
if (places === 0) {
|
||||
return parts[0]
|
||||
}
|
||||
parts[0] = parts[0].replace(new RegExp('\\B(?=(\\d{' + django.get_format('NUMBER_GROUPING') + '})+(?!\\d))', 'g'), django.get_format('THOUSAND_SEPARATOR'))
|
||||
return parts[0] + django.get_format('DECIMAL_SEPARATOR') + parts[1]
|
||||
}
|
||||
|
||||
var floatformat = function (val, places) {
|
||||
"use strict";
|
||||
if (places === undefined) {
|
||||
places = 2;
|
||||
}
|
||||
if (typeof val === "string") {
|
||||
val = parseFloat(val);
|
||||
}
|
||||
var parts = roundTo(val, places).toFixed(places).split(".");
|
||||
if (places === 0) {
|
||||
return parts[0];
|
||||
}
|
||||
parts[0] = parts[0].replace(new RegExp("\\B(?=(\\d{" + django.get_format("NUMBER_GROUPING") + "})+(?!\\d))", "g"), django.get_format("THOUSAND_SEPARATOR"));
|
||||
return parts[0] + django.get_format("DECIMAL_SEPARATOR") + parts[1];
|
||||
};
|
||||
|
||||
|
||||
var autofloatformat = function (val, places) {
|
||||
"use strict";
|
||||
if (val == roundTo(val, 0)) {
|
||||
places = 0;
|
||||
}
|
||||
return floatformat(val, places);
|
||||
};
|
||||
let autofloatformat = function (val, places) {
|
||||
'use strict'
|
||||
if (val == roundTo(val, 0)) {
|
||||
places = 0
|
||||
}
|
||||
return floatformat(val, places)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -86,7 +86,8 @@ const taxline = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const showTaxline = computed(() => props.price.rate !== '0' && props.price.gross !== '0.00')
|
||||
// rate can either be "0.00" or "0" => parseFloat to check
|
||||
const showTaxline = computed(() => Number.parseFloat(props.price.rate) && Number.parseFloat(props.price.gross))
|
||||
</script>
|
||||
<template lang="pug">
|
||||
.pretix-widget-pricebox
|
||||
|
||||
@@ -514,10 +514,6 @@ export function createWidgetStore (config: {
|
||||
// button with subevent but no items
|
||||
redirectUrl += `${this.subevent}/`
|
||||
}
|
||||
if (this.subevent && !this.cartId) {
|
||||
// button with subevent but no items
|
||||
redirectUrl += `${this.subevent}/`
|
||||
}
|
||||
redirectUrl += `?iframe=1&locale=${LANG}`
|
||||
if (this.cartId) {
|
||||
// ajax to make sure the cart-id is used, even if the cart is currently empty
|
||||
|
||||
@@ -1434,8 +1434,12 @@ def test_get_event_settings(token_client, organizer, event):
|
||||
'/api/v1/organizers/{}/events/{}/settings/'.format(organizer.slug, event.slug),
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.data['imprint_url'] == "https://example.org"
|
||||
assert resp.data['contact_url'] == "https://example.org/contact"
|
||||
assert resp.data['imprint_url'] == {
|
||||
"en": "https://example.org",
|
||||
}
|
||||
assert resp.data['contact_url'] == {
|
||||
"en": "https://example.org/contact",
|
||||
}
|
||||
assert resp.data['seating_allow_blocked_seats_for_channel'] == []
|
||||
|
||||
resp = token_client.get(
|
||||
@@ -1443,7 +1447,9 @@ def test_get_event_settings(token_client, organizer, event):
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.data['imprint_url'] == {
|
||||
"value": "https://example.org",
|
||||
"value": {
|
||||
"en": "https://example.org",
|
||||
},
|
||||
"label": "Imprint URL",
|
||||
"help_text": "This should point e.g. to a part of your website that has your contact details and legal "
|
||||
"information.",
|
||||
@@ -1478,8 +1484,12 @@ def test_patch_event_settings(token_client, organizer, event, team):
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.data['contact_url'] == "https://example.com/contact"
|
||||
assert resp.data['imprint_url'] == "https://example.com"
|
||||
assert resp.data['contact_url'] == {
|
||||
"en": "https://example.com/contact",
|
||||
}
|
||||
assert resp.data['imprint_url'] == {
|
||||
"en": "https://example.com",
|
||||
}
|
||||
assert resp.data['seating_allow_blocked_seats_for_channel'] == ['web']
|
||||
assert not resp.data['reusable_media_active']
|
||||
event.settings.flush()
|
||||
@@ -1542,8 +1552,12 @@ def test_patch_event_settings(token_client, organizer, event, team):
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.data['contact_url'] == "https://example.org/contact"
|
||||
assert resp.data['imprint_url'] == "https://example.org"
|
||||
assert resp.data['contact_url'] == {
|
||||
"en": "https://example.org/contact",
|
||||
}
|
||||
assert resp.data['imprint_url'] == {
|
||||
"en": "https://example.org",
|
||||
}
|
||||
event.settings.flush()
|
||||
assert event.settings.contact_url == 'https://example.org/contact'
|
||||
assert event.settings.imprint_url == 'https://example.org'
|
||||
|
||||
@@ -25,6 +25,7 @@ from datetime import datetime
|
||||
import pytest
|
||||
from django.core.files.base import ContentFile
|
||||
from django_scopes import scopes_disabled
|
||||
from i18nfield.strings import LazyI18nString
|
||||
from tests.const import SAMPLE_PNG
|
||||
|
||||
TEST_ORGANIZER_RES = {
|
||||
@@ -165,9 +166,11 @@ def test_patch_settings(token_client, organizer):
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.data['contact_url'] == 'https://example.org/contact'
|
||||
assert resp.data['contact_url'] == {
|
||||
'en': 'https://example.org/contact',
|
||||
}
|
||||
organizer.settings.flush()
|
||||
assert organizer.settings.contact_url == 'https://example.org/contact'
|
||||
assert organizer.settings.contact_url == LazyI18nString('https://example.org/contact')
|
||||
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/settings/'.format(organizer.slug),
|
||||
|
||||
@@ -89,11 +89,11 @@ def _widget_assets():
|
||||
f'Start it with: npm run dev:widget'
|
||||
)
|
||||
yield
|
||||
elif os.environ.get("PRETIX_WIDGET_VITE"):
|
||||
elif os.environ.get("PRETIX_WIDGET_VUE"):
|
||||
yield # Old widget, no build needed
|
||||
else:
|
||||
subprocess.check_call(['npm', 'run', 'build:widget'], cwd=PROJECT_ROOT)
|
||||
yield
|
||||
else:
|
||||
yield # Old widget, no build needed
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -155,10 +155,6 @@ def live_server_url(live_server, settings):
|
||||
|
||||
settings.SITE_URL = live_server.url
|
||||
|
||||
# Enable Vite widget if requested via env var
|
||||
if os.environ.get("PRETIX_WIDGET_VITE") or os.environ.get("PRETIX_WIDGET_VITE_DEV"):
|
||||
settings.PRETIX_WIDGET_VITE = True
|
||||
|
||||
return live_server.url
|
||||
|
||||
|
||||
|
||||
@@ -299,6 +299,30 @@ def get_test_order_review_pending():
|
||||
'method': 'GET'}]}
|
||||
|
||||
|
||||
def get_test_empty_captures():
|
||||
return {'id': '806440346Y391300T',
|
||||
'intent': 'CAPTURE',
|
||||
'status': 'COMPLETED',
|
||||
'purchase_units': [{'reference_id': 'default',
|
||||
'amount': {'currency_code': 'EUR', 'value': '43.59'},
|
||||
'payee': {'email_address': 'dummy-facilitator@dummy.dummy',
|
||||
'merchant_id': 'G6R2B9YXADKWW'},
|
||||
'description': 'Order JWJGC for PayPal v2',
|
||||
'custom_id': 'Order PAYPALV2-JWJGC',
|
||||
'soft_descriptor': 'MARTINFACIL',
|
||||
'payments': {'captures': []}
|
||||
}],
|
||||
'payer': {'name': {'given_name': 'test', 'surname': 'buyer'},
|
||||
'email_address': 'dummy@dummy.dummy',
|
||||
'payer_id': 'Q739JNKWH67HE',
|
||||
'address': {'country_code': 'DE'}},
|
||||
'create_time': '2022-04-28T11:59:59Z',
|
||||
'update_time': '2022-04-28T12:00:22Z',
|
||||
'links': [{'href': 'https://api.sandbox.paypal.com/v2/checkout/orders/806440346Y391300T',
|
||||
'rel': 'self',
|
||||
'method': 'GET'}]}
|
||||
|
||||
|
||||
class Object():
|
||||
pass
|
||||
|
||||
@@ -456,6 +480,100 @@ def test_webhook_all_good(env, client, monkeypatch):
|
||||
assert order.status == Order.STATUS_PAID
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_webhook_empty_captures(env, client, monkeypatch):
|
||||
order = env[1]
|
||||
with scopes_disabled():
|
||||
p = order.payments.first()
|
||||
p.state = OrderPayment.PAYMENT_STATE_PENDING
|
||||
p.save()
|
||||
order.status = Order.STATUS_PENDING
|
||||
order.save()
|
||||
|
||||
pp_order = Result(get_test_empty_captures())
|
||||
monkeypatch.setattr("paypalcheckoutsdk.orders.OrdersGetRequest", lambda *args: pp_order)
|
||||
monkeypatch.setattr("pretix.plugins.paypal2.payment.PaypalMethod.init_api", init_api)
|
||||
|
||||
with scopes_disabled():
|
||||
ReferencedPayPalObject.objects.create(order=order, payment=order.payments.first(),
|
||||
reference="806440346Y391300T")
|
||||
|
||||
client.post('/_paypal/webhook/', json.dumps(
|
||||
{
|
||||
"id": "WH-4T867178D0574904F-7TT11736YU643990P",
|
||||
"create_time": "2022-04-28T12:00:37.077Z",
|
||||
"resource_type": "checkout-order",
|
||||
"event_type": "CHECKOUT.ORDER.COMPLETED",
|
||||
"summary": "Checkout Order Completed",
|
||||
"resource": {
|
||||
"update_time": "2022-04-28T12:00:22Z",
|
||||
"create_time": "2022-04-28T11:59:59Z",
|
||||
"purchase_units": [
|
||||
{
|
||||
"reference_id": "default",
|
||||
"amount": {
|
||||
"currency_code": "EUR",
|
||||
"value": "43.59"
|
||||
},
|
||||
"payee": {
|
||||
"email_address": "dummy-facilitator@dummy.dummy",
|
||||
"merchant_id": "G6R2B9YXADKWW"
|
||||
},
|
||||
"description": "Order JWJGC for PayPal v2",
|
||||
"custom_id": "Order PAYPALV2-JWJGC",
|
||||
"soft_descriptor": "MARTINFACIL",
|
||||
"payments": {
|
||||
"captures": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/806440346Y391300T",
|
||||
"rel": "self",
|
||||
"method": "GET"
|
||||
}
|
||||
],
|
||||
"id": "806440346Y391300T",
|
||||
"intent": "CAPTURE",
|
||||
"payer": {
|
||||
"name": {
|
||||
"given_name": "test",
|
||||
"surname": "buyer"
|
||||
},
|
||||
"email_address": "dummy@dummy.dummy",
|
||||
"payer_id": "Q739JNKWH67HE",
|
||||
"address": {
|
||||
"country_code": "DE"
|
||||
}
|
||||
},
|
||||
"status": "COMPLETED"
|
||||
},
|
||||
"status": "SUCCESS",
|
||||
"links": [
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-4T867178D0574904F-7TT11736YU643990P",
|
||||
"rel": "self",
|
||||
"method": "GET",
|
||||
"encType": "application/json"
|
||||
},
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-4T867178D0574904F-7TT11736YU643990P/resend",
|
||||
"rel": "resend",
|
||||
"method": "POST",
|
||||
"encType": "application/json"
|
||||
}
|
||||
],
|
||||
"event_version": "1.0",
|
||||
"resource_version": "2.0"
|
||||
}
|
||||
), content_type='application_json')
|
||||
|
||||
order = env[1]
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_PENDING
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_webhook_mark_paid(env, client, monkeypatch):
|
||||
order = env[1]
|
||||
|
||||
@@ -2372,6 +2372,97 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
||||
assert p2.fee.value == Decimal("0.46")
|
||||
assert o.total == Decimal("25.76")
|
||||
|
||||
def test_payment_postpone_not_allowed(self):
|
||||
self.event.settings.set('payment_banktransfer__enabled', True)
|
||||
with scopes_disabled():
|
||||
CartPosition.objects.create(
|
||||
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||
price=23, expires=now() + timedelta(minutes=10)
|
||||
)
|
||||
response = self.client.post('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), {
|
||||
'postpone': 'on',
|
||||
}, follow=False)
|
||||
assert 'Please select' in response.content.decode()
|
||||
|
||||
def test_payment_postpone_allowed(self):
|
||||
self.event.settings.set('payment_banktransfer__enabled', True)
|
||||
self.event.settings.payment_choice_postpone_allowed_channels = ['web']
|
||||
with scopes_disabled():
|
||||
CartPosition.objects.create(
|
||||
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||
price=23, expires=now() + timedelta(minutes=10)
|
||||
)
|
||||
response = self.client.post('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), {
|
||||
'postpone': 'on',
|
||||
}, follow=True)
|
||||
self.assertRedirects(response, '/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug),
|
||||
target_status_code=200)
|
||||
|
||||
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
self.assertEqual(len(doc.select(".thank-you")), 1)
|
||||
with scopes_disabled():
|
||||
o = Order.objects.last()
|
||||
assert not o.payments.exists()
|
||||
|
||||
def test_payment_postpone_cleared_on_selection(self):
|
||||
self.event.settings.set('payment_banktransfer__enabled', True)
|
||||
self.event.settings.payment_choice_postpone_allowed_channels = ['web']
|
||||
with scopes_disabled():
|
||||
CartPosition.objects.create(
|
||||
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||
price=23, expires=now() + timedelta(minutes=10)
|
||||
)
|
||||
|
||||
response = self.client.post('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), {
|
||||
'postpone': 'on',
|
||||
}, follow=False)
|
||||
self.assertRedirects(response, '/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug),
|
||||
target_status_code=200)
|
||||
assert self.client.session['carts'][self.session_key].get('payments_postpone')
|
||||
|
||||
# The only available provider must not be preselected while the choice is postponed
|
||||
response = self.client.get('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
self.assertEqual(len(doc.select('input[name="payment"]')), 1)
|
||||
self.assertEqual(len(doc.select('input[name="payment"][checked]')), 0)
|
||||
|
||||
# Selecting a payment method takes the order out of the postponed state again
|
||||
response = self.client.post('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), {
|
||||
'payment': 'banktransfer',
|
||||
}, follow=False)
|
||||
self.assertRedirects(response, '/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug),
|
||||
target_status_code=200)
|
||||
assert not self.client.session['carts'][self.session_key].get('payments_postpone')
|
||||
|
||||
def test_payment_postpone_disabled_with_partial_payment(self):
|
||||
self.event.settings.set('payment_banktransfer__enabled', True)
|
||||
self.event.settings.payment_choice_postpone_allowed_channels = ['web']
|
||||
gc = self.orga.issued_gift_cards.create(currency="EUR")
|
||||
gc.transactions.create(value=20, acceptor=self.orga)
|
||||
with scopes_disabled():
|
||||
CartPosition.objects.create(
|
||||
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||
price=23, expires=now() + timedelta(minutes=10)
|
||||
)
|
||||
|
||||
response = self.client.get('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
self.assertEqual(len(doc.select('button[name="postpone"]')), 1)
|
||||
self.assertEqual(len(doc.select('button[name="postpone"][disabled]')), 0)
|
||||
|
||||
# Apply a gift card that only covers part of the total
|
||||
response = self.client.post('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), {
|
||||
'payment': 'giftcard',
|
||||
'payment_giftcard-code': gc.secret,
|
||||
}, follow=True)
|
||||
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
|
||||
target_status_code=200)
|
||||
|
||||
# Postponing would silently drop the gift card, so it is no longer offered
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
self.assertEqual(len(doc.select('button[name="postpone"][disabled]')), 1)
|
||||
|
||||
def test_premature_confirm(self):
|
||||
response = self.client.get('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
self.assertRedirects(response, '/%s/%s/?require_cookie=true' % (self.orga.slug, self.event.slug),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user