diff --git a/network-api/networkapi/mozfest/migrations/0025_add_current_events_slider.py b/network-api/networkapi/mozfest/migrations/0025_add_current_events_slider.py new file mode 100644 index 00000000000..bb18d4442ef --- /dev/null +++ b/network-api/networkapi/mozfest/migrations/0025_add_current_events_slider.py @@ -0,0 +1,24 @@ +# Generated by Django 3.1.11 on 2021-10-29 09:09 + +from django.db import migrations +import networkapi.wagtailpages.pagemodels.blog.blog_category +import wagtail.core.blocks +import wagtail.core.blocks.static_block +import wagtail.core.fields +import wagtail.documents.blocks +import wagtail.images.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('mozfest', '0024_mozfesthomepage_banner_cta_label'), + ] + + operations = [ + migrations.AlterField( + model_name='mozfestprimarypage', + name='body', + field=wagtail.core.fields.StreamField([('paragraph', wagtail.core.blocks.RichTextBlock(features=['bold', 'italic', 'large', 'h2', 'h3', 'h4', 'h5', 'ol', 'ul', 'link', 'hr'], template='wagtailpages/blocks/rich_text_block.html')), ('card_grid', wagtail.core.blocks.StructBlock([('cards', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('alt_text', wagtail.core.blocks.CharBlock(help_text="Alt text for card's image.", required=False)), ('title', wagtail.core.blocks.CharBlock(help_text='Heading for the card.')), ('body', wagtail.core.blocks.TextBlock(help_text='Body text of the card.')), ('link_url', wagtail.core.blocks.CharBlock(help_text='Optional URL that this card should link out to. (Note: If left blank, link will not render.) ', required=False)), ('link_label', wagtail.core.blocks.CharBlock(help_text='Optional Label for the URL link above. (Note: If left blank, link will not render.) ', required=False))]), help_text='Please use a minimum of 2 cards.'))])), ('image', wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('altText', wagtail.core.blocks.CharBlock(help_text='Image description (for screen readers).', required=True)), ('caption', wagtail.core.blocks.CharBlock(required=False)), ('captionURL', wagtail.core.blocks.CharBlock(help_text='Optional URL that this caption should link out to.', required=False)), ('image_width', wagtail.core.blocks.ChoiceBlock(choices=[('normal', 'Normal'), ('wide', 'Wide'), ('full_width', 'Full Width')], help_text='Wide images are col-12, Full-Width Images reach both ends of the screen (16:6 images recommended for full width)'))])), ('image_text', wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('altText', wagtail.core.blocks.CharBlock(help_text='Image description (for screen readers).', required=True)), ('text', wagtail.core.blocks.RichTextBlock(features=['bold', 'italic', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'ul', 'link'])), ('url', wagtail.core.blocks.CharBlock(help_text='Optional URL that this image should link out to.', required=False)), ('top_divider', wagtail.core.blocks.BooleanBlock(help_text='Optional divider above content block.', required=False)), ('bottom_divider', wagtail.core.blocks.BooleanBlock(help_text='Optional divider below content block.', required=False))])), ('image_text_mini', wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('altText', wagtail.core.blocks.CharBlock(help_text='Image description (for screen readers).', required=True)), ('text', wagtail.core.blocks.RichTextBlock(features=['bold', 'italic', 'link']))])), ('image_grid', wagtail.core.blocks.StructBlock([('grid_items', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('alt_text', wagtail.core.blocks.CharBlock(help_text='Alt text for this image.', required=False)), ('caption', wagtail.core.blocks.CharBlock(help_text='Please remember to properly attribute any images we use.', required=False)), ('url', wagtail.core.blocks.CharBlock(help_text='Optional URL that this figure should link out to.', required=False)), ('square_image', wagtail.core.blocks.BooleanBlock(default=True, help_text='If left checked, the image will be cropped to be square.', required=False))])))])), ('video', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.CharBlock(help_text='For YouTube: go to your YouTube video and click “Share,” then “Embed,” and then copy and paste the provided URL only. For example: https://www.youtube.com/embed/3FIVXBawyQw For Vimeo: follow similar steps to grab the embed URL. For example: https://player.vimeo.com/video/9004979')), ('caption', wagtail.core.blocks.CharBlock(required=False)), ('captionURL', wagtail.core.blocks.CharBlock(help_text='Optional URL for caption to link to.', required=False)), ('video_width', wagtail.core.blocks.ChoiceBlock(choices=[('normal', 'Normal'), ('wide', 'Wide'), ('full_width', 'Full Width')], help_text='Wide videos are col-12, Full-Width videos reach both ends of the screen.'))])), ('iframe', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.CharBlock(help_text='Please note that only URLs from allow-listed domains will work.')), ('height', wagtail.core.blocks.IntegerBlock(help_text='Optional integer pixel value for custom iFrame height', required=False)), ('caption', wagtail.core.blocks.CharBlock(required=False)), ('captionURL', wagtail.core.blocks.CharBlock(help_text='Optional URL that this caption should link out to.', required=False)), ('iframe_width', wagtail.core.blocks.ChoiceBlock(choices=[('normal', 'Normal'), ('wide', 'Wide'), ('full_width', 'Full Width')], help_text='Wide iframes are col-12, Full-Width iframes reach both ends of the screen'))])), ('linkbutton', wagtail.core.blocks.StructBlock([('label', wagtail.core.blocks.CharBlock()), ('URL', wagtail.core.blocks.CharBlock()), ('styling', wagtail.core.blocks.ChoiceBlock(choices=[('btn-primary', 'Primary button'), ('btn-secondary', 'Secondary button')]))])), ('spacer', wagtail.core.blocks.StructBlock([('size', wagtail.core.blocks.ChoiceBlock(choices=[('1', 'quarter spacing'), ('2', 'half spacing'), ('3', 'single spacing'), ('4', 'one and a half spacing'), ('5', 'triple spacing')]))])), ('quote', wagtail.core.blocks.StructBlock([('quotes', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('quote', wagtail.core.blocks.RichTextBlock(features=['bold'])), ('attribution', wagtail.core.blocks.CharBlock(required=False))])))])), ('pulse_listing', wagtail.core.blocks.StructBlock([('search_terms', wagtail.core.blocks.CharBlock(help_text='Test your search at mozillapulse.org/search', label='Search', required=False)), ('max_number_of_results', wagtail.core.blocks.IntegerBlock(default=6, help_text='Choose 1-12. If you want visitors to see more, link to a search or tag on Pulse.', max_value=12, min_value=0, required=True)), ('only_featured_entries', wagtail.core.blocks.BooleanBlock(default=False, help_text='Featured items are selected by Pulse moderators.', label='Display only featured entries', required=False)), ('newest_first', wagtail.core.blocks.ChoiceBlock(choices=[('True', 'Show newer entries first'), ('False', 'Show older entries first')], label='Sort')), ('advanced_filter_header', wagtail.core.blocks.static_block.StaticBlock(admin_text='-------- ADVANCED FILTERS: OPTIONS TO DISPLAY FEWER, MORE TARGETED RESULTS. --------', label=' ')), ('issues', wagtail.core.blocks.ChoiceBlock(choices=[('all', 'All'), ('Decentralization', 'Decentralization'), ('Digital Inclusion', 'Digital Inclusion'), ('Online Privacy & Security', 'Online Privacy & Security'), ('Open Innovation', 'Open Innovation'), ('Web Literacy', 'Web Literacy')])), ('help', wagtail.core.blocks.ChoiceBlock(choices=[('all', 'All'), ('Attend', 'Attend'), ('Create content', 'Create content'), ('Code', 'Code'), ('Design', 'Design'), ('Fundraise', 'Fundraise'), ('Join community', 'Join community'), ('Localize & translate', 'Localize & translate'), ('Mentor', 'Mentor'), ('Plan & organize', 'Plan & organize'), ('Promote', 'Promote'), ('Take action', 'Take action'), ('Test & feedback', 'Test & feedback'), ('Write documentation', 'Write documentation')], label='Type of help needed')), ('direct_link', wagtail.core.blocks.BooleanBlock(default=False, help_text='Checked: user goes to project link. Unchecked: user goes to pulse entry', label='Direct link', required=False))])), ('profile_listing', wagtail.core.blocks.StructBlock([('max_number_of_results', wagtail.core.blocks.IntegerBlock(default=12, help_text='Pick up to 48 profiles.', max_value=48, min_value=1, required=True)), ('advanced_filter_header', wagtail.core.blocks.static_block.StaticBlock(admin_text='-------- ADVANCED FILTERS: OPTIONS TO DISPLAY FEWER, MORE TARGETED RESULTS. --------', label=' ')), ('profile_type', wagtail.core.blocks.CharBlock(default='', help_text='Example: Fellow.', required=False)), ('program_type', wagtail.core.blocks.CharBlock(default='', help_text='Example: Tech Policy.', required=False)), ('year', wagtail.core.blocks.CharBlock(default='', required=False))])), ('profile_by_id', wagtail.core.blocks.StructBlock([('ids', wagtail.core.blocks.CharBlock(help_text='Show profiles for pulse users with specific profile ids (mozillapulse.org/profile/[##]). For multiple profiles, specify a comma separated list (e.g. 85,105,332).', label='Profile by ID'))])), ('profile_directory', wagtail.core.blocks.StructBlock([('max_number_of_results', wagtail.core.blocks.IntegerBlock(default=12, help_text='Pick up to 48 profiles.', max_value=48, min_value=1, required=True)), ('advanced_filter_header', wagtail.core.blocks.static_block.StaticBlock(admin_text='-------- ADVANCED FILTERS: OPTIONS TO DISPLAY FEWER, MORE TARGETED RESULTS. --------', label=' ')), ('profile_type', wagtail.core.blocks.CharBlock(default='', help_text='Example: Fellow.', required=False)), ('program_type', wagtail.core.blocks.CharBlock(default='', help_text='Example: Tech Policy.', required=False)), ('year', wagtail.core.blocks.CharBlock(default='', required=False)), ('filter_values', wagtail.core.blocks.CharBlock(default='2019,2018,2017,2016,2015,2014,2013', help_text='Example: 2019,2018,2017,2016,2015,2014,2013', required=True))])), ('recent_blog_entries', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock(required=False)), ('tag_filter', wagtail.core.blocks.CharBlock(help_text='Test this filter at foundation.mozilla.org/blog/tags/', label='Filter by Tag', required=False)), ('category_filter', wagtail.core.blocks.ChoiceBlock(choices=networkapi.wagtailpages.pagemodels.blog.blog_category.BlogPageCategory.get_categories, help_text='Test this filter at foundation.mozilla.org/blog/category/', label='Filter by Category', required=False)), ('top_divider', wagtail.core.blocks.BooleanBlock(help_text='Optional divider above content block.', required=False)), ('bottom_divider', wagtail.core.blocks.BooleanBlock(help_text='Optional divider below content block.', required=False))])), ('blog_set', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock()), ('top_divider', wagtail.core.blocks.BooleanBlock(help_text='Optional divider above content block.', required=False)), ('bottom_divider', wagtail.core.blocks.BooleanBlock(help_text='Optional divider below content block.', required=False)), ('blog_pages', wagtail.core.blocks.ListBlock(wagtail.core.blocks.PageChooserBlock(page_type=['wagtailpages.BlogPage'])))])), ('airtable', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock(help_text="Copied from the Airtable embed code. The word 'embed' will be in the url")), ('height', wagtail.core.blocks.IntegerBlock(default=533, help_text='The pixel height on desktop view, usually copied from the Airtable embed code'))])), ('typeform', wagtail.core.blocks.StructBlock([('embed_id', wagtail.core.blocks.CharBlock(help_text='The embed id of your Typeform page (e.g. if the form is on admin.typeform.com/form/e8zScc6t, the id will be: e8zScc6t)', required=True)), ('button_type', wagtail.core.blocks.ChoiceBlock(choices=[('btn-primary', 'Primary button'), ('btn-secondary', 'Secondary button')])), ('button_text', wagtail.core.blocks.CharBlock(help_text='This is a text prompt for users to open the typeform content', required=True))])), ('current_events_slider', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock(help_text='Heading for the slider.')), ('current_events', wagtail.core.blocks.StreamBlock([('current_event', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock(help_text='Heading of the card.')), ('subheading_link', wagtail.core.blocks.StreamBlock([('internal', wagtail.core.blocks.StructBlock([('label', wagtail.core.blocks.CharBlock(help_text='Label for this link.')), ('link', wagtail.core.blocks.PageChooserBlock(help_text='Page that this should link out to.'))])), ('external', wagtail.core.blocks.StructBlock([('label', wagtail.core.blocks.CharBlock(help_text='Label for this link.')), ('link', wagtail.core.blocks.URLBlock(help_text='URL that this should link out to.'))]))], help_text='The link that appears below the card heading.', max_num=1, required=False)), ('image', wagtail.images.blocks.ImageChooserBlock(help_text='The image associated with this event.')), ('body', wagtail.core.blocks.TextBlock(help_text='Body text of the card.')), ('buttons', wagtail.core.blocks.StreamBlock([('internal', wagtail.core.blocks.StructBlock([('label', wagtail.core.blocks.CharBlock(help_text='Label for this link.')), ('link', wagtail.core.blocks.PageChooserBlock(help_text='Page that this should link out to.'))])), ('external', wagtail.core.blocks.StructBlock([('label', wagtail.core.blocks.CharBlock(help_text='Label for this link.')), ('link', wagtail.core.blocks.URLBlock(help_text='URL that this should link out to.'))])), ('document', wagtail.core.blocks.StructBlock([('label', wagtail.core.blocks.CharBlock(help_text='Label for this link.')), ('document', wagtail.documents.blocks.DocumentChooserBlock(help_text='Document that this should link out to.'))], help_text='An iCal document can be attached here for an "Add to Calendar" button.'))], help_text='A list of buttons that will appear at the bottom of the card.', max_num=2))]))], help_text='A list of current events in the slider.'))])), ('space_card_list', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock()), ('space_cards', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('title', wagtail.core.blocks.CharBlock(help_text='Heading for the card.')), ('body', wagtail.core.blocks.TextBlock(help_text='Body text of the card.')), ('link', wagtail.core.blocks.StreamBlock([('internal', wagtail.core.blocks.StructBlock([('link', wagtail.core.blocks.PageChooserBlock(help_text='Page that this should link out to.'))])), ('external', wagtail.core.blocks.StructBlock([('link', wagtail.core.blocks.URLBlock(help_text='URL that this should link out to.'))]))], help_text='Page or external URL this card will link out to.', max_num=1))]), help_text='A list of Space Cards.'))]))]), + ), + ] diff --git a/network-api/networkapi/mozfest/models.py b/network-api/networkapi/mozfest/models.py index 99627fadb6b..f87bcd27cf4 100644 --- a/network-api/networkapi/mozfest/models.py +++ b/network-api/networkapi/mozfest/models.py @@ -53,6 +53,7 @@ class MozfestPrimaryPage(FoundationMetadataPageMixin, FoundationBannerInheritanc body = StreamField( base_fields + [ + ('current_events_slider', customblocks.CurrentEventsSliderBlock()), ('space_card_list', customblocks.SpaceCardListBlock()), ] ) diff --git a/network-api/networkapi/mozfest/templates/fragments/carousel/carousel_navigation.html b/network-api/networkapi/mozfest/templates/fragments/carousel/carousel_navigation.html new file mode 100644 index 00000000000..f1918195f2d --- /dev/null +++ b/network-api/networkapi/mozfest/templates/fragments/carousel/carousel_navigation.html @@ -0,0 +1,20 @@ +
+
+
+ + + + +
+
+ + + + +
+
+
diff --git a/network-api/networkapi/mozfest/templates/fragments/event_card.html b/network-api/networkapi/mozfest/templates/fragments/event_card.html new file mode 100644 index 00000000000..981ab05df2b --- /dev/null +++ b/network-api/networkapi/mozfest/templates/fragments/event_card.html @@ -0,0 +1,19 @@ +
+
+

{{ title }}

+ {% if category_url %} + + {% endif %} +
+ {% if image %} + {{ image_alt }} + {% endif %} +

{{ text }}

+
+ {% for button in buttons %} + {{ button.value.label }} + {% endfor %} +
+
diff --git a/network-api/networkapi/wagtailpages/pagemodels/customblocks/__init__.py b/network-api/networkapi/wagtailpages/pagemodels/customblocks/__init__.py index a6ded0a97d9..d458a999abb 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/customblocks/__init__.py +++ b/network-api/networkapi/wagtailpages/pagemodels/customblocks/__init__.py @@ -5,6 +5,7 @@ from .blog_set_block import BlogSetBlock from .bootstrap_spacer_block import BootstrapSpacerBlock from .card_grid import CardGrid, CardGridBlock +from .current_events_slider_block import CurrentEventsSliderBlock from .banner_carousel import BannerCarouselSlideBlock from .iframe_block import iFrameBlock from .image_block import ImageBlock @@ -42,6 +43,7 @@ BootstrapSpacerBlock, CardGrid, CardGridBlock, + CurrentEventsSliderBlock, DearInternetLetterBlock, ExternalVideoBlock, iFrameBlock, diff --git a/network-api/networkapi/wagtailpages/pagemodels/customblocks/current_events_slider_block.py b/network-api/networkapi/wagtailpages/pagemodels/customblocks/current_events_slider_block.py new file mode 100644 index 00000000000..d93835abd81 --- /dev/null +++ b/network-api/networkapi/wagtailpages/pagemodels/customblocks/current_events_slider_block.py @@ -0,0 +1,57 @@ +from wagtail.core import blocks +from wagtail.images.blocks import ImageChooserBlock + +from .common.link_blocks import LabelledExternalLinkBlock, LabelledInternalLinkBlock, LabelledDocumentLinkBlock + + +class CurrentEventBlock(blocks.StructBlock): + title = blocks.CharBlock(help_text='Heading of the card.') + + subheading_link = blocks.StreamBlock( + [ + ('internal', LabelledInternalLinkBlock()), + ('external', LabelledExternalLinkBlock()), + ], + help_text='The link that appears below the card heading.', + max_num=1, + required=False, + ) + + image = ImageChooserBlock(help_text='The image associated with this event.') + + body = blocks.TextBlock(help_text='Body text of the card.') + + buttons = blocks.StreamBlock( + [ + ('internal', LabelledInternalLinkBlock()), + ('external', LabelledExternalLinkBlock()), + ('document', LabelledDocumentLinkBlock( + help_text='An iCal document can be attached here for an "Add to Calendar" button.' + )), + ], + help_text='A list of buttons that will appear at the bottom of the card.', + max_num=2, + ) + + class Meta: + icon = 'form' + label = 'Current Event Item' + + +class CurrentEventsSliderBlock(blocks.StructBlock): + title = blocks.CharBlock(help_text='Heading for the slider.') + + current_events = blocks.StreamBlock( + [ + ('current_event', CurrentEventBlock()), + ], + help_text='A list of current events in the slider.', + ) + + class Meta: + icon = 'list-ul' + help_text = ( + 'Recommendation: No more than 5 items should be in this slider. ' + 'This slider cannot be placed at the top of the page when a signup form is present as they will overlap.' + ) + template = 'wagtailpages/blocks/current_events_slider_block.html' diff --git a/network-api/networkapi/wagtailpages/templates/wagtailpages/blocks/current_events_slider_block.html b/network-api/networkapi/wagtailpages/templates/wagtailpages/blocks/current_events_slider_block.html new file mode 100644 index 00000000000..0f74f6b6488 --- /dev/null +++ b/network-api/networkapi/wagtailpages/templates/wagtailpages/blocks/current_events_slider_block.html @@ -0,0 +1,33 @@ +{% load wagtailcore_tags wagtailimages_tags static %} + +{% block block_content %} +
+
+ + {% block navigation %} +
+

{{ value.title }}

+ {% if self.current_events|length > 2 %} + {% include 'fragments/carousel/carousel_navigation.html' %} + {% endif %} +
+ {% endblock %} + + {% block slides %} +
+ {% for current_event in self.current_events %} + {% image current_event.value.image fill-445x185 as img %} +
+ {% include 'fragments/event_card.html' with title=current_event.value.title category_url=current_event.value.subheading_link.0.value.link_url category_title=current_event.value.subheading_link.0.value.label buttons=current_event.value.buttons image=img.url text=current_event.value.body %} +
+ {% endfor %} +
+ {% endblock %} + + {% block pagination %} +
+ {% endblock %} + +
+
+{% endblock %} diff --git a/source/js/components/carousel/carousel.js b/source/js/components/carousel/carousel.js new file mode 100644 index 00000000000..7f041e23b61 --- /dev/null +++ b/source/js/components/carousel/carousel.js @@ -0,0 +1,46 @@ +import Swiper, {A11y, Autoplay, Pagination, Navigation, Keyboard} from 'swiper'; + +Swiper.use([A11y, Autoplay, Pagination, Navigation, Keyboard]); + +class Carousel { + constructor(node) { + this.node = node; + this.swiper = new Swiper(this.node, { + spaceBetween: 20, + watchOverflow: true, + centeredSlides: false, + simulateTouch: true, + slidesPerView: 1, + autoHeight: true, + keyboard: { + enabled: true, + }, + pagination: { + el: '.swiper-pagination', + clickable: true, + }, + navigation: { + nextEl: ".swiper-button-next", + prevEl: ".swiper-button-prev", + }, + // Responsive breakpoints + breakpoints: { + 992: { + slidesPerView: 2, + spaceBetween: 30, + }, + }, + }); + this.slideTotal = this.node.dataset.slidetotal; + } +} + +const MozfestCarousels = { + init: function () { + document + .querySelectorAll(`[data-carousel]`) + .forEach((e) => new Carousel(e)); + }, +}; + +export default MozfestCarousels; diff --git a/source/js/main.js b/source/js/main.js index e1dc3d7eaa3..5742bc6c500 100644 --- a/source/js/main.js +++ b/source/js/main.js @@ -16,6 +16,7 @@ import { import primaryNav from "./primary-nav.js"; import EmbedTypeform from "./embed-typeform.js"; import Dropdowns from "./dropdowns.js"; +import MozfestCarousels from "./components/carousel/carousel.js"; import MozfestHeroCarousels from "./components/mozfest-hero-carousel/mozfest-hero-carousel"; import initializeSentry from "./common/sentry-config.js"; import YouTubeRegretsTunnel from "./foundation/pages/youtube-regrets/intro-tunnel"; @@ -142,6 +143,7 @@ let main = { // Mozfest pages if (document.querySelector(`.mozfest`)) { + MozfestCarousels.init(); MozfestHeroCarousels.init(); } }, diff --git a/source/sass/mozfest.scss b/source/sass/mozfest.scss index 0c2efe735e9..c480cb1f7ff 100644 --- a/source/sass/mozfest.scss +++ b/source/sass/mozfest.scss @@ -220,7 +220,7 @@ body.mozfest { } .swiper-pagination-bullet-active { - @apply tw-bg-festival-blue-100; + @apply tw-bg-blue-dark; } @keyframes slide-progress-bar {