diff --git a/doc/user/markdown.rst b/doc/user/markdown.rst
index 5873d785d1..7ca590e35b 100644
--- a/doc/user/markdown.rst
+++ b/doc/user/markdown.rst
@@ -11,6 +11,9 @@ In many places of your shop, like frontpage texts, product descriptions and emai
since it is way easier to learn than languages like HTML but allows all basic formatting options required
for text in those places.
+.. note:: Some fields that are used in one-line context only allow formatting that refers to individual words
+ (such as bold or italic font or a link) but do not allow block-level formatting like lists or headlines.
+
Formatting rules
----------------
diff --git a/src/pretix/base/forms/__init__.py b/src/pretix/base/forms/__init__.py
index 46afc6557e..fb850a7f55 100644
--- a/src/pretix/base/forms/__init__.py
+++ b/src/pretix/base/forms/__init__.py
@@ -39,6 +39,7 @@ from django import forms
from django.core.validators import URLValidator
from django.forms.models import ModelFormMetaclass
from django.utils.crypto import get_random_string
+from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
from formtools.wizard.views import SessionWizardView
from hierarkey.forms import HierarkeyForm
@@ -85,6 +86,43 @@ class I18nInlineFormSet(i18nfield.forms.I18nInlineFormSet):
super().__init__(*args, **kwargs)
+class MarkdownTextarea(forms.Textarea):
+
+ def _render(self, template_name, context, renderer=None):
+ return mark_safe(
+ '
' % (
+ super()._render(template_name, context, renderer=None),
+ _("You can use {markup_name} in this field.").format(
+ markup_name='Markdown '
+ )
+ )
+ )
+
+
+class I18nMarkdownTextarea(i18nfield.forms.I18nTextarea):
+ def format_output(self, rendered_widgets) -> str:
+ rendered_widgets = rendered_widgets + [
+ '%s
' % (
+ _("You can use {markup_name} in this field.").format(
+ markup_name='Markdown '
+ )
+ )
+ ]
+ return super().format_output(rendered_widgets)
+
+
+class I18nMarkdownTextInput(i18nfield.forms.I18nTextInput):
+ def format_output(self, rendered_widgets) -> str:
+ rendered_widgets = rendered_widgets + [
+ '%s
' % (
+ _("You can use {markup_name} in this field.").format(
+ markup_name='Markdown '
+ )
+ )
+ ]
+ return super().format_output(rendered_widgets)
+
+
SECRET_REDACTED = '*****'
diff --git a/src/pretix/base/models/event.py b/src/pretix/base/models/event.py
index a37941380e..15daa9627b 100644
--- a/src/pretix/base/models/event.py
+++ b/src/pretix/base/models/event.py
@@ -1461,7 +1461,7 @@ class SubEvent(EventMixin, LoggedModel):
)
frontpage_text = I18nTextField(
null=True, blank=True,
- verbose_name=_("Frontpage text")
+ verbose_name=_("Frontpage text"),
)
seating_plan = models.ForeignKey('SeatingPlan', on_delete=models.PROTECT, null=True, blank=True,
related_name='subevents', verbose_name=_('Seating plan'))
diff --git a/src/pretix/base/payment.py b/src/pretix/base/payment.py
index 89dd094683..4dad532cf7 100644
--- a/src/pretix/base/payment.py
+++ b/src/pretix/base/payment.py
@@ -57,7 +57,7 @@ from i18nfield.forms import I18nFormField, I18nTextarea, I18nTextInput
from i18nfield.strings import LazyI18nString
from pretix.base.channels import get_all_sales_channels
-from pretix.base.forms import PlaceholderValidator
+from pretix.base.forms import I18nMarkdownTextarea, PlaceholderValidator
from pretix.base.models import (
CartPosition, Event, GiftCard, InvoiceAddress, Order, OrderPayment,
OrderRefund, Quota, TaxRule,
@@ -1185,14 +1185,14 @@ class ManualPayment(BasePaymentProvider):
label=_('Payment process description during checkout'),
help_text=_('This text will be shown during checkout when the user selects this payment method. '
'It should give a short explanation on this payment method.'),
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
)),
('email_instructions', I18nFormField(
label=_('Payment process description in order confirmation emails'),
help_text=_('This text will be included for the {payment_info} placeholder in order confirmation '
'mails. It should instruct the user on how to proceed with the payment. You can use '
'the placeholders {order}, {amount}, {currency} and {amount_with_currency}.'),
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
validators=[PlaceholderValidator(['{order}', '{amount}', '{currency}', '{amount_with_currency}'])],
)),
('pending_description', I18nFormField(
@@ -1200,7 +1200,7 @@ class ManualPayment(BasePaymentProvider):
help_text=_('This text will be shown on the order confirmation page for pending orders. '
'It should instruct the user on how to proceed with the payment. You can use '
'the placeholders {order}, {amount}, {currency} and {amount_with_currency}.'),
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
validators=[PlaceholderValidator(['{order}', '{amount}', '{currency}', '{amount_with_currency}'])],
)),
('invoice_immediately',
@@ -1325,7 +1325,7 @@ class GiftCardPayment(BasePaymentProvider):
(
"public_description",
I18nFormField(
- label=_("Payment method description"), widget=I18nTextarea, required=False
+ label=_("Payment method description"), widget=I18nMarkdownTextarea, required=False
),
),
]
diff --git a/src/pretix/base/settings.py b/src/pretix/base/settings.py
index 14b9c8709f..5665f7377a 100644
--- a/src/pretix/base/settings.py
+++ b/src/pretix/base/settings.py
@@ -64,7 +64,7 @@ from pretix.api.serializers.fields import (
ListMultipleChoiceField, UploadedFileField,
)
from pretix.api.serializers.i18n import I18nField, I18nURLField
-from pretix.base.forms import I18nURLFormField
+from pretix.base.forms import I18nMarkdownTextarea, I18nURLFormField
from pretix.base.models.tax import VAT_ID_COUNTRIES, TaxRule
from pretix.base.reldate import (
RelativeDateField, RelativeDateTimeField, RelativeDateWrapper,
@@ -602,7 +602,7 @@ DEFAULTS = {
'serializer_class': I18nField,
'form_kwargs': dict(
label=_("Invoice address explanation"),
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
widget_kwargs={'attrs': {'rows': '2'}},
help_text=_("This text will be shown above the invoice address form during checkout.")
)
@@ -801,7 +801,7 @@ DEFAULTS = {
'serializer_class': I18nField,
'form_kwargs': dict(
label=_("End of presale text"),
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
widget_kwargs={'attrs': {'rows': '2'}},
help_text=_("This text will be shown above the ticket shop once the designated sales timeframe for this event "
"is over. You can use it to describe other options to get a ticket, such as a box office.")
@@ -813,7 +813,7 @@ DEFAULTS = {
'form_class': I18nFormField,
'serializer_class': I18nField,
'form_kwargs': dict(
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
widget_kwargs={'attrs': {
'rows': 3,
}},
@@ -1459,7 +1459,7 @@ DEFAULTS = {
'serializer_class': I18nField,
'form_kwargs': dict(
label=_("Phone number explanation"),
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
widget_kwargs={'attrs': {'rows': '2'}},
help_text=_("If you ask for a phone number, explain why you do so and what you will use the phone number for.")
)
@@ -1876,7 +1876,7 @@ DEFAULTS = {
'form_class': I18nFormField,
'form_kwargs': dict(
label=_("Voluntary lower refund explanation"),
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
widget_kwargs={'attrs': {'rows': '2'}},
help_text=_("This text will be shown in between the explanation of how the refunds work and the slider "
"which your customers can use to choose the amount they would like to receive. You can use it "
@@ -1958,7 +1958,7 @@ DEFAULTS = {
'form_class': I18nFormField,
'form_kwargs': dict(
label=_("Terms of cancellation"),
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
widget_kwargs={'attrs': {'rows': '2'}},
help_text=_("This text will be shown when cancellation is allowed for a paid order. Leave empty if you "
"want pretix to automatically generate the terms of cancellation based on your settings.")
@@ -1971,7 +1971,7 @@ DEFAULTS = {
'form_class': I18nFormField,
'form_kwargs': dict(
label=_("Terms of cancellation"),
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
widget_kwargs={'attrs': {'rows': '2'}},
help_text=_("This text will be shown when cancellation is allowed for an unpaid or free order. Leave empty "
"if you want pretix to automatically generate the terms of cancellation based on your settings.")
@@ -2060,7 +2060,7 @@ DEFAULTS = {
'form_class': I18nFormField,
'form_kwargs': dict(
label=_("Event description"),
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
help_text=_(
"You can use this to share information with your attendees, such as travel information or the link to a digital event. "
"If you keep it empty, we will put a link to the event shop, the admission time, and your organizer name in there. "
@@ -2991,7 +2991,7 @@ Your {organizer} team""")) # noqa: W291
'form_class': I18nFormField,
'form_kwargs': dict(
label=_("Frontpage text"),
- widget=I18nTextarea
+ widget=I18nMarkdownTextarea,
)
},
'event_info_text': {
@@ -3013,7 +3013,7 @@ Your {organizer} team""")) # noqa: W291
'form_class': I18nFormField,
'form_kwargs': dict(
label=_("Banner text (top)"),
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
widget_kwargs={'attrs': {'rows': '2'}},
help_text=_("This text will be shown above every page of your shop. Please only use this for "
"very important messages.")
@@ -3026,7 +3026,7 @@ Your {organizer} team""")) # noqa: W291
'form_class': I18nFormField,
'form_kwargs': dict(
label=_("Banner text (bottom)"),
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
widget_kwargs={'attrs': {'rows': '2'}},
help_text=_("This text will be shown below every page of your shop. Please only use this for "
"very important messages.")
@@ -3039,7 +3039,7 @@ Your {organizer} team""")) # noqa: W291
'form_class': I18nFormField,
'form_kwargs': dict(
label=_("Voucher explanation"),
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
widget_kwargs={'attrs': {'rows': '2'}},
help_text=_("This text will be shown next to the input for a voucher code. You can use it e.g. to explain "
"how to obtain a voucher code.")
@@ -3052,7 +3052,7 @@ Your {organizer} team""")) # noqa: W291
'form_class': I18nFormField,
'form_kwargs': dict(
label=_("Attendee data explanation"),
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
widget_kwargs={'attrs': {'rows': '2'}},
help_text=_("This text will be shown above the questions asked for every personalized product. You can use it e.g. to explain "
"why you need information from them.")
@@ -3068,7 +3068,7 @@ Your {organizer} team""")) # noqa: W291
help_text=_("This message will be shown after an order has been created successfully. It will be shown in additional "
"to the default text."),
widget_kwargs={'attrs': {'rows': '2'}},
- widget=I18nTextarea
+ widget=I18nMarkdownTextarea,
)
},
'checkout_phone_helptext': {
@@ -3079,7 +3079,7 @@ Your {organizer} team""")) # noqa: W291
'form_kwargs': dict(
label=_("Help text of the phone number field"),
widget_kwargs={'attrs': {'rows': '2'}},
- widget=I18nTextarea
+ widget=I18nMarkdownTextarea,
)
},
'checkout_email_helptext': {
@@ -3093,7 +3093,7 @@ Your {organizer} team""")) # noqa: W291
'form_kwargs': dict(
label=_("Help text of the email field"),
widget_kwargs={'attrs': {'rows': '2'}},
- widget=I18nTextarea
+ widget=I18nMarkdownTextarea,
)
},
'order_import_settings': {
@@ -3223,7 +3223,7 @@ Your {organizer} team""")) # noqa: W291
'form_class': I18nFormField,
'form_kwargs': dict(
label=_('Homepage text'),
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
help_text=_('This will be displayed on the organizer homepage.')
)
},
@@ -3280,7 +3280,7 @@ Your {organizer} team""")) # noqa: W291
'form_class': I18nFormField,
'form_kwargs': dict(
label=_("Dialog text"),
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
widget_kwargs={'attrs': {'rows': '3', 'data-display-dependency': '#id_settings-cookie_consent'}},
)
},
@@ -3295,7 +3295,7 @@ Your {organizer} team""")) # noqa: W291
'form_class': I18nFormField,
'form_kwargs': dict(
label=_("Secondary dialog text"),
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
widget_kwargs={'attrs': {'rows': '3', 'data-display-dependency': '#id_settings-cookie_consent'}},
)
},
diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py
index d530e8cc46..c9a0ec9258 100644
--- a/src/pretix/control/forms/event.py
+++ b/src/pretix/control/forms/event.py
@@ -55,12 +55,14 @@ from django.utils.timezone import get_current_timezone_name
from django.utils.translation import gettext, gettext_lazy as _, pgettext_lazy
from django_countries.fields import LazyTypedChoiceField
from i18nfield.forms import (
- I18nForm, I18nFormField, I18nFormSetMixin, I18nTextarea, I18nTextInput,
+ I18nForm, I18nFormField, I18nFormSetMixin, I18nTextInput,
)
from pytz import common_timezones
from pretix.base.channels import get_all_sales_channels
-from pretix.base.forms import I18nModelForm, PlaceholderValidator, SettingsForm
+from pretix.base.forms import (
+ I18nMarkdownTextarea, I18nModelForm, PlaceholderValidator, SettingsForm,
+)
from pretix.base.models import Event, Organizer, TaxRule, Team
from pretix.base.models.event import EventFooterLink, EventMetaValue, SubEvent
from pretix.base.reldate import RelativeDateField, RelativeDateTimeField
@@ -988,7 +990,7 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
mail_text_signature = I18nFormField(
label=_("Signature"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
help_text=_("This will be attached to every email. Available placeholders: {event}"),
validators=[PlaceholderValidator(['{event}'])],
widget_kwargs={'attrs': {
@@ -1011,7 +1013,7 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
mail_text_order_placed = I18nFormField(
label=_("Text sent to order contact address"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
)
mail_send_order_placed_attendee = forms.BooleanField(
label=_("Send an email to attendees"),
@@ -1027,7 +1029,7 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
mail_text_order_placed_attendee = I18nFormField(
label=_("Text sent to attendees"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
)
mail_subject_order_paid = I18nFormField(
@@ -1038,7 +1040,7 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
mail_text_order_paid = I18nFormField(
label=_("Text sent to order contact address"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
)
mail_send_order_paid_attendee = forms.BooleanField(
label=_("Send an email to attendees"),
@@ -1054,7 +1056,7 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
mail_text_order_paid_attendee = I18nFormField(
label=_("Text sent to attendees"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
)
mail_subject_order_free = I18nFormField(
@@ -1065,7 +1067,7 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
mail_text_order_free = I18nFormField(
label=_("Text sent to order contact address"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
)
mail_send_order_free_attendee = forms.BooleanField(
label=_("Send an email to attendees"),
@@ -1081,7 +1083,7 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
mail_text_order_free_attendee = I18nFormField(
label=_("Text sent to attendees"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
)
mail_subject_order_changed = I18nFormField(
@@ -1092,7 +1094,7 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
mail_text_order_changed = I18nFormField(
label=_("Text"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
)
mail_subject_resend_link = I18nFormField(
label=_("Subject (sent by admin)"),
@@ -1107,7 +1109,7 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
mail_text_resend_link = I18nFormField(
label=_("Text (sent by admin)"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
)
mail_subject_resend_all_links = I18nFormField(
label=_("Subject (requested by user)"),
@@ -1117,7 +1119,7 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
mail_text_resend_all_links = I18nFormField(
label=_("Text (requested by user)"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
)
mail_days_order_expire_warning = forms.IntegerField(
label=_("Number of days"),
@@ -1129,7 +1131,7 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
mail_text_order_expire_warning = I18nFormField(
label=_("Text (if order will expire automatically)"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
)
mail_subject_order_expire_warning = I18nFormField(
label=_("Subject (if order will expire automatically)"),
@@ -1139,7 +1141,7 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
mail_text_order_pending_warning = I18nFormField(
label=_("Text (if order will not expire automatically)"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
)
mail_subject_order_pending_warning = I18nFormField(
label=_("Subject (if order will not expire automatically)"),
@@ -1154,7 +1156,7 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
mail_text_order_incomplete_payment = I18nFormField(
label=_("Text"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
help_text=_("This email only applies to payment methods that can receive incomplete payments, "
"such as bank transfer."),
)
@@ -1166,7 +1168,7 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
mail_text_order_payment_failed = I18nFormField(
label=_("Text"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
)
mail_subject_waiting_list = I18nFormField(
label=_("Subject"),
@@ -1176,7 +1178,7 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
mail_text_waiting_list = I18nFormField(
label=_("Text"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
)
mail_subject_order_canceled = I18nFormField(
label=_("Subject"),
@@ -1186,12 +1188,12 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
mail_text_order_canceled = I18nFormField(
label=_("Text"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
)
mail_text_order_custom_mail = I18nFormField(
label=_("Text"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
)
mail_subject_download_reminder = I18nFormField(
label=_("Subject sent to order contact address"),
@@ -1201,7 +1203,7 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
mail_text_download_reminder = I18nFormField(
label=_("Text sent to order contact address"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
)
mail_send_download_reminder_attendee = forms.BooleanField(
label=_("Send an email to attendees"),
@@ -1217,7 +1219,7 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
mail_text_download_reminder_attendee = I18nFormField(
label=_("Text sent to attendees"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
)
mail_days_download_reminder = forms.IntegerField(
label=_("Number of days"),
@@ -1234,7 +1236,7 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
mail_text_order_placed_require_approval = I18nFormField(
label=_("Text for received order"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
)
mail_subject_order_approved = I18nFormField(
label=_("Subject for approved order"),
@@ -1244,7 +1246,7 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
mail_text_order_approved = I18nFormField(
label=_("Text for approved order"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
help_text=_("This will only be sent out for non-free orders. Free orders will receive the free order "
"template from below instead."),
)
@@ -1262,7 +1264,7 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
mail_text_order_approved_attendee = I18nFormField(
label=_("Text sent to attendees"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
help_text=_("This will only be sent out for non-free orders. Free orders will receive the free order "
"template from below instead."),
)
@@ -1274,7 +1276,7 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
mail_text_order_approved_free = I18nFormField(
label=_("Text for approved free order"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
help_text=_("This will only be sent out for free orders. Non-free orders will receive the non-free order "
"template from above instead."),
)
@@ -1292,7 +1294,7 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
mail_text_order_approved_free_attendee = I18nFormField(
label=_("Text sent to attendees"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
help_text=_("This will only be sent out for free orders. Non-free orders will receive the non-free order "
"template from above instead."),
)
@@ -1304,7 +1306,7 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
mail_text_order_denied = I18nFormField(
label=_("Text for denied order"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
)
base_context = {
'mail_text_order_placed': ['event', 'order', 'payments'],
@@ -1737,7 +1739,7 @@ class ItemMetaPropertyForm(forms.ModelForm):
class ConfirmTextForm(I18nForm):
text = I18nFormField(
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
widget_kwargs={'attrs': {'rows': '2'}},
)
diff --git a/src/pretix/control/forms/global_settings.py b/src/pretix/control/forms/global_settings.py
index 7140306814..6852d409e3 100644
--- a/src/pretix/control/forms/global_settings.py
+++ b/src/pretix/control/forms/global_settings.py
@@ -36,10 +36,12 @@ from collections import OrderedDict
from django import forms
from django.utils.translation import gettext_lazy as _
-from i18nfield.forms import I18nFormField, I18nTextarea, I18nTextInput
+from i18nfield.forms import I18nFormField, I18nTextInput
from pretix import settings
-from pretix.base.forms import SecretKeySettingsField, SettingsForm
+from pretix.base.forms import (
+ I18nMarkdownTextarea, SecretKeySettingsField, SettingsForm,
+)
from pretix.base.settings import GlobalSettingsObject
from pretix.base.signals import register_global_settings
@@ -67,12 +69,12 @@ class GlobalSettingsForm(SettingsForm):
help_text=_("Will be included as the link in the additional footer text.")
)),
('banner_message', I18nFormField(
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
required=False,
label=_("Global message banner"),
)),
('banner_message_detail', I18nFormField(
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
required=False,
label=_("Global message banner detail text"),
)),
diff --git a/src/pretix/control/forms/item.py b/src/pretix/control/forms/item.py
index d511698a0a..48d910d995 100644
--- a/src/pretix/control/forms/item.py
+++ b/src/pretix/control/forms/item.py
@@ -56,7 +56,7 @@ from django_scopes.forms import (
from i18nfield.forms import I18nFormField, I18nTextarea
from pretix.base.channels import get_all_sales_channels
-from pretix.base.forms import I18nFormSet, I18nModelForm
+from pretix.base.forms import I18nFormSet, I18nMarkdownTextarea, I18nModelForm
from pretix.base.forms.widgets import DatePickerWidget
from pretix.base.models import (
Item, ItemCategory, ItemVariation, Question, QuestionOption, Quota,
@@ -82,6 +82,9 @@ class CategoryForm(I18nModelForm):
'description',
'is_addon'
]
+ widgets = {
+ 'description': I18nMarkdownTextarea,
+ }
class QuestionForm(I18nModelForm):
@@ -188,6 +191,7 @@ class QuestionForm(I18nModelForm):
attrs={'class': 'scrolling-multiple-choice'}
),
'dependency_values': forms.SelectMultiple,
+ 'help_text': I18nMarkdownTextarea,
}
field_classes = {
'valid_datetime_min': SplitDateTimeField,
@@ -823,6 +827,7 @@ class ItemUpdateForm(I18nModelForm):
'max_per_order': forms.widgets.NumberInput(attrs={'min': 0}),
'min_per_order': forms.widgets.NumberInput(attrs={'min': 0}),
'checkin_text': forms.TextInput(),
+ 'description': I18nMarkdownTextarea,
}
diff --git a/src/pretix/control/forms/orders.py b/src/pretix/control/forms/orders.py
index 6bced68792..70bab945e7 100644
--- a/src/pretix/control/forms/orders.py
+++ b/src/pretix/control/forms/orders.py
@@ -47,11 +47,14 @@ from django.utils.translation import (
gettext_lazy as _, gettext_noop, pgettext_lazy,
)
from django_scopes.forms import SafeModelChoiceField
-from i18nfield.forms import I18nFormField, I18nTextarea, I18nTextInput
+from i18nfield.forms import I18nFormField, I18nTextInput
from i18nfield.strings import LazyI18nString
from pretix.base.email import get_available_placeholders
-from pretix.base.forms import I18nModelForm, PlaceholderValidator
+from pretix.base.forms import (
+ I18nMarkdownTextarea, I18nModelForm, MarkdownTextarea,
+ PlaceholderValidator,
+)
from pretix.base.forms.questions import WrappedPhoneNumberPrefixWidget
from pretix.base.forms.widgets import (
DatePickerWidget, SplitDateTimePickerWidget, format_placeholders_help_text,
@@ -699,7 +702,7 @@ class OrderMailForm(forms.Form):
self.fields['message'] = forms.CharField(
label=_("Message"),
required=True,
- widget=forms.Textarea,
+ widget=MarkdownTextarea,
initial=order.event.settings.mail_text_order_custom_mail.localize(order.locale),
)
self.fields['attach_invoices'].queryset = order.invoices.all()
@@ -716,7 +719,7 @@ class OrderPositionMailForm(OrderMailForm):
self.fields['message'] = forms.CharField(
label=_("Message"),
required=True,
- widget=forms.Textarea,
+ widget=MarkdownTextarea,
initial=self.order.event.settings.mail_text_order_custom_mail.localize(self.order.locale),
)
self._set_field_placeholders('message', ['event', 'order', 'position'])
@@ -883,7 +886,7 @@ class EventCancelForm(FormPlaceholderMixin, forms.Form):
)
self.fields['send_message'] = I18nFormField(
label=_('Message'),
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
required=True,
widget_kwargs={'attrs': {'data-display-dependency': '#id_send'}},
locales=self.event.settings.get('locales'),
@@ -910,7 +913,7 @@ class EventCancelForm(FormPlaceholderMixin, forms.Form):
)
self.fields['send_waitinglist_message'] = I18nFormField(
label=_('Message'),
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
required=True,
locales=self.event.settings.get('locales'),
widget_kwargs={'attrs': {'data-display-dependency': '#id_send_waitinglist'}},
diff --git a/src/pretix/control/forms/organizer.py b/src/pretix/control/forms/organizer.py
index da5bf0266d..d22d59c2f6 100644
--- a/src/pretix/control/forms/organizer.py
+++ b/src/pretix/control/forms/organizer.py
@@ -48,7 +48,7 @@ from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django_scopes.forms import SafeModelChoiceField
from i18nfield.forms import (
- I18nForm, I18nFormField, I18nFormSetMixin, I18nTextarea, I18nTextInput,
+ I18nForm, I18nFormField, I18nFormSetMixin, I18nTextInput,
)
from phonenumber_field.formfields import PhoneNumberField
from pytz import common_timezones
@@ -56,7 +56,9 @@ from pytz import common_timezones
from pretix.api.models import WebHook
from pretix.api.webhooks import get_all_webhook_events
from pretix.base.customersso.oidc import oidc_validate_and_complete_config
-from pretix.base.forms import I18nModelForm, PlaceholderValidator, SettingsForm
+from pretix.base.forms import (
+ I18nMarkdownTextarea, I18nModelForm, PlaceholderValidator, SettingsForm,
+)
from pretix.base.forms.questions import (
NamePartsFormField, WrappedPhoneNumberPrefixWidget, get_country_by_locale,
get_phone_prefix,
@@ -524,7 +526,7 @@ class MailSettingsForm(SettingsForm):
mail_text_signature = I18nFormField(
label=_("Signature"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
help_text=_("This will be attached to every email."),
validators=[PlaceholderValidator([])],
widget_kwargs={'attrs': {
@@ -543,7 +545,7 @@ class MailSettingsForm(SettingsForm):
mail_text_customer_registration = I18nFormField(
label=_("Text"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
)
mail_subject_customer_email_change = I18nFormField(
label=_("Subject"),
@@ -553,7 +555,7 @@ class MailSettingsForm(SettingsForm):
mail_text_customer_email_change = I18nFormField(
label=_("Text"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
)
mail_subject_customer_reset = I18nFormField(
label=_("Subject"),
@@ -563,7 +565,7 @@ class MailSettingsForm(SettingsForm):
mail_text_customer_reset = I18nFormField(
label=_("Text"),
required=False,
- widget=I18nTextarea,
+ widget=I18nMarkdownTextarea,
)
base_context = {
diff --git a/src/pretix/control/forms/vouchers.py b/src/pretix/control/forms/vouchers.py
index 95357f2e8c..96866bb41b 100644
--- a/src/pretix/control/forms/vouchers.py
+++ b/src/pretix/control/forms/vouchers.py
@@ -45,7 +45,9 @@ from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django_scopes.forms import SafeModelChoiceField
from pretix.base.email import get_available_placeholders
-from pretix.base.forms import I18nModelForm, PlaceholderValidator
+from pretix.base.forms import (
+ I18nModelForm, MarkdownTextarea, PlaceholderValidator,
+)
from pretix.base.forms.widgets import format_placeholders_help_text
from pretix.base.models import Item, Voucher
from pretix.control.forms import SplitDateTimeField, SplitDateTimePickerWidget
@@ -271,7 +273,7 @@ class VoucherBulkForm(VoucherForm):
)
send_message = forms.CharField(
label=_("Message"),
- widget=forms.Textarea(attrs={'data-display-dependency': '#id_send'}),
+ widget=MarkdownTextarea(attrs={'data-display-dependency': '#id_send'}),
required=False,
initial=_('Hello,\n\n'
'with this email, we\'re sending you one or more vouchers for {event}:\n\n{voucher_list}\n\n'
diff --git a/src/pretix/plugins/sendmail/forms.py b/src/pretix/plugins/sendmail/forms.py
index 8720527648..a21be49cf6 100644
--- a/src/pretix/plugins/sendmail/forms.py
+++ b/src/pretix/plugins/sendmail/forms.py
@@ -39,9 +39,9 @@ from django.core.exceptions import ValidationError
from django.urls import reverse
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django_scopes.forms import SafeModelMultipleChoiceField
-from i18nfield.forms import I18nFormField, I18nTextarea, I18nTextInput
+from i18nfield.forms import I18nFormField, I18nTextInput
-from pretix.base.forms import I18nModelForm
+from pretix.base.forms import I18nMarkdownTextarea, I18nModelForm
from pretix.base.forms.widgets import (
SplitDateTimePickerWidget, TimePickerWidget,
)
@@ -76,7 +76,7 @@ class BaseMailForm(FormPlaceholderMixin, forms.Form):
)
self.fields['message'] = I18nFormField(
label=_('Message'),
- widget=I18nTextarea, required=True,
+ widget=I18nMarkdownTextarea, required=True,
locales=event.settings.get('locales'),
)
self._set_field_placeholders('subject', context_parameters)
@@ -317,6 +317,7 @@ class RuleForm(FormPlaceholderMixin, I18nModelForm):
),
'send_to': forms.RadioSelect,
'checked_in_status': forms.RadioSelect,
+ 'template': I18nMarkdownTextarea,
}
def __init__(self, *args, **kwargs):
diff --git a/src/pretix/static/pretixcontrol/img/icons/markdown-mark-solid.svg b/src/pretix/static/pretixcontrol/img/icons/markdown-mark-solid.svg
new file mode 100644
index 0000000000..fc3e20bbb4
--- /dev/null
+++ b/src/pretix/static/pretixcontrol/img/icons/markdown-mark-solid.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/pretix/static/pretixcontrol/scss/_forms.scss b/src/pretix/static/pretixcontrol/scss/_forms.scss
index 7a2b0e0453..7d77d3147b 100644
--- a/src/pretix/static/pretixcontrol/scss/_forms.scss
+++ b/src/pretix/static/pretixcontrol/scss/_forms.scss
@@ -147,8 +147,27 @@ div[data-formset-body], div[data-formset-form], div[data-nested-formset-form], d
padding: 5px 0;
}
+.i18n-form-group .i18n-field-markdown-note {
+ border: 1px solid $input-border;
+ color: $text-muted;
+ font-size: 0.8em;
+ padding: 0.2em;
+ &::before {
+ display: inline-block;
+ height: 0.6em;
+ width: 1.5em;
+ position: relative;
+ top: 0.1em;
+ left: 0.1em;
+ opacity: 50%;
+ margin-right: 0.5em;
+ content: url(static('pretixcontrol/img/icons/markdown-mark-solid.svg'));
+ }
+}
+
.i18n-form-group input,
-.i18n-form-group textarea {
+.i18n-form-group textarea,
+.i18n-form-group .i18n-field-markdown-note {
@include border-top-radius(0px);
@include border-bottom-radius(0px);
border-top-width: 0;