Merge branch 'main' into 9647-nav

This commit is contained in:
Simon Fessehaye 2023-01-12 15:23:27 -08:00 коммит произвёл GitHub
Родитель dab822c94b fbc848fc3a
Коммит 29e9157323
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
58 изменённых файлов: 19127 добавлений и 1229 удалений

2
.github/workflows/ci-image-diff-sync.yml поставляемый
Просмотреть файл

@ -53,7 +53,7 @@ jobs:
python-version: 3.9.9
- uses: actions/setup-node@v2
with:
node-version: 14
node-version: 18
- name: Install Python Dependencies
run: pip install -r requirements.txt -r dev-requirements.txt

2
.github/workflows/ci-image-diff.yml поставляемый
Просмотреть файл

@ -61,7 +61,7 @@ jobs:
- uses: actions/setup-node@v2
with:
node-version: 14
node-version: 18
- name: Configure AWS Credentials for visual diffing
uses: aws-actions/configure-aws-credentials@v1

12
.github/workflows/continous-integration.yml поставляемый
Просмотреть файл

@ -23,7 +23,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 14
node-version: 18
cache: "npm"
- name: Install Node Dependencies
run: npm ci
@ -73,7 +73,7 @@ jobs:
cache: "pip"
- uses: actions/setup-node@v2
with:
node-version: 14
node-version: 18
- name: Install Python Dependencies
run: pip install -r requirements.txt -r dev-requirements.txt
- name: Install Node Dependencies
@ -122,7 +122,7 @@ jobs:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
env:
ALLOWED_HOSTS: localhost,mozfest.localhost,default-site.com,secondary-site.com
ALLOWED_HOSTS: 127.0.0.1,localhost,mozfest.localhost,default-site.com,secondary-site.com
CONTENT_TYPE_NO_SNIFF: True
CORS_ALLOWED_ORIGINS: "*"
DATABASE_URL: postgres://postgres:postgres@localhost:5432/network
@ -149,7 +149,7 @@ jobs:
cache: "pip"
- uses: actions/setup-node@v2
with:
node-version: 14
node-version: 18
cache: "npm"
- name: Install Python Dependencies
run: pip install -r requirements.txt -r dev-requirements.txt
@ -187,7 +187,7 @@ jobs:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
env:
ALLOWED_HOSTS: localhost,mozfest.localhost,default-site.com,secondary-site.com
ALLOWED_HOSTS: 127.0.0.1,localhost,mozfest.localhost,default-site.com,secondary-site.com
CONTENT_TYPE_NO_SNIFF: True
CORS_ALLOWED_ORIGINS: "*"
DATABASE_URL: postgres://postgres:postgres@localhost:5432/network
@ -217,7 +217,7 @@ jobs:
cache: "pip"
- uses: actions/setup-node@v2
with:
node-version: 14
node-version: 18
cache: "npm"
- name: Install Python Dependencies
run: pip install -r requirements.txt -r dev-requirements.txt

Просмотреть файл

@ -1,6 +1,6 @@
#
# This file is autogenerated by pip-compile with Python 3.9
# by the following command:
# This file is autogenerated by pip-compile with python 3.9
# To update, run:
#
# pip-compile dev-requirements.in
#

Просмотреть файл

@ -1,3 +1,3 @@
FROM node:14.13.1-stretch-slim
FROM node:18-bullseye-slim
WORKDIR /app/

Просмотреть файл

@ -13,6 +13,14 @@
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="static/images/apple-touch-icon-180x180@2x.png">
<link rel="icon" type="image/png" sizes="196x196" href="static/images/favicon-196x196@2x.png">
<link rel="shortcut icon" href="static/images/favicon.ico">
<!-- Fundraiseup Script -->
<script>(function(w,d,s,n,a){if(!w[n]){var l='call,catch,on,once,set,then,track'
.split(','),i,o=function(n){return'function'==typeof n?o.l.push([arguments])&&o
:function(){return o.l.push([n,arguments])&&o}},t=d.getElementsByTagName(s)[0],
j=d.createElement(s);j.async=!0;j.src='https://cdn.fundraiseup.com/widget/'+a;
t.parentNode.insertBefore(j,t);o.s=Date.now();o.v=4;o.h=w.location.href;o.l=[];
for(i=0;i<7;i++)o[l[i]]=o(l[i]);w[n]=o}
})(window,document,'script','FundraiseUp','ADCYPWMX');</script>
<title>Mozilla Foundation - Under Maintenance</title>
</head>
@ -75,9 +83,7 @@
</div>
</div>
<div class="donate-btn-wrapper">
<a id="donate-header-btn" class="tw-btn-pop"
href="https://donate.mozilla.org/?utm_source=foundation.mozilla.org&utm_medium=referral&utm_campaign=fmonav&utm_content=header"
target="_blank" rel="noopener noreferrer">Donate</a>
<a id="donate-header-btn" class="tw-btn-pop" href="?form=donate">Donate</a>
</div>
</div>
</div>
@ -107,7 +113,7 @@
<h3 class="tw-h3-heading mb-0">We all love the Web. Join Mozilla in defending it.</h3>
<h4 class="tw-h3-heading mb-4">Lets protect the worlds largest resource for future generations.</h4>
<div>
<a href="https://donate.mozilla.org/?utm_source=foundation.mozilla.org&utm_medium=referral&utm_campaign=fmonav&utm_content=maintenance_cta" target="_blank" rel="noopener noreferrer" c class="tw-btn-secondary dark-theme">Donate now</a>
<a href="?form=donate" class="tw-btn-secondary dark-theme">Donate now</a>
</div>
</div>
</div>
@ -132,7 +138,7 @@
</div>
<div class="col-lg-6 col-xl-7">
<ul class="link-list list-unstyled mb-0">
<li class="mb-2"><a id="donate-footer-btn" href="https://donate.mozilla.org/?utm_source=foundation.mozilla.org&utm_medium=referral&utm_campaign=fmonav&utm_content=footer" target="_blank" rel="noopener noreferrer" class="dark-theme">Donate</a></li>
<li class="mb-2"><a id="donate-footer-btn" href="?form=donate" class="dark-theme">Donate</a></li>
<li class="mb-2"><a href="https://www.mozilla.org/privacy/websites/#cookies" class="dark-theme">Cookies</a></li>
<li class="mb-2"><a href="https://www.mozilla.org/about/legal/terms/mozilla/" class="dark-theme">Legal</a></li>
<li class="mb-2"><a href="https://www.mozilla.org/about/governance/policies/participation/" class="dark-theme">Participation Guidelines</a></li>

Просмотреть файл

@ -4,7 +4,7 @@ import sys
# Django runs twice to support live-reloading, so check Django's internal settings to determine whether or not
# to start the debugger.
if (os.environ.get("RUN_MAIN") or os.environ.get("WERKZEUG_RUN_MAIN")) and os.environ.get("VSCODE_DEBUGGER"):
if (os.environ.get("RUN_MAIN") or os.environ.get("WERKZEUG_RUN_MAIN")) and os.environ.get("VSCODE_DEBUGGER") == "True":
import ptvsd # noqa
ptvsd_port = 8001

Просмотреть файл

@ -0,0 +1,60 @@
from django.contrib.auth.models import Group, User
from django.core.management import call_command
from django.test import TestCase
class DeleteNonStaffTest(TestCase):
def setUp(self):
User.objects.create(username="Alex"),
def test_non_staff_is_deleted(self):
"""
Simple users are deleted
"""
call_command("delete_non_staff", "--now")
self.assertEqual(User.objects.count(), 0)
class IsStaffNotDeletedTest(TestCase):
def setUp(self):
User.objects.create(username="Alex", is_staff=True)
def test_is_staff_not_deleted(self):
"""
Users with 'is_staff' flag at True are not deleted
"""
call_command("delete_non_staff", "--now")
self.assertEqual(User.objects.count(), 1)
class InGroupNotDeletedTest(TestCase):
def setUp(self):
group = Group.objects.create(name="TestGroup")
group.user_set.create(username="Alex")
def test_in_group_not_deleted(self):
"""
Users in a group are not deleted
"""
call_command("delete_non_staff", "--now")
self.assertEqual(User.objects.count(), 1)
class MozillaFoundationUsersNotDeletedTest(TestCase):
def setUp(self):
User.objects.create(username="Alex", email="alex@mozillafoundation.org")
def test_mozilla_foundation_users_not_deleted(self):
"""
Mozilla Foundation Users are not deleted
"""
call_command("delete_non_staff", "--now")
self.assertEqual(User.objects.count(), 1)

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -32,7 +32,7 @@
{% endblock %}
{% block general_links %}
<li><a id="donate-footer-btn" href="https://donate.mozilla.org/?utm_source=mozillafestival.org&utm_medium=referral&utm_campaign=fmonav&utm_content=footer" rel="noopener noreferrer" class="tw-dark">{% trans "Donate" %}</a></li>
<li><a id="donate-footer-btn" href="?form=donate" class="tw-dark">{% trans "Donate" %}</a></li>
<!-- the rest of the links should be listed alphabetically -->
<li><a href="https://www.mozillafestival.org/sponsor" class="tw-dark">{% trans "Support MozFest" %}</a></li>
<li><a href="https://careers.mozilla.org/listings/?team=Mozilla%20Foundation" class="tw-dark">{% trans "Careers" %}</a></li>

Просмотреть файл

@ -2,9 +2,9 @@
<div class="tw-flex tw-flex-col tw-h-full tw-justify-between">
<div>
<div class="tw-flex tw-flex-wrap">
{% include "fragments/buyersguide/content_category_links.html" with page=page %}
<span class="tw-h6-heading tw-text-gray-40 tw-py-1 tw-mb-0">{{ page.first_published_at|date:"DATE_FORMAT" }}</span>
<div class="tw-flex tw-flex-wrap tw-align-center">
{% include "fragments/buyersguide/content_category_links.html" with content_categories=page.get_content_categories %}
<span class="tw-h6-heading tw-text-gray-40 tw-mb-0">{{ page.first_published_at|date:"DATE_FORMAT" }}</span>
</div>
<a href="{% relocalized_url page.localized.url %}" class="tw-group tw-block hover:tw-no-underline">

Просмотреть файл

@ -1,7 +1,7 @@
{% load i18n wagtailcore_tags %}
<div class="tw-grid tw-grid-cols-2 {% if columns_above_large == 3 %} large:tw-grid-cols-3 {% endif %} tw-gap-6">
<div class="tw-col-span-full tw-flex tw-flex-row tw-justify-between large:tw-items-baseline tw-gap-5">
<div class="tw-col-span-full tw-flex tw-flex-row tw-justify-between tw-items-center tw-gap-5">
<div class="tw-h3-heading tw-m-0 tw-whitespace-nowrap">
{{ heading }}
</div>

Просмотреть файл

@ -1,6 +1,7 @@
{% load static wagtailcore_tags wagtailimages_tags %}
<div class="
tw-w-full
tw-bg-gradient-to-b tw-from-yellow-10 tw-to-purple-05
tw-rounded-2xl
tw-p-[24px] {% if not large %} medium:tw-p-[32px] {% else %} medium:tw-p-[40px] {% endif %}

Просмотреть файл

@ -1,10 +1,12 @@
{% with content_categories=page.get_content_categories editorial_content_index=page.get_editorial_content_index %}
{% for content_category in content_categories %}
{% with classes="tw-h6-heading tw-text-blue-80 tw-mr-2 tw-py-1 tw-mb-0" %}
<a class="{{ classes }} {{ extra_classes }}" href="/privacynotincluded/articles/">{{ content_category.title|title }}</a>
{% if not forloop.last %}
<span class="{{ classes }} {{ extra_classes }}">/</span>
{% endif %}
{% endwith %}
{% endfor %}
{% endwith %}
{% if content_categories %}
<div class="tw-h6-heading tw-mr-2 tw-mb-0 {{ extra_classes }}">
{% for content_category in content_categories %}
<span>
{{ content_category.title }}
</span>
{% if not forloop.last %}
<span>/</span>
{% endif %}
{% endfor %}
</div>
{% endif %}

Просмотреть файл

@ -8,12 +8,6 @@
tw-justify-between
tw-m-0
{# We need to unset the order rule at the next breakpoint, because the nth-child pseudo-class changes. #}
tw-order-none
[&:nth-child(n+10)]:tw-order-2 medium:[&:nth-child(n+10)]:tw-order-[unset]
medium:[&:nth-child(n+9)]:tw-order-2 large:[&:nth-child(n+9)]:tw-order-[unset]
large:[&:nth-child(n+12)]:tw-order-2
{% if product.draft %}draft-product{% endif %}
{% if product.adult_content %}adult-content{% endif %}
{% if product.privacy_ding %}privacy-ding{% endif%}

Просмотреть файл

@ -1,5 +1,5 @@
{% load bg_nav_tags localization i18n static wagtailroutablepage_tags wagtailmetadata_tags %}
<nav id="multipage-nav" class="pni-category-nav text-center d-none d-md-block tw-no-scrollbar" title="{% trans "site navigation" context "Tooltip on menu items" %}">
<nav id="multipage-nav" class="pni-category-nav text-center d-none d-md-block tw-no-scrollbar tw-border-b tw-border-blue-10" title="{% trans "site navigation" context "Tooltip on menu items" %}">
<div class="container tw-py-2" id="product-review">
<div class="row">
<div class="col">

Просмотреть файл

@ -1,7 +1,7 @@
{% load bg_nav_tags localization i18n static wagtailroutablepage_tags %}
{% get_bg_home_page as home_page %}
<div class="d-md-none mt-0 mb-0" id="pni-nav-mobile">
<div class="d-md-none mt-0 mb-0 tw-bg-white tw-border-b tw-border-blue-10" id="pni-nav-mobile">
<div class="container">
<div class="row px-3 px-sm-0">
<div class="col-12 tw-border-0">
@ -37,7 +37,7 @@
{% if original.published_product_page_count > 0 %}
{% localizedroutablepageurl home_page 'category-view' original.slug as cat_url %}
<div>
<a
<a
class="multipage-link {% check_active_category current_category cat %}{% if original.featured is True %} featured{% endif %}"
href="{{ cat_url }}"
data-name="{{ cat.name }}"

Просмотреть файл

@ -1,6 +1,6 @@
{% load wagtailcore_tags i18n static %}
<div class="tw-relative tw-py-5 tw-px-5 tw-rounded-lg medium:tw-px-7 medium:tw-py-6 tw-bg-gradient-to-r tw-from-purple-05 tw-to-blue-05">
<div id="buyersguide-newsletter-box" class="tw-hidden tw-relative tw-py-5 tw-px-5 tw-rounded-lg medium:tw-px-7 medium:tw-py-6 tw-bg-gradient-to-r tw-from-purple-05 tw-to-blue-05">
<img
class="tw-absolute tw-right-0 tw-top-0 tw-hidden large:tw-block"
src="{% static "_images/buyers-guide/asterick.svg" %}"
@ -16,3 +16,4 @@
>
</div>
</div>

Просмотреть файл

@ -56,7 +56,7 @@
{% block donate_and_newsletter %}
<div class="d-flex align-items-center">
{% include "fragments/buyersguide/pni_nav_links.html" with class="primary-nav-special-link pni-nav-link tw-hidden large:tw-block" %}
<a id="donate-header-btn" class="primary-nav-special-link pni-nav-link tw-hidden large:tw-block" href="https://donate.mozilla.org/?utm_source=foundation.mozilla.org&utm_medium=referral&utm_campaign=fmonav&utm_content=header" target="_blank" rel="noopener noreferrer">{% trans "Donate" %}</a>
<a id="donate-header-btn" class="primary-nav-special-link pni-nav-link tw-hidden large:tw-block" href="?form=donate">{% trans "Donate" %}</a>
{% if page.signup == None %}<button class="tw-btn-secondary btn-newsletter d-none d-lg-block ml-md-3">{% trans "Newsletter" %}</button>{% endif %}
<button class="tw-bg-transparent" id="mobile-search">
<img

Просмотреть файл

@ -3,7 +3,7 @@
{% with tab_class="tw-bg-white tw-text-black tw-font-zilla tw-text-3xl tw-py-2 tw-px-5 tw-flex tw-justify-center tw-items-center medium:tw-flex-1 tw-shrink-0 tw-text-center tw-relative tw-cursor-pointer tw-ease-in tw-transform tw-duration-150 medium:tw-w-auto" %}
<div id="product-tab-group" class="tw-bg-white tw-px-4 medium:tw-px-6 tw-flex tw-border-b-4 tw-justify-center tw-mb-7 tw-no-scrollbar tw-overflow-auto tw-sticky medium:tw-static tw-top-0 tw-z-[1000] tw--mx-4 medium:tw--mx-6">
<!-- Need this span for x-scrolling on smaller devices so the first tab doesn't get cut off -->
<span class="tw-py-2 tw-px-7 medium:tw-flex-1 tw-shrink-0 tw-relative medium:tw-w-auto tw-w-[100px] tw-opacity-0 small:tw-hidden">mobile</span>
<span class="tw-py-2 tw-px-7 tw-shrink-0 tw-relative tw-opacity-0 min-[330px]:tw-w-[50px] min-[400px]:tw-hidden">mobile</span>
<span
data-product-label="0"
class="{{tab_class}} "

Просмотреть файл

@ -153,7 +153,7 @@
<p class="tw-body">
<em>
{% blocktrans with mofo_url='href="https://foundation.mozilla.org" target="_blank"' donate_url='href="https://donate.mozilla.org" target="_blank"' mozilla_url='href="https://www.mozilla.org/" target="_blank"' trimmed %}
{% blocktrans with mofo_url='href="https://foundation.mozilla.org" target="_blank"' donate_url='href="?form=donate"' mozilla_url='href="https://www.mozilla.org/" target="_blank"' trimmed %}
Mozilla fights daily for a healthier internet as a non-profit tech company that puts people
before profit. This guide was created by the <a {{mofo_url}}>Mozilla Foundation</a> which
relies on <a {{donate_url}}>donations</a> from people like you to do our work. You can also

Просмотреть файл

@ -34,10 +34,10 @@
<div class="blog-sticky-side d-none d-lg-flex justify-content-lg-end mt-1">
{% include "fragments/buyersguide/share-buttons.html" with version="mini" layout="stacked" share_text=share_text_translated page=page %}
</div>
</div>
</div>
<div class="pt-5 pt-md-5 pb-4 col-lg-8">
<div class="cms {% if show_comments %}mb-5{% endif %}">
{% include "fragments/buyersguide/content_category_links.html" with page=page extra_classes="tw-text-base" %}
{% include "fragments/buyersguide/content_category_links.html" with content_categories=page.get_content_categories extra_classes="tw-text-base" %}
<h1 class="tw-h1-heading">{{ page.title }}</h1>
{% include "fragments/byline.html" with authors=page.get_author_profiles publication_date=page.first_published_at show_full_info=True %}
</div>
@ -46,64 +46,51 @@
</div>
{% for block in page.body %}
{% include_block block with parent_page=page page_type="blog" %}
{% include_block block with parent_page=page page_type="blog" %}
{% endfor %}
{% with related_articles=page.get_primary_related_articles %}
{% if related_articles %}
<div class="tw-container tw-my-4">
<div class="tw-row">
<div id="article-primary-related-articles" class="tw-px-4 tw-w-8/12 tw-mx-auto">
{% include "fragments/buyersguide/related_reading.html" with articles=related_articles %}
{% with author_profiles=page.get_author_profiles %}
{% if author_profiles %}
<div class="tw-container tw-mt-7">
<div class="tw-row tw-px-4 tw-justify-center">
<div class="large:tw-w-2/3 tw-border-t-4 tw-border-black tw-divide-y tw-divide-gray-20">
{% for author_profile in author_profiles %}
<div class="tw-flex tw-py-4 last:tw-pb-0">
{% if author_profile.image %}
<div class="tw-mr-4 tw-flex tw-items-start tw-flex-shrink-0">
<img
class="tw-rounded-full tw-border-2 tw-border-white tw-w-[75px] tw-h-[75px]"
src="{% image_url author_profile.image "fill-75x75" %}"
srcset="{% image_url author_profile.image "fill-150x150" %} 2x, {% image_url author_profile.image "fill-225x225" %} 3x"
alt="{{ author_profile.name }}"
title="{{ author_profile.name }}"
/>
</div>
{% endif %}
<div class="">
<p class="tw-font-zilla tw-mb-2 tw-font-medium tw-text-2xl tw-leading-7">{{ author_profile.name }}</p>
<p class="tw-font-sans tw-font-normal tw-text-lg tw-leading-6 tw-m-0">{{ author_profile.introduction }}</p>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
{% endif %}
{% endwith %}
<div class="tw-container tw-mt-6">
<div class="tw-row tw-justify-center">
<div class="tw-border-4 tw-border-black col-lg-8"></div>
</div>
<div class="tw-row tw-justify-center tw-divide-y tw-divide-gray-20">
{% with author_profiles=page.get_author_profiles %}
{% if author_profiles %}
{% for author_profile in author_profiles %}
<div class="col-lg-8 tw-flex tw-py-4">
{% if author_profile.image %}
<div class="tw-mr-4 tw-flex tw-items-start tw-flex-shrink-0">
<img
class="tw-rounded-full tw-border-2 tw-border-white tw-w-[75px] tw-h-[75px]"
src="{% image_url author_profile.image "fill-75x75" %}"
srcset="{% image_url author_profile.image "fill-150x150" %} 2x, {% image_url author_profile.image "fill-225x225" %} 3x"
alt="{{ author_profile.name }}"
title="{{ author_profile.name }}"
/>
</div>
{% endif %}
<div class="">
<p class="tw-font-zilla tw-mb-2 tw-font-medium tw-text-2xl tw-leading-7">{{ author_profile.name }}</p>
<p class="tw-font-sans tw-font-normal tw-text-lg tw-leading-6">{{ author_profile.introduction }}</p>
</div>
</div>
{% endfor %}
{% endif %}
{% endwith %}
</div>
</div>
<div id="article-newsletter-signup" class="tw-container">
<div class="tw-row tw-justify-center">
<div class="col-12 col-lg-8">
<div id="article-newsletter-signup" class="tw-container tw-mt-[2.75rem]">
<div class="tw-row tw-px-4 tw-justify-center">
<div class="tw-w-full large:tw-w-2/3">
{% include "fragments/buyersguide/pni_newsletter_box.html" %}
</div>
</div>
</div>
{% with related_articles=page.get_secondary_related_articles %}
{% with related_articles=page.get_related_articles %}
{% if related_articles %}
{% get_bg_home_page as bg_home_page %}
<div id="article-secondary-related-articles" class="tw-container">
<div class="tw-container tw-mt-7" data-gtm="related-articles">
{% include "fragments/buyersguide/article_listing_what_to_read_next.html" with articles=related_articles index_page=bg_home_page.get_editorial_content_index use_wide_above="medium" %}
</div>
{% endif %}

Просмотреть файл

@ -76,13 +76,13 @@
{% get_bg_home_page as home_page %}
<div class="primary-nav-container-wrapper tw-border-b tw-border-blue-10">
<div class="primary-nav-container-wrapper">
{% include "fragments/buyersguide/primary_nav.html" %}
{% include "fragments/buyersguide/pni_mobile_nav.html" with pagetype=pagetype categories=categories current_category=current_category %}
{% block category_nav %}
{% include "fragments/buyersguide/pni_category_nav.html" with show_all_reviews_as_active_category=False categories=categories current_category=current_category %}
{% endblock %}
</div>
{% include "fragments/buyersguide/pni_mobile_nav.html" with pagetype=pagetype categories=categories current_category=current_category %}
{% block category_nav %}
{% include "fragments/buyersguide/pni_category_nav.html" with show_all_reviews_as_active_category=False categories=categories current_category=current_category %}
{% endblock %}
<!-- Need to position relatives to deal with potential CSS transformations that change some stacking context of elements -->
<div class="tw-relative">
@ -95,7 +95,7 @@
{% block guts %}{% endblock %}
</main>
</div>
{% block background_parallax %}
{% endblock %}
<div class="tw-relative tw-z-10">

Просмотреть файл

@ -3,32 +3,6 @@
{% block body_id %}{{ category.slug }}{% endblock %}
{% block guts %}
{{ block.super }}
{% for category in categories %}
{% with secondary_related_articles=category.get_secondary_related_articles %}
{% if secondary_related_articles %}
<div
id="category-secondary-related-articles"
data-show-for-category="{{ category.name }}"
class="
{% if category != current_category %}
tw-hidden
{% endif %}
tw-container
tw-mt-[4rem]
tw-mb-[120px]
"
>
{% include "fragments/buyersguide/article_listing_what_to_read_next.html" with articles=secondary_related_articles index_page=page.get_editorial_content_index use_wide_above="medium" %}
</div>
{% endif %}
{% endwith %}
{% endfor %}
{% endblock guts %}
{% block extra_product_box_list_items %}
{% if featured_cta %}
{% comment %}
@ -42,7 +16,7 @@
data-show-for-categories="{% for category in categories %}{% if category.show_cta %}{{ category.name }}, {% endif %}{% endfor %}"
>
{% with cta=featured_cta %}
{% include "fragments/buyersguide/call_to_action_box.html" with icon=cta.sticker_image heading=cta.title body=cta.content link_text=cta.link_label link_href=cta.get_target_url %}
{% include "fragments/buyersguide/call_to_action_box.html" with icon=cta.sticker_image heading=cta.title body=cta.content link_text=cta.link_label link_href=cta.get_target_url large=True %}
{% endwith %}
</div>
{% endif %}
@ -74,3 +48,30 @@
{% endwith %}
{% endfor %}
{% endblock extra_product_box_list_items %}
{% block guts %}
{{ block.super }}
{% for category in categories %}
{% with secondary_related_articles=category.get_secondary_related_articles %}
{% if secondary_related_articles %}
<div
id="category-secondary-related-articles"
data-show-for-category="{{ category.name }}"
class="
{% if category != current_category %}
tw-hidden
{% endif %}
tw-container
tw-mt-[4rem]
tw-mb-[120px]
"
>
{% include "fragments/buyersguide/article_listing_what_to_read_next.html" with articles=secondary_related_articles index_page=page.get_editorial_content_index use_wide_above="medium" %}
</div>
{% endif %}
{% endwith %}
{% endfor %}
{% endblock guts %}

Просмотреть файл

@ -13,7 +13,7 @@
{% with hero_featured_article=page.get_hero_featured_article %}
{% if hero_featured_article %}
<div class="tw-container">
<div class="tw-container tw-mb-7">
{% include "fragments/buyersguide/featured_article.html" with featured_article=hero_featured_article supporting_articles=page.get_hero_supporting_articles supporting_articles_heading=page.hero_supporting_articles_heading %}
</div>
{% endif %}
@ -47,6 +47,12 @@
{% endif %}
{% endwith %}
{% comment %}
We want to be able to use the CTA and have it show up on other pages, except for the homepage.
Displaying the CTA on the homepage should not happen until the redesign has been implemented.
See also: https://github.com/mozilla/foundation.mozilla.org/issues/9758.
{% endcomment %}
{% comment %}
{% with cta=featured_cta %}
{% if cta %}
<div class="{{row_item_classes}}">
@ -54,6 +60,7 @@
</div>
{% endif %}
{% endwith %}
{% endcomment %}
{% endwith %}
</div>
@ -64,14 +71,13 @@
{% endblock hero %}
{% block extra_product_box_list_items %}
<div
<div
id="product-grid-newsletter-signup"
class="
tw-col-span-full medium:tw-col-span-2
tw-col-end-5
tw-order-1
tw-flex
tw-col-[span_2_/_-1]
tw-row-start-5 medium:tw-row-start-4 large:tw-row-start-3
tw-items-stretch
tw-hidden
">
{% include "fragments/buyersguide/pni_newsletter_box.html" %}
</div>

Просмотреть файл

@ -1,7 +1,7 @@
{% load settings_value i18n static %}
{% with wrapper_class="tw-py-7" background_class="tw-bg-cover tw-bg-center" btn_class="tw-text-white tw-border-black tw-bg-blue-40 hover:tw-bg-blue-80 tw-shadow-black"%}
<div class="donate-banner tw-relative tw-z-20 {{background_class}} tw-bg-[url(../_images/mozilla-donate-dear-internet-mobile.png)] large:tw-bg-[url(../_images/mozilla-donate-dear-internet.png)] {{wrapper_class}} print:tw-hidden tw-w-full tw-hidden">
{% with wrapper_class="tw-py-7 tw-shadow-[0_0_3px_2px_rgba(9,32,77,.12)_inset]" background_class="tw-bg-[url(../_images/donate-banner-giving.jpg)] tw-bg-[100%_0] tw-bg-no-repeat tw-bg-cover medium:tw-bg-contain" btn_class="tw-text-white tw-border-black tw-bg-blue-40 hover:tw-bg-blue-80 tw-shadow-black"%}
<div class="donate-banner {{background_class}} {{wrapper_class}} tw-relative tw-z-20 tw-w-full tw-hidden print:tw-hidden">
<a href="#"
class="tw-p-4
tw-box-content
@ -15,40 +15,29 @@
</a>
<div class="tw-container">
<div class="tw-row">
<div class="tw-row tw-px-3">
{% block left_banner_column %}
<div class="medium:tw-w-6/12 large:tw-w-6/12 tw-px-4">
<div class="tw-px-5 tw-py-6 tw-bg-[hsla(0,0%,100%,.7)] tw-backdrop-blur-sm large:tw-w-8/12 xlarge:tw-w-6/12">
<p class="tw-h2-heading tw-mb-0 tw-text-blue-80 tw-font-semibold tw-italic">
{% trans "Help Mozilla fight for a better internet this holiday season" %}
</p>
<p class="tw-body-large tw-mt-4 tw-leading-normal">
{% trans "We're proudly nonprofit, working to keep the web healthy." %}
{% blocktrans trimmed %}
We're proudly nonprofit, working to keep the web healthy. Your contributions help build a safe and open internet.
{% endblocktrans %}
</p>
<p class="tw-body-large">
{% trans "Can you donate today?" %}
</p>
<div class="tw-w-full tw-pt-3">
<a class="tw-btn-pop {{btn_class}} tw-shadow-[4px_4px]" tw-w-full tw-text-base active:tw-text-white active:tw-bg-[#0a0c8f] active:tw-border-black" href="?form=donate">
{% trans "Support Mozilla" %}
</a>
</div>
</div>
{% endblock %}
{% block right_banner_column %}
<div
class=" medium:tw-w-6/12 large:tw-w-3/12 tw-px-4 medium:tw-ml-auto large:tw-mt-2">
<div class="tw-row">
<div class="tw-px-4">
<p class="tw-body">
{% trans "Your contributions help build a safe and open internet." %}
</p>
<p class="tw-body">
{% trans "Can you donate today?" %}
</p>
</div>
</div>
<div class="tw-row">
<div class="tw-w-full tw-px-4">
<a class="tw-btn-pop {{btn_class}} tw-shadow-[4px_4px]" tw-w-full tw-text-base active:tw-text-white active:tw-bg-[#0a0c8f] active:tw-border-black" href="https://donate.mozilla.org/?utm_source=foundation.mozilla.org&utm_medium=referral&utm_campaign=fmonav&utm_content=2021_EOY_bannner_MoFo" target="_blank" rel="noopener noreferrer">
{% trans "Support Mozilla" %}
</a>
</div>
</div>
</div>
{% endblock %}
</div>

Просмотреть файл

@ -12,7 +12,7 @@
<p class="tw-h3-heading tw-mb-0">{% trans "We all love the Web. Join Mozilla in defending it." %}</p>
<p class="tw-h3-heading tw-mb-5">{% trans "Lets protect the worlds largest resource for future generations." %}</p>
<div>
<a href="https://donate.mozilla.org/?utm_source=foundation.mozilla.org&utm_medium=referral&utm_campaign=fmonav&utm_content=footer" target="_blank" rel="noopener noreferrer" class="tw-btn-secondary tw-dark" id="donate-banner-cta">{% trans "Donate now" %}</a>
<a href="?form=donate" class="tw-btn-secondary tw-dark" id="donate-banner-cta">{% trans "Donate now" %}</a>
</div>
</div>
</div>
@ -49,7 +49,7 @@
<div class="large:tw-w-6/12 xlarge:tw-w-7/12 tw-px-4">
<ul class="tw-p-0 tw-ml-0 tw-list-none tw-mb-0 link-list large:tw-columns-2">
{% block general_links %}
<li class="tw-mb-2"><a id="donate-footer-btn" href="https://donate.mozilla.org/?utm_source=foundation.mozilla.org&utm_medium=referral&utm_campaign=fmonav&utm_content=footer" target="_blank" rel="noopener noreferrer" class="tw-dark tw-text-white hover:tw-text-blue-20 hover:tw-underline">{% trans "Donate" %}</a></li>
<li class="tw-mb-2"><a id="donate-footer-btn" href="?form=donate" class="tw-dark tw-text-white hover:tw-text-blue-20 hover:tw-underline">{% trans "Donate" %}</a></li>
<!-- the rest of the links should be listed alphabetically -->
<li class="tw-mb-2"><a href="https://careers.mozilla.org/listings/?team=Mozilla%20Foundation" class="tw-dark tw-text-white hover:tw-text-blue-20 hover:tw-underline">{% trans "Careers" %}</a></li>
<li class="tw-mb-2"><a href="https://www.mozilla.org/privacy/websites/#cookies" class="tw-dark tw-text-white hover:tw-text-blue-20 hover:tw-underline">{% trans "Cookies" %}</a></li>

Просмотреть файл

@ -63,7 +63,7 @@
{% block donate_and_newsletter %}
<div class="d-flex align-items-center">
<a id="donate-header-btn" class="primary-nav-special-link tw-heart-glyph tw-flex" href="https://donate.mozilla.org/?utm_source=foundation.mozilla.org&utm_medium=referral&utm_campaign=fmonav&utm_content=header" target="_blank" rel="noopener noreferrer">{% trans "Donate" %}</a>
<a id="donate-header-btn" class="primary-nav-special-link tw-heart-glyph tw-flex" href="?form=donate">{% trans "Donate" %}</a>
{% if page.signup == None %}<button class="tw-btn-secondary btn-newsletter d-none d-lg-block ml-md-3">{% trans "Newsletter" %}</button>{% endif %}
</div>
{% endblock %}

Просмотреть файл

@ -0,0 +1,16 @@
from io import StringIO
from django.core.management import call_command
from django.test import TestCase
class MissingMigrationsTests(TestCase):
def test_no_migrations_missing(self):
"""
Ensure we didn't forget a migration
"""
output = StringIO()
call_command("makemigrations", interactive=False, dry_run=True, stdout=output)
if output.getvalue() != "No changes detected\n":
raise AssertionError("Missing migrations detected:\n" + output.getvalue())

Просмотреть файл

@ -83,6 +83,10 @@ def get_random_objects(
random.shuffle(primary_keys)
count = len(primary_keys)
if not count:
# No items available, return empty queryset.
return queryset.none()
if exact_count:
return_max = min(count, exact_count)
elif max_count:

Просмотреть файл

@ -0,0 +1,35 @@
from unittest.mock import MagicMock
from django.test import TestCase
from networkapi.utility.middleware import ReferrerMiddleware, XRobotsTagMiddleware
class ReferrerMiddlewareTests(TestCase):
def setUp(self):
referrer_middleware = ReferrerMiddleware("response")
self.assertEqual(referrer_middleware.get_response, "response")
def test_requestProcessing(self):
"""
Ensure that the middleware assigns a Referrer-Policy header to the response object
"""
referrer_middleware = ReferrerMiddleware(MagicMock())
response = referrer_middleware(MagicMock())
response.__setitem__.assert_called_with("Referrer-Policy", "same-origin")
class XRobotsTagMiddlewareTest(TestCase):
def test_returns_response(self):
xrobotstag_middleware = XRobotsTagMiddleware("response")
self.assertEqual(xrobotstag_middleware.get_response, "response")
def test_sends_x_robots_tag(self):
"""
Ensure that the middleware assigns an X-Robots-Tag to the response
"""
xrobotstag_middleware = XRobotsTagMiddleware(MagicMock())
response = xrobotstag_middleware(MagicMock())
response.__setitem__.assert_called_with("X-Robots-Tag", "noindex")

Просмотреть файл

@ -0,0 +1,57 @@
from unittest import skip
from django.test import RequestFactory, TestCase
from wagtail.core.models import Site
from wagtail_factories import SiteFactory
from networkapi.utility.redirects import redirect_to_default_cms_site
class RedirectDefaultSiteDecoratorTests(TestCase):
def setUp(self):
self.factory = RequestFactory()
# Change the default site away from localhost
self.original_default_site = Site.objects.get(is_default_site=True, hostname="localhost")
self.original_default_site.is_default_site = False
self.original_default_site.save()
# Add a default site, and a secondary site.
self.default_site = SiteFactory(hostname="default-site.com", is_default_site=True)
self.secondary_site = SiteFactory(hostname="secondary-site.com")
def test_redirect_decorator(self):
"""
Test that the decorator redirects.
"""
decorated_view = redirect_to_default_cms_site(lambda request: None)
response = decorated_view(self.factory.get("/example/", HTTP_HOST="secondary-site.com"))
self.assertEqual(response.status_code, 302)
def test_redirect_decorator_doesnt_redirect(self):
"""
Test that the redirect is triggered only when needed.
"""
decorated_view = redirect_to_default_cms_site(lambda request: "untouched response")
response = decorated_view(self.factory.get("/example/"))
self.assertEqual(response, "untouched response")
@skip("TODO: REENABLE: TEMPORARY SKIP TO MAKE PNI-AS-WAGTAIL LAUNCH POSSIBLE")
# @override_settings(STATICFILES_STORAGE="django.contrib.staticfiles.storage.StaticFilesStorage")
def test_PNI_homepage_redirect_to_foundation_site(self):
"""
Test that users gets redirected to PNI on the foundation site when they visit it from a non-default CMS site
"""
response = self.client.get("/en/privacynotincluded/", HTTP_HOST="secondary-site.com")
self.assertRedirects(
response,
"https://default-site.com/en/privacynotincluded/",
fetch_redirect_response=False,
)
def tearDown(self):
# Re-instate localhost as the default site
self.original_default_site.is_default_site = True
self.original_default_site.save()
# Remove the Site Factories
self.default_site.delete()
self.secondary_site.delete()

Просмотреть файл

@ -1,10 +1,10 @@
# TODO: Move these factories to the wagtailpages app.
# To avoid too many code conflicts, this should happen after PR 6433 is merged
from datetime import date, datetime, timedelta, timezone
from random import choice, randint, random, randrange, shuffle
from django.utils import text as text_utils
from factory import Faker, LazyFunction, SubFactory, post_generation
from factory.django import DjangoModelFactory
from wagtail.core.models import Locale
from wagtail.images.models import Image
from wagtail_factories import PageFactory
@ -261,6 +261,11 @@ class BuyersGuideContentCategoryFactory(DjangoModelFactory):
model = pagemodels.BuyersGuideContentCategory
title = Faker("word")
locale = LazyFunction(lambda: Locale.get_default())
@post_generation
def set_slug(obj, created, extracted, **kwargs):
obj.slug = text_utils.slugify(obj.title)
class BuyersGuideArticlePageAuthorProfileRelationFactory(DjangoModelFactory):
@ -404,30 +409,40 @@ def generate(seed):
print("Generating buyers guide editorial content")
editorial_content_index = BuyersGuideEditorialContentIndexPageFactory(parent=pni_homepage)
# Create content categories
for _ in range(3):
BuyersGuideContentCategoryFactory()
articles = []
# Create articles
for _ in range(12):
article = BuyersGuideArticlePageFactory(parent=editorial_content_index)
for profile in get_random_objects(pagemodels.Profile, max_count=3):
for index, profile in enumerate(get_random_objects(pagemodels.Profile, max_count=3), start=1):
BuyersGuideArticlePageAuthorProfileRelationFactory(
page=article,
author_profile=profile,
sort_order=index,
)
if article.id % 2 == 0:
# Articles with even id get the content category
for category in get_random_objects(pagemodels.BuyersGuideContentCategory, max_count=2):
for index, category in enumerate(
get_random_objects(pagemodels.BuyersGuideContentCategory, max_count=2),
start=1,
):
BuyersGuideArticlePageContentCategoryRelationFactory(
page=article,
content_category=category,
sort_order=index,
)
# Add all previously existing articles as related articles
for existing_article in articles:
# Add up to 3 previously existing articles as related articles (but not the article itself)
existing_articles = get_random_objects(
pagemodels.BuyersGuideArticlePage.objects.exclude(id=article.id),
max_count=3,
)
for index, existing_article in enumerate(existing_articles, start=1):
BuyersGuideArticlePageRelatedArticleRelationFactory(
page=article,
article=existing_article,
sort_order=index,
)
articles.append(article)
# Creating Buyersguide Campaign pages and accompanying donation modals
for _ in range(5):
@ -446,28 +461,34 @@ def generate(seed):
article=article,
sort_order=index,
)
# Buyerguide homepage featured advice article
pni_homepage.featured_advice_article = pagemodels.BuyersGuideArticlePage.objects.last()
pni_homepage.full_clean()
pni_homepage.save()
# Buyersguide homepage featured articles
featured_articles = get_random_objects(
source=pagemodels.BuyersGuideArticlePage.objects.exclude(id__in=supporting_articles),
exact_count=3,
)
for index, article in enumerate(featured_articles):
BuyersGuidePageFeaturedArticleRelationFactory(
page=pni_homepage,
article=article,
sort_order=index,
)
# Buyersguide homepage featured product updates
for index, update in enumerate(get_random_objects(pagemodels.Update, exact_count=3)):
BuyersGuidePageFeaturedUpdateRelationFactory(
page=pni_homepage,
update=update,
sort_order=index,
)
# The following section is commented, because the homepage redesign needs to be implemented.
# Until then, we want the generated homepage to be closer to the production site, where none of these
# relations are used.
# See also: https://github.com/mozilla/foundation.mozilla.org/issues/9758
#
# # Buyerguide homepage featured advice article
# pni_homepage.featured_advice_article = pagemodels.BuyersGuideArticlePage.objects.last()
# pni_homepage.full_clean()
# pni_homepage.save()
# # Buyersguide homepage featured articles
# featured_articles = get_random_objects(
# source=pagemodels.BuyersGuideArticlePage.objects.exclude(id__in=supporting_articles),
# exact_count=3,
# )
# for index, article in enumerate(featured_articles):
# BuyersGuidePageFeaturedArticleRelationFactory(
# page=pni_homepage,
# article=article,
# sort_order=index,
# )
# # Buyersguide homepage featured product updates
# for index, update in enumerate(get_random_objects(pagemodels.Update, exact_count=3)):
# BuyersGuidePageFeaturedUpdateRelationFactory(
# page=pni_homepage,
# update=update,
# sort_order=index,
# )
# Adding related articles to the Editorial Content Index Page
for index, article in enumerate(get_random_objects(pagemodels.BuyersGuideArticlePage, exact_count=3)):

Просмотреть файл

@ -0,0 +1,25 @@
# Generated by Django 3.2.16 on 2022-12-14 00:42
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("wagtailpages", "0070_adds_url_field_for_listing_card"),
]
operations = [
migrations.RemoveField(
model_name="buyersguidepage",
name="dark_theme",
),
migrations.RemoveField(
model_name="buyersguidepage",
name="header",
),
migrations.RemoveField(
model_name="buyersguidepage",
name="hero_image",
),
]

Просмотреть файл

@ -0,0 +1,26 @@
# Generated by Django 3.2.16 on 2022-12-21 19:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("wagtailcore", "0066_collection_management_permissions"),
("wagtailpages", "0071_buyersguidepage_remove_outdated_fields"),
]
operations = [
migrations.AlterField(
model_name="buyersguidecontentcategory",
name="slug",
field=models.SlugField(
help_text="The slug is auto-generated from the title, but can be customized if needed. It needs to be unique per locale. ",
max_length=100,
),
),
migrations.AlterUniqueTogether(
name="buyersguidecontentcategory",
unique_together={("locale", "slug"), ("translation_key", "locale")},
),
]

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -82,13 +82,13 @@ class BuyersGuideArticlePage(foundation_metadata.FoundationMetadataPageMixin, wa
label="Content category",
max_num=2,
),
panels.StreamFieldPanel("body"),
panels.InlinePanel(
"related_article_relations",
heading="Related articles",
heading="What to read next (related articles)",
label="Article",
max_num=6,
max_num=3,
),
panels.StreamFieldPanel("body"),
]
settings_panels = [
@ -120,16 +120,34 @@ class BuyersGuideArticlePage(foundation_metadata.FoundationMetadataPageMixin, wa
return context
def get_author_profiles(self) -> list["Profile"]:
return orderables.get_related_items(
author_profiles = orderables.get_related_items(
self.author_profile_relations.all(),
"author_profile",
)
# The relations are synchronized from the default locale on non-default
# locales. But, then working with a page of a non-default locale we are of
# course interested in the related articles in that same locale.
#
# FIXME: This version is bad, because it uses 1+n queries to generate the list
# of author profiles in the correct locale. We should create something
# more elegant to retrieve the author profiles of the correct locale
# directly from the database.
return [ap.localized for ap in author_profiles]
def get_content_categories(self) -> list["BuyersGuideContentCategory"]:
return orderables.get_related_items(
content_categories = orderables.get_related_items(
self.content_category_relations.all(),
"content_category",
)
# The relations are synchronized from the default locale on non-default
# locales. But, then working with a page of a non-default locale we are of
# course interested in the related articles in that same locale.
#
# FIXME: This version is bad, because it uses 1+n queries to generate the list
# content categories in the correct locale. We should create something
# more elegant to retrieve the content categories of the correct locale
# directly from the database.
return [cc.localized for cc in content_categories]
def get_related_articles(self) -> list["BuyersGuideArticlePage"]:
related_articles = orderables.get_related_items(
@ -146,12 +164,6 @@ class BuyersGuideArticlePage(foundation_metadata.FoundationMetadataPageMixin, wa
# directly from the database.
return [a.localized for a in related_articles]
def get_primary_related_articles(self) -> list["BuyersGuideArticlePage"]:
return self.get_related_articles()[:3]
def get_secondary_related_articles(self) -> list["BuyersGuideArticlePage"]:
return self.get_related_articles()[3:]
class BuyersGuideArticlePageAuthorProfileRelation(
wagtail_models.TranslatableMixin,

Просмотреть файл

@ -95,28 +95,6 @@ class BuyersGuidePage(RoutablePageMixin, FoundationMetadataPageMixin, Page):
related_name="+",
)
# TODO: Remove this field
hero_image = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="pni_hero_image",
)
# TODO: Remove this field
header = models.CharField(
max_length=120,
blank=True,
help_text="The header text for the PNI homepage",
)
# TODO: Remove this field
dark_theme = models.BooleanField(
default=False,
help_text="Does the intro need to be white text (for dark backgrounds)?",
)
content_panels = [
FieldPanel("title"),
MultiFieldPanel(

Просмотреть файл

@ -1,22 +1,44 @@
from django.db import models
from django.utils import text as text_utils
from wagtail.admin import edit_handlers as admin_panels
from wagtail.core import models as wagtail_models
from wagtail.snippets import models as snippet_models
from wagtail_localize import fields as localize_fields
@snippet_models.register_snippet
class BuyersGuideContentCategory(wagtail_models.TranslatableMixin, models.Model):
title = models.CharField(max_length=100, null=False, blank=False)
slug = models.SlugField(max_length=100, null=False, blank=True, unique=True)
slug = models.SlugField(
max_length=100,
null=False,
blank=False,
help_text=(
"The slug is auto-generated from the title, but can be customized if needed. "
"It needs to be unique per locale. "
),
)
panels = [
admin_panels.FieldPanel("title"),
admin_panels.FieldPanel("slug"),
]
translatable_fields = [
localize_fields.TranslatableField("title"),
localize_fields.SynchronizedField("slug"),
]
class Meta(wagtail_models.TranslatableMixin.Meta):
verbose_name = "Buyers Guide Content Category"
verbose_name_plural = "Buyers Guide Content Categories"
unique_together = wagtail_models.TranslatableMixin.Meta.unique_together + [
("locale", "slug"),
]
def __str__(self) -> str:
return self.title
def save(self, *args, **kwargs) -> None:
if not self.slug:
self.slug = text_utils.slugify(self.title)
super().save(*args, **kwargs)
def validate_unique(self, exclude=None):
if exclude and "locale" in exclude:
exclude.remove("locale")
return super().validate_unique(exclude)

Просмотреть файл

@ -44,4 +44,5 @@ base_fields = [
("image_teaser_block", customblocks.ImageTeaserBlock()),
("text_only_teaser", customblocks.TextOnlyTeaserBlock()),
("block_with_aside", customblocks.BlockWithAside()),
("accordion", customblocks.AccordionBlock()),
]

Просмотреть файл

@ -82,7 +82,8 @@ class RecentBlogEntries(blocks.StructBlock):
entries = blog_page.get_entries(context)
# Updates the href for the 'More from our blog' button
url = f"/{blog_page.slug}/{type}/{query}"
blog_page_url = blog_page.get_url()
url = f"{blog_page_url}{type}/{query}"
context["more_entries_link"] = url
# We only want to grab no more than the first 6 entries

Просмотреть файл

@ -12,6 +12,14 @@
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="{% static 'images/apple-touch-icon-180x180@2x.png' %}">
<link rel="icon" type="image/png" sizes="196x196" href="{% static 'images/favicon-196x196@2x.png' %}">
<link rel="shortcut icon" href="{% static 'images/favicon.ico' %}">
<!-- Fundraiseup Script -->
<script>(function(w,d,s,n,a){if(!w[n]){var l='call,catch,on,once,set,then,track'
.split(','),i,o=function(n){return'function'==typeof n?o.l.push([arguments])&&o
:function(){return o.l.push([n,arguments])&&o}},t=d.getElementsByTagName(s)[0],
j=d.createElement(s);j.async=!0;j.src='https://cdn.fundraiseup.com/widget/'+a;
t.parentNode.insertBefore(j,t);o.s=Date.now();o.v=4;o.h=w.location.href;o.l=[];
for(i=0;i<7;i++)o[l[i]]=o(l[i]);w[n]=o}
})(window,document,'script','FundraiseUp','ADCYPWMX');</script>
<title>Mozilla Foundation - Under Maintenance</title>
</head>
@ -74,9 +82,7 @@
</div>
</div>
<div class="donate-btn-wrapper">
<a id="donate-header-btn" class="tw-btn-pop"
href="https://donate.mozilla.org/?utm_source=foundation.mozilla.org&utm_medium=referral&utm_campaign=fmonav&utm_content=header"
target="_blank" rel="noopener noreferrer">Donate</a>
<a id="donate-header-btn" class="tw-btn-pop" href="?form=donate">Donate</a>
</div>
</div>
</div>
@ -106,7 +112,7 @@
<h3 class="tw-h3-heading mb-0">We all love the Web. Join Mozilla in defending it.</h3>
<h4 class="tw-h3-heading mb-4">Lets protect the worlds largest resource for future generations.</h4>
<div>
<a href="https://donate.mozilla.org/?utm_source=foundation.mozilla.org&utm_medium=referral&utm_campaign=fmonav&utm_content=maintenance_cta" target="_blank" rel="noopener noreferrer" c class="tw-btn-secondary dark-theme">Donate now</a>
<a href="?form=donate" class="tw-btn-secondary dark-theme">Donate now</a>
</div>
</div>
</div>
@ -131,7 +137,7 @@
</div>
<div class="col-lg-6 col-xl-7">
<ul class="link-list list-unstyled mb-0">
<li class="mb-2"><a id="donate-footer-btn" href="https://donate.mozilla.org/?utm_source=foundation.mozilla.org&utm_medium=referral&utm_campaign=fmonav&utm_content=footer" target="_blank" rel="noopener noreferrer" class="dark-theme">Donate</a></li>
<li class="mb-2"><a id="donate-footer-btn" href="?form=donate" class="dark-theme">Donate</a></li>
<li class="mb-2"><a href="https://www.mozilla.org/privacy/websites/#cookies" class="dark-theme">Cookies</a></li>
<li class="mb-2"><a href="https://www.mozilla.org/about/legal/terms/mozilla/" class="dark-theme">Legal</a></li>
<li class="mb-2"><a href="https://www.mozilla.org/about/governance/policies/participation/" class="dark-theme">Participation Guidelines</a></li>

Просмотреть файл

@ -14,7 +14,7 @@
{% endblock %}
{% block slides %}
<div class="swiper-wrapper">
<div class="swiper-wrapper tw-items-stretch">
{% for current_event in self.current_events %}
{% image current_event.value.image fill-445x185 as img %}
<div class="swiper-slide">

Просмотреть файл

@ -12,19 +12,28 @@
{% with card=block %}
<li>
{% image card.image fill-690x388 as img %}
{% if card.url %}
<a href="{{ card.url }}" class="tw-block tw-group hover:tw-no-underline focus:tw-no-underline hover:tw-text-black focus:tw-text-black" aria-label="{{ card.title }}">
{% endif %}
<div class="tw-px-4 tw-grid tw-grid-cols-1 large:tw-grid-cols-3 tw-gap-4 large:tw-gap-6">
<div class="tw-col-span-1">
<img class="tw-w-full xlarge:tw-max-w-[345px]" src="{{ img.url }}" alt="{{ card.alt_text }}" width="{{ img.width }}" height="{{ img.height }}"/>
{% if card.url %}
<a href="{{ card.url }}" class="tw-block tw-group" aria-label="{{ card.title }}">
{% endif %}
<img class="tw-w-full xlarge:tw-max-w-[345px]" src="{{ img.url }}" alt="{{ card.alt_text }}" width="{{ img.width }}" height="{{ img.height }}"/>
{% if card.url %}
</a>
{% endif %}
</div>
<div class="tw-col-span-1 large:tw-col-span-2 large:tw-items-center large:tw-flex">
<div class="tw-bg-white tw-relative tw-w-full tw-flex tw-flex-col">
<span class="tw-h6-heading tw-text-gray-40 tw-py-1 tw-mb-0">{{ card.meta_data }}</span>
{% if card.url %}
<a href="{{ card.url }}" class="tw-block tw-group hover:tw-no-underline focus:tw-no-underline hover:tw-text-black focus:tw-text-black" aria-label="{{ card.title }}">
{% endif %}
<h3 class="tw-h3-heading tw-mb-2 group-hover:tw-underline group-focus:tw-underline group-hover:tw-text-inherit group-focus:tw-text-inherit group-hover:tw-font-inherit">
{{ card.title }}
</h3>
{% if card.url %}
</a>
{% endif %}
{# Adjust font sizes of rich text elements using [&_p,li,ul] to select them #}
<div class="[&_p,li,ul]:tw-text-sm [&_p,li,ul]:tw-text-gray-80">
{{ card.body|richtext }}
@ -32,7 +41,6 @@
</div>
</div>
</div>
{% if card.url %}</a>{% endif %}
</li>
{% endwith %}
{% endfor %}

Просмотреть файл

@ -1,225 +1,12 @@
from datetime import date, datetime, timezone
from io import StringIO
from os.path import abspath, dirname, join
from unittest import skip
from unittest.mock import MagicMock
from django.contrib.auth.models import Group, User
from django.core.management import call_command
from django.test import RequestFactory, TestCase
from django.urls import reverse
from django.utils import translation
from django.utils.translation.trans_real import (
parse_accept_lang_header as django_parse_accept_lang_header,
)
from django.utils.translation.trans_real import to_language as django_to_language
from wagtail.core.models import Collection, Site
from wagtail.images.models import Image
from wagtail_factories import SiteFactory
from django.test import TestCase
from networkapi.utility.middleware import ReferrerMiddleware, XRobotsTagMiddleware
from networkapi.utility.redirects import redirect_to_default_cms_site
from networkapi.wagtailpages import (
language_code_to_iso_3166,
parse_accept_lang_header,
to_language,
)
from networkapi.wagtailpages.factory.buyersguide import (
BuyersGuidePageFactory,
GeneralProductPageFactory,
)
from networkapi.wagtailpages.pagemodels.base import Homepage
from networkapi.wagtailpages.utils import create_wagtail_image
# from django.test.utils import override_settings
class ReferrerMiddlewareTests(TestCase):
def setUp(self):
referrer_middleware = ReferrerMiddleware("response")
self.assertEqual(referrer_middleware.get_response, "response")
def test_requestProcessing(self):
"""
Ensure that the middleware assigns a Referrer-Policy header to the response object
"""
referrer_middleware = ReferrerMiddleware(MagicMock())
response = referrer_middleware(MagicMock())
response.__setitem__.assert_called_with("Referrer-Policy", "same-origin")
class MissingMigrationsTests(TestCase):
def test_no_migrations_missing(self):
"""
Ensure we didn't forget a migration
"""
output = StringIO()
call_command("makemigrations", interactive=False, dry_run=True, stdout=output)
if output.getvalue() != "No changes detected\n":
raise AssertionError("Missing migrations detected:\n" + output.getvalue())
class DeleteNonStaffTest(TestCase):
def setUp(self):
User.objects.create(username="Alex"),
def test_non_staff_is_deleted(self):
"""
Simple users are deleted
"""
call_command("delete_non_staff", "--now")
self.assertEqual(User.objects.count(), 0)
class IsStaffNotDeletedTest(TestCase):
def setUp(self):
User.objects.create(username="Alex", is_staff=True)
def test_is_staff_not_deleted(self):
"""
Users with 'is_staff' flag at True are not deleted
"""
call_command("delete_non_staff", "--now")
self.assertEqual(User.objects.count(), 1)
class InGroupNotDeletedTest(TestCase):
def setUp(self):
group = Group.objects.create(name="TestGroup")
group.user_set.create(username="Alex")
def test_in_group_not_deleted(self):
"""
Users in a group are not deleted
"""
call_command("delete_non_staff", "--now")
self.assertEqual(User.objects.count(), 1)
class MozillaFoundationUsersNotDeletedTest(TestCase):
def setUp(self):
User.objects.create(username="Alex", email="alex@mozillafoundation.org")
def test_mozilla_foundation_users_not_deleted(self):
"""
Mozilla Foundation Users are not deleted
"""
call_command("delete_non_staff", "--now")
self.assertEqual(User.objects.count(), 1)
class RedirectDefaultSiteDecoratorTests(TestCase):
def setUp(self):
self.factory = RequestFactory()
# Change the default site away from localhost
self.original_default_site = Site.objects.get(is_default_site=True, hostname="localhost")
self.original_default_site.is_default_site = False
self.original_default_site.save()
# Add a default site, and a secondary site.
self.default_site = SiteFactory(hostname="default-site.com", is_default_site=True)
self.secondary_site = SiteFactory(hostname="secondary-site.com")
def test_redirect_decorator(self):
"""
Test that the decorator redirects.
"""
decorated_view = redirect_to_default_cms_site(lambda request: None)
response = decorated_view(self.factory.get("/example/", HTTP_HOST="secondary-site.com"))
self.assertEqual(response.status_code, 302)
def test_redirect_decorator_doesnt_redirect(self):
"""
Test that the redirect is triggered only when needed.
"""
decorated_view = redirect_to_default_cms_site(lambda request: "untouched response")
response = decorated_view(self.factory.get("/example/"))
self.assertEqual(response, "untouched response")
@skip("TODO: REENABLE: TEMPORARY SKIP TO MAKE PNI-AS-WAGTAIL LAUNCH POSSIBLE")
# @override_settings(STATICFILES_STORAGE="django.contrib.staticfiles.storage.StaticFilesStorage")
def test_PNI_homepage_redirect_to_foundation_site(self):
"""
Test that users gets redirected to PNI on the foundation site when they visit it from a non-default CMS site
"""
response = self.client.get("/en/privacynotincluded/", HTTP_HOST="secondary-site.com")
self.assertRedirects(
response,
"https://default-site.com/en/privacynotincluded/",
fetch_redirect_response=False,
)
def tearDown(self):
# Re-instante localhost as the default site
self.original_default_site.is_default_site = True
self.original_default_site.save()
# Remove the Site Factories
self.default_site.delete()
self.secondary_site.delete()
class WagtailPagesTestCase(TestCase):
def test_get_language_code_to_iso_3166(self):
self.assertEqual(language_code_to_iso_3166("en-gb"), "en-GB")
self.assertEqual(language_code_to_iso_3166("en-us"), "en-US")
self.assertEqual(language_code_to_iso_3166("fr"), "fr")
def test_to_language(self):
self.assertEqual(to_language("en_US"), "en-US")
def test_parse_accept_lang_header_returns_iso_3166_language(self):
self.assertEqual(
parse_accept_lang_header("en-GB,en;q=0.5"),
(("en-GB", 1.0), ("en", 0.5)),
)
class WagtailPagesIntegrationTestCase(TestCase):
"""
Test that our overrides to Django translation functions work.
"""
def test_to_language(self):
self.assertEqual(django_to_language("fy_NL"), "fy-NL")
def test_parse_accept_lang_header_returns_iso_3166_language(self):
self.assertEqual(
django_parse_accept_lang_header("fy-NL,fy;q=0.5"),
(("fy-NL", 1.0), ("fy", 0.5)),
)
@skip("TODO: REMOVE: NOW DONE BY WAGTAIL")
def test_reverse_produces_correct_url_prefix(self):
translation.activate("fy-NL")
url = reverse("buyersguide-home")
self.assertTrue(url.startswith("/fy-NL/"))
translation.deactivate()
class XRobotsTagMiddlewareTest(TestCase):
def test_returns_response(self):
xrobotstag_middleware = XRobotsTagMiddleware("response")
self.assertEqual(xrobotstag_middleware.get_response, "response")
def test_sends_x_robots_tag(self):
"""
Ensure that the middleware assigns an X-Robots-Tag to the response
"""
xrobotstag_middleware = XRobotsTagMiddleware(MagicMock())
response = xrobotstag_middleware(MagicMock())
response.__setitem__.assert_called_with("X-Robots-Tag", "noindex")
class TestPNIAirtableConnections(TestCase):
@ -236,7 +23,6 @@ class TestPNIAirtableConnections(TestCase):
parent=Homepage.objects.first(),
title="* Privacy not included",
slug="privacynotincluded",
header="Be Smart. Shop Safe.",
)
self.general_product_page = GeneralProductPageFactory.create(
title="General Percy Product",
@ -403,48 +189,3 @@ class TestPNIAirtableConnections(TestCase):
self.assertIn("Uses AI", export_fields)
self.assertIn("AI is transparent", export_fields)
self.assertIn("AI help text", export_fields)
class TestCreateWagtailImageUtility(TestCase):
def setUp(self):
self.image_path = abspath(join(dirname(__file__), "../../media/images/placeholders/products/teddy.jpg"))
def create_new_image(self):
"""A generic test to ensure the image is created properly."""
new_image = create_wagtail_image(self.image_path, image_name="fake teddy.jpg", collection_name="pni products")
# Image was created
self.assertIsNotNone(new_image)
# Image has a collection and is in the proper collection
self.assertIsNotNone(new_image.collection_id)
self.assertEqual(new_image.collection.name, "pni products")
def test_empty_image_name_and_no_collection(self):
new_image = create_wagtail_image(
self.image_path,
)
self.assertEqual(new_image.title, "teddy.jpg")
self.assertEqual(new_image.collection.name, "Root")
def test_new_collection(self):
collection_name = "brand new collection"
new_image = create_wagtail_image(
self.image_path,
image_name="fake teddy.jpg",
collection_name=collection_name,
)
self.assertEqual(new_image.collection.name, collection_name)
def test_existing_collection(self):
new_collection_name = "first collection"
root_collection = Collection.get_first_root_node()
new_collection = root_collection.add_child(name=new_collection_name)
total_images_in_new_collection = Image.objects.filter(collection=new_collection).count()
self.assertEqual(total_images_in_new_collection, 0)
new_image = create_wagtail_image(
self.image_path,
image_name="fake teddy.jpg",
collection_name=new_collection_name,
)
self.assertEqual(new_image.collection.name, new_collection_name)

Просмотреть файл

@ -143,66 +143,3 @@ class BuyersGuideArticlePageTest(test_base.WagtailpagesTestCase):
related_articles_fr = article_page_fr.get_related_articles()
self.assertIn(related_article_fr, related_articles_fr)
def test_primary_related_articles(self):
"""First three related articles are primary."""
article_page = buyersguide_factories.BuyersGuideArticlePageFactory(
parent=self.content_index,
)
related_articles = []
for _ in range(4):
related_article = buyersguide_factories.BuyersGuideArticlePageFactory(
parent=self.content_index,
)
buyersguide_factories.BuyersGuideArticlePageRelatedArticleRelationFactory(
page=article_page,
article=related_article,
)
related_articles.append(related_article)
result = article_page.get_primary_related_articles()
for related_article in related_articles[:3]:
self.assertIn(related_article, result)
self.assertNotIn(related_articles[-1], result)
def test_primary_related_articles_no_related_articles(self):
article_page = buyersguide_factories.BuyersGuideArticlePageFactory(
parent=self.content_index,
)
result = article_page.get_primary_related_articles()
self.assertListEqual(result, [])
def test_secondary_related_articles(self):
"""Second three related articles are secondary."""
article_page = buyersguide_factories.BuyersGuideArticlePageFactory(
parent=self.content_index,
)
related_articles = []
for _ in range(6):
related_article = buyersguide_factories.BuyersGuideArticlePageFactory(
parent=self.content_index,
)
buyersguide_factories.BuyersGuideArticlePageRelatedArticleRelationFactory(
page=article_page,
article=related_article,
)
related_articles.append(related_article)
result = article_page.get_secondary_related_articles()
for related_article in related_articles[:3]:
self.assertNotIn(related_article, result)
for related_article in related_articles[3:]:
self.assertIn(related_article, result)
def test_secondary_related_articles_no_related_articles(self):
article_page = buyersguide_factories.BuyersGuideArticlePageFactory(
parent=self.content_index,
)
result = article_page.get_secondary_related_articles()
self.assertListEqual(result, [])

Просмотреть файл

@ -1,71 +1,40 @@
import unittest
from django.core import exceptions
from networkapi.wagtailpages.factory import buyersguide as buyersguide_factories
from networkapi.wagtailpages.tests import base as test_base
TEST_CATEGORY_TITLE = "Test category"
class TestBuyersGuideContentCategory(test_base.WagtailpagesTestCase):
def test_factory(self):
buyersguide_factories.BuyersGuideContentCategoryFactory()
def test_slug_set_during_save(self):
category = buyersguide_factories.BuyersGuideContentCategoryFactory.build()
self.assertEqual(category.slug, "")
category.save()
self.assertNotEqual(category.slug, "")
def test_exisiting_slug_is_kept_during_save(self):
category = buyersguide_factories.BuyersGuideContentCategoryFactory(
title="Test category",
slug="not-the-slugified-title",
def test_factory_sets_slug(self):
category = buyersguide_factories.BuyersGuideContentCategoryFactory.build(
title=TEST_CATEGORY_TITLE,
)
self.assertEqual(category.slug, "test-category")
category.save()
self.assertEqual(category.title, "Test category")
self.assertEqual(category.slug, "not-the-slugified-title")
def test_slug_has_to_be_unique(self):
def test_full_clean_raises_if_category_with_same_title_and_locale_in_db(self):
buyersguide_factories.BuyersGuideContentCategoryFactory(
title="Test category",
slug="test-category",
title=TEST_CATEGORY_TITLE,
)
category = buyersguide_factories.BuyersGuideContentCategoryFactory.build(
title="Test category",
slug="test-category",
title=TEST_CATEGORY_TITLE,
)
with self.assertRaises(exceptions.ValidationError):
category.full_clean()
@unittest.skip(
"We would really want the slugs to be only unique per locale. "
"If we implement that with a UniqueContraint or unique_together, "
"Wagtail will crash if the constraint is violated. "
"Wagtail does handle the simple unique requirement on the slug field gracefully. "
"Therefore, we are using slugs that are unique regardless of locale. "
"We need to manually create unique slugs if there are clashes between the locales. "
"Because we are using slugs that need to be unique regradless of locale, this "
"test would fail. Therefore it is skipped."
"See also: https://github.com/wagtail/wagtail/issues/8918"
)
def test_same_slug_allowed_on_different_locale(self):
def test_can_clean_and_save_copy_for_translation(self):
category_default_locale = buyersguide_factories.BuyersGuideContentCategoryFactory(
title="Test category",
slug="test-category",
locale=self.default_locale,
)
category_fr_locale = buyersguide_factories.BuyersGuideContentCategoryFactory.build(
title="Test category",
slug="test-category",
locale=self.fr_locale,
title=TEST_CATEGORY_TITLE,
)
category_fr_locale.save()
fr_copy = category_default_locale.copy_for_translation(locale=self.fr_locale)
fr_copy.full_clean()
fr_copy.save()
self.assertEqual(category_fr_locale.slug, category_default_locale.slug)
self.assertNotEqual(category_fr_locale.locale, category_default_locale.locale)
self.assertEqual(category_default_locale.slug, "test-category")
self.assertEqual(fr_copy.slug, "test-category")

Просмотреть файл

@ -0,0 +1,92 @@
from os.path import abspath, dirname, join
from django.test import TestCase
from django.utils.translation.trans_real import (
parse_accept_lang_header as django_parse_accept_lang_header,
)
from django.utils.translation.trans_real import to_language as django_to_language
from wagtail.core.models import Collection
from wagtail.images.models import Image
from networkapi.wagtailpages import (
language_code_to_iso_3166,
parse_accept_lang_header,
to_language,
)
from networkapi.wagtailpages.utils import create_wagtail_image
class TestCreateWagtailImageUtility(TestCase):
def setUp(self):
self.image_path = abspath(join(dirname(__file__), "../../../media/images/placeholders/products/teddy.jpg"))
def create_new_image(self):
"""A generic test to ensure the image is created properly."""
new_image = create_wagtail_image(self.image_path, image_name="fake teddy.jpg", collection_name="pni products")
# Image was created
self.assertIsNotNone(new_image)
# Image has a collection and is in the proper collection
self.assertIsNotNone(new_image.collection_id)
self.assertEqual(new_image.collection.name, "pni products")
def test_empty_image_name_and_no_collection(self):
new_image = create_wagtail_image(
self.image_path,
)
self.assertEqual(new_image.title, "teddy.jpg")
self.assertEqual(new_image.collection.name, "Root")
def test_new_collection(self):
collection_name = "brand new collection"
new_image = create_wagtail_image(
self.image_path,
image_name="fake teddy.jpg",
collection_name=collection_name,
)
self.assertEqual(new_image.collection.name, collection_name)
def test_existing_collection(self):
new_collection_name = "first collection"
root_collection = Collection.get_first_root_node()
new_collection = root_collection.add_child(name=new_collection_name)
total_images_in_new_collection = Image.objects.filter(collection=new_collection).count()
self.assertEqual(total_images_in_new_collection, 0)
new_image = create_wagtail_image(
self.image_path,
image_name="fake teddy.jpg",
collection_name=new_collection_name,
)
self.assertEqual(new_image.collection.name, new_collection_name)
class TestLanguageUtilities(TestCase):
def test_get_language_code_to_iso_3166(self):
self.assertEqual(language_code_to_iso_3166("en-gb"), "en-GB")
self.assertEqual(language_code_to_iso_3166("en-us"), "en-US")
self.assertEqual(language_code_to_iso_3166("fr"), "fr")
def test_to_language(self):
self.assertEqual(to_language("en_US"), "en-US")
def test_parse_accept_lang_header_returns_iso_3166_language(self):
self.assertEqual(
parse_accept_lang_header("en-GB,en;q=0.5"),
(("en-GB", 1.0), ("en", 0.5)),
)
class TestDjangoTranslationUtilityOverrides(TestCase):
"""
Test that our overrides to Django translation functions work.
"""
def test_to_language(self):
self.assertEqual(django_to_language("fy_NL"), "fy-NL")
def test_parse_accept_lang_header_returns_iso_3166_language(self):
self.assertEqual(
django_parse_accept_lang_header("fy-NL,fy;q=0.5"),
(("fy-NL", 1.0), ("fy", 0.5)),
)

15146
package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -3,7 +3,7 @@
"issues": "https://github.com/mozilla/foundation.mozilla.org/issues",
"description": "Mozilla Foundation site",
"engines": {
"node": ">=14.4.0"
"node": "18.x"
},
"scripts": {
"build": "run-s build:clean && run-p build:js:prod build:common",
@ -47,10 +47,10 @@
"playwright:install": "playwright install chromium firefox webkit",
"playwright:wait:dev": "wait-on -i 3000 http://localhost:8000/static/_css/tailwind.compiled.css",
"playwright:percy": "run-p --race server playwright:visual",
"playwright:urls": "wait-on -i 3000 http://localhost:8000/cms && playwright test ./tests/urls.spec.js",
"playwright:visual": "wait-on -i 3000 http://localhost:8000/cms && playwright test ./tests/visual.spec.js",
"playwright:urls": "wait-on -i 3000 -t 120000 -v http://127.0.0.1:8000/cms && playwright test ./tests/urls.spec.js",
"playwright:visual": "wait-on -i 3000 -t 120000 -v http://127.0.0.1:8000/cms && playwright test ./tests/visual.spec.js",
"playwright:ci": "run-p --race server playwright:integration",
"playwright:integration": "wait-on -i 3000 http://localhost:8000/cms && playwright test ./tests/integration.spec.js",
"playwright:integration": "wait-on -i 3000 -t 120000 -v http://127.0.0.1:8000/cms && playwright test ./tests/integration.spec.js",
"precommit": "npm run test",
"prettier:js": "prettier \"source/js/**/*.js\" \"source/js/**/*.jsx\" \"network-api/networkapi/wagtailcustomization/**/*.js\" \"tests/**/*.js\" ./*.js",
"prettier:scss": "prettier \"source/sass/**/*.scss\" \"source/js/**/*.scss\" \"network-api/networkapi/{,!(frontend)/**/}*.css\"",
@ -81,28 +81,28 @@
"axe-core": "^4.1.4",
"bootstrap": "^4.6.0",
"chart.js": "3.5.1",
"classnames": "2.3.1",
"countup.js": "^2.0.7",
"classnames": "2.3.2",
"countup.js": "^2.3.2",
"cssnano": "^5.0.8",
"esbuild": "^0.12.24",
"event-stream": "3.3.4",
"gsap": "^3.9.1",
"htmx.org": "^1.8.0",
"locomotive-scroll": "^4.1.4",
"moment": "^2.29.1",
"moment": "^2.29.4",
"npm-run-all": "^4.1.3",
"postcss": "^8.4.5",
"postcss": "^8.4.21",
"postcss-cli": "^8.3.1",
"postcss-import": "^14.0.2",
"prop-types": "^15.7.2",
"react": "^17.0.2",
"react-axe": "^3.5.4",
"react-dom": "^17.0.2",
"react-ga": "3.3.0",
"react-ga": "3.3.1",
"sass": "^1.38.2",
"shx": "^0.3.3",
"swiper": "^6.8.4",
"tailwindcss": "^3.2.1",
"tailwindcss": "^3.2.4",
"uuid": "^8.3.2",
"whatwg-fetch": "^3.6.2"
},
@ -111,19 +111,19 @@
"@percy/playwright": "^1.0.1",
"@playwright/test": "^1.17.0",
"browser-sync": "^2.27.5",
"browserslist": "^4.17.0",
"browserslist": "^4.21.4",
"chokidar-cli": "^2.1.0",
"dotenv": "^8.2.0",
"eslint": "^7.32.0",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.25.1",
"optipng-bin": "^7.0.0",
"optipng-bin": "^9.0.0",
"prettier": "^2.3.2",
"stylelint": "^13.13.1",
"stylelint-config-standard": "^20.0.0",
"stylelint-prettier": "^1.2.0",
"svgo": "^2.5.0",
"wait-on": "^5.2.1"
"wait-on": "^6"
}
}

Просмотреть файл

@ -5,7 +5,7 @@ cached_property==1.5.2
Django==3.2.16
dj-database-url
djangorestframework
django-admin-sortable==2.2.3
django-admin-sortable==2.3
django-cors-headers
django_csp
django-environ
@ -29,7 +29,7 @@ wagtail==2.16.3
wagtail-color-panel
wagtail-factories
wagtail-localize==1.1
wagtail-localize-git==0.12.0
wagtail-localize-git==0.13.0
wagtail-inventory
wagtailmedia
wagtail-metadata

Просмотреть файл

@ -1,6 +1,6 @@
#
# This file is autogenerated by pip-compile with Python 3.9
# by the following command:
# This file is autogenerated by pip-compile with python 3.9
# To update, run:
#
# pip-compile
#
@ -68,9 +68,9 @@ django==3.2.16
# wagtail-footnotes
# wagtail-localize
# wagtail-localize-git
django-admin-sortable==2.2.3
django-admin-sortable==2.3
# via -r requirements.in
django-cors-headers==3.11.0
django-cors-headers==3.13.0
# via -r requirements.in
django-csp==3.7
# via -r requirements.in
@ -135,7 +135,7 @@ jmespath==0.10.0
# botocore
l18n==2021.3
# via wagtail
oauthlib==3.2.0
oauthlib==3.2.1
# via
# requests-oauthlib
# social-auth-core
@ -244,7 +244,7 @@ wagtail==2.16.3
# wagtailmedia
wagtail-airtable==0.2.1
# via -r requirements.in
wagtail-color-panel==1.3.1
wagtail-color-panel==1.4.0
# via -r requirements.in
wagtail-factories==2.0.1
# via -r requirements.in
@ -256,7 +256,7 @@ wagtail-localize==1.1
# via
# -r requirements.in
# wagtail-localize-git
wagtail-localize-git==0.12.0
wagtail-localize-git==0.13.0
# via -r requirements.in
wagtail-metadata==3.5.0
# via -r requirements.in

Двоичные данные
source/images/donate-banner-giving.jpg Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 98 KiB

Просмотреть файл

@ -13,7 +13,7 @@ import injectMultipageNav from "../multipage-nav.js";
import primaryNav from "../primary-nav.js";
import HomepageSlider from "./homepage-c-slider.js";
import RelatedArticles from "./related-articles.js";
import NewsletterBox from "./newsletter-box.js";
import AnalyticsEvents from "./analytics-events.js";
import initializeSentry from "../common/sentry-config.js";
import PNIMobileNav from "./pni-mobile-nav.js";
@ -114,9 +114,7 @@ let main = {
if (document.querySelector(`body.pni.catalog`)) {
HomepageSlider.init();
}
if (document.querySelector("#view-article")) {
RelatedArticles.floatRelatedArticlesNextToThirdElement();
}
NewsletterBox.toggleVisibilityClasses();
},
};

Просмотреть файл

@ -0,0 +1,20 @@
const NewsletterBox = {
toggleVisibilityClasses: () => {
const buyersGuideNewsletterBox = document.querySelector(
"#buyersguide-newsletter-box"
);
// Used for toggle visibility for buyersguide newsletter container
if (buyersGuideNewsletterBox) {
buyersGuideNewsletterBox.classList.remove("tw-hidden");
}
// Used for buyersguide product review grid toggle visibility
const buyersGuideGridContainer = document.querySelector(
"#product-grid-newsletter-signup"
);
if (buyersGuideGridContainer) {
buyersGuideGridContainer.classList.add("tw-flex");
buyersGuideGridContainer.classList.remove("tw-hidden");
}
},
};

Просмотреть файл

@ -1,28 +0,0 @@
const RelatedArticles = {
floatRelatedArticlesNextToThirdElement: () => {
const firstParagraph = document.querySelector(
".paragraph-block .rich-text"
);
const relatedContainer = document.querySelector(
"#article-primary-related-articles"
);
if (!firstParagraph || !relatedContainer) return;
const relatedContent = relatedContainer.querySelector("div");
/*
* if there is at least 3 children inside the rich body container.
* Usually there is a header(h1-6) and a few paragraphs blocks to start most articles.
* If we do not have at least that many elements we leave the related articles at the bottom of the article page
*/
const relatedPlace = firstParagraph.querySelector("*:nth-child(3)");
if (!relatedPlace) return;
firstParagraph.insertBefore(relatedContent, relatedPlace);
relatedContent.classList.add(
"large:tw-float-right",
"tw-p-4",
"tw-pr-0",
"large:tw-mr-[-20%]"
);
},
};
export default RelatedArticles;

Просмотреть файл

@ -71,15 +71,7 @@ class DonationModal extends Component {
getModalContent() {
if (!this.donateURL) {
let base = `https://donate.mozilla.org/?`,
query = [
`utm_source=foundation.mozilla.org`,
`utm_medium=petitionmodal`,
`utm_campaign=${this.props.slug}`,
`utm_content=${this.props.name}`,
].join(`&`);
this.donateURL = `${base}${query}`;
this.donateURL = `?form=donate`;
}
return (
<div className="modal-content" role="dialog">
@ -107,7 +99,6 @@ class DonationModal extends Component {
ref={(e) => (this.userElectedToDonateLink = e)}
className="tw-btn-primary"
href={this.donateURL}
target="_blank"
tabIndex="0"
>
{this.props.donateText}

Просмотреть файл

@ -203,7 +203,7 @@ def npm(ctx, command):
def npm_install(ctx):
"""Install Node dependencies"""
with ctx.cd(ROOT):
ctx.run("docker-compose run --rm watch-static-files npm install")
ctx.run("docker-compose run --rm watch-static-files npm ci")
@task(aliases=["copy-stage-db"])