diff --git a/doc/user/markdown.rst b/doc/user/markdown.rst index 5873d785d..7ca590e35 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 46afc6557..fb850a7f5 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( + '
%s
%s
' % ( + 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 a37941380..15daa9627 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 89dd09468..4dad532cf 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 14b9c8709..5665f7377 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 d530e8cc4..c9a0ec925 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 714030681..6852d409e 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 d511698a0..48d910d99 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 6bced6879..70bab945e 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 da5bf0266..d22d59c2f 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 95357f2e8..96866bb41 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 872052764..a21be49cf 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 000000000..fc3e20bbb --- /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 7a2b0e045..7d77d3147 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;