diff --git a/.github/workflows/continous-integration.yml b/.github/workflows/continous-integration.yml index f544c5608a0..f60956ce08f 100644 --- a/.github/workflows/continous-integration.yml +++ b/.github/workflows/continous-integration.yml @@ -16,7 +16,6 @@ jobs: env: ALLOWED_HOSTS: localhost,mozfest.localhost,default-site.com,secondary-site.com GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NETWORK_SITE_URL: https://foundation.mozilla.org PULSE_API_DOMAIN: https://network-pulse-api-production.herokuapp.com PULSE_DOMAIN: https://www.mozillapulse.org steps: @@ -54,7 +53,6 @@ jobs: DJANGO_SECRET_KEY: secret DOMAIN_REDIRECT_MIDDLEWARE_ENABLED: False GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NETWORK_SITE_URL: http://localhost:8000 PIPENV_VERBOSITY: -1 PULSE_API_DOMAIN: https://network-pulse-api-production.herokuapp.com PULSE_DOMAIN: https://www.mozillapulse.org @@ -132,7 +130,6 @@ jobs: DJANGO_SECRET_KEY: secret DOMAIN_REDIRECT_MIDDLEWARE_ENABLED: False GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NETWORK_SITE_URL: http://localhost:8000 PIPENV_VERBOSITY: -1 PULSE_API_DOMAIN: https://network-pulse-api-production.herokuapp.com PULSE_DOMAIN: https://www.mozillapulse.org @@ -146,7 +143,7 @@ jobs: CSP_CONNECT_SRC: "*" CSP_FONT_SRC: "'self' https://code.cdn.mozilla.net https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/fonts/ data: https://static.fundraiseup.com/common-fonts/" CSP_IMG_SRC: "* data: blob: https://*.fundraiseup.com https://ucarecdn.com https://pay.google.com https://*.paypalobjects.com" - CSP_FRAME_SRC: "'self' https://www.google.com/recaptcha/ https://*.stripe.com https://pay.google.com https://*.paypal.com https://*.fundraiseup.com" + CSP_FRAME_SRC: "'self' https://www.google.com/recaptcha/ https://*.stripe.com https://pay.google.com https://*.paypal.com https://*.fundraiseup.com" CSP_SCRIPT_SRC: "'self' 'unsafe-inline' https://www.google-analytics.com/analytics.js http://*.shpg.org/ https://comments.mozillafoundation.org/ https://airtable.com https://platform.twitter.com https://cdnjs.cloudflare.com/ajax/libs/gsap/3.8.0/gsap.min.js https://cdnjs.cloudflare.com/ajax/libs/gsap/3.8.0/ScrollTrigger.min.js https://*.googletagmanager.com https://*.fundraiseup.com https://mozillafoundation.tfaforms.net https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ 'unsafe-eval' https://*.stripe.com https://m.stripe.network https://*.paypal.com https://*.paypalobjects.com https://pay.google.com" CSP_STYLE_SRC: "'self' 'unsafe-inline' https://code.cdn.mozilla.net https://platform.twitter.com https://mozillafoundation.tfaforms.net https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" SECURE_CROSS_ORIGIN_OPENER_POLICY: "same-origin-allow-popups" diff --git a/.github/workflows/visual-regression-testing.yml b/.github/workflows/visual-regression-testing.yml index 4c7667ce5e8..2d097cfb815 100644 --- a/.github/workflows/visual-regression-testing.yml +++ b/.github/workflows/visual-regression-testing.yml @@ -43,7 +43,6 @@ jobs: DJANGO_SECRET_KEY: secret DOMAIN_REDIRECT_MIDDLEWARE_ENABLED: False GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NETWORK_SITE_URL: http://localhost:8000 PIPENV_VERBOSITY: -1 PULSE_API_DOMAIN: https://network-pulse-api-production.herokuapp.com PULSE_DOMAIN: https://www.mozillapulse.org diff --git a/env.default b/env.default index 3d2c935931b..1fee3ab316c 100644 --- a/env.default +++ b/env.default @@ -10,8 +10,7 @@ PULSE_API_DOMAIN=https://network-pulse-api-production.herokuapp.com PULSE_DOMAIN=https://www.mozillapulse.org -# Always add a protocol (ex: https://) in front of the NETWORK_SITE_URL, including for localhost -NETWORK_SITE_URL=http://localhost:8000 +# Always add a protocol (ex: https://) in front of the URL, including for localhost WAGTAILADMIN_BASE_URL=http://localhost:8000 DOMAIN_REDIRECT_MIDDLEWARE_ENABLED=False TARGET_DOMAINS=foundation.mozilla.org diff --git a/network-api/networkapi/settings.py b/network-api/networkapi/settings.py index 94151fcedb3..5a3393ad0dc 100644 --- a/network-api/networkapi/settings.py +++ b/network-api/networkapi/settings.py @@ -56,7 +56,6 @@ HEROKU_RELEASE_VERSION=(str, None), INDEX_PAGE_CACHE_TIMEOUT=(int, 60 * 60 * 24), MOZFEST_DOMAIN_REDIRECT_ENABLED=(bool, False), - NETWORK_SITE_URL=(str, ""), PETITION_TEST_CAMPAIGN_ID=(str, ""), PNI_STATS_DB_URL=(str, None), PULSE_API_DOMAIN=(str, ""), @@ -695,7 +694,6 @@ class DatabasesDict(TypedDict): FRONTEND = { "PULSE_API_DOMAIN": env("PULSE_API_DOMAIN"), "PULSE_DOMAIN": env("PULSE_DOMAIN"), - "NETWORK_SITE_URL": env("NETWORK_SITE_URL"), "TARGET_DOMAINS": env("TARGET_DOMAINS"), "SENTRY_DSN": env("SENTRY_DSN"), "RELEASE_VERSION": env("HEROKU_RELEASE_VERSION"), @@ -718,9 +716,6 @@ class DatabasesDict(TypedDict): # privacynotincluded statistics DB PNI_STATS_DB_URL = env("PNI_STATS_DB_URL") -# Use network_url to check if we're running prod or not -NETWORK_SITE_URL = env("NETWORK_SITE_URL") - # Blog/Campaign index cache setting INDEX_PAGE_CACHE_TIMEOUT = env("INDEX_PAGE_CACHE_TIMEOUT") diff --git a/network-api/networkapi/templates/tags/horizontal_nav.html b/network-api/networkapi/templates/tags/horizontal_nav.html index 2633eaf3abe..7f5c3c97e2c 100644 --- a/network-api/networkapi/templates/tags/horizontal_nav.html +++ b/network-api/networkapi/templates/tags/horizontal_nav.html @@ -5,7 +5,7 @@ {% for entry in menu_pages %} {% pageurl entry.page as entry_page_url %} {% if entry.depth < 2 %} - {{ entry.menu_title }} + {{ entry.menu_title }} {% include "tags/multipage_menu_link_note.html" with menu_entry=entry %} {% endif %} {% endfor %} diff --git a/network-api/networkapi/templates/tags/multipage_menu_link_note.html b/network-api/networkapi/templates/tags/multipage_menu_link_note.html new file mode 100644 index 00000000000..e20fd33fbc7 --- /dev/null +++ b/network-api/networkapi/templates/tags/multipage_menu_link_note.html @@ -0,0 +1,18 @@ +{% load i18n %} + +{% with class="tw-text-red-80 tw-text-sm tw-ml-1" %} + {% comment %}These notes are internal facing only. Only logged in users can see them.{% endcomment %} + + {% if menu_entry.restriction == "login" %} + (logged in users only) + {% elif menu_entry.restriction == "groups" %} + (restricted to specific groups) + {% elif menu_entry.restriction == "password" %} + (password protected) + {% endif %} + + {% if not menu_entry.page.live %} + (draft) + {% endif %} + +{% endwith %} diff --git a/network-api/networkapi/wagtailpages/__init__.py b/network-api/networkapi/wagtailpages/__init__.py index bf649cad20b..b8b6be5aa82 100644 --- a/network-api/networkapi/wagtailpages/__init__.py +++ b/network-api/networkapi/wagtailpages/__init__.py @@ -1,7 +1,6 @@ -import functools +from django.utils.translation import trans_real -import django -from django.utils.translation.trans_real import accept_language_re +from .override_utils import parse_accept_lang_header, to_language # WARNING: this is not necessarily a good idea, but is the only way to override # Django's default behaviour of requiring language codes to be lowercased. @@ -9,46 +8,7 @@ # all the core functionality that relies on this - e.g., url resolvers that # the Django admin and third party apps use. # A fix upstream has been asked in https://code.djangoproject.com/ticket/31795 - - -def language_code_to_iso_3166(language): - """Turn a language name (en-us) into an ISO 3166 format (en-US).""" - language, _, country = language.lower().partition("-") - if country: - return f"{language}-{country.upper()}" - return language - - -def to_language(locale): - """Turn a locale name (en_US) into a language name (en-US).""" - return locale.replace("_", "-") - - -@functools.lru_cache(maxsize=1000) -def parse_accept_lang_header(lang_string): - """ - Parse the lang_string, which is the body of an HTTP Accept-Language - header, and return a tuple of (lang, q-value), ordered by 'q' values. - Return an empty tuple if there are any format errors in lang_string. - """ - result = [] - pieces = accept_language_re.split(lang_string.lower()) - if pieces[-1]: - return () - for i in range(0, len(pieces) - 1, 3): - first, lang, priority = pieces[i : i + 3] - if first: - return () - if priority: - priority = float(priority) - else: - priority = 1.0 - result.append((language_code_to_iso_3166(lang), priority)) - result.sort(key=lambda k: k[1], reverse=True) - return tuple(result) - - # Replace some functions in django.utils.translation.trans_real with our own # versions that support a language in the form en-US instead of en-us. -django.utils.translation.trans_real.to_language = to_language -django.utils.translation.trans_real.parse_accept_lang_header = parse_accept_lang_header +trans_real.to_language = to_language +trans_real.parse_accept_lang_header = parse_accept_lang_header diff --git a/network-api/networkapi/wagtailpages/override_utils.py b/network-api/networkapi/wagtailpages/override_utils.py new file mode 100644 index 00000000000..c16953c0f44 --- /dev/null +++ b/network-api/networkapi/wagtailpages/override_utils.py @@ -0,0 +1,40 @@ +import functools + +from django.utils.translation.trans_real import accept_language_re + + +def language_code_to_iso_3166(language): + """Turn a language name (en-us) into an ISO 3166 format (en-US).""" + language, _, country = language.lower().partition("-") + if country: + return f"{language}-{country.upper()}" + return language + + +def to_language(locale): + """Turn a locale name (en_US) into a language name (en-US).""" + return locale.replace("_", "-") + + +@functools.lru_cache(maxsize=1000) +def parse_accept_lang_header(lang_string): + """ + Parse the lang_string, which is the body of an HTTP Accept-Language + header, and return a tuple of (lang, q-value), ordered by 'q' values. + Return an empty tuple if there are any format errors in lang_string. + """ + result = [] + pieces = accept_language_re.split(lang_string.lower()) + if pieces[-1]: + return () + for i in range(0, len(pieces) - 1, 3): + first, lang, priority = pieces[i : i + 3] + if first: + return () + if priority: + priority = float(priority) + else: + priority = 1.0 + result.append((language_code_to_iso_3166(lang), priority)) + result.sort(key=lambda k: k[1], reverse=True) + return tuple(result) diff --git a/network-api/networkapi/wagtailpages/templates/wagtailpages/tags/mini_site_sidebar.html b/network-api/networkapi/wagtailpages/templates/wagtailpages/tags/mini_site_sidebar.html index 3059ea832b7..3467fb6da30 100644 --- a/network-api/networkapi/wagtailpages/templates/wagtailpages/tags/mini_site_sidebar.html +++ b/network-api/networkapi/wagtailpages/templates/wagtailpages/tags/mini_site_sidebar.html @@ -7,7 +7,7 @@ {% for entry in menu_pages %} {% pageurl entry.page as entry_page_url %}
{% endfor %} diff --git a/network-api/networkapi/wagtailpages/tests/test_utils.py b/network-api/networkapi/wagtailpages/tests/test_utils.py index d71c7cb8ea4..0df2dc14c7f 100644 --- a/network-api/networkapi/wagtailpages/tests/test_utils.py +++ b/network-api/networkapi/wagtailpages/tests/test_utils.py @@ -10,11 +10,6 @@ from wagtail.images.models import Image from wagtail.models import Collection, Locale -from networkapi.wagtailpages import ( - language_code_to_iso_3166, - parse_accept_lang_header, - to_language, -) from networkapi.wagtailpages.factory import blog as blog_factories from networkapi.wagtailpages.factory.blog import ( BlogIndexPageFactory, @@ -22,6 +17,11 @@ FeaturedBlogPagesFactory, ) from networkapi.wagtailpages.factory.profiles import ProfileFactory +from networkapi.wagtailpages.override_utils import ( + language_code_to_iso_3166, + parse_accept_lang_header, + to_language, +) from networkapi.wagtailpages.pagemodels.blog.blog import BlogAuthors, BlogPage from networkapi.wagtailpages.pagemodels.profiles import Profile from networkapi.wagtailpages.tests.base import WagtailpagesTestCase diff --git a/package-lock.json b/package-lock.json index c36f5790045..5b214b4e01e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "autoprefixer": "^10.4.20", "axe-core": "^4.10.0", "bootstrap": "^4.6.0", - "chart.js": "4.4.3", + "chart.js": "4.4.4", "classnames": "2.5.1", "countup.js": "^2.8.0", "cssnano": "^6.1.2", @@ -32,9 +32,9 @@ "react-ga": "3.3.1", "sass": "^1.59.3", "shx": "^0.3.4", - "uuid": "^10.0.0", "swiper": "^11.1.9", "tailwindcss": "^3.4.9", + "uuid": "^10.0.0", "whatwg-fetch": "^3.6.20" }, "devDependencies": { @@ -2656,9 +2656,9 @@ } }, "node_modules/chart.js": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.3.tgz", - "integrity": "sha512-qK1gkGSRYcJzqrrzdR6a+I0vQ4/R+SoODXyAjscQ/4mzuNzySaMCd+hyVxitSY1+L2fjPD1Gbn+ibNqRmwQeLw==", + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.4.tgz", + "integrity": "sha512-emICKGBABnxhMjUjlYRR12PmOXhJ2eJjEHL2/dZlWjxRAZT1D8xplLFq5M0tMQK8ja+wBS/tuVEJB5C6r7VxJA==", "dependencies": { "@kurkle/color": "^0.3.0" }, @@ -6635,11 +6635,11 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -12896,9 +12896,9 @@ } }, "chart.js": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.3.tgz", - "integrity": "sha512-qK1gkGSRYcJzqrrzdR6a+I0vQ4/R+SoODXyAjscQ/4mzuNzySaMCd+hyVxitSY1+L2fjPD1Gbn+ibNqRmwQeLw==", + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.4.tgz", + "integrity": "sha512-emICKGBABnxhMjUjlYRR12PmOXhJ2eJjEHL2/dZlWjxRAZT1D8xplLFq5M0tMQK8ja+wBS/tuVEJB5C6r7VxJA==", "requires": { "@kurkle/color": "^0.3.0" } @@ -15886,11 +15886,11 @@ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" }, "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "requires": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" } }, diff --git a/package.json b/package.json index 11b83d37451..9b491747067 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "autoprefixer": "^10.4.20", "axe-core": "^4.10.0", "bootstrap": "^4.6.0", - "chart.js": "4.4.3", + "chart.js": "4.4.4", "classnames": "2.5.1", "countup.js": "^2.8.0", "cssnano": "^6.1.2", diff --git a/requirements.txt b/requirements.txt index 0bfa96ba1a2..51ad99c678c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -99,7 +99,7 @@ django-querystring-tag==1.0.3 # via -r requirements.in django-redis==5.4.0 # via -r requirements.in -django-storages==1.14.3 +django-storages==1.14.4 # via -r requirements.in django-taggit==3.0.0 # via wagtail @@ -113,7 +113,7 @@ draftjs-exporter==2.1.7 # via wagtail et-xmlfile==1.1.0 # via openpyxl -factory-boy==3.3.0 +factory-boy==3.3.1 # via # -r requirements.in # wagtail-factories @@ -182,7 +182,7 @@ python-dateutil==2.8.2 # botocore # faker # heroku3 -python-slugify==8.0.3 +python-slugify==8.0.4 # via -r requirements.in python3-openid==3.2.0 # via social-auth-core @@ -221,7 +221,7 @@ six==1.16.0 # python-dateutil smmap==5.0.0 # via gitdb -social-auth-app-django==5.4.1 +social-auth-app-django==5.4.2 # via -r requirements.in social-auth-core==4.4.1 # via social-auth-app-django @@ -263,7 +263,7 @@ wagtail==5.2.6 # wagtail-localize-git # wagtail-metadata # wagtailmedia -wagtail-ab-testing==0.9 +wagtail-ab-testing==0.10 # via -r requirements.in wagtail-color-panel==1.5.0 # via -r requirements.in diff --git a/source/js/buyers-guide/bg-main.js b/source/js/buyers-guide/bg-main.js index a0639fb24f4..98383318ba7 100644 --- a/source/js/buyers-guide/bg-main.js +++ b/source/js/buyers-guide/bg-main.js @@ -42,7 +42,7 @@ let main = { this.fetchEnv((envData) => { env = envData; - networkSiteURL = env.NETWORK_SITE_URL; + networkSiteURL = window.location.origin; if (env.SENTRY_DSN) { // Initialize Sentry error reporting initializeSentry( diff --git a/source/js/buyers-guide/search/history.js b/source/js/buyers-guide/search/history.js index 54bfaffa240..cb11b8012f3 100644 --- a/source/js/buyers-guide/search/history.js +++ b/source/js/buyers-guide/search/history.js @@ -6,10 +6,13 @@ const parentTitle = document.querySelector(`.parent-title`); const toggle = document.querySelector(`#product-filter-pni-toggle`); /** - * ... + * Set up history management + * * @param {*} instance - * @param {*} searchBar - * @param {*} searchInput + * @param {Element} searchBar - The search bar element + * @param {Element} searchInput - The search input element + * @param {Element} mobileSearchBar - The mobile search bar element + * @param {Element} mobileSearchInput - The mobile search input element */ export function setupHistoryManagement( instance, @@ -35,10 +38,18 @@ export function setupHistoryManagement( } /** - * ... + * Update History state and update page UI accordingly + * * @param {*} instance - * @param {*} searchBar - * @param {*} searchInput + * @param {Element} searchBar - The search bar element + * @param {Element} searchInput - The search input element + * @param {Element} mobileSearchBar - The mobile search bar element + * @param {Element} mobileSearchInput - The mobile search input element + * + * @todo FIXME. We need to revisit and improve the implementation of this function. + * It's unclear why history.replaceState needs to be called twice. + * It's also unclear why there are repeated if-else statements especially for search parameters. + * Further investigation is needed to understand the purpose of this function. */ export function performInitialHistoryReplace( instance, @@ -120,10 +131,13 @@ export function performInitialHistoryReplace( } /** - * ... + * Set up the window.popstate event listener + * * @param {*} instance - * @param {*} searchBar - * @param {*} searchInput + * @param {Element} searchBar - The search bar element + * @param {Element} searchInput - The search input element + * @param {Element} mobileSearchBar - The mobile search bar element + * @param {Element} mobileSearchInput - The mobile search input element */ export function setupPopStateHandler( instance, @@ -183,7 +197,8 @@ export function setupPopStateHandler( } /** - * ... + * Apply data stored in the history state to the page + * * @param {*} instance */ export function applyHistory(instance) { diff --git a/source/js/buyers-guide/search/member-functions.js b/source/js/buyers-guide/search/member-functions.js index bb178e42d4c..9cfe673255b 100644 --- a/source/js/buyers-guide/search/member-functions.js +++ b/source/js/buyers-guide/search/member-functions.js @@ -5,7 +5,8 @@ const parentTitle = document.querySelector(`.parent-title`); const subcategories = document.querySelectorAll(`.subcategories`); /** - * ... + * Attach event listeners to the nav links and subcategory links + * * @param {*} instance */ export function setupNavLinks(instance) { @@ -116,7 +117,8 @@ export function setupNavLinks(instance) { } /** - * ... + * Attach event listeners to "go back to all" link + * * @param {*} instance */ export function setupGoBackToAll(instance) { @@ -153,8 +155,11 @@ export function setupGoBackToAll(instance) { } /** - * ... + * Attach event listeners to the Product Review nav links + * * @param {*} instance + * + * @todo See if we can make this DRYer by making use of the toggleProductReviewView function? */ export function setupReviewLinks(instance) { const navLinks = document.querySelectorAll(`.product-review-link`); @@ -179,6 +184,9 @@ export function setupReviewLinks(instance) { } } +/** + * Toggle the Product Review view + */ export function toggleProductReviewView() { const editorialContent = document.querySelector(".editorial-content"); const navLinks = document.querySelectorAll(`.product-review-link`); @@ -190,6 +198,9 @@ export function toggleProductReviewView() { } } +/** + * Toggle the category related articles section + */ export function toggleCategoryRelatedArticles(category) { const relatedArticles = document.querySelectorAll("[data-show-for-category]"); diff --git a/source/js/buyers-guide/search/pni-sort-dropdown.js b/source/js/buyers-guide/search/pni-sort-dropdown.js index 154ec8305fd..a4813c3455c 100644 --- a/source/js/buyers-guide/search/pni-sort-dropdown.js +++ b/source/js/buyers-guide/search/pni-sort-dropdown.js @@ -4,6 +4,9 @@ const DOWN_ARROW_KEY_CODE = 40; const UP_ARROW_KEY_CODE = 38; const ESCAPE_KEY_CODE = 27; +/** + * Dropdown component for sorting products on PNI + */ export class PNISortDropdown { constructor(searchFilter) { this.searchFilter = searchFilter; @@ -81,6 +84,11 @@ export class PNISortDropdown { } } + /** + * Sets the selected list item and updates the dropdown button content. + * @param {Event} e - The event (e.g., click, keydown) + * @param {boolean} [pushUpdate=true] - Whether to push the update to the history state. + */ setSelectedListItem(e, pushUpdate = true) { this.listItems.forEach((item) => { const itemDiv = item.querySelector("div"); @@ -99,12 +107,20 @@ export class PNISortDropdown { } } + /** + * Closes the dropdown list. + */ closeList() { this.listContainer.classList.add("tw-hidden"); this.dropdownButton.setAttribute("aria-expanded", false); this.dropdownButtonArrow.classList.remove("tw-rotate-180"); } + /** + * Opens the dropdown list. + * + * @param {boolean} [withFocus=false] - Whether to focus on the first list item. + */ openList(withFocus = false) { this.listContainer.classList.remove("tw-hidden"); this.dropdownButton.setAttribute("aria-expanded", true); @@ -118,6 +134,11 @@ export class PNISortDropdown { } } + /** + * Toggle the visibility of the dropdown list + * + * @param {Event} e The event (e.g., click, keydown) + */ toggleListVisibility(e) { const isExpanded = this.dropdownButton.getAttribute("aria-expanded") === "true"; @@ -140,6 +161,13 @@ export class PNISortDropdown { } } + /** + * Focus on the next list item based on the given direction. + * + * @param {number} direction - They key code of the direction to move the focus (e.g., key code for up arrow / down arrow) + * + * @todo Consider refactoring this method to make it more readable and maintainable. + */ focusNextListItem(direction) { const activeElementId = document.activeElement.id; const currentActiveElementIndex = this.listItemIds.indexOf(activeElementId); diff --git a/source/js/buyers-guide/search/pni-toggle.js b/source/js/buyers-guide/search/pni-toggle.js index b5c3a47b576..9afbd22218b 100644 --- a/source/js/buyers-guide/search/pni-toggle.js +++ b/source/js/buyers-guide/search/pni-toggle.js @@ -1,6 +1,9 @@ import { Utils } from "./utils.js"; import { gsap } from "gsap"; +/** + * The control that toggles the "Privacy Not Included" filter on the product list. + */ export class PNIToggle { constructor(searchFilter) { this.searchFilter = searchFilter; @@ -51,7 +54,13 @@ export class PNIToggle { }); } + /** + * Toggle the "Privacy Not Included" filter on the product list. + * + * @param {boolean} doFilter - Whether to filter the list for "Privacy Not Included" products. + */ togglePrivacyOnly(doFilter) { + console.log(doFilter); const { searchFilter, categoryTitle } = this; gsap.set("figure.product-box.privacy-ding", { opacity: 1, y: 0 }); diff --git a/source/js/buyers-guide/search/slider-area.js b/source/js/buyers-guide/search/slider-area.js index 524d14323cc..395030816eb 100644 --- a/source/js/buyers-guide/search/slider-area.js +++ b/source/js/buyers-guide/search/slider-area.js @@ -1,8 +1,8 @@ /** - * mouse/touch scroll functionality for the category area - * @param {*} event + * Mouse/touch scroll functionality for the sub category area on mobile + * + * @todo Rename this file to something more specific. Maybe something like subcategory-scroll.js */ - const subcategories = document.querySelectorAll(`.subcategories`); const subContainer = document.querySelector(`.subcategory-header`); const subClasses = subContainer.classList; @@ -14,6 +14,11 @@ function stop(evt) { evt.stopImmediatePropagation(); } +/** + * A event handler that initiate the scroll functionality on the sub category area + * + * @param {Event} event The event that triggered the function + */ export function markScrollStart(event) { stop(event); subClasses.add("cursor-grabbing", "select-none"); @@ -27,6 +32,11 @@ export function markScrollStart(event) { document.addEventListener(`mouseup`, markScrollEnd); } +/** + * A event handler for moving the scorll on the sub category area + * + * @param {Event} event The event that triggered the function + */ function markScrollMove(event) { subcategories.forEach((subcategory) => { subcategory.classList.add("pointer-events-none"); @@ -35,6 +45,11 @@ function markScrollMove(event) { subContainer.scrollLeft = pos.left - dx; } +/** + * A event handler for stopping the scorll on the sub category area + * + * @param {Event} event The event that triggered the function + */ function markScrollEnd(event) { stop(event); diff --git a/source/js/buyers-guide/search/utils.js b/source/js/buyers-guide/search/utils.js index f6544c1e62e..5e57d3c32e7 100644 --- a/source/js/buyers-guide/search/utils.js +++ b/source/js/buyers-guide/search/utils.js @@ -14,9 +14,10 @@ const PARENT_TITLE = document.querySelector(`.parent-title`); export class Utils { /** - *... - * @param {*} category - * @returns + * Return the title of the page based on the category passed in the argument + * + * @param {String} category name of the category + * @returns {String} title of the page */ static getTitle(category) { if (category == "None") @@ -29,9 +30,12 @@ export class Utils { } /** - * ... + * Update page header to the category passed in the argument + * * @param {*} category * @param {*} parent + * + * @todo Improve the implementation to increase code readibility */ static updateHeader(category, parent) { const headerText = document.querySelector(".category-header"); @@ -90,6 +94,7 @@ export class Utils { /** * Activate a specific category nav link + * * @param {String} category category name */ static activateCatNav(category = "None") { @@ -113,6 +118,7 @@ export class Utils { /** * Set active category nav link + * * @param {String} category name of the category */ static setActiveCatNavLink(category) { @@ -128,8 +134,11 @@ export class Utils { } /** - * ... - * @param {*} text + * Toggle products' visibility based on search text + * + * @param {String} text search text + * + * @todo Rename to "filterProductsBySearchText" */ static toggleProducts(text) { gsap.set(ALL_PRODUCTS, { opacity: 1, y: 0 }); @@ -147,7 +156,7 @@ export class Utils { } /** - * Scroll Animation used solely for the 'All Products' section + * Scroll animation used solely for the 'All Products' section */ static toggleScrollAnimation() { gsap.set("figure.product-box.d-flex", { opacity: 0, y: 100 }); @@ -204,8 +213,11 @@ export class Utils { } /** - * ... - * @param {*} category + * Toggle products' visibility based on category + * + * @param {String} category category name + * + * @todo Rename to "filterProductsByCategory" */ static showProductsForCategory(category) { gsap.set(ALL_PRODUCTS, { opacity: 1, y: 0 }); @@ -222,8 +234,9 @@ export class Utils { } /** - * ... - * @param {*} category + * Toggle CTA visibility based on category + * + * @param {String} category category name */ static toggleCtaForCategory(category) { const categoryPageCta = document.getElementById("category-featured-cta"); @@ -239,10 +252,13 @@ export class Utils { } /** - * ... - * @param {*} product - * @param {*} text - * @returns + * Test if any of the product fields contains the search text + * + * @param {Element} product DOM element of the product + * @param {String} text search text + * @returns {Boolean} Whether the product contains the search text + * + * @todo Rename to "doesProductContainSearchText" */ static test(product, text) { // Note that the following is absolutely not true for all @@ -263,16 +279,18 @@ export class Utils { } /** - * ... - * @param {*} product - * @param {*} category - * @returns + * Check if the product belongs to the category + * + * @param {Element} product DOM element of the product + * @param {String} category category name + * @returns {Boolean} Whether the product belongs to the category */ static testCategories(product, category) { if (category === "None") { return true; } + // all the categories this product belongs to const productCategories = Array.from( product.querySelectorAll(".product-categories") ); @@ -281,7 +299,7 @@ export class Utils { } /** - * ... + * Sort the products by the default sort order */ static sortFilteredProducts() { const container = document.querySelector(`.product-box-list`); @@ -316,7 +334,9 @@ export class Utils { } /** + * Toggle the visibility of "no results" notice * + * @todo Rename to "toggleNoResultsNotice" */ static checkForEmptyNotice() { let qs = `figure.product-box:not(.d-none)`; @@ -336,6 +356,11 @@ export class Utils { } } + /** + * Toggle the visibility of the creepy face and speech + * + * @todo Rename to "toggleCreepyFace" + */ static moveCreepyFace() { const CREEPINESS_FACE = document.querySelector( ".creep-o-meter-information" @@ -383,6 +408,7 @@ export class Utils { /** * Scroll to a specific subcategory nav link if it exists on the page + * * @param {string} category - The name of the category */ static scrollToSubCategory(category) { diff --git a/source/js/buyers-guide/template-js-handler/mobile-search-bar.js b/source/js/buyers-guide/template-js-handler/mobile-search-bar.js index 53170666cba..259aa6b94a1 100644 --- a/source/js/buyers-guide/template-js-handler/mobile-search-bar.js +++ b/source/js/buyers-guide/template-js-handler/mobile-search-bar.js @@ -17,6 +17,12 @@ export default () => { if (burger && burger.classList.contains("menu-open")) { document.querySelector(".burger").click(); } + + // if the search bar is open, clear the search bar + if (!searchContainer.classList.contains("tw-hidden")) { + searchContainer.querySelector(`.clear-icon`).click(); + } + searchContainer.classList.toggle("tw-hidden"); mobileCatNav.classList.toggle("tw-hidden"); }); diff --git a/source/js/main.js b/source/js/main.js index b42d024ad24..ca8d6b234ac 100644 --- a/source/js/main.js +++ b/source/js/main.js @@ -66,7 +66,7 @@ let main = { this.fetchEnv((envData) => { env = envData; - networkSiteURL = env.NETWORK_SITE_URL; + networkSiteURL = window.location.origin; if (env.SENTRY_DSN) { // Initialize Sentry error reporting diff --git a/source/sass/components/multipage-nav.scss b/source/sass/components/multipage-nav.scss index c63fdb4efaa..97193066a96 100644 --- a/source/sass/components/multipage-nav.scss +++ b/source/sass/components/multipage-nav.scss @@ -36,20 +36,4 @@ @apply tw-mb-12; } } - - .multipage-login-restricted::after { - content: "🙈"; - } - - .multipage-groups-restricted::after { - content: "👥"; - } - - .multipage-password-restricted::after { - content: "🔒"; - } - - .multipage-draft::after { - content: "🐣"; - } }