Merge `feature/donate-help-page` into `main` (#11359)
* DonateHelpPage model, factory, and tests * formatting * html formatting * template for notice section is done * added "show_notice" flag * Added newline at the end of help_page_notice.html * feedback from PR * formatting * saving progress, page is now working with body but need to update scss still * feedback from PR * added height/width attributes to image for linter * feedback from PR * updated scss, factory, and template * backend work done * added new notice block instead so we can update template individually * backend done, unless we want to make image optional * updated help page to use new notice block instead of imagetextmini * updated migrations * linting * formatting * fixed typo * updated to use StructBlockValidationError * updated tests * smal front end update * linting notice-block.scss * updated migrations, having trouble with NoticeBlockFactory * block factories & tests, updating of factory file structure * updated import * updated some factory file paths to account for changes * import formatting * removed forgotten factory.py file * first pass, have the form rendering with default styling * formatting * updated spacing * updated linting file to ignore errors from FA code * added donate and donate help page to visual regression tests * removed donate pages from visual regression tests since they live on a different subdomain (donate.localhost) * very first pass, borrowing existing file to get feedback from design * fixed bug where submit button would not render for last two dropdown options * fixed error where text box wouldnt show up for 2nd to last option * feedback from pr (add comments, update env.default and other small changes) * added newline at end of formassembly_body.html * updated chevron in the select dropdown * feedback from stakeholders (increased textarea height and implemented word wrapping on select element for chrome) * fixed dropdown styling/wrap issue * updated scss, ready for review * Formatting of scss file * updated app.json and continuous-integration.yml to include updated sp directives * Feedback from PR (height to min-height) * localized form and updated instructions * updated formassembly JS * formatting * localized submit button value * updated revision number * feedback from PR (wrapping dropdown options in trans blocks)
This commit is contained in:
Родитель
01ee5213cd
Коммит
87a3ac5085
|
@ -147,7 +147,8 @@ jobs:
|
|||
XSS_PROTECTION: True
|
||||
CSP_CONNECT_SRC: "*"
|
||||
CSP_FONT_SRC: "'self' https://fonts.gstatic.com https://fonts.googleapis.com https://code.cdn.mozilla.net https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/fonts/ data:"
|
||||
CSP_SCRIPT_SRC: "'self' 'unsafe-inline' https://www.google-analytics.com/analytics.js http://*.shpg.org/ https://comments.mozillafoundation.org/ https://airtable.com https://platform.twitter.com https://cdnjs.cloudflare.com/ajax/libs/gsap/3.8.0/gsap.min.js https://cdnjs.cloudflare.com/ajax/libs/gsap/3.8.0/ScrollTrigger.min.js https://*.googletagmanager.com https://*.fundraiseup.com https://mozillafoundation.tfaforms.net 'unsafe-eval'"
|
||||
CSP_FRAME_SRC: "'self' https://www.google.com/recaptcha/"
|
||||
CSP_SCRIPT_SRC: "'self' 'unsafe-inline' https://www.google-analytics.com/analytics.js http://*.shpg.org/ https://comments.mozillafoundation.org/ https://airtable.com https://platform.twitter.com https://cdnjs.cloudflare.com/ajax/libs/gsap/3.8.0/gsap.min.js https://cdnjs.cloudflare.com/ajax/libs/gsap/3.8.0/ScrollTrigger.min.js https://*.googletagmanager.com https://*.fundraiseup.com https://mozillafoundation.tfaforms.net https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ 'unsafe-eval'"
|
||||
CSP_STYLE_SRC: "'self' 'unsafe-inline' https://code.cdn.mozilla.net https://fonts.googleapis.com https://platform.twitter.com https://mozillafoundation.tfaforms.net https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
|
4
app.json
4
app.json
|
@ -27,11 +27,11 @@
|
|||
"CSP_CONNECT_SRC": "*",
|
||||
"CSP_DEFAULT_SRC": "'none'",
|
||||
"CSP_FRAME_ANCESTORS": "'none'",
|
||||
"CSP_FRAME_SRC": "'self' https://js.tito.io",
|
||||
"CSP_FRAME_SRC": "'self' https://js.tito.io https://www.google.com/recaptcha/",
|
||||
"CSP_FONT_SRC": "'self' https://fonts.gstatic.com https://fonts.googleapis.com https://code.cdn.mozilla.net https://static.fundraiseup.com/fonts/ https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/fonts/",
|
||||
"CSP_IMG_SRC": "* data:",
|
||||
"CSP_MEDIA_SRC": "'self' https://s3.amazonaws.com/mofo-assets/foundation/video/",
|
||||
"CSP_SCRIPT_SRC": "'self' 'unsafe-inline' https://www.google-analytics.com/analytics.js http://*.shpg.org/ https://comments.mozillafoundation.org/ https://airtable.com https://platform.twitter.com https://cdn.syndication.twimg.com https://js.tito.io https://js-plugins.tito.io/gtm.js https://*.fundraiseup.com *.googletagmanager.com https://mozillafoundation.tfaforms.net 'unsafe-eval'",
|
||||
"CSP_SCRIPT_SRC": "'self' 'unsafe-inline' https://www.google-analytics.com/analytics.js http://*.shpg.org/ https://comments.mozillafoundation.org/ https://airtable.com https://platform.twitter.com https://cdn.syndication.twimg.com https://js.tito.io https://js-plugins.tito.io/gtm.js https://*.fundraiseup.com *.googletagmanager.com https://mozillafoundation.tfaforms.net https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ 'unsafe-eval'",
|
||||
"CSP_STYLE_SRC": "'self' 'unsafe-inline' https://code.cdn.mozilla.net https://fonts.googleapis.com https://platform.twitter.com https://js.tito.io https://mozillafoundation.tfaforms.net https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css",
|
||||
"NPM_CONFIG_PRODUCTION": "true",
|
||||
"REVIEW_APP": "True",
|
||||
|
|
|
@ -53,10 +53,10 @@ CSP_CONNECT_SRC=" * "
|
|||
CSP_DEFAULT_SRC=" 'none' "
|
||||
CSP_FONT_SRC=" 'self' data: https://fonts.gstatic.com https://fonts.googleapis.com https://code.cdn.mozilla.net https://static.fundraiseup.com/fonts/ https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/fonts/"
|
||||
CSP_FRAME_ANCESTORS=" 'none' "
|
||||
CSP_FRAME_SRC=" 'self' https://www.youtube.com https://comments.mozillafoundation.org/ https://airtable.com https://docs.google.com/ https://platform.twitter.com https://public.zenkit.com https://calendar.google.com https://www.youtube-nocookie.com https://form.typeform.com https://js.tito.io https://datawrapper.dwcdn.net"
|
||||
CSP_FRAME_SRC=" 'self' https://www.youtube.com https://comments.mozillafoundation.org/ https://airtable.com https://docs.google.com/ https://platform.twitter.com https://public.zenkit.com https://calendar.google.com https://www.youtube-nocookie.com https://form.typeform.com https://js.tito.io https://datawrapper.dwcdn.net https://www.google.com/recaptcha/"
|
||||
CSP_IMG_SRC=" * data: "
|
||||
CSP_MEDIA_SRC=" 'self' data: https://s3.amazonaws.com/mofo-assets/foundation/video/ "
|
||||
CSP_SCRIPT_SRC=" 'self' 'unsafe-inline' https://www.google-analytics.com/analytics.js http://*.shpg.org/ https://comments.mozillafoundation.org/ https://airtable.com https://platform.twitter.com https://cdn.syndication.twimg.com https://embed.typeform.com https://js.tito.io https://js-plugins.tito.io/gtm.js https://tagmanager.google.com *.googletagmanager.com https://*.fundraiseup.com https://mozillafoundation.tfaforms.net 'unsafe-eval'"
|
||||
CSP_SCRIPT_SRC=" 'self' 'unsafe-inline' https://www.google-analytics.com/analytics.js http://*.shpg.org/ https://comments.mozillafoundation.org/ https://airtable.com https://platform.twitter.com https://cdn.syndication.twimg.com https://embed.typeform.com https://js.tito.io https://js-plugins.tito.io/gtm.js https://tagmanager.google.com *.googletagmanager.com https://*.fundraiseup.com https://mozillafoundation.tfaforms.net https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ 'unsafe-eval'"
|
||||
CSP_STYLE_SRC=" 'self' 'unsafe-inline' https://code.cdn.mozilla.net https://fonts.googleapis.com https://platform.twitter.com https://js.tito.io https://tagmanager.google.com https://mozillafoundation.tfaforms.net https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
|
||||
CSP_INCLUDE_NONCE_IN=script-src
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
from . import help_page, landing_page
|
||||
|
||||
|
||||
def generate(seed):
|
||||
# these are not, and should not be, alphabetically ordered.
|
||||
landing_page.generate(seed)
|
||||
help_page.generate(seed)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"generate",
|
||||
]
|
|
@ -0,0 +1,2 @@
|
|||
# flake8: noqa
|
||||
from .notice_block import NoticeBlockFactory
|
|
@ -0,0 +1,20 @@
|
|||
import wagtail_factories
|
||||
from factory import Faker, LazyAttribute, SubFactory
|
||||
from wagtail.rich_text import RichText
|
||||
|
||||
from networkapi.donate.pagemodels.customblocks.notice_block import NoticeBlock
|
||||
|
||||
description_faker = Faker("paragraphs", nb=2)
|
||||
|
||||
|
||||
class NoticeBlockFactory(wagtail_factories.StructBlockFactory):
|
||||
class Meta:
|
||||
model = NoticeBlock
|
||||
exclude = ("description_text",)
|
||||
|
||||
image = SubFactory(wagtail_factories.ImageChooserBlockFactory)
|
||||
image_alt_text = Faker("sentence", nb_words=4)
|
||||
text = LazyAttribute(lambda o: RichText("".join([f"<p>{p}</p>" for p in o.description_text])))
|
||||
|
||||
# Lazy Values
|
||||
description_text = description_faker
|
|
@ -0,0 +1,29 @@
|
|||
import wagtail_factories
|
||||
from factory import Faker, SubFactory
|
||||
from wagtail_factories import PageFactory
|
||||
|
||||
from networkapi.donate.factory.customblocks.notice_block import NoticeBlockFactory
|
||||
from networkapi.donate.models import DonateHelpPage, DonateLandingPage
|
||||
from networkapi.utility.faker import StreamfieldProvider
|
||||
from networkapi.utility.faker.helpers import reseed
|
||||
|
||||
Faker.add_provider(StreamfieldProvider)
|
||||
|
||||
streamfield_fields = ["paragraph", "spacer", "image", "image_text", "quote"]
|
||||
|
||||
|
||||
class DonateHelpPageFactory(PageFactory):
|
||||
class Meta:
|
||||
model = DonateHelpPage
|
||||
|
||||
title = Faker("sentence", nb_words=2)
|
||||
body = Faker("streamfield", fields=streamfield_fields)
|
||||
notice = wagtail_factories.StreamFieldFactory({"notice": SubFactory(NoticeBlockFactory)})
|
||||
|
||||
|
||||
def generate(seed):
|
||||
reseed(seed)
|
||||
|
||||
print("Generating a Help page")
|
||||
home_page = DonateLandingPage.objects.get(title="Donate Now")
|
||||
DonateHelpPageFactory(parent=home_page, title="Donate Help", slug="help", notice__0="notice")
|
|
@ -5,9 +5,17 @@ from wagtail.models import Site as WagtailSite
|
|||
from wagtail_factories import PageFactory
|
||||
|
||||
from networkapi.donate.models import DonateLandingPage
|
||||
from networkapi.utility.faker import StreamfieldProvider
|
||||
from networkapi.utility.faker.helpers import reseed
|
||||
from networkapi.wagtailpages.factory.image_factory import ImageFactory
|
||||
|
||||
description_faker = Faker("paragraphs", nb=2)
|
||||
|
||||
|
||||
Faker.add_provider(StreamfieldProvider)
|
||||
|
||||
streamfield_fields = ["paragraph", "spacer", "image", "image_text", "quote"]
|
||||
|
||||
|
||||
class DonateLandingPageFactory(PageFactory):
|
||||
class Meta:
|
||||
|
@ -31,8 +39,6 @@ def generate(seed):
|
|||
|
||||
home_page = DonateLandingPageFactory.create(parent=site_root, title="Donate Now", slug=None)
|
||||
|
||||
reseed(seed)
|
||||
|
||||
print("Creating Donate Site record in Wagtail")
|
||||
tds = settings.TARGET_DOMAINS
|
||||
if tds and len(tds) > 2:
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,41 @@
|
|||
# Generated by Django 3.2.21 on 2023-10-17 06:37
|
||||
|
||||
import wagtail.blocks
|
||||
import wagtail.fields
|
||||
import wagtail.images.blocks
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("donate", "0002_donatehelppage"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="donatehelppage",
|
||||
name="notice",
|
||||
field=wagtail.fields.StreamField(
|
||||
[
|
||||
(
|
||||
"notice",
|
||||
wagtail.blocks.StructBlock(
|
||||
[
|
||||
("image", wagtail.images.blocks.ImageChooserBlock(required=False)),
|
||||
(
|
||||
"image_alt_text",
|
||||
wagtail.blocks.CharBlock(
|
||||
help_text="Image description (for screen readers).", required=False
|
||||
),
|
||||
),
|
||||
("text", wagtail.blocks.RichTextBlock(features=["bold", "italic", "link"])),
|
||||
]
|
||||
),
|
||||
)
|
||||
],
|
||||
blank=True,
|
||||
help_text="Optional notice that will render at the top of the page.",
|
||||
use_json_field=True,
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,46 +1,4 @@
|
|||
from django.db import models
|
||||
from wagtail.admin.panels import FieldPanel
|
||||
from wagtail.fields import RichTextField
|
||||
from wagtail.models import Page
|
||||
from wagtail_localize.fields import SynchronizedField, TranslatableField
|
||||
|
||||
from networkapi.wagtailpages.models import FoundationMetadataPageMixin
|
||||
|
||||
|
||||
class BaseDonationPage(FoundationMetadataPageMixin, Page):
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class DonateLandingPage(BaseDonationPage):
|
||||
template = "donate/pages/landing_page.html"
|
||||
|
||||
# Only allow creating landing pages at the root level
|
||||
parent_page_types = ["wagtailcore.Page"]
|
||||
|
||||
subpage_types: list = []
|
||||
|
||||
featured_image = models.ForeignKey(
|
||||
"wagtailimages.Image",
|
||||
models.PROTECT,
|
||||
related_name="+",
|
||||
)
|
||||
intro = RichTextField()
|
||||
|
||||
content_panels = Page.content_panels + [
|
||||
FieldPanel("featured_image"),
|
||||
FieldPanel("intro"),
|
||||
]
|
||||
|
||||
translatable_fields = [
|
||||
# Promote tab fields
|
||||
SynchronizedField("slug"),
|
||||
TranslatableField("seo_title"),
|
||||
SynchronizedField("show_in_menus"),
|
||||
TranslatableField("search_description"),
|
||||
SynchronizedField("search_image"),
|
||||
# Content tab fields
|
||||
TranslatableField("title"),
|
||||
SynchronizedField("featured_image"),
|
||||
TranslatableField("intro"),
|
||||
]
|
||||
# flake8: noqa
|
||||
from .pagemodels.base_page import BaseDonationPage
|
||||
from .pagemodels.help_page import DonateHelpPage
|
||||
from .pagemodels.landing_page import DonateLandingPage
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
from wagtail.models import Page
|
||||
|
||||
from networkapi.wagtailpages.models import FoundationMetadataPageMixin
|
||||
|
||||
|
||||
class BaseDonationPage(FoundationMetadataPageMixin, Page):
|
||||
class Meta:
|
||||
abstract = True
|
|
@ -0,0 +1,31 @@
|
|||
from django.forms.utils import ErrorList
|
||||
from wagtail.blocks.struct_block import StructBlockValidationError
|
||||
from wagtail.core import blocks
|
||||
from wagtail.images.blocks import ImageChooserBlock
|
||||
|
||||
from networkapi.wagtailpages.pagemodels.customblocks.base_rich_text_options import (
|
||||
base_rich_text_options,
|
||||
)
|
||||
|
||||
|
||||
class NoticeBlock(blocks.StructBlock):
|
||||
image = ImageChooserBlock(required=False)
|
||||
image_alt_text = blocks.CharBlock(required=False, help_text="Image description (for screen readers).")
|
||||
text = blocks.RichTextBlock(features=base_rich_text_options)
|
||||
|
||||
class Meta:
|
||||
icon = "doc-full"
|
||||
template = "donate/blocks/notice_block.html"
|
||||
|
||||
def clean(self, value):
|
||||
cleaned_data = super().clean(value)
|
||||
errors = {}
|
||||
|
||||
if cleaned_data["image"] and not cleaned_data["image_alt_text"]:
|
||||
errors["image"] = ErrorList(["Image must include alt text."])
|
||||
if cleaned_data["image_alt_text"] and not cleaned_data["image"]:
|
||||
errors["image_alt_text"] = ErrorList(["Alt text must have an associated image."])
|
||||
if errors:
|
||||
raise StructBlockValidationError(errors)
|
||||
|
||||
return cleaned_data
|
|
@ -0,0 +1,45 @@
|
|||
from wagtail.admin.panels import FieldPanel
|
||||
from wagtail.fields import StreamField
|
||||
from wagtail.models import Page
|
||||
from wagtail_localize.fields import SynchronizedField, TranslatableField
|
||||
|
||||
from networkapi.donate.models import BaseDonationPage
|
||||
from networkapi.donate.pagemodels.customblocks.notice_block import NoticeBlock
|
||||
from networkapi.wagtailpages.pagemodels.customblocks.base_fields import base_fields
|
||||
|
||||
|
||||
class DonateHelpPage(BaseDonationPage):
|
||||
template = "donate/pages/help_page.html"
|
||||
|
||||
parent_page_types = ["DonateLandingPage"]
|
||||
|
||||
subpage_types: list = []
|
||||
|
||||
max_count = 1
|
||||
|
||||
notice = StreamField(
|
||||
[("notice", NoticeBlock())],
|
||||
help_text="Optional notice that will render at the top of the page.",
|
||||
blank=True,
|
||||
max_num=1,
|
||||
use_json_field=True,
|
||||
)
|
||||
|
||||
body = StreamField(base_fields, blank=True, use_json_field=True)
|
||||
|
||||
content_panels = Page.content_panels + [
|
||||
FieldPanel("notice"),
|
||||
FieldPanel("body"),
|
||||
]
|
||||
|
||||
translatable_fields = [
|
||||
# Promote tab fields
|
||||
SynchronizedField("slug"),
|
||||
TranslatableField("seo_title"),
|
||||
SynchronizedField("show_in_menus"),
|
||||
TranslatableField("search_description"),
|
||||
SynchronizedField("search_image"),
|
||||
# Content tab fields
|
||||
TranslatableField("notice"),
|
||||
TranslatableField("body"),
|
||||
]
|
|
@ -0,0 +1,41 @@
|
|||
from django.db import models
|
||||
from wagtail.admin.panels import FieldPanel
|
||||
from wagtail.fields import RichTextField
|
||||
from wagtail.models import Page
|
||||
from wagtail_localize.fields import SynchronizedField, TranslatableField
|
||||
|
||||
from networkapi.donate.models import BaseDonationPage
|
||||
|
||||
|
||||
class DonateLandingPage(BaseDonationPage):
|
||||
template = "donate/pages/landing_page.html"
|
||||
|
||||
# Only allow creating landing pages at the root level
|
||||
parent_page_types: list = ["wagtailcore.Page"]
|
||||
|
||||
subpage_types: list = ["DonateHelpPage"]
|
||||
|
||||
featured_image = models.ForeignKey(
|
||||
"wagtailimages.Image",
|
||||
models.PROTECT,
|
||||
related_name="+",
|
||||
)
|
||||
intro = RichTextField()
|
||||
|
||||
content_panels = Page.content_panels + [
|
||||
FieldPanel("featured_image"),
|
||||
FieldPanel("intro"),
|
||||
]
|
||||
|
||||
translatable_fields = [
|
||||
# Promote tab fields
|
||||
SynchronizedField("slug"),
|
||||
TranslatableField("seo_title"),
|
||||
SynchronizedField("show_in_menus"),
|
||||
TranslatableField("search_description"),
|
||||
SynchronizedField("search_image"),
|
||||
# Content tab fields
|
||||
TranslatableField("title"),
|
||||
SynchronizedField("featured_image"),
|
||||
TranslatableField("intro"),
|
||||
]
|
|
@ -0,0 +1,98 @@
|
|||
from django.test import TestCase
|
||||
from wagtail import rich_text
|
||||
from wagtail.blocks.struct_block import StructBlockValidationError
|
||||
from wagtail.images.tests.utils import Image, get_test_image_file
|
||||
|
||||
from networkapi.donate.factory.customblocks.notice_block import NoticeBlockFactory
|
||||
from networkapi.donate.pagemodels.customblocks.notice_block import NoticeBlock
|
||||
|
||||
|
||||
class NoticeBlockTest(TestCase):
|
||||
def setUp(self):
|
||||
self.notice_block = NoticeBlock()
|
||||
|
||||
def test_notice_block_factory(self):
|
||||
"""
|
||||
Testing that the factory can successfully create a valid NoticeBlock.
|
||||
"""
|
||||
notice_block_factory_default_values = NoticeBlockFactory()
|
||||
|
||||
is_valid = self.notice_block.clean(notice_block_factory_default_values)
|
||||
|
||||
self.assertTrue(is_valid)
|
||||
|
||||
def test_valid_notice_block(self):
|
||||
"""
|
||||
Testing that a notice block with all fields is valid.
|
||||
"""
|
||||
value = {
|
||||
"image": Image.objects.create(title="Test Image", file=get_test_image_file()),
|
||||
"image_alt_text": "Alt text",
|
||||
"text": rich_text.RichText("<p>Some content</p>"),
|
||||
}
|
||||
|
||||
is_valid = self.notice_block.clean(value)
|
||||
|
||||
self.assertTrue(is_valid)
|
||||
|
||||
def test_image_without_alt_text_raises_error(self):
|
||||
"""
|
||||
Testing that a notice block with an image but no alt text is invalid.
|
||||
"""
|
||||
value = {
|
||||
"image": Image.objects.create(title="Test Image", file=get_test_image_file()),
|
||||
"image_alt_text": "",
|
||||
"text": rich_text.RichText("<p>Some content</p>"),
|
||||
}
|
||||
|
||||
with self.assertRaises(StructBlockValidationError) as ValidationError:
|
||||
self.notice_block.clean(value)
|
||||
exception = ValidationError.exception
|
||||
self.assertIn("image", exception.params)
|
||||
self.assertEqual(exception.params["image"], ["Image must include alt text."])
|
||||
|
||||
def test_alt_text_without_image_raises_error(self):
|
||||
"""
|
||||
Testing that a notice block with alt text but no image is invalid.
|
||||
"""
|
||||
value = {
|
||||
"image": None,
|
||||
"image_alt_text": "Alt text",
|
||||
"text": rich_text.RichText("<p>Some content</p>"),
|
||||
}
|
||||
|
||||
with self.assertRaises(StructBlockValidationError) as ValidationError:
|
||||
self.notice_block.clean(value)
|
||||
exception = ValidationError.exception
|
||||
self.assertIn("image_alt_text", exception.params)
|
||||
self.assertEqual(exception.params["image_alt_text"], ["Alt text must have an associated image."])
|
||||
|
||||
def test_notice_text_field_is_required(self):
|
||||
"""
|
||||
Testing that a notice block with no body text is invalid.
|
||||
"""
|
||||
value = {
|
||||
"image": Image.objects.create(title="Test Image", file=get_test_image_file()),
|
||||
"image_alt_text": "Alt text",
|
||||
"text": rich_text.RichText(""),
|
||||
}
|
||||
|
||||
with self.assertRaises(StructBlockValidationError) as ValidationError:
|
||||
self.notice_block.clean(value)
|
||||
exception = ValidationError.exception
|
||||
self.assertIn("text", exception.params)
|
||||
self.assertEqual(exception.params["text"], ["This field is required."])
|
||||
|
||||
def test_valid_block_without_image_and_alt_text(self):
|
||||
"""
|
||||
Testing that a notice block with only text is valid.
|
||||
"""
|
||||
value = {
|
||||
"image": None,
|
||||
"image_alt_text": "",
|
||||
"text": rich_text.RichText("<p>Some content</p>"),
|
||||
}
|
||||
|
||||
is_valid = self.notice_block.clean(value)
|
||||
|
||||
self.assertTrue(is_valid)
|
|
@ -0,0 +1,73 @@
|
|||
from http import HTTPStatus
|
||||
|
||||
from networkapi.donate import models as pagemodels
|
||||
from networkapi.donate.factory import help_page as help_page_factories
|
||||
from networkapi.wagtailpages.tests import base as test_base
|
||||
|
||||
|
||||
class FactoriesTest(test_base.WagtailpagesTestCase):
|
||||
def test_page_factory(self):
|
||||
"""
|
||||
Testing the factory can successfully create a DonateHelpPage.
|
||||
"""
|
||||
help_page_factories.DonateHelpPageFactory()
|
||||
|
||||
|
||||
class DonateHelpPageTest(test_base.WagtailpagesTestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
super().setUpTestData()
|
||||
cls.donate_landing_page = help_page_factories.DonateHelpPageFactory(
|
||||
parent=cls.homepage,
|
||||
)
|
||||
cls.donate_help_page = help_page_factories.DonateHelpPageFactory(
|
||||
parent=cls.donate_landing_page,
|
||||
notice__0="notice",
|
||||
)
|
||||
|
||||
def test_parent_page_types(self):
|
||||
"""
|
||||
Testing that the DonateHelpPage model can only be created as a child of a landing page.
|
||||
"""
|
||||
self.assertAllowedParentPageTypes(
|
||||
child_model=pagemodels.DonateHelpPage,
|
||||
parent_models={pagemodels.DonateLandingPage},
|
||||
)
|
||||
|
||||
def test_subpage_types(self):
|
||||
"""
|
||||
Testing the DonateHelpPage's allowed subpage types.
|
||||
"""
|
||||
self.assertAllowedSubpageTypes(
|
||||
parent_model=pagemodels.DonateHelpPage,
|
||||
child_models={},
|
||||
)
|
||||
|
||||
def test_page_success(self):
|
||||
"""
|
||||
Testing that visiting a DonateHelpPage's URL returns a successful HTTP status.
|
||||
"""
|
||||
url = self.donate_help_page.get_url()
|
||||
|
||||
response = self.client.get(url)
|
||||
|
||||
self.assertEqual(response.status_code, HTTPStatus.OK)
|
||||
|
||||
def test_template(self):
|
||||
"""
|
||||
Testing that visiting a DonateHelpPage's URL renders the correct templates.
|
||||
"""
|
||||
url = self.donate_help_page.get_url()
|
||||
|
||||
response = self.client.get(url)
|
||||
|
||||
self.assertTemplateUsed(
|
||||
response=response,
|
||||
template_name="donate/pages/help_page.html",
|
||||
)
|
||||
|
||||
def test_help_page_notice_field(self):
|
||||
"""
|
||||
Asserts that a 'notice' block was created in the 'notice' field by the factory.
|
||||
"""
|
||||
self.assertEqual(self.donate_help_page.notice[0].block_type, "notice")
|
|
@ -2,8 +2,8 @@ from http import HTTPStatus
|
|||
|
||||
from wagtail.models import Page as WagtailPage
|
||||
|
||||
from networkapi.donate import factory as donate_factories
|
||||
from networkapi.donate import models as pagemodels
|
||||
from networkapi.donate.factory import landing_page as landing_page_factories
|
||||
from networkapi.wagtailpages.tests import base as test_base
|
||||
|
||||
|
||||
|
@ -12,14 +12,14 @@ class FactoriesTest(test_base.WagtailpagesTestCase):
|
|||
"""
|
||||
Testing the factory can successfully create a DonateLandingPage.
|
||||
"""
|
||||
donate_factories.DonateLandingPageFactory()
|
||||
landing_page_factories.DonateLandingPageFactory()
|
||||
|
||||
|
||||
class DonateLandingPageTest(test_base.WagtailpagesTestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
super().setUpTestData()
|
||||
cls.donate_landing_page = donate_factories.DonateLandingPageFactory(
|
||||
cls.donate_landing_page = landing_page_factories.DonateLandingPageFactory(
|
||||
parent=cls.homepage,
|
||||
)
|
||||
|
||||
|
@ -38,7 +38,7 @@ class DonateLandingPageTest(test_base.WagtailpagesTestCase):
|
|||
"""
|
||||
self.assertAllowedSubpageTypes(
|
||||
parent_model=pagemodels.DonateLandingPage,
|
||||
child_models={},
|
||||
child_models={pagemodels.DonateHelpPage},
|
||||
)
|
||||
|
||||
def test_page_success(self):
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
{% load l10n wagtailcore_tags wagtailimages_tags %}
|
||||
|
||||
<div class="tw-py-4 tw-px-0 medium:tw-p-8 tw-my-24 tw-w-full tw-flex tw-flex-col large:tw-flex-row tw-items-start large:tw-items-center tw-border-t tw-border-b tw-border-black">
|
||||
{% if value.image %}
|
||||
<div class="tw-flex-shrink-0 tw-grow-0 tw-basis-24 tw-mr-4 medium:tw-mr-8">
|
||||
{% image value.image fill-100x100-c100 format-jpeg as img %}
|
||||
<img src="{{ img.url }}" alt="{{ value.image_alt_text }}" class="tw-w-full" width="{{ img.width|unlocalize }}" height="{{ img.height|unlocalize }}">
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="notice-block-text">
|
||||
{{ value.text|richtext }}
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,162 @@
|
|||
{% load i18n %}
|
||||
|
||||
{% comment %}
|
||||
*** IMPORTANT ***
|
||||
|
||||
The code below has been copied directly over from FormAssembly.
|
||||
|
||||
Please follow the steps below every time after you paste a new version of FormAssembly code.
|
||||
|
||||
1. Replace everything starting from <!-- FORM: BODY SECTION -->
|
||||
|
||||
2. Run `inv format-html` in terminal
|
||||
|
||||
3. Add nonce="{{ csp_nonce }}" to all script tags in this file
|
||||
|
||||
4. Get localization working again.
|
||||
- Localize "title" attribute and label text for all fields using {% trans %} or {% blocktrans %}.
|
||||
- If applicable, localize any other text on the form. For example, text within htmlContent divs.
|
||||
- Localize "value" attribute for <input type="submit" ...>
|
||||
- Go to formassembly_head.html and make sure everything has been updated according to the instructions.
|
||||
|
||||
5. Update the revision note on the next line. Revision number can be found on https://mozillafoundation.tfaforms.net/versions/index/12
|
||||
|
||||
The code snippet below is based on FormAssembly form revision #43
|
||||
|
||||
{% endcomment %}
|
||||
|
||||
<!-- FORM: BODY SECTION -->
|
||||
<div class="wFormContainer" style="max-width: 100%; width:auto;">
|
||||
<div class="wFormHeader"></div>
|
||||
<div class=""><div class="wForm" id="12-WRPR" dir="ltr">
|
||||
<div class="codesection" id="code-12"><script nonce="{{ request.csp_nonce }}">
|
||||
// From Mel A at Thunderbird. 06.09.2023. Edited by Mia to change adding en- locales to map and defaulting to 'Other'. Approved by Mel.
|
||||
// localeMap updated by Daniel at Mozilla 10.31.2023, to reflect languages that are currently supported on the foundation site.
|
||||
window.addEventListener('DOMContentLoaded', function () {
|
||||
// Retrieve the Language alias (locale field) on the form.
|
||||
const varLocale = document.getElementById('tfa_72');
|
||||
|
||||
// Exit if we can't find the locale selection dropdown
|
||||
if (!varLocale) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the url, and attempt to get the first parameter (locale)
|
||||
const url = new URL(window.location);
|
||||
const locale = url.pathname.split('/')[1] || null;
|
||||
|
||||
// If locale does not exist in the url, then there's nothing more to do.
|
||||
if (!locale) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Match up locale codes with the locale dropdown entries
|
||||
const localeMap = {
|
||||
'nl': 'tfa_221',
|
||||
'fy-NL': 'tfa_221',
|
||||
'en': 'tfa_222',
|
||||
'fr': 'tfa_223',
|
||||
'de': 'tfa_224',
|
||||
'pl': 'tfa_228',
|
||||
'pt-BR': 'tfa_229',
|
||||
'es': 'tfa_231',
|
||||
}
|
||||
|
||||
// Assign the locale, or default to 'Other' if the locale isn't supported.
|
||||
varLocale.value = localeMap[locale] || 'tfa_227';
|
||||
});
|
||||
|
||||
</script></div>
|
||||
<form method="post" action="https://mozillafoundation.tfaforms.net/api_v2/workflow/processor" class=" labelsAbove" id="12" role="form" enctype="multipart/form-data" style="padding:0;">
|
||||
<div class="oneField field-container-D labelsAbove " id="tfa_95-D">
|
||||
<label id="tfa_95-L" class="label preField" for="tfa_95">{% trans "I need" %}</label><br><div class="inputWrapper"><select aria-required="true" id="tfa_95" name="tfa_95" title="{% trans "I need" %}" class="required"><option value="">{% trans "Please select..." %}</option>
|
||||
<option value="tfa_194" id="tfa_194" data-conditionals="#tfa_212,#submit_button,#tfa_214,#tfa_163" class="">{% trans "Help with the donation form" %}</option>
|
||||
<option value="tfa_195" id="tfa_195" data-conditionals="#tfa_212,#submit_button,#tfa_163,#tfa_234" class="">{% trans "To change or cancel my monthly recurring donation" %}</option>
|
||||
<option value="tfa_193" id="tfa_193" data-conditionals="#tfa_212,#submit_button,#tfa_163" class="">{% trans "To update my email address or contact info" %}</option>
|
||||
<option value="tfa_192" id="tfa_192" data-conditionals="#tfa_212,#submit_button,#tfa_217,#tfa_163" class="">{% trans "Help donating by bank transfer (SEPA/IBAN)" %}</option>
|
||||
<option value="tfa_196" id="tfa_196" data-conditionals="#tfa_212,#submit_button,#tfa_163" class="">{% trans "Help donating with another currency or payment method" %}</option>
|
||||
<option value="tfa_197" id="tfa_197" data-conditionals="#tfa_212,#submit_button,#tfa_214,#tfa_163" class="">{% trans "Help with an unauthorized or fraudulent donation" %}</option>
|
||||
<option value="tfa_190" id="tfa_190" data-conditionals="#tfa_212,#submit_button,#tfa_163" class="">{% trans "A copy of my donation receipt" %}</option>
|
||||
<option value="tfa_191" id="tfa_191" data-conditionals="#tfa_212,#submit_button,#tfa_163" class="">{% trans "A year-end donation summary" %}</option>
|
||||
<option value="tfa_198" id="tfa_198" data-conditionals="#tfa_212,#submit_button,#tfa_163" class="">{% trans "My donation refunded" %}</option>
|
||||
<option value="tfa_199" id="tfa_199" data-conditionals="#tfa_212" class="">{% trans "To submit feedback" %}</option>
|
||||
<option value="tfa_200" id="tfa_200" data-conditionals="#tfa_212,#tfa_214,#tfa_163" class="">{% trans "Help with something else" %}</option></select></div>
|
||||
</div>
|
||||
<div id="tfa_217" class="section group" data-condition="`#tfa_192`"><div class="htmlSection" id="tfa_216"><div class="htmlContent" id="tfa_216-HTML"><span style="font-size:13px;color:#000000;font-weight:normal;text-decoration:none;font-family:'Arial';font-style:normal;text-decoration-skip-ink:none;">
|
||||
{% blocktrans %}
|
||||
We truly appreciate your generous support and are happy to now offer an
|
||||
easier way for you to give through online bank transfer. We can accept
|
||||
donations via direct debit in Euros through Single European Payment Area
|
||||
(SEPA) transfer.
|
||||
{% endblocktrans %}
|
||||
</span><br><br><div><b><a target="_blank" href="https://foundation.mozilla.org/?form=donate&utm_medium=web&utm_source=faq-page&utm_campaign=23-Donor-Care&utm_term=en"><u>{% trans "Make your donation online now" %}<br><br></u></a></b><i>{% trans "Still have questions? Contact us by submitting a case." %}</i><br></div></div></div></div>
|
||||
<div id="tfa_234" class="section group" data-condition="`#tfa_195`"><div class="htmlSection" id="tfa_235"><div class="htmlContent" id="tfa_235-HTML"><span style="font-size:13px;color:#000000;font-weight:normal;text-decoration:none;font-family:'Arial';font-style:normal;text-decoration-skip-ink:none;">
|
||||
{% blocktrans %}
|
||||
Access your donation account settings using the '<b>Manage My Donation</b>'
|
||||
button on your recent donation confirmation email. There you can
|
||||
change the amount, frequency, payment method and charge date. You can
|
||||
also cancel your recurring donation plan.
|
||||
{% endblocktrans %}
|
||||
</span><br><br><i>{% trans "Still have questions? Contact us by submitting a case." %}</i></div></div></div>
|
||||
<div class="oneField field-container-D labelsAbove " id="tfa_163-D">
|
||||
<label id="tfa_163-L" class="label preField " for="tfa_163">{% trans "Any other details that might help" %}</label><br><div class="inputWrapper"><textarea id="tfa_163" name="tfa_163" data-condition="`#tfa_190` OR `#tfa_191` OR `#tfa_192` OR `#tfa_193` OR `#tfa_194` OR `#tfa_195` OR `#tfa_196` OR `#tfa_197` OR `#tfa_198` OR `#tfa_199` OR `#tfa_200`" title="{% trans "Any other details that might help" %}" class=""></textarea></div>
|
||||
</div>
|
||||
<div class="oneField field-container-D hintsBelow " id="tfa_214-D">
|
||||
<label id="tfa_214-L" class="label preField " for="tfa_214">{% trans "Add a screenshot to help us with your request (optional)" %}</label><br><div class="inputWrapper">
|
||||
<input type="file" id="tfa_214" name="tfa_214" size="" data-condition="`#tfa_194` OR `#tfa_197` OR `#tfa_200`" title="{% trans "Add a screenshot to help us with your request (optional)" %}" class=""><span class="field-hint-inactive" id="tfa_214-H"><span id="tfa_214-HH" class="hint">{% trans "JPG, PNG and BMP files only" %}</span></span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="tfa_212" class="section group" data-condition="`#tfa_190` OR `#tfa_191` OR `#tfa_192` OR `#tfa_193` OR `#tfa_194` OR `#tfa_195` OR `#tfa_196` OR `#tfa_197` OR `#tfa_198` OR `#tfa_199` OR `#tfa_200`"><div id="tfa_213" class="section inline group">
|
||||
<div class="oneField field-container-D " id="tfa_1-D">
|
||||
<label id="tfa_1-L" class="label preField reqMark" for="tfa_1">{% trans "Name" %}</label><br><div class="inputWrapper"><input aria-required="true" type="text" id="tfa_1" name="tfa_1" value="" title="{% trans "Name" %}" class="required"></div>
|
||||
</div>
|
||||
<div class="oneField field-container-D " id="tfa_10-D">
|
||||
<label id="tfa_10-L" class="label preField reqMark" for="tfa_10">{% trans "Email" %}</label><br><div class="inputWrapper"><input aria-required="true" type="text" id="tfa_10" name="tfa_10" value="" title="{% trans "Email" %}" class="validate-email required"></div>
|
||||
</div>
|
||||
<div class="htmlSection" id="tfa_182"><div class="htmlContent" id="tfa_182-HTML">{% blocktrans with link="https://www.mozilla.org/privacy/websites/" %}Mozilla will only use your submitted information for purposes of communicating with you about your request. See our <a href="{{ link }}" target="_blank">privacy policy</a> for further information.{% endblocktrans %}
|
||||
</div></div>
|
||||
</div></div>
|
||||
<div class="oneField field-container-D labelsLeftAligned wf-acl-hidden" id="tfa_72-D">
|
||||
<label id="tfa_72-L" class="label preField " for="tfa_72">{% trans "Language" %}</label><div class="inputWrapper"><select id="tfa_72" name="tfa_72" title="{% trans "Language" %}" class=""><option value="">{% trans "Please select..." %}</option>
|
||||
<option value="tfa_221" id="tfa_221" class="">Dutch</option>
|
||||
<option value="tfa_222" id="tfa_222" class="">English</option>
|
||||
<option value="tfa_223" id="tfa_223" class="">French</option>
|
||||
<option value="tfa_224" id="tfa_224" class="">German</option>
|
||||
<option value="tfa_227" id="tfa_227" class="">Other</option>
|
||||
<option value="tfa_228" id="tfa_228" class="">Polish</option>
|
||||
<option value="tfa_229" id="tfa_229" class="">Portuguese</option>
|
||||
<option value="tfa_231" id="tfa_231" class="">Spanish</option></select></div>
|
||||
</div>
|
||||
<div class="actions" id="12-A" data-contentid="submit_button">
|
||||
<div id="google-captcha" style="display: none">
|
||||
<br><div class="captcha">
|
||||
<div class="oneField">
|
||||
<div class="g-recaptcha" id="g-recaptcha-render-div"></div>
|
||||
<div class="g-captcha-error"></div>
|
||||
<br>
|
||||
</div>
|
||||
<div class="captchaHelp">{% trans "reCAPTCHA helps prevent automated form spam." %}<br>
|
||||
</div>
|
||||
<div id="disabled-explanation" class="captchaHelp" style="display: none">{% trans "The submit button will be disabled until you complete the CAPTCHA." %}</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="submit" data-label="Submit" class="primaryAction" id="submit_button" value="{% trans "Submit" %}" data-condition="`#tfa_190` OR `#tfa_191` OR `#tfa_192` OR `#tfa_193` OR `#tfa_194` OR `#tfa_195` OR `#tfa_196` OR `#tfa_197` OR `#tfa_198` OR `#tfa_199` OR `#tfa_200`" data-conditional-mode="hidden">
|
||||
</div>
|
||||
<div style="clear:both"></div>
|
||||
<input type="hidden" value="12" name="tfa_dbFormId" id="tfa_dbFormId"><input type="hidden" value="" name="tfa_dbResponseId" id="tfa_dbResponseId"><input type="hidden" value="248931fbbdeec7bc8eff2364638bb781" name="tfa_dbControl" id="tfa_dbControl"><input type="hidden" value="" name="tfa_dbWorkflowSessionUuid" id="tfa_dbWorkflowSessionUuid"><input type="hidden" value="40" name="tfa_dbVersionId" id="tfa_dbVersionId"><input type="hidden" value="" name="tfa_switchedoff" id="tfa_switchedoff">
|
||||
</form>
|
||||
</div></div><div class="wFormFooter"><p class="supportInfo"><br></p></div>
|
||||
<p class="supportInfo" >
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script
|
||||
nonce="{{ request.csp_nonce }}"
|
||||
id="open-telemetry-script"
|
||||
type="text/javascript"
|
||||
src="https://mozillafoundation.tfaforms.net/dist/open-telemetry/open-telemetry.3e6c1bedaa7fb4452dd0.js"
|
||||
data-customer-id="170610"
|
||||
data-exporter-url="https://us-east-1-otel.formassembly.com/v1/traces"
|
||||
data-exporter-console="0"
|
||||
></script>
|
||||
<script src="https://mozillafoundation.tfaforms.net/api_v2/sst/copy-and-paste" nonce="{{ request.csp_nonce }}"></script>
|
|
@ -0,0 +1,224 @@
|
|||
{% load i18n formassembly_helper %}
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
|
||||
{% comment %}
|
||||
*** IMPORTANT ***
|
||||
|
||||
The code below has been copied directly over from FormAssembly.
|
||||
|
||||
Please follow the steps below every time after you paste a new version of FormAssembly code.
|
||||
|
||||
1. Replace everything starting from <!-- FORM: HEAD SECTION -->
|
||||
|
||||
2. Run `inv format-html` in terminal
|
||||
|
||||
3. Add nonce="{{ csp_nonce }}" to all script tags in this file
|
||||
|
||||
4. Get localization working again.
|
||||
- Update script file name https://mozillafoundation.tfaforms.net/wForms/3.11/js/localization-en_US.js?v=...
|
||||
with {% fa_locale_code %}
|
||||
e.g., https://mozillafoundation.tfaforms.net/wForms/3.11/js/localization-{% fa_locale_code %}.js?v=...
|
||||
- Go to formassembly_body.html and make sure everything has been updated according to the instructions
|
||||
|
||||
5. Update the revision note on the next line. Revision number can be found on https://mozillafoundation.tfaforms.net/versions/index/12
|
||||
|
||||
The code snippet below is based on FormAssembly form revision #43
|
||||
|
||||
{% endcomment %}
|
||||
|
||||
<!-- FORM: HEAD SECTION -->
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="referrer" content="no-referrer-when-downgrade">
|
||||
<!-- THIS SCRIPT NEEDS TO BE LOADED FIRST BEFORE wforms.js -->
|
||||
<script nonce="{{ request.csp_nonce }}" type="text/javascript" data-for="FA__DOMContentLoadedEventDispatch" src="https://mozillafoundation.tfaforms.net/js/FA__DOMContentLoadedEventDispatcher.js" defer></script>
|
||||
<style>
|
||||
.captcha {
|
||||
padding-bottom: 1em !important;
|
||||
}
|
||||
.wForm .captcha .oneField {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript" nonce="{{ request.csp_nonce }}">
|
||||
// initialize our variables
|
||||
var captchaReady = 0;
|
||||
var wFORMSReady = 0;
|
||||
var isConditionalSubmitEnabled = false;
|
||||
|
||||
// when wForms is loaded call this
|
||||
var wformsReadyCallback = function () {
|
||||
// using this var to denote if wForms is loaded
|
||||
wFORMSReady = 1;
|
||||
isConditionalSubmitEnabled = document.getElementById('submit_button').hasAttribute('data-condition');
|
||||
// call our recaptcha function which is dependent on both
|
||||
// wForms and an async call to google
|
||||
// note the meat of this function wont fire until both
|
||||
// wFORMSReady = 1 and captchaReady = 1
|
||||
onloadCallback();
|
||||
}
|
||||
var gCaptchaReadyCallback = function() {
|
||||
// using this var to denote if captcha is loaded
|
||||
captchaReady = 1;
|
||||
isConditionalSubmitEnabled = document.getElementById('submit_button').hasAttribute('data-condition');
|
||||
// call our recaptcha function which is dependent on both
|
||||
// wForms and an async call to google
|
||||
// note the meat of this function wont fire until both
|
||||
// wFORMSReady = 1 and captchaReady = 1
|
||||
onloadCallback();
|
||||
};
|
||||
|
||||
// add event listener to fire when wForms is fully loaded
|
||||
document.addEventListener("wFORMSLoaded", wformsReadyCallback);
|
||||
|
||||
var enableSubmitButton = function() {
|
||||
var submitButton = document.getElementById('submit_button');
|
||||
var explanation = document.getElementById('disabled-explanation');
|
||||
var isConditionalSubmitConditionMet = wFORMS.behaviors.condition.isConditionalSubmitConditionMet;
|
||||
if (
|
||||
submitButton != null &&
|
||||
(isConditionalSubmitEnabled && isConditionalSubmitConditionMet) ||
|
||||
!isConditionalSubmitEnabled
|
||||
)
|
||||
{
|
||||
submitButton.removeAttribute('disabled');
|
||||
if (explanation != null) {
|
||||
explanation.style.display = 'none';
|
||||
}
|
||||
}
|
||||
};
|
||||
var disableSubmitButton = function() {
|
||||
var submitButton = document.getElementById('submit_button');
|
||||
var explanation = document.getElementById('disabled-explanation');
|
||||
if (submitButton != null) {
|
||||
submitButton.disabled = true;
|
||||
if (explanation != null) {
|
||||
explanation.style.display = 'block';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// call this on both captcha async complete and wforms fully
|
||||
// initialized since we can't be sure which will complete first
|
||||
// and we need both done for this to function just check that they are
|
||||
// done to fire the functionality
|
||||
var onloadCallback = function () {
|
||||
// if our captcha is ready (async call completed)
|
||||
// and wFORMS is completely loaded then we are ready to add
|
||||
// the captcha to the page
|
||||
if (captchaReady && wFORMSReady) {
|
||||
grecaptcha.enterprise.render('g-recaptcha-render-div', {
|
||||
'sitekey': '6LfMg_EaAAAAAMhDNLMlgqDChzmtYHlx1yU2y7GI',
|
||||
'theme': 'light',
|
||||
'size': 'normal',
|
||||
'callback': 'enableSubmitButton',
|
||||
'expired-callback': 'disableSubmitButton'
|
||||
})
|
||||
var oldRecaptchaCheck = parseInt('1');
|
||||
if (oldRecaptchaCheck === -1) {
|
||||
var standardCaptcha = document.getElementById("tfa_captcha_text");
|
||||
standardCaptcha = standardCaptcha.parentNode.parentNode.parentNode;
|
||||
standardCaptcha.parentNode.removeChild(standardCaptcha);
|
||||
}
|
||||
|
||||
if (!wFORMS.instances['paging']) {
|
||||
document.getElementById("g-recaptcha-render-div").parentNode.parentNode.parentNode.style.display = "block";
|
||||
//document.getElementById("g-recaptcha-render-div").parentNode.parentNode.parentNode.removeAttribute("hidden");
|
||||
}
|
||||
document.getElementById("g-recaptcha-render-div").getAttributeNode('id').value = 'tfa_captcha_text';
|
||||
|
||||
var captchaError = '';
|
||||
if (captchaError == '1') {
|
||||
var errMsgText = 'The CAPTCHA was not completed successfully.';
|
||||
var errMsgDiv = document.createElement('div');
|
||||
errMsgDiv.id = "tfa_captcha_text-E";
|
||||
errMsgDiv.className = "err errMsg";
|
||||
errMsgDiv.innerText = errMsgText;
|
||||
var loc = document.querySelector('.g-captcha-error');
|
||||
loc.insertBefore(errMsgDiv, loc.childNodes[0]);
|
||||
|
||||
/* See wFORMS.behaviors.paging.applyTo for origin of this code */
|
||||
if (wFORMS.instances['paging']) {
|
||||
var b = wFORMS.instances['paging'][0];
|
||||
var pp = base2.DOM.Element.querySelector(document, wFORMS.behaviors.paging.CAPTCHA_ERROR);
|
||||
if (pp) {
|
||||
var lastPage = 1;
|
||||
for (var i = 1; i < 100; i++) {
|
||||
if (b.behavior.isLastPageIndex(i)) {
|
||||
lastPage = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
b.jumpTo(lastPage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script src='https://www.google.com/recaptcha/enterprise.js?onload=gCaptchaReadyCallback&render=explicit&hl=en_US' nonce="{{ request.csp_nonce }}" async
|
||||
defer></script>
|
||||
<script type="text/javascript" nonce="{{ request.csp_nonce }}">
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
var warning = document.getElementById("javascript-warning");
|
||||
if (warning != null) {
|
||||
warning.parentNode.removeChild(warning);
|
||||
}
|
||||
var oldRecaptchaCheck = parseInt('1');
|
||||
if (oldRecaptchaCheck !== -1) {
|
||||
var explanation = document.getElementById('disabled-explanation');
|
||||
var submitButton = document.getElementById('submit_button');
|
||||
if (submitButton != null) {
|
||||
submitButton.disabled = true;
|
||||
if (explanation != null) {
|
||||
explanation.style.display = 'block';
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script type="text/javascript" nonce="{{ request.csp_nonce }}">
|
||||
document.addEventListener("FA__DOMContentLoaded", function(){
|
||||
const FORM_TIME_START = Math.floor((new Date).getTime()/1000);
|
||||
let formElement = document.getElementById("tfa_0");
|
||||
if (null === formElement) {
|
||||
formElement = document.getElementById("0");
|
||||
}
|
||||
let appendJsTimerElement = function(){
|
||||
let formTimeDiff = Math.floor((new Date).getTime()/1000) - FORM_TIME_START;
|
||||
let cumulatedTimeElement = document.getElementById("tfa_dbCumulatedTime");
|
||||
if (null !== cumulatedTimeElement) {
|
||||
let cumulatedTime = parseInt(cumulatedTimeElement.value);
|
||||
if (null !== cumulatedTime && cumulatedTime > 0) {
|
||||
formTimeDiff += cumulatedTime;
|
||||
}
|
||||
}
|
||||
let jsTimeInput = document.createElement("input");
|
||||
jsTimeInput.setAttribute("type", "hidden");
|
||||
jsTimeInput.setAttribute("value", formTimeDiff.toString());
|
||||
jsTimeInput.setAttribute("name", "tfa_dbElapsedJsTime");
|
||||
jsTimeInput.setAttribute("id", "tfa_dbElapsedJsTime");
|
||||
jsTimeInput.setAttribute("autocomplete", "off");
|
||||
if (null !== formElement) {
|
||||
formElement.appendChild(jsTimeInput);
|
||||
}
|
||||
};
|
||||
if (null !== formElement) {
|
||||
if(formElement.addEventListener){
|
||||
formElement.addEventListener('submit', appendJsTimerElement, false);
|
||||
} else if(formElement.attachEvent){
|
||||
formElement.attachEvent('onsubmit', appendJsTimerElement);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<link href="https://mozillafoundation.tfaforms.net/dist/form-builder/5.0.0/wforms-layout.css?v=910ede8cddaa51d331c5781cd8fc809dbb1cdd98" rel="stylesheet" type="text/css" />
|
||||
|
||||
<link href="https://mozillafoundation.tfaforms.net/uploads/themes/theme-27.css" rel="stylesheet" type="text/css" />
|
||||
<link href="https://mozillafoundation.tfaforms.net/dist/form-builder/5.0.0/wforms-jsonly.css?v=910ede8cddaa51d331c5781cd8fc809dbb1cdd98" rel="alternate stylesheet" title="This stylesheet activated by javascript" type="text/css" />
|
||||
<script type="text/javascript" nonce="{{ request.csp_nonce }}" src="https://mozillafoundation.tfaforms.net/wForms/3.11/js/wforms.js?v=910ede8cddaa51d331c5781cd8fc809dbb1cdd98"></script>
|
||||
<script type="text/javascript" nonce="{{ request.csp_nonce }}">
|
||||
wFORMS.behaviors.prefill.skip = false;
|
||||
</script>
|
||||
<script type="text/javascript" nonce="{{ request.csp_nonce }}" src="https://mozillafoundation.tfaforms.net/wForms/3.11/js/localization-{% fa_locale_code %}.js?v=910ede8cddaa51d331c5781cd8fc809dbb1cdd98"></script>
|
|
@ -0,0 +1,35 @@
|
|||
{% extends "../pages/base.html" %}
|
||||
{% load static i18n l10n wagtailcore_tags wagtailimages_tags %}
|
||||
|
||||
{% block body_id %}help{% endblock body_id %}
|
||||
|
||||
{% block extended_head %}
|
||||
{% include "../fragments/formassembly_head.html" %}
|
||||
{% endblock extended_head %}
|
||||
|
||||
{% block content %}
|
||||
<div class="tw-container tw-py-10">
|
||||
<div class="cms donate-help-page-content">
|
||||
<h1 class="tw-h1-heading"> {{ page.title }} </h1>
|
||||
{% if page.notice %}
|
||||
{% for block in page.notice %}
|
||||
{% include_block block %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
If you need help with a <a href="https://foundation.mozilla.org/?form=donate&utm_medium=web&utm_source=faq-page&utm_campaign=23-Donor-Care" target="_blank">donation</a> to the Mozilla Foundation, please select the reason for your inquiry from the drop down and a donor care representative will get back to you as soon as possible.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<div class="tw-border-t tw-border-b tw-py-10 tw-mt-28 tw-mb-10 tw-border-gray-20">
|
||||
<p class="tw-h3-heading"> Contact Us </p>
|
||||
{% include "../fragments/formassembly_body.html" %}
|
||||
<link rel="stylesheet" href="{% static "_css/formassembly-override.compiled.css" %}">
|
||||
</div>
|
||||
{% for block in page.body %}
|
||||
{% include_block block with parent_page=page %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
"build:css": "run-p build:css:file:**",
|
||||
"build:css:file:main": "sass source/sass/main.scss network-api/networkapi/temp/frontend/_css/main.compiled.css",
|
||||
"build:css:file:bg": "sass source/sass/buyers-guide/bg-main.scss network-api/networkapi/temp/frontend/_css/buyers-guide.compiled.css",
|
||||
"build:css:file:donate": "sass source/sass/donate.scss network-api/networkapi/temp/frontend/_css/donate.compiled.css",
|
||||
"build:css:file:donate": "sass source/sass/donate/donate-main.scss network-api/networkapi/temp/frontend/_css/donate.compiled.css",
|
||||
"build:css:file:formassembly": "sass source/sass/formassembly-override.scss network-api/networkapi/temp/frontend/_css/formassembly-override.compiled.css",
|
||||
"docker:up": "docker-compose up",
|
||||
"docker:down": "docker-compose down",
|
||||
|
|
|
@ -15,6 +15,7 @@ ignore="H017"
|
|||
[tool.djlint.per-file-ignores]
|
||||
"maintenance/maintenance.html" = "D004,H005,H006,H013,H014,H025,H026,H030,H031"
|
||||
"network-api/networkapi/templates/donate/fragments/footer.html" = "T003"
|
||||
"network-api/networkapi/templates/donate/fragments/formassembly_body.html" = "H020,H021,H026"
|
||||
"network-api/networkapi/templates/donate/pages/base.html" = "T003"
|
||||
"network-api/networkapi/templates/donate/pages/landing_page.html" = "H006, T003"
|
||||
"network-api/networkapi/mozfest/templates/fragments/event_card.html" = "H006"
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 4.9 KiB |
|
@ -1,30 +0,0 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
*,
|
||||
::before,
|
||||
::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
article,
|
||||
aside,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
main,
|
||||
nav,
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
#view-landing .intro .rich-text {
|
||||
@apply medium:tw-body-large;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
.notice-block-text {
|
||||
.rich-text {
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
// mofo-bootstrap
|
||||
@import "../mofo-bootstrap/mofo-bootstrap";
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
// Custom variables
|
||||
@import "../variables";
|
||||
|
||||
// Misc
|
||||
@import "../cms";
|
||||
|
||||
// Wagtail Stream Blocks
|
||||
@import "./blocks/notice-block.scss";
|
||||
@import "../wagtail/blocks/airtable";
|
||||
@import "../wagtail/blocks/feature-quote";
|
||||
@import "../wagtail/blocks/image-text-mini";
|
||||
@import "../wagtail/blocks/iframe-block";
|
||||
@import "../wagtail/blocks/link-button.scss";
|
||||
@import "../wagtail/blocks/rich-text";
|
||||
@import "../wagtail/blocks/profiles";
|
||||
@import "../wagtail/blocks/video-block";
|
||||
@import "../wagtail/blocks/article-blocks";
|
||||
@import "../wagtail/blocks/image-feature";
|
||||
@import "../wagtail/blocks";
|
||||
|
||||
*,
|
||||
::before,
|
||||
::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
article,
|
||||
aside,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
main,
|
||||
nav,
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
#view-landing .intro .rich-text {
|
||||
@apply medium:tw-body-large;
|
||||
}
|
|
@ -72,6 +72,11 @@ $input-font-size: 1.25rem !important;
|
|||
border-color: transparent;
|
||||
text-decoration: none;
|
||||
}
|
||||
&:disabled {
|
||||
background-color: $light-blue;
|
||||
border-color: transparent;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
|
@ -96,10 +101,14 @@ $input-font-size: 1.25rem !important;
|
|||
font-family: $font-family-sans-serif;
|
||||
width: 100%;
|
||||
padding: 1rem 0.75rem;
|
||||
height: auto !important;
|
||||
height: auto;
|
||||
font-size: $input-font-size;
|
||||
}
|
||||
|
||||
input[type="text"].validate-email {
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
textarea:focus,
|
||||
textarea.required:focus,
|
||||
input[type="text"]:focus,
|
||||
|
@ -152,3 +161,114 @@ $input-font-size: 1.25rem !important;
|
|||
#tfa_494-D {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
// DonateHelpPage form specific overrides
|
||||
.donate-help-page-content {
|
||||
.wForm {
|
||||
label {
|
||||
font-family: $font-family-sans-serif;
|
||||
color: $black !important;
|
||||
font-size: 1.125rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="text"].validate-email,
|
||||
textarea,
|
||||
select {
|
||||
border-radius: 0 !important;
|
||||
border: 1px solid black !important;
|
||||
}
|
||||
|
||||
input[type="file"] {
|
||||
padding: 0 !important;
|
||||
height: auto !important;
|
||||
|
||||
&::file-selector-button {
|
||||
transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out;
|
||||
font-family: $font-family-sans-serif;
|
||||
background: $light-blue;
|
||||
width: auto;
|
||||
padding: 0rem 1.5rem;
|
||||
font-size: 1rem;
|
||||
border: none;
|
||||
height: 2.25rem;
|
||||
|
||||
&:hover {
|
||||
background: $blue;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 10em !important;
|
||||
}
|
||||
|
||||
select {
|
||||
appearance: none;
|
||||
background: url("../_images/glyphs/down-chevron.svg") no-repeat 99% center;
|
||||
padding-right: 30px !important;
|
||||
white-space: normal;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
option {
|
||||
@media (max-width: 576px) {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.oneField {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.htmlSection {
|
||||
padding: 0;
|
||||
font-size: 0.75rem;
|
||||
width: 100%;
|
||||
|
||||
span {
|
||||
color: $black !important;
|
||||
font-family: $font-family-sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
.htmlContent {
|
||||
font-family: $font-family-sans-serif;
|
||||
color: $black !important;
|
||||
a {
|
||||
color: $dark-blue;
|
||||
}
|
||||
}
|
||||
|
||||
.inline {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
|
||||
.oneField {
|
||||
width: 48.5%;
|
||||
@media (max-width: 768px) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.captcha {
|
||||
.oneField {
|
||||
margin: 0;
|
||||
}
|
||||
.captchaHelp {
|
||||
display: inline-flex !important;
|
||||
padding: 0;
|
||||
margin-top: 0.25rem;
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
|
||||
.actions .primaryAction#submit_button {
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,6 +69,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
.donate-help-page-content {
|
||||
.streamfield-content {
|
||||
@extend .px-0;
|
||||
@extend .col-12;
|
||||
}
|
||||
}
|
||||
|
||||
.mozfest-content {
|
||||
&.two-col {
|
||||
@extend .p-0;
|
||||
|
|
Загрузка…
Ссылка в новой задаче