From b736656be11ee881b41de394147544eb064d20a4 Mon Sep 17 00:00:00 2001 From: Aviran Levi <143411291+aviranLevi1@users.noreply.github.com> Date: Tue, 11 Nov 2025 14:02:58 +0200 Subject: [PATCH 1/2] Release/v3.9.0 (#428) --- .../dev/js/components/confirm-dialog/index.js | 2 +- .../dev/js/components/notifications/index.js | 2 +- .../dev}/js/icons/crown-filled.js | 0 .../js/services/mixpanel/mixpanel-events.js | 5 +- assets/dev/js/utils/color-contrast-helpers.js | 94 ++ composer.json | 3 +- modules/remediation/actions/attribute.php | 7 +- modules/remediation/actions/replace.php | 11 +- modules/remediation/actions/styles.php | 138 ++- .../assets/js/actions/attribute.js | 6 +- modules/remediation/assets/js/actions/base.js | 64 +- .../remediation/assets/js/actions/replace.js | 8 +- .../remediation/assets/js/actions/styles.js | 127 ++- modules/remediation/assets/js/module.js | 5 +- .../remediation/classes/remediation-base.php | 90 +- .../components/remediation-runner.php | 26 +- .../global-remediation-relationship-entry.php | 108 ++ .../global-remediation-relationship-table.php | 74 ++ .../database/remediation-entry.php | 162 ++- .../database/remediation-table.php | 11 +- modules/remediation/module.php | 11 +- modules/remediation/rest/global-item.php | 150 +++ .../remediation/rest/global-items-group.php | 100 ++ modules/remediation/rest/global-items.php | 126 +++ modules/remediation/rest/heading-level.php | 3 +- modules/remediation/rest/item.php | 7 +- .../assets/src/components/dismiss-button.js | 46 +- .../assets/src/components/feedback-form.js | 16 +- .../assets/src/components/rating-form.js | 33 +- .../assets/src/components/review-form.js | 59 +- .../reviews/assets/src/hooks/use-settings.js | 120 ++- .../assets/src/layouts/user-feedback-form.js | 116 +- modules/reviews/module.php | 33 + modules/scanner/assets/js/api/APIScanner.js | 72 ++ modules/scanner/assets/js/app.js | 84 +- .../js/components/alt-text-form/index.js | 77 +- .../js/components/block-button/button-menu.js | 126 +++ .../block-button/global-button-menu.js | 217 ++++ .../js/components/block-button/index.js | 6 +- .../components/block-button/manage-button.js | 47 +- .../components/color-contrast-form/index.js | 85 +- .../components/empty-manage-message/index.js | 41 + .../js/components/form-navigation/index.js | 69 +- .../{header-container.js => app-header.js} | 35 +- .../js/components/header/dropdown-menu.js | 83 +- .../assets/js/components/header/index.js | 68 +- .../js/components/header/stats/index.js | 19 - .../header/stats/management-stats.js | 41 +- .../header/subheader/breadcrumbs.js | 28 +- .../subheader/heading-structure/index.js | 17 +- .../js/components/header/subheader/index.js | 11 +- .../title/{main-title.js => app-title.js} | 6 +- .../js/components/header/title/index.js | 21 - .../header/title/manage-fixes-title.js | 17 - .../header/title/manage-headings-title.js | 27 - .../assets/js/components/main-list/index.js | 2 +- .../js/components/manage-alt-text/index.js | 84 ++ .../manage-color-contrast/color-data.js | 42 + .../components/manage-color-contrast/index.js | 86 ++ .../global/delete-global-item.js | 55 + .../global/manage-global-actions.js | 97 ++ .../components/manage-footer-actions/index.js | 12 + .../page/manage-page-actions.js | 97 ++ .../page/set-global-remediation-modal.js | 45 + .../manage-footer-actions/page/set-global.js | 177 ++++ .../js/components/manage-item-header/index.js | 73 ++ .../assets/js/components/manage-list/index.js | 31 - .../disable-button.js | 29 - .../manage-remediation-list/index.js | 43 + .../global/delete-global-button.js | 59 ++ .../global/global-remediation-control-menu.js | 126 +++ .../manage-global-remediation-control.js | 38 + .../manage-remediation-main-controls/index.js | 2 + .../page}/delete-button.js | 41 +- .../page/disable-button.js | 22 + .../page}/enable-button.js | 14 +- .../page/manage-remediation-control.js} | 22 +- .../js/components/manual-fix-form/index.js | 60 +- .../manual-fix-form/resolve-with-ai.js | 223 ++-- .../js/components/quota-message/index.js | 2 +- .../delete-global-remediation-modal.js | 64 ++ .../delete-page-remediation-modal.js} | 28 +- .../disable-global-remediation-modal.js | 54 + .../enable-global-remediation-modal.js | 54 + .../remediation-confirmation-modal/index.js | 4 + .../js/components/remediation-form/index.js | 65 +- .../remediation-form/remediation-snippet.js | 190 ++-- .../js/components/resolved-message/index.js | 16 +- .../upgrade-info-tip/upgrade-content.js | 14 +- modules/scanner/assets/js/constants/index.js | 4 +- .../js/context/scanner-wizard-context.js | 401 +------ .../scanner/assets/js/context/tabs-context.js | 49 + .../useScannerWizardActions.js | 254 +++++ .../useScannerWizardEffects.js | 95 ++ .../scanner-context/useScannerWizardState.js | 90 ++ .../assets/js/hooks/use-alt-text-form.js | 159 ++- .../js/hooks/use-color-contrast-form.js | 267 ++--- .../js/hooks/use-global-manage-actions.js | 234 ++++ .../assets/js/hooks/use-manage-actions.js | 23 +- .../assets/js/hooks/use-manual-fix-form.js | 99 +- .../assets/js/hooks/use-scanner-settings.js | 29 +- .../scanner/assets/js/images/empty-image.js | 67 ++ modules/scanner/assets/js/images/index.js | 1 + modules/scanner/assets/js/index.js | 29 +- modules/scanner/assets/js/layouts/index.js | 14 +- .../assets/js/layouts/manage-main-layout.js | 47 - .../management/manage-alt-text-layout.js | 75 ++ .../manage-color-contrast-layout.js | 98 ++ .../layouts/management/manage-main-layout.js | 120 +++ .../manage-manual-layout.js} | 37 +- .../layouts/{ => scanner}/alt-text-layout.js | 4 +- .../{ => scanner}/color-contrast-layout.js | 2 +- .../{ => scanner}/heading-structure-layout.js | 0 .../js/layouts/{ => scanner}/main-layout.js | 0 .../js/layouts/{ => scanner}/manual-layout.js | 15 +- .../assets/js/services/scanner-wizard.js | 4 +- .../assets/js/static/global-infotip-image.png | Bin 0 -> 94429 bytes .../scanner/assets/js/styles/app.styles.js | 69 +- .../assets/js/styles/manual-fixes.styles.js | 20 +- .../scanner/assets/js/types/scanner-item.js | 4 + .../assets/js/utils/build-path-to-parent.js | 26 + .../assets/js/utils/calc-color-ratio.js | 2 + .../scanner/assets/js/utils/convert-colors.js | 30 - .../assets/js/utils/focus-on-element.js | 17 - .../js/utils/get-element-css-selector.js | 4 +- .../assets/js/utils/get-initial-tab.js | 7 + .../js/utils/get-outer-html-by-xpath.js | 44 + .../assets/js/utils/validate-headings.js | 4 +- modules/scanner/rest/scanner-results.php | 4 +- modules/settings/assets/js/app.js | 4 +- .../js/components/analytics/charts-list.js | 3 +- .../components/analytics/charts/line-chart.js | 2 +- .../capabilities-item/pro-item-infotip.js | 2 +- .../js/components/connect-modal/check-icon.js | 2 +- .../assets/js/components/copy-link/index.js | 2 +- .../assets/js/components/edit-link/index.js | 2 +- .../settings/assets/js/components/index.js | 1 - .../media-uploader/media-uploader.js | 2 +- .../js/components/my-account-menu/index.js | 12 +- .../components/my-account-menu/popup-menu.js | 8 +- .../assets/js/components/quota-bar/index.js | 5 +- .../components/quota-bar/quota-bar-group.js | 3 +- .../components/quota-bar/quota-indicator.js | 6 +- .../js/components/sidebar-menu/menu-item.js | 2 +- .../assets/js/components/sidebar-menu/menu.js | 12 +- .../tooltips/accessibility-statement.js | 2 +- .../sidebar-menu/tooltips/analytics.js | 2 +- .../js/components/sitemap-settings/index.js | 2 +- .../skip-to-content-settings/index.js | 2 +- .../js/components/upgrade-modal/index.js | 2 +- .../assets/js/layouts/position-settings.js | 3 +- .../settings/assets/js/layouts/quota-bar.js | 2 +- modules/settings/assets/js/layouts/sidebar.js | 2 +- .../assets/js/layouts/top-bar-menu.js | 4 +- .../js/pages/accessibility-statement.js | 2 +- .../assets/js/pages/assistant/stats/index.js | 2 +- .../settings/banners/bf-sale-2025-banner.php | 172 +++ .../banners/images/bf-2025-banner.png | Bin 0 -> 55061 bytes modules/settings/module.php | 2 + package-lock.json | 997 +++++++++--------- package.json | 5 +- pojo-accessibility.php | 4 +- readme.txt | 10 +- 163 files changed, 6376 insertions(+), 2286 deletions(-) rename {modules/settings/assets => assets/dev}/js/icons/crown-filled.js (100%) create mode 100644 assets/dev/js/utils/color-contrast-helpers.js create mode 100644 modules/remediation/database/global-remediation-relationship-entry.php create mode 100644 modules/remediation/database/global-remediation-relationship-table.php create mode 100644 modules/remediation/rest/global-item.php create mode 100644 modules/remediation/rest/global-items-group.php create mode 100644 modules/remediation/rest/global-items.php create mode 100644 modules/scanner/assets/js/components/block-button/button-menu.js create mode 100644 modules/scanner/assets/js/components/block-button/global-button-menu.js create mode 100644 modules/scanner/assets/js/components/empty-manage-message/index.js rename modules/scanner/assets/js/components/header/{header-container.js => app-header.js} (70%) delete mode 100644 modules/scanner/assets/js/components/header/stats/index.js rename modules/scanner/assets/js/components/header/title/{main-title.js => app-title.js} (78%) delete mode 100644 modules/scanner/assets/js/components/header/title/index.js delete mode 100644 modules/scanner/assets/js/components/header/title/manage-fixes-title.js delete mode 100644 modules/scanner/assets/js/components/header/title/manage-headings-title.js create mode 100644 modules/scanner/assets/js/components/manage-alt-text/index.js create mode 100644 modules/scanner/assets/js/components/manage-color-contrast/color-data.js create mode 100644 modules/scanner/assets/js/components/manage-color-contrast/index.js create mode 100644 modules/scanner/assets/js/components/manage-footer-actions/global/delete-global-item.js create mode 100644 modules/scanner/assets/js/components/manage-footer-actions/global/manage-global-actions.js create mode 100644 modules/scanner/assets/js/components/manage-footer-actions/index.js create mode 100644 modules/scanner/assets/js/components/manage-footer-actions/page/manage-page-actions.js create mode 100644 modules/scanner/assets/js/components/manage-footer-actions/page/set-global-remediation-modal.js create mode 100644 modules/scanner/assets/js/components/manage-footer-actions/page/set-global.js create mode 100644 modules/scanner/assets/js/components/manage-item-header/index.js delete mode 100644 modules/scanner/assets/js/components/manage-list/index.js delete mode 100644 modules/scanner/assets/js/components/manage-remediation-buttons/disable-button.js create mode 100644 modules/scanner/assets/js/components/manage-remediation-list/index.js create mode 100644 modules/scanner/assets/js/components/manage-remediation-main-controls/global/delete-global-button.js create mode 100644 modules/scanner/assets/js/components/manage-remediation-main-controls/global/global-remediation-control-menu.js create mode 100644 modules/scanner/assets/js/components/manage-remediation-main-controls/global/manage-global-remediation-control.js create mode 100644 modules/scanner/assets/js/components/manage-remediation-main-controls/index.js rename modules/scanner/assets/js/components/{manage-remediation-buttons => manage-remediation-main-controls/page}/delete-button.js (52%) create mode 100644 modules/scanner/assets/js/components/manage-remediation-main-controls/page/disable-button.js rename modules/scanner/assets/js/components/{manage-remediation-buttons => manage-remediation-main-controls/page}/enable-button.js (58%) rename modules/scanner/assets/js/components/{manage-remediation-buttons/index.js => manage-remediation-main-controls/page/manage-remediation-control.js} (54%) create mode 100644 modules/scanner/assets/js/components/remediation-confirmation-modal/delete-global-remediation-modal.js rename modules/scanner/assets/js/components/{delete-remediation-modal/index.js => remediation-confirmation-modal/delete-page-remediation-modal.js} (61%) create mode 100644 modules/scanner/assets/js/components/remediation-confirmation-modal/disable-global-remediation-modal.js create mode 100644 modules/scanner/assets/js/components/remediation-confirmation-modal/enable-global-remediation-modal.js create mode 100644 modules/scanner/assets/js/components/remediation-confirmation-modal/index.js create mode 100644 modules/scanner/assets/js/context/tabs-context.js create mode 100644 modules/scanner/assets/js/hooks/scanner-context/useScannerWizardActions.js create mode 100644 modules/scanner/assets/js/hooks/scanner-context/useScannerWizardEffects.js create mode 100644 modules/scanner/assets/js/hooks/scanner-context/useScannerWizardState.js create mode 100644 modules/scanner/assets/js/hooks/use-global-manage-actions.js create mode 100644 modules/scanner/assets/js/images/empty-image.js delete mode 100644 modules/scanner/assets/js/layouts/manage-main-layout.js create mode 100644 modules/scanner/assets/js/layouts/management/manage-alt-text-layout.js create mode 100644 modules/scanner/assets/js/layouts/management/manage-color-contrast-layout.js create mode 100644 modules/scanner/assets/js/layouts/management/manage-main-layout.js rename modules/scanner/assets/js/layouts/{remediation-layout.js => management/manage-manual-layout.js} (60%) rename modules/scanner/assets/js/layouts/{ => scanner}/alt-text-layout.js (94%) rename modules/scanner/assets/js/layouts/{ => scanner}/color-contrast-layout.js (97%) rename modules/scanner/assets/js/layouts/{ => scanner}/heading-structure-layout.js (100%) rename modules/scanner/assets/js/layouts/{ => scanner}/main-layout.js (100%) rename modules/scanner/assets/js/layouts/{ => scanner}/manual-layout.js (78%) create mode 100644 modules/scanner/assets/js/static/global-infotip-image.png create mode 100644 modules/scanner/assets/js/utils/build-path-to-parent.js create mode 100644 modules/scanner/assets/js/utils/get-initial-tab.js create mode 100644 modules/scanner/assets/js/utils/get-outer-html-by-xpath.js create mode 100644 modules/settings/banners/bf-sale-2025-banner.php create mode 100644 modules/settings/banners/images/bf-2025-banner.png diff --git a/assets/dev/js/components/confirm-dialog/index.js b/assets/dev/js/components/confirm-dialog/index.js index fd294ad0..d0cadc9d 100644 --- a/assets/dev/js/components/confirm-dialog/index.js +++ b/assets/dev/js/components/confirm-dialog/index.js @@ -1,4 +1,4 @@ -import { AlertTriangleFilledIcon } from '@elementor/icons'; +import AlertTriangleFilledIcon from '@elementor/icons/AlertTriangleFilledIcon'; import Button from '@elementor/ui/Button'; import Dialog from '@elementor/ui/Dialog'; import DialogActions from '@elementor/ui/DialogActions'; diff --git a/assets/dev/js/components/notifications/index.js b/assets/dev/js/components/notifications/index.js index 66cc545b..2237dfb4 100644 --- a/assets/dev/js/components/notifications/index.js +++ b/assets/dev/js/components/notifications/index.js @@ -1,6 +1,6 @@ import Alert from '@elementor/ui/Alert'; import Snackbar from '@elementor/ui/Snackbar'; -import { useNotificationSettings } from '@ea11y/hooks'; +import { useNotificationSettings } from '@ea11y-apps/global/hooks/use-notifications'; const Notifications = ({ type, message }) => { const { diff --git a/modules/settings/assets/js/icons/crown-filled.js b/assets/dev/js/icons/crown-filled.js similarity index 100% rename from modules/settings/assets/js/icons/crown-filled.js rename to assets/dev/js/icons/crown-filled.js diff --git a/assets/dev/js/services/mixpanel/mixpanel-events.js b/assets/dev/js/services/mixpanel/mixpanel-events.js index de113e44..9e5b25fa 100644 --- a/assets/dev/js/services/mixpanel/mixpanel-events.js +++ b/assets/dev/js/services/mixpanel/mixpanel-events.js @@ -38,7 +38,7 @@ export const mixpanelEvents = { fixWithAiButtonClicked: 'fix_with_ai_button_clicked', markAsDecorativeSelected: 'mark_as_decorative_selected', resolveButtonClicked: 'resolve_button_clicked', - navigationImageClicked: 'navigation_image_clicked', + navigationChanged: 'navigation_changed', aiSuggestionAccepted: 'ai_suggestion_accepted', markAsResolveClicked: 'mark_as_resolve_clicked', issueSkipped: 'issue_skipped', @@ -50,6 +50,9 @@ export const mixpanelEvents = { contrastResetClicked: 'contrast_reset_clicked', backgroundAdaptorTriggered: 'background_adaptor_triggered', backgroundAdaptorChanged: 'background_adaptor_changed', + tabSelected: 'tab_selected', + markAsGlobalToggled: 'mark_as_global_toggled', + applyGlobalFixConfirmationClicked: 'apply_global_fix_confirmation_clicked', // Accessibility assistant dashboard assistantDashboardHistoryLogsButtonClicked: 'history_logs_button_clicked', diff --git a/assets/dev/js/utils/color-contrast-helpers.js b/assets/dev/js/utils/color-contrast-helpers.js new file mode 100644 index 00000000..1edbaefb --- /dev/null +++ b/assets/dev/js/utils/color-contrast-helpers.js @@ -0,0 +1,94 @@ +import postcss from 'postcss'; + +export const isValidCSS = (cssText) => { + try { + // Basic checks for common malicious patterns + if (!cssText || typeof cssText !== 'string') { + return false; + } + postcss.parse(cssText); + return true; + } catch (e) { + return false; + } +}; + +export const rgbOrRgbaToHex = (color) => { + const match = color.match( + /rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/i, + ); + if (!match) { + return null; + } // Not an RGB or RGBA string + + const r = parseInt(match[1]).toString(16).padStart(2, '0'); + const g = parseInt(match[2]).toString(16).padStart(2, '0'); + const b = parseInt(match[3]).toString(16).padStart(2, '0'); + + // If alpha present and less than 1, include it + if (match[4] !== undefined && parseFloat(match[4]) < 1) { + const a = Math.round(parseFloat(match[4]) * 255) + .toString(16) + .padStart(2, '0'); + return `#${r}${g}${b}${a}`.toUpperCase(); // 8-digit hex with alpha + } + + return `#${r}${g}${b}`.toUpperCase(); // 6-digit hex +}; + +export const getDataFromCss = (cssRules) => { + const result = { color: null, background: null }; + + const ruleMatches = cssRules.matchAll(/([^{]+)\s*\{([^}]+)\}/g); + + for (const [, selector, declarations] of ruleMatches) { + let element; + try { + element = document.querySelector(selector.trim()); + } catch { + continue; // Skip invalid selectors + } + + const colorMatch = declarations.match( + /(?get_element_by_xpath( $this->data['xpath'] ); + $element_node = $this->data['global'] + ? $this->get_element_by_xpath_with_snippet_fallback( $this->data['xpath'], $this->data['find'] ) + : $this->get_element_by_xpath( $this->data['xpath'] ); + if ( ! $element_node ) { + $this->use_frontend = true; return null; } + switch ( $this->data['action'] ) { case 'update': case 'add': diff --git a/modules/remediation/actions/replace.php b/modules/remediation/actions/replace.php index d785e313..28e4df69 100644 --- a/modules/remediation/actions/replace.php +++ b/modules/remediation/actions/replace.php @@ -17,15 +17,20 @@ class Replace extends Remediation_Base { public static string $type = 'replace'; public function run() : ?DOMDocument { - $element_node = $this->get_element_by_xpath( $this->data['xpath'] ); + $element_node = $this->data['global'] + ? $this->get_element_by_xpath_with_snippet_fallback( $this->data['xpath'], $this->data['find'] ) + : $this->get_element_by_xpath( $this->data['xpath'] ); + if ( ! $element_node instanceof \DOMElement ) { + $this->use_frontend = true; return null; // nothing to do } $outer_html = $this->dom->saveHTML( $element_node ); if ( stripos( $outer_html, $this->data['find'] ) === false ) { - return $this->dom; + $this->use_frontend = true; + return null; } $updated_html = str_ireplace( $this->data['find'], $this->data['replace'], $outer_html ); @@ -36,7 +41,7 @@ public function run() : ?DOMDocument { $tmp_dom = new DOMDocument( '1.0', 'UTF-8' ); $tmp_dom->loadHTML( - mb_convert_encoding( $updated_html, 'HTML-ENTITIES', 'UTF-8' ), + mb_encode_numericentity( $updated_html, [ 0x80, 0x10FFFF, 0, 0x1FFFFF ], 'UTF-8' ), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD | LIBXML_NOERROR | LIBXML_NOWARNING ); diff --git a/modules/remediation/actions/styles.php b/modules/remediation/actions/styles.php index c709f0e8..fdba7c61 100644 --- a/modules/remediation/actions/styles.php +++ b/modules/remediation/actions/styles.php @@ -3,6 +3,7 @@ namespace EA11y\Modules\Remediation\Actions; use DOMDocument; +use DOMElement; use EA11y\Modules\Remediation\Classes\Remediation_Base; if ( ! defined( 'ABSPATH' ) ) { @@ -14,14 +15,145 @@ */ class Styles extends Remediation_Base { public static string $type = 'styles'; + public static string $style_id = 'ea11y-remediation-styles'; - public function __construct( DOMDocument $dom, $data ) { - parent::__construct( $dom, $data ); - $this->use_frontend = true; + /** + * Build a CSS selector string for a given DOMElement. + * + * @param DOMElement|null $element + * @return string|null + */ + public function get_element_css_selector( ?DOMElement $element ): ?string { + if ( ! $element ) { + return null; + } + + $parts = []; + + while ( $element && XML_ELEMENT_NODE === $element->nodeType ) { + $selector = strtolower( $element->tagName ); + + // If element has ID, stop here + if ( $element->hasAttribute( 'id' ) ) { + $selector .= '#' . $element->getAttribute( 'id' ); + array_unshift( $parts, $selector ); + break; + } + + // Add classes unless body + if ( $element->hasAttribute( 'class' ) && strtolower( $element->tagName ) !== 'body' ) { + $classes = preg_split( '/\s+/', trim( $element->getAttribute( 'class' ) ) ); + if ( ! empty( $classes ) ) { + $selector .= '.' . implode( '.', $classes ); + } + } + + // Add nth-of-type if needed + $parent = $element->parentNode; + if ( $parent instanceof DOMElement ) { + $tag_name = $element->tagName; + $siblings = []; + foreach ( $parent->childNodes as $child ) { + if ( $child instanceof DOMElement && $child->tagName === $tag_name ) { + $siblings[] = $child; + } + } + + if ( count( $siblings ) > 1 ) { + foreach ( $siblings as $i => $sibling ) { + if ( $sibling->isSameNode( $element ) ) { + $selector .= ':nth-of-type(' . ( $i + 1 ) . ')'; + break; + } + } + } + } + + array_unshift( $parts, $selector ); + $element = $element->parentNode instanceof DOMElement ? $element->parentNode : null; + } + + return implode( ' > ', $parts ); + } + + /** + * Replace CSS selectors for color and background-color rules. + * + * @param string $css The original CSS string + * @param string|null $color_selector New selector for color rule + * @param string|null $bg_selector New selector for background-color rule + * @return string The modified CSS + */ + public function replace_css_selectors( + string $css, + ?string $color_selector = null, + ?string $bg_selector = null + ): string { + // Match full CSS blocks like "selector { ... }" + preg_match_all( '/([^{]+)\{([^}]+)\}/', $css, $matches, PREG_SET_ORDER ); + + $result = ''; + + foreach ( $matches as $match ) { + $rules = trim( $match[2] ); + + // Find color value + if ( $color_selector && preg_match( '/(?data['rule']; + if ( $this->data['global'] ) { + $el_color = $this->get_element_by_xpath_with_snippet_fallback( $this->data['xpath'], $this->data['find'] ); + $el_bg = $this->get_element_by_xpath_with_snippet_fallback( $this->data['parentXPath'], $this->data['parentFind'] ); + $color_css_selector = $this->get_element_css_selector( $el_color ); + $bg_css_selector = $this->get_element_css_selector( $el_bg ); + + if ( ! $color_css_selector && ! $bg_css_selector ) { + $this->use_frontend = true; + return null; + } + + $rule = $this->replace_css_selectors( $rule, $color_css_selector, $bg_css_selector ); + } + + // Find or create element + $head = $this->dom->getElementsByTagName( 'head' )->item( 0 ); + if ( ! $head ) { + $head = $this->dom->createElement( 'head' ); + $html_element = $this->dom->getElementsByTagName( 'html' )->item( 0 ); + if ( $html_element ) { + $html_element->insertBefore( $head, $this->dom->getElementsByTagName( 'body' )->item( 0 ) ); + } else { + $this->dom->appendChild( $head ); + } + } + + // Create '; + + return $links; + } + public function __construct() { add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); add_action( 'admin_init', [ $this, 'register_base_data' ] ); add_action( 'rest_api_init', [ $this, 'register_settings' ] ); + add_filter( 'plugin_row_meta', array( $this, 'add_plugin_row_meta' ), 10, 2 ); $this->register_routes(); } diff --git a/modules/scanner/assets/js/api/APIScanner.js b/modules/scanner/assets/js/api/APIScanner.js index 2b0130b0..8b84b6c3 100644 --- a/modules/scanner/assets/js/api/APIScanner.js +++ b/modules/scanner/assets/js/api/APIScanner.js @@ -104,6 +104,38 @@ export class APIScanner extends API { }); } + static async setRemediationAsGlobal(data) { + return APIScanner.request({ + method: 'POST', + path: `${v1Prefix}/remediation/global-item`, + data, + }); + } + + static async updateGlobalRemediationForPage(data) { + return APIScanner.request({ + method: 'PUT', + path: `${v1Prefix}/remediation/global-item`, + data, + }); + } + + static async updateGlobalRemediationForAllPages(data) { + return APIScanner.request({ + method: 'PATCH', + path: `${v1Prefix}/remediation/global-item`, + data, + }); + } + + static async deleteGlobalRemediation(data) { + return APIScanner.request({ + method: 'DELETE', + path: `${v1Prefix}/remediation/global-item`, + data, + }); + } + static async updateRemediationStatusForPage(data) { return APIScanner.request({ method: 'PATCH', @@ -112,6 +144,38 @@ export class APIScanner extends API { }); } + static async updateGlobalRemediationGroupForPage(data) { + return APIScanner.request({ + method: 'PUT', + path: `${v1Prefix}/remediation/global-items-group`, + data, + }); + } + + static async updateAllGlobalRemediationForAllPages(data) { + return APIScanner.request({ + method: 'PATCH', + path: `${v1Prefix}/remediation/global-items`, + data, + }); + } + + static async updateAllGlobalRemediationForPage(data) { + return APIScanner.request({ + method: 'PUT', + path: `${v1Prefix}/remediation/global-items`, + data, + }); + } + + static async updateGlobalRemediationGroupForAllPages(data) { + return APIScanner.request({ + method: 'PATCH', + path: `${v1Prefix}/remediation/global-items-group`, + data, + }); + } + static async deleteRemediationForPage(data) { return APIScanner.request({ method: 'DELETE', @@ -120,6 +184,14 @@ export class APIScanner extends API { }); } + static async deleteGlobalRemediations(data) { + return APIScanner.request({ + method: 'DELETE', + path: `${v1Prefix}/remediation/global-items`, + data, + }); + } + static async setHeadingLevel(data) { return APIScanner.request({ method: 'POST', diff --git a/modules/scanner/assets/js/app.js b/modules/scanner/assets/js/app.js index 758eef4f..67c02e3f 100644 --- a/modules/scanner/assets/js/app.js +++ b/modules/scanner/assets/js/app.js @@ -1,28 +1,39 @@ +import Box from '@elementor/ui/Box'; import ErrorBoundary from '@elementor/ui/ErrorBoundary'; +import Tab from '@elementor/ui/Tab'; import { FocusTrap } from 'focus-trap-react'; -import { Notifications } from '@ea11y/components'; +import Notifications from '@ea11y-apps/global/components/notifications'; import { useNotificationSettings } from '@ea11y-apps/global/hooks/use-notifications'; import { mixpanelEvents, mixpanelService } from '@ea11y-apps/global/services'; import { ErrorMessage } from '@ea11y-apps/scanner/components/error-message'; import Header from '@ea11y-apps/scanner/components/header'; +import AppHeader from '@ea11y-apps/scanner/components/header/app-header'; import { Loader } from '@ea11y-apps/scanner/components/list-loader'; import { NotConnectedMessage } from '@ea11y-apps/scanner/components/not-connected-message'; import { QuotaMessage } from '@ea11y-apps/scanner/components/quota-message'; import { ResolvedMessage } from '@ea11y-apps/scanner/components/resolved-message'; import { BLOCKS, PAGE_QUOTA_LIMIT } from '@ea11y-apps/scanner/constants'; import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; +import { useTabsContext } from '@ea11y-apps/scanner/context/tabs-context'; import { AltTextLayout, + ColorContrastLayout, + HeadingStructureLayout, MainLayout, + ManageAltTextLayout, + ManageColorContrastLayout, ManageMainLayout, + ManageManualLayout, ManualLayout, - RemediationLayout, } from '@ea11y-apps/scanner/layouts'; -import { ColorContrastLayout } from '@ea11y-apps/scanner/layouts/color-contrast-layout'; -import { HeadingStructureLayout } from '@ea11y-apps/scanner/layouts/heading-structure-layout'; -import { AppContainer } from '@ea11y-apps/scanner/styles/app.styles'; +import { + AppContainer, + AppsTabPanel, + AppsTabs, +} from '@ea11y-apps/scanner/styles/app.styles'; import { removeExistingFocus } from '@ea11y-apps/scanner/utils/focus-on-element'; import { useEffect, useRef } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; const App = () => { const { notificationMessage, notificationType } = useNotificationSettings(); @@ -31,15 +42,18 @@ const App = () => { violation, resolved, openedBlock, - isManage, isError, + isManage, quotaExceeded, loading, } = useScannerWizardContext(); + + const { tabsProps, getTabProps, getTabPanelProps } = useTabsContext(); + const containerRef = useRef(null); const showResolvedMessage = Boolean( - (resolved > 0 && violation === resolved) || violation === 0, + !isManage && ((resolved > 0 && violation === resolved) || violation === 0), ); useEffect(() => { @@ -65,7 +79,7 @@ const App = () => { } }, [PAGE_QUOTA_LIMIT, quotaExceeded]); - const getBlock = () => { + const getMsgStateBlock = () => { if (!window.ea11yScannerData?.isConnected) { return ; } @@ -79,11 +93,16 @@ const App = () => { return ; } + return null; + }; + + const getIssuesBlock = () => { + if (showResolvedMessage) { + return ; + } switch (openedBlock) { case BLOCKS.main: return ; - case BLOCKS.management: - return ; case BLOCKS.altText: return ; case BLOCKS.colorContrast: @@ -91,7 +110,22 @@ const App = () => { case BLOCKS.headingStructure: return ; default: - return isManage ? : ; + return ; + } + }; + + const getManageBlock = () => { + switch (openedBlock) { + case BLOCKS.management: + return ; + case BLOCKS.altText: + return ; + case BLOCKS.colorContrast: + return ; + case BLOCKS.headingStructure: + return ; + default: + return ; } }; @@ -102,9 +136,35 @@ const App = () => { > }> + + + + + + + +
- {showResolvedMessage && !isManage ? : getBlock()} + + {getMsgStateBlock() || getIssuesBlock()} + + + {getMsgStateBlock() || getManageBlock()} + { +export const AltTextForm = ({ item, current, setCurrent, setIsEdit }) => { + const { isManage } = useScannerWizardContext(); const { error } = useToastNotification(); const { + isGlobal, + setIsGlobal, data, loadingAiText, isSubmitDisabled, @@ -36,12 +40,21 @@ export const AltTextForm = ({ items, current, setCurrent }) => { handleChange, handleCheck, handleSubmit, + handleUpdate, generateAltText, } = useAltTextForm({ current, - item: items[current], + item, }); + const onGlobalChange = (value) => { + setIsGlobal(value); + }; + + const onCancel = () => { + setIsEdit(false); + }; + const onSubmit = async () => { try { await handleSubmit(); @@ -51,6 +64,11 @@ export const AltTextForm = ({ items, current, setCurrent }) => { } }; + const onUpdate = async () => { + await handleUpdate(); + void (setIsEdit ? setIsEdit(false) : setCurrent(current + 1)); + }; + const onUpgradeHover = () => { mixpanelService.sendEvent(mixpanelEvents.upgradeSuggestionViewed, { current_plan: window.ea11yScannerData?.planData?.plan?.name, @@ -59,11 +77,15 @@ export const AltTextForm = ({ items, current, setCurrent }) => { }); }; + const applyBtnText = isManage + ? __('Apply changes', 'pojo-accessibility') + : __('Apply fix', 'pojo-accessibility'); + return ( - + { InputProps={{ endAdornment: ( - {IS_AI_ENABLED && AI_QUOTA_LIMIT ? ( + {IS_PRO_PLAN && AI_QUOTA_LIMIT ? ( { )} - + + {!isManage && ( + + )} + + + {isManage && ( + + )} + + + ); }; AltTextForm.propTypes = { - items: PropTypes.arrayOf(scannerItem).isRequired, + item: scannerItem, current: PropTypes.number.isRequired, setCurrent: PropTypes.func.isRequired, + setIsEdit: PropTypes.func, }; diff --git a/modules/scanner/assets/js/components/block-button/button-menu.js b/modules/scanner/assets/js/components/block-button/button-menu.js new file mode 100644 index 00000000..5fdc64d7 --- /dev/null +++ b/modules/scanner/assets/js/components/block-button/button-menu.js @@ -0,0 +1,126 @@ +import DotsVerticalIcon from '@elementor/icons/DotsVerticalIcon'; +import ReloadIcon from '@elementor/icons/ReloadIcon'; +import TrashIcon from '@elementor/icons/TrashIcon'; +import Box from '@elementor/ui/Box'; +import IconButton from '@elementor/ui/IconButton'; + +import Menu from '@elementor/ui/Menu'; +import MenuItem from '@elementor/ui/MenuItem'; +import MenuItemIcon from '@elementor/ui/MenuItemIcon'; +import MenuItemText from '@elementor/ui/MenuItemText'; +import PropTypes from 'prop-types'; +import { DeletePageRemediationModal } from '@ea11y-apps/scanner/components/remediation-confirmation-modal'; +import { BLOCK_TITLES } from '@ea11y-apps/scanner/constants'; +import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; +import { useManageActions } from '@ea11y-apps/scanner/hooks/use-manage-actions'; +import { StyledBanIcon } from '@ea11y-apps/scanner/styles/app.styles'; +import { useRef, useState } from '@wordpress/element'; +import { __, sprintf } from '@wordpress/i18n'; + +export const ButtonMenu = ({ group }) => { + const { sortedRemediation } = useScannerWizardContext(); + const { deleteAllRemediationForPage, updateAllRemediationForPage } = + useManageActions(); + const anchorEl = useRef(null); + + const [showDeleteModal, setShowDeleteModal] = useState(false); + const [isOpened, setIsOpened] = useState(false); + + const count = sortedRemediation[group]?.length; + + const isAllDisabled = + count === + sortedRemediation[group]?.filter( + (remediation) => !Number(remediation.active), + )?.length; + + const handleOpen = () => { + setIsOpened(true); + }; + const handleClose = () => setIsOpened(false); + + const toggleDeleteModal = () => setShowDeleteModal(!showDeleteModal); + + const onDeleteRemediation = async () => { + setShowDeleteModal(false); + await deleteAllRemediationForPage(group); + }; + + return ( + + + + + + {isAllDisabled ? ( + + + + + + + {__('Enable all', 'pojo-accessibility')} + + + ) : ( + + + + + + + {__('Disable all', 'pojo-accessibility')} + + + )} + + + + + + {__('Remove all', 'pojo-accessibility')} + + + + + ); +}; + +ButtonMenu.propTypes = { + group: PropTypes.string.isRequired, +}; diff --git a/modules/scanner/assets/js/components/block-button/global-button-menu.js b/modules/scanner/assets/js/components/block-button/global-button-menu.js new file mode 100644 index 00000000..a04e2f98 --- /dev/null +++ b/modules/scanner/assets/js/components/block-button/global-button-menu.js @@ -0,0 +1,217 @@ +import DotsVerticalIcon from '@elementor/icons/DotsVerticalIcon'; +import ReloadIcon from '@elementor/icons/ReloadIcon'; +import TrashIcon from '@elementor/icons/TrashIcon'; +import Box from '@elementor/ui/Box'; +import IconButton from '@elementor/ui/IconButton'; + +import Menu from '@elementor/ui/Menu'; +import MenuItem from '@elementor/ui/MenuItem'; +import MenuItemIcon from '@elementor/ui/MenuItemIcon'; +import MenuItemText from '@elementor/ui/MenuItemText'; +import PropTypes from 'prop-types'; +import { mixpanelEvents, mixpanelService } from '@ea11y-apps/global/services'; +import { + DeleteGlobalRemediationModal, + DisableGlobalRemediationModal, + EnableGlobalRemediationModal, +} from '@ea11y-apps/scanner/components/remediation-confirmation-modal'; +import { BLOCK_TITLES } from '@ea11y-apps/scanner/constants'; +import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; +import { useGlobalManageActions } from '@ea11y-apps/scanner/hooks/use-global-manage-actions'; +import { StyledBanIcon } from '@ea11y-apps/scanner/styles/app.styles'; +import { useRef, useState } from '@wordpress/element'; +import { __, sprintf } from '@wordpress/i18n'; + +export const GlobalButtonMenu = ({ group }) => { + const { sortedGlobalRemediation } = useScannerWizardContext(); + const { + updateGlobalRemediationGroupForPage, + updateGlobalRemediationGroupForAllPages, + deleteGlobalRemediations, + } = useGlobalManageActions(); + const anchorEl = useRef(null); + + const [showDeleteModal, setShowDeleteModal] = useState(false); + const [showDisableModal, setShowDisableModal] = useState(false); + const [showEnableModal, setShowEnableModal] = useState(false); + + const [isOpened, setIsOpened] = useState(false); + + const count = sortedGlobalRemediation[group]?.length; + + const isAllExcluded = + count === + sortedGlobalRemediation[group]?.filter( + (remediation) => !Number(remediation.active_for_page), + )?.length; + + const isAllDisabled = + count === + sortedGlobalRemediation[group]?.filter( + (remediation) => !Number(remediation.active), + )?.length; + + const handleClose = () => setIsOpened(false); + const handleOpen = () => { + setIsOpened(true); + }; + + const onShowDeleteModal = () => { + setShowDeleteModal(true); + mixpanelService.sendEvent(mixpanelEvents.popupButtonClicked, { + data: { + popupType: 'global_delete_confirmation', + buttonName: 'Remove across scans', + }, + }); + }; + const onHideDeleteModal = () => setShowDeleteModal(false); + const toggleDisableModal = () => setShowDisableModal(!showDisableModal); + const toggleEnableModal = () => setShowDisableModal(!showDisableModal); + + const onUpdateStatus = () => { + void (isAllDisabled ? setShowEnableModal(true) : setShowDisableModal(true)); + mixpanelService.sendEvent(mixpanelEvents.popupButtonClicked, { + data: { + popupType: isAllDisabled + ? 'global_enable_confirmation' + : 'global_disable_confirmation', + buttonName: isAllDisabled + ? __('Enable across scans', 'pojo-accessibility') + : __('Disable across scans', 'pojo-accessibility'), + }, + }); + }; + + const onUpdateForPage = () => { + void updateGlobalRemediationGroupForPage(isAllExcluded, group); + }; + + const onUpdateForAllPages = () => { + void updateGlobalRemediationGroupForAllPages(isAllDisabled, group); + }; + + const onDeleteRemediation = () => { + setShowDeleteModal(false); + void deleteGlobalRemediations(group); + }; + + return ( + + + + + + + {isAllExcluded ? ( + <> + + + + + + {__('Enable on this page', 'pojo-accessibility')} + + + ) : ( + <> + + + + + + {__('Disable on this page', 'pojo-accessibility')} + + + )} + + + + {isAllDisabled ? ( + <> + + + + + + {__('Enable across scans', 'pojo-accessibility')} + + + ) : ( + <> + + + + + + {__('Disable across scans', 'pojo-accessibility')} + + + )} + + + + + + + + + {__('Remove across scans', 'pojo-accessibility')} + + + + + + + + ); +}; + +GlobalButtonMenu.propTypes = { + group: PropTypes.string.isRequired, +}; diff --git a/modules/scanner/assets/js/components/block-button/index.js b/modules/scanner/assets/js/components/block-button/index.js index f6725056..b006890c 100644 --- a/modules/scanner/assets/js/components/block-button/index.js +++ b/modules/scanner/assets/js/components/block-button/index.js @@ -13,6 +13,8 @@ import { export const BlockButton = ({ title, count, block }) => { const { setOpenedBlock, isResolved } = useScannerWizardContext(); + const resolved = count === 0 || isResolved(block); + const handleClick = () => { setOpenedBlock(block); mixpanelService.sendEvent(mixpanelEvents.categoryClicked, { @@ -23,8 +25,6 @@ export const BlockButton = ({ title, count, block }) => { }); }; - const resolved = count === 0 || isResolved(block); - return ( { color={resolved ? 'success' : 'default'} > - + {title} diff --git a/modules/scanner/assets/js/components/block-button/manage-button.js b/modules/scanner/assets/js/components/block-button/manage-button.js index a36de740..33531fba 100644 --- a/modules/scanner/assets/js/components/block-button/manage-button.js +++ b/modules/scanner/assets/js/components/block-button/manage-button.js @@ -1,24 +1,28 @@ +import WorldIcon from '@elementor/icons/WorldIcon'; import Chip from '@elementor/ui/Chip'; import Typography from '@elementor/ui/Typography'; import PropTypes from 'prop-types'; import { mixpanelEvents, mixpanelService } from '@ea11y-apps/global/services'; -import { DeleteButton } from '@ea11y-apps/scanner/components/manage-remediation-buttons/delete-button'; -import { DisableButton } from '@ea11y-apps/scanner/components/manage-remediation-buttons/disable-button'; -import { EnableButton } from '@ea11y-apps/scanner/components/manage-remediation-buttons/enable-button'; -import { BLOCKS } from '@ea11y-apps/scanner/constants'; +import { ButtonMenu } from '@ea11y-apps/scanner/components/block-button/button-menu'; +import { GlobalButtonMenu } from '@ea11y-apps/scanner/components/block-button/global-button-menu'; import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; import { ActionButton, - ManageButtonGroup, ManageButtonWrap, } from '@ea11y-apps/scanner/styles/app.styles'; import { __, sprintf } from '@wordpress/i18n'; -export const ManageButton = ({ title, count, block }) => { - const { sortedRemediation, setOpenedBlock } = useScannerWizardContext(); +export const ManageButton = ({ title, count, block, global = false }) => { + const { + sortedRemediation, + sortedGlobalRemediation, + setOpenedBlock, + setIsManageGlobal, + } = useScannerWizardContext(); const handleClick = () => { setOpenedBlock(block); + setIsManageGlobal(global); mixpanelService.sendEvent(mixpanelEvents.categoryClicked, { page_url: window.ea11yScannerData?.pageData?.url, issue_count: count, @@ -27,29 +31,24 @@ export const ManageButton = ({ title, count, block }) => { }); }; - const total = sortedRemediation[block].length; - const disabled = block === BLOCKS.colorContrast || block === BLOCKS.altText; - const isAllDisabled = - sortedRemediation[block]?.length === - sortedRemediation[block]?.filter( - (remediation) => !Number(remediation.active), - )?.length; + const remediations = global ? sortedGlobalRemediation : sortedRemediation; + const total = remediations[block].length; return ( - + - + {title} : null} label={sprintf( // Translators: %1$s - active, %2$s - total __('%1$s/%2$s active', 'pojo-accessibility'), @@ -61,14 +60,11 @@ export const ManageButton = ({ title, count, block }) => { size="tiny" disabled={count === 0} /> - - {isAllDisabled ? ( - - ) : ( - - )} - - + {global ? ( + + ) : ( + + )} ); }; @@ -77,4 +73,5 @@ ManageButton.propTypes = { title: PropTypes.string.isRequired, count: PropTypes.number.isRequired, block: PropTypes.string.isRequired, + global: PropTypes.bool, }; diff --git a/modules/scanner/assets/js/components/color-contrast-form/index.js b/modules/scanner/assets/js/components/color-contrast-form/index.js index 6176623e..09efdca8 100644 --- a/modules/scanner/assets/js/components/color-contrast-form/index.js +++ b/modules/scanner/assets/js/components/color-contrast-form/index.js @@ -1,36 +1,44 @@ import Alert from '@elementor/ui/Alert'; import AlertTitle from '@elementor/ui/AlertTitle'; +import Box from '@elementor/ui/Box'; import Button from '@elementor/ui/Button'; import Divider from '@elementor/ui/Divider'; import Typography from '@elementor/ui/Typography'; import PropTypes from 'prop-types'; import { mixpanelEvents, mixpanelService } from '@ea11y-apps/global/services'; +import { rgbOrRgbaToHex } from '@ea11y-apps/global/utils/color-contrast-helpers'; import { ColorSet } from '@ea11y-apps/scanner/components/color-contrast-form/color-set'; import { ColorSetDisabled } from '@ea11y-apps/scanner/components/color-contrast-form/color-set-disabled'; import { ParentSelector } from '@ea11y-apps/scanner/components/color-contrast-form/parent-selector'; +import { SetGlobal } from '@ea11y-apps/scanner/components/manage-footer-actions/page/set-global'; import { BLOCKS } from '@ea11y-apps/scanner/constants'; +import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; import { useColorContrastForm } from '@ea11y-apps/scanner/hooks/use-color-contrast-form'; import { StyledBox } from '@ea11y-apps/scanner/styles/app.styles'; import { scannerItem } from '@ea11y-apps/scanner/types/scanner-item'; import { checkContrastAA } from '@ea11y-apps/scanner/utils/calc-color-ratio'; -import { rgbOrRgbaToHex } from '@ea11y-apps/scanner/utils/convert-colors'; import { useEffect, useRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -export const ColorContrastForm = ({ items, current, setCurrent }) => { - const item = items[current]; +export const ColorContrastForm = ({ item, current, setCurrent, setIsEdit }) => { + const { isManage } = useScannerWizardContext(); const { + isGlobal, + setIsGlobal, color, background, parents, + isDisabled, resolved, backgroundChanged, + parentChanged, loading, changeColor, changeBackground, setParentSmaller, setParentLarger, onSubmit, + onUpdate, } = useColorContrastForm({ item, current, @@ -47,10 +55,15 @@ export const ColorContrastForm = ({ items, current, setCurrent }) => { } }, [item]); - const isBackgroundEnabled = item.messageArgs[3] && item.messageArgs[4]; + const isBackgroundEnabled = + item.messageArgs[3] && item.messageArgs[4] && !item.isPotential; const colorData = checkContrastAA(item.node); + const onCancel = () => { + setIsEdit(false); + }; + const handleSubmit = async () => { await onSubmit(); mixpanelService.sendEvent(mixpanelEvents.applyFixButtonClicked, { @@ -60,9 +73,32 @@ export const ColorContrastForm = ({ items, current, setCurrent }) => { background_level: parents.length, category_name: BLOCKS.colorContrast, page_url: window.ea11yScannerData?.pageData?.url, + is_global: isGlobal ? 'yes' : 'no', }); }; + const onGlobalChange = (value) => { + setIsGlobal(value); + }; + + const handleUpdate = async () => { + await onUpdate(); + void (setIsEdit ? setIsEdit(false) : setCurrent(current + 1)); + }; + + const isSubmitDisabled = + !colorData.passesAA || + isDisabled || + loading || + (item.isEdit && + color === item.messageArgs[3] && + background === item.messageArgs[4] && + !parentChanged); + + const applyBtnText = isManage + ? __('Apply changes', 'pojo-accessibility') + : __('Apply fix', 'pojo-accessibility'); + return ( @@ -113,22 +149,41 @@ export const ColorContrastForm = ({ items, current, setCurrent }) => { {colorData.ratio} - + + {!isManage && ( + + )} + + {isManage && ( + + )} + + + ); }; ColorContrastForm.propTypes = { - items: PropTypes.arrayOf(scannerItem).isRequired, + item: scannerItem, current: PropTypes.number.isRequired, setCurrent: PropTypes.func.isRequired, + setIsEdit: PropTypes.func, }; diff --git a/modules/scanner/assets/js/components/empty-manage-message/index.js b/modules/scanner/assets/js/components/empty-manage-message/index.js new file mode 100644 index 00000000..032e59d5 --- /dev/null +++ b/modules/scanner/assets/js/components/empty-manage-message/index.js @@ -0,0 +1,41 @@ +import Button from '@elementor/ui/Button'; +import Typography from '@elementor/ui/Typography'; + +import { BLOCKS } from '@ea11y-apps/scanner/constants'; +import { useTabsContext } from '@ea11y-apps/scanner/context/tabs-context'; +import { EmptyImage } from '@ea11y-apps/scanner/images'; +import { StateContainer } from '@ea11y-apps/scanner/styles/app.styles'; +import { __ } from '@wordpress/i18n'; + +export const EmptyManageMessage = () => { + const { tabsProps } = useTabsContext(); + + const handleChangeTab = (event) => { + tabsProps.onChange(event, BLOCKS.main); + }; + + return ( + + + + + {__('No fixes yet', 'pojo-accessibility')} + + + + {__( + 'Once you start fixing issues for this page, they’ll appear here.', + 'pojo-accessibility', + )} + + + + ); +}; diff --git a/modules/scanner/assets/js/components/form-navigation/index.js b/modules/scanner/assets/js/components/form-navigation/index.js index 579b053e..ff77a70c 100644 --- a/modules/scanner/assets/js/components/form-navigation/index.js +++ b/modules/scanner/assets/js/components/form-navigation/index.js @@ -1,65 +1,40 @@ -import ChevronLeftIcon from '@elementor/icons/ChevronLeftIcon'; -import ChevronRightIcon from '@elementor/icons/ChevronRightIcon'; import Box from '@elementor/ui/Box'; import Divider from '@elementor/ui/Divider'; -import IconButton from '@elementor/ui/IconButton'; -import Typography from '@elementor/ui/Typography'; +import Pagination from '@elementor/ui/Pagination'; import { styled } from '@elementor/ui/styles'; import PropTypes from 'prop-types'; import { mixpanelEvents, mixpanelService } from '@ea11y-apps/global/services'; -import { isRTL } from '@ea11y-apps/scanner/constants'; import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; -import { __, sprintf } from '@wordpress/i18n'; export const FormNavigation = ({ total, current, setCurrent }) => { const { openedBlock } = useScannerWizardContext(); - const previous = current - 1; - const next = current + 1; - const navigate = (index, direction) => () => { - setCurrent(index); - mixpanelService.sendEvent(mixpanelEvents.navigationImageClicked, { - direction, + const onChange = (event, index) => { + setCurrent(index - 1); + mixpanelService.sendEvent(mixpanelEvents.navigationChanged, { + page: index, category: openedBlock, }); }; return ( - - - - - - - - - {sprintf( - // Translators: %1$s - current, %2$s - total - __('%1$s of %2$s issues', 'pojo-accessibility'), - current + 1, - total, - )} - - - - - - - + + + + + + + + + + ); }; -const Navigation = styled(Box)` - position: absolute; +const StyledNavigationContainer = styled(Box)` + position: fixed; bottom: 0; - left: 0; - width: 100%; + right: ${({ theme }) => theme.spacing(2)}; + width: 393px; + background: ${({ theme }) => theme.palette.background.paper}; `; const StyledBox = styled(Box)` display: flex; @@ -74,10 +49,6 @@ const StyledNavigation = styled(Box)` gap: ${({ theme }) => theme.spacing(2)}; `; -const StyledIconButton = styled(IconButton)` - ${isRTL ? 'transform: rotate(180deg)' : ''} -`; - FormNavigation.propTypes = { total: PropTypes.number.isRequired, current: PropTypes.number.isRequired, diff --git a/modules/scanner/assets/js/components/header/header-container.js b/modules/scanner/assets/js/components/header/app-header.js similarity index 70% rename from modules/scanner/assets/js/components/header/header-container.js rename to modules/scanner/assets/js/components/header/app-header.js index 33f255ed..8e125269 100644 --- a/modules/scanner/assets/js/components/header/header-container.js +++ b/modules/scanner/assets/js/components/header/app-header.js @@ -1,27 +1,19 @@ -import ChevronLeftIcon from '@elementor/icons/ChevronLeftIcon'; import XIcon from '@elementor/icons/XIcon'; import Box from '@elementor/ui/Box'; import IconButton from '@elementor/ui/IconButton'; import Paper from '@elementor/ui/Paper'; import { styled } from '@elementor/ui/styles'; -import PropTypes from 'prop-types'; import { APIScanner } from '@ea11y-apps/scanner/api/APIScanner'; import { DropdownMenu } from '@ea11y-apps/scanner/components/header/dropdown-menu'; -import HeaderTitle from '@ea11y-apps/scanner/components/header/title'; -import { BLOCKS, ROOT_ID } from '@ea11y-apps/scanner/constants'; +import AppTitle from '@ea11y-apps/scanner/components/header/title/app-title'; +import { ROOT_ID } from '@ea11y-apps/scanner/constants'; import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; import { StyledHeaderContent } from '@ea11y-apps/scanner/styles/app.styles'; import { closeWidget } from '@ea11y-apps/scanner/utils/close-widget'; import { __ } from '@wordpress/i18n'; -const HeaderContainer = ({ children }) => { - const { isChanged, setOpenedBlock, isManage, setIsManage } = - useScannerWizardContext(); - - const goBack = () => { - setIsManage(false); - setOpenedBlock(BLOCKS.main); - }; +const AppHeader = () => { + const { isChanged } = useScannerWizardContext(); const onClose = () => { const widget = document.getElementById(ROOT_ID); @@ -46,17 +38,8 @@ const HeaderContainer = ({ children }) => { alignItems="center" > - {isManage && ( - - - - )} - - + @@ -73,8 +56,6 @@ const HeaderContainer = ({ children }) => { - - {children} ); }; @@ -96,8 +77,4 @@ const StyledTitleWrapper = styled(Box)` } `; -HeaderContainer.propTypes = { - children: PropTypes.node, -}; - -export default HeaderContainer; +export default AppHeader; diff --git a/modules/scanner/assets/js/components/header/dropdown-menu.js b/modules/scanner/assets/js/components/header/dropdown-menu.js index 2628f507..1bf39bca 100644 --- a/modules/scanner/assets/js/components/header/dropdown-menu.js +++ b/modules/scanner/assets/js/components/header/dropdown-menu.js @@ -1,9 +1,9 @@ import CalendarDollarIcon from '@elementor/icons/CalendarDollarIcon'; +import ChecklistIcon from '@elementor/icons/ChecklistIcon'; import ClearIcon from '@elementor/icons/ClearIcon'; import DotsHorizontalIcon from '@elementor/icons/DotsHorizontalIcon'; import ExternalLinkIcon from '@elementor/icons/ExternalLinkIcon'; import RefreshIcon from '@elementor/icons/RefreshIcon'; -import SettingsIcon from '@elementor/icons/SettingsIcon'; import ThemeBuilderIcon from '@elementor/icons/ThemeBuilderIcon'; import Box from '@elementor/ui/Box'; import IconButton from '@elementor/ui/IconButton'; @@ -11,27 +11,22 @@ import Menu from '@elementor/ui/Menu'; import MenuItem from '@elementor/ui/MenuItem'; import MenuItemIcon from '@elementor/ui/MenuItemIcon'; import MenuItemText from '@elementor/ui/MenuItemText'; -import Tooltip from '@elementor/ui/Tooltip'; import { ELEMENTOR_URL } from '@ea11y-apps/global/constants'; import { useToastNotification } from '@ea11y-apps/global/hooks'; import { mixpanelEvents, mixpanelService } from '@ea11y-apps/global/services'; import { APIScanner } from '@ea11y-apps/scanner/api/APIScanner'; import { BLOCKS } from '@ea11y-apps/scanner/constants'; import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; +import useScannerSettings from '@ea11y-apps/scanner/hooks/use-scanner-settings'; import { DisabledMenuItemText } from '@ea11y-apps/scanner/styles/app.styles'; import { areNoHeadingsDefined } from '@ea11y-apps/scanner/utils/page-headings'; import { useRef, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; export const DropdownMenu = () => { - const { - remediations, - isManage, - openedBlock, - setOpenedBlock, - setIsManage, - runNewScan, - } = useScannerWizardContext(); + const { remediations, isManage, openedBlock, setOpenedBlock, runNewScan } = + useScannerWizardContext(); + const { dashboardUrl } = useScannerSettings(); const { error } = useToastNotification(); const [isOpened, setIsOpened] = useState(false); const [loading, setLoading] = useState(false); @@ -69,16 +64,8 @@ export const DropdownMenu = () => { } }; - const goToManagement = () => { - handleClose(); - setIsManage(true); - setOpenedBlock(BLOCKS.management); - sendOnClickEvent('Manage fixes'); - }; - const goToHeadingManager = () => { handleClose(); - setIsManage(true); setOpenedBlock(BLOCKS.headingStructure); }; @@ -120,6 +107,21 @@ export const DropdownMenu = () => { {__('Rescan', 'pojo-accessibility')} + + sendOnClickEvent('View all scans')} + dense + > + + + + + {__('View all scans', 'pojo-accessibility')} + + + {remediations.length > 0 ? ( { )} - {!remediations.length ? ( - - - - - - - {__('Manage fixes', 'pojo-accessibility')} - - - - ) : ( - - - - - - {__('Manage fixes', 'pojo-accessibility')} - - - )} - {!areNoHeadingsDefined() && ( { results, loading, isError, + isManage, violation: violationsCount, } = useScannerWizardContext(); const { pageData, isConnected } = useScannerSettings(); @@ -29,53 +30,48 @@ const Header = () => { openedBlock === BLOCKS.main && violationsCount > 0; - const showShortHeader = - !isConnected || - isError || - !PAGE_QUOTA_LIMIT || - (!violationsCount && !loading); + const hideHeader = + !isManage && + (!isConnected || + isError || + !PAGE_QUOTA_LIMIT || + (!violationsCount && !loading)); const showStatsBlock = openedBlock === BLOCKS.main || openedBlock === BLOCKS.management; - if (showShortHeader) { - return ; + if (hideHeader) { + return null; } if (showStatsBlock) { return ( - - - - - {pageData.title} - + + + + {pageData.title} + - {showViolationsChip && ( - - )} - + {showViolationsChip && ( + + )} + - - - + {isManage ? : } + ); } - return ( - - - - ); + return ; }; export default Header; diff --git a/modules/scanner/assets/js/components/header/stats/index.js b/modules/scanner/assets/js/components/header/stats/index.js deleted file mode 100644 index 908b1460..00000000 --- a/modules/scanner/assets/js/components/header/stats/index.js +++ /dev/null @@ -1,19 +0,0 @@ -import { ManagementStats } from '@ea11y-apps/scanner/components/header/stats/management-stats'; -import { ScanStats } from '@ea11y-apps/scanner/components/header/stats/scan-stats'; -import { BLOCKS } from '@ea11y-apps/scanner/constants'; -import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; - -const Stats = () => { - const { openedBlock } = useScannerWizardContext(); - - switch (openedBlock) { - case BLOCKS.main: - return ; - case BLOCKS.management: - return ; - default: - return false; - } -}; - -export default Stats; diff --git a/modules/scanner/assets/js/components/header/stats/management-stats.js b/modules/scanner/assets/js/components/header/stats/management-stats.js index 2e390cf4..02511b3b 100644 --- a/modules/scanner/assets/js/components/header/stats/management-stats.js +++ b/modules/scanner/assets/js/components/header/stats/management-stats.js @@ -5,21 +5,42 @@ import { StyledSkeleton } from '@ea11y-apps/scanner/styles/app.styles'; import { __, sprintf } from '@wordpress/i18n'; export const ManagementStats = () => { - const { loading, remediations } = useScannerWizardContext(); + const { loading, remediations, globalRemediations } = + useScannerWizardContext(); + + const pageRemediationsActive = remediations?.filter((remediation) => + Number(remediation.active), + ).length; + + const globalRemediationsActive = globalRemediations.filter( + ({ active, active_for_page: activeForPage }) => + Number(active) && (!activeForPage || Number(activeForPage)), + ).length; + + const active = pageRemediationsActive + globalRemediationsActive; + const total = remediations?.length + globalRemediations.length; + + const getStats = () => + active > 0 ? ( + + {sprintf( + // Translators: %1$s - active, %2$s - total + __('%1$s/%2$s fixes are currently active', 'pojo-accessibility'), + active, + total, + )} + + ) : ( + + {__('0 fixes are currently active', 'pojo-accessibility')} + + ); return loading ? ( ) : ( - - {sprintf( - // Translators: %1$s - active, %2$s - total - __('%1$s/%2$s fixes are currently active', 'pojo-accessibility'), - remediations?.filter((remediation) => Number(remediation.active)) - .length, - remediations?.length, - )} - + getStats() ); }; diff --git a/modules/scanner/assets/js/components/header/subheader/breadcrumbs.js b/modules/scanner/assets/js/components/header/subheader/breadcrumbs.js index da72d962..8fa23c24 100644 --- a/modules/scanner/assets/js/components/header/subheader/breadcrumbs.js +++ b/modules/scanner/assets/js/components/header/subheader/breadcrumbs.js @@ -17,9 +17,11 @@ import { __ } from '@wordpress/i18n'; const Breadcrumbs = () => { const { + isManageGlobal, openedBlock, sortedViolations, sortedRemediation, + sortedGlobalRemediation, setOpenedBlock, altTextData, manualData, @@ -31,16 +33,27 @@ const Breadcrumbs = () => { setOpenedBlock(isManage ? BLOCKS.management : BLOCKS.main); }; + const type = isManage ? 'manage' : 'main'; const itemsData = - openedBlock === BLOCKS.altText ? altTextData : manualData[openedBlock]; + openedBlock === BLOCKS.altText + ? altTextData[type] + : manualData[openedBlock]; const resolved = itemsData?.filter((item) => item?.resolved === true).length || 0; - const items = isManage ? sortedRemediation : sortedViolations; - const count = isManage - ? items[openedBlock].length - : items[openedBlock].length - resolved; + const remediations = isManageGlobal + ? sortedGlobalRemediation + : sortedRemediation; + const items = isManage ? remediations : sortedViolations; + const itemsResolved = + items[openedBlock]?.filter((item) => + item?.global === '1' + ? item.active_for_page === '1' + : item?.active === '1', + ).length || 0; + + const count = isManage ? itemsResolved : items[openedBlock].length - resolved; return ( @@ -72,16 +85,15 @@ const Breadcrumbs = () => { } > - + )} - {items[openedBlock].length > 0 && ( + {count > 0 && ( )} diff --git a/modules/scanner/assets/js/components/header/subheader/heading-structure/index.js b/modules/scanner/assets/js/components/header/subheader/heading-structure/index.js index 7b5d628a..be7fa2bb 100644 --- a/modules/scanner/assets/js/components/header/subheader/heading-structure/index.js +++ b/modules/scanner/assets/js/components/header/subheader/heading-structure/index.js @@ -1,7 +1,6 @@ import ArrowLeftIcon from '@elementor/icons/ArrowLeftIcon'; import InfoCircleIcon from '@elementor/icons/InfoCircleIcon'; import Box from '@elementor/ui/Box'; -import Divider from '@elementor/ui/Divider'; import IconButton from '@elementor/ui/IconButton'; import Infotip from '@elementor/ui/Infotip'; import Typography from '@elementor/ui/Typography'; @@ -27,18 +26,6 @@ const HeadingStructureSubheader = () => { setOpenedBlock(isManage ? BLOCKS.management : BLOCKS.main); }; - if (isManage) { - return ( - <> - - - ); - } - return ( <> @@ -72,15 +59,13 @@ const HeadingStructureSubheader = () => { } > - + )} - - {!areNoHeadingsDefined() && ( { const { openedBlock } = useScannerWizardContext(); - switch (openedBlock) { - case BLOCKS.headingStructure: - return ; - default: - return ; - } + return openedBlock === BLOCKS.headingStructure ? ( + + ) : ( + + ); }; export default Subheader; diff --git a/modules/scanner/assets/js/components/header/title/main-title.js b/modules/scanner/assets/js/components/header/title/app-title.js similarity index 78% rename from modules/scanner/assets/js/components/header/title/main-title.js rename to modules/scanner/assets/js/components/header/title/app-title.js index bdd2254a..70a2eaa4 100644 --- a/modules/scanner/assets/js/components/header/title/main-title.js +++ b/modules/scanner/assets/js/components/header/title/app-title.js @@ -1,9 +1,9 @@ import Chip from '@elementor/ui/Chip'; -import { StyledTitle } from '@ea11y/pages/pages.styles'; import { Logo } from '@ea11y-apps/scanner/images'; +import { StyledTitle } from '@ea11y-apps/scanner/styles/app.styles'; import { __ } from '@wordpress/i18n'; -const MainTitle = () => { +const AppTitle = () => { return ( <> @@ -23,4 +23,4 @@ const MainTitle = () => { ); }; -export default MainTitle; +export default AppTitle; diff --git a/modules/scanner/assets/js/components/header/title/index.js b/modules/scanner/assets/js/components/header/title/index.js deleted file mode 100644 index d7785cbb..00000000 --- a/modules/scanner/assets/js/components/header/title/index.js +++ /dev/null @@ -1,21 +0,0 @@ -import MainTitle from '@ea11y-apps/scanner/components/header/title/main-title'; -import ManageFixesTitle from '@ea11y-apps/scanner/components/header/title/manage-fixes-title'; -import ManageHeadingsTitle from '@ea11y-apps/scanner/components/header/title/manage-headings-title'; -import { BLOCKS } from '@ea11y-apps/scanner/constants'; -import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; - -const HeaderTitle = () => { - const { openedBlock, isManage } = useScannerWizardContext(); - - if (!isManage) { - return ; - } - - if (BLOCKS.headingStructure === openedBlock) { - return ; - } - - return ; -}; - -export default HeaderTitle; diff --git a/modules/scanner/assets/js/components/header/title/manage-fixes-title.js b/modules/scanner/assets/js/components/header/title/manage-fixes-title.js deleted file mode 100644 index c88610b1..00000000 --- a/modules/scanner/assets/js/components/header/title/manage-fixes-title.js +++ /dev/null @@ -1,17 +0,0 @@ -import SettingsIcon from '@elementor/icons/SettingsIcon'; -import { StyledTitle } from '@ea11y/pages/pages.styles'; -import { __ } from '@wordpress/i18n'; - -const ManageFixesTitle = () => { - return ( - <> - - - - {__('Manage fixes', 'pojo-accessibility')} - - - ); -}; - -export default ManageFixesTitle; diff --git a/modules/scanner/assets/js/components/header/title/manage-headings-title.js b/modules/scanner/assets/js/components/header/title/manage-headings-title.js deleted file mode 100644 index 491a0944..00000000 --- a/modules/scanner/assets/js/components/header/title/manage-headings-title.js +++ /dev/null @@ -1,27 +0,0 @@ -import { ThemeBuilderIcon } from '@elementor/icons'; -import { StyledTitle } from '@ea11y/pages/pages.styles'; -import { useHeadingStructureContext } from '@ea11y-apps/scanner/context/heading-structure-context'; -import { __, sprintf } from '@wordpress/i18n'; - -const ManageHeadingsTitle = () => { - const { validationStats } = useHeadingStructureContext(); - const title = validationStats.total - ? sprintf( - // Translators: %s - headings count - __('Manage headings (%s)', 'pojo-accessibility'), - validationStats.total, - ) - : __('Manage headings', 'pojo-accessibility'); - - return ( - <> - - - - {title} - - - ); -}; - -export default ManageHeadingsTitle; diff --git a/modules/scanner/assets/js/components/main-list/index.js b/modules/scanner/assets/js/components/main-list/index.js index 2368677c..7d129580 100644 --- a/modules/scanner/assets/js/components/main-list/index.js +++ b/modules/scanner/assets/js/components/main-list/index.js @@ -19,7 +19,7 @@ export const MainList = () => { return []; } const itemsData = - key === BLOCKS.altText ? altTextData : manualData[key]; + key === BLOCKS.altText ? altTextData.main : manualData[key]; const resolved = itemsData?.filter((item) => item?.resolved === true).length || 0; diff --git a/modules/scanner/assets/js/components/manage-alt-text/index.js b/modules/scanner/assets/js/components/manage-alt-text/index.js new file mode 100644 index 00000000..72c6da5b --- /dev/null +++ b/modules/scanner/assets/js/components/manage-alt-text/index.js @@ -0,0 +1,84 @@ +import AlertTitle from '@elementor/ui/AlertTitle'; +import Box from '@elementor/ui/Box'; +import Divider from '@elementor/ui/Divider'; +import Typography from '@elementor/ui/Typography'; +import PropTypes from 'prop-types'; +import { ImagePreview } from '@ea11y-apps/scanner/components/alt-text-form/image-preview'; +import ManageFooterActions from '@ea11y-apps/scanner/components/manage-footer-actions'; +import { ManageItemHeader } from '@ea11y-apps/scanner/components/manage-item-header'; +import { + AlertWithDisabledState, + StyledBox, +} from '@ea11y-apps/scanner/styles/app.styles'; +import { remediationItem } from '@ea11y-apps/scanner/types/remediation-item'; +import { + focusOnElement, + removeExistingFocus, +} from '@ea11y-apps/scanner/utils/focus-on-element'; +import { getElementByXPath } from '@ea11y-apps/scanner/utils/get-element-by-xpath'; +import { useEffect } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +export const ManageAltText = ({ item, current, openEdit }) => { + const data = JSON.parse(item?.content); + const node = getElementByXPath(data?.xpath); + const global = item.global === '1'; + const isActive = global ? item.active_for_page === '1' : item.active === '1'; + + useEffect(() => { + void (node ? focusOnElement(node) : removeExistingFocus()); + }, [current]); + + const isDecorative = + data.attribute_name === 'role' && data.attribute_value === 'presentation'; + + return ( + + + + + + + {isDecorative ? ( + + + + {__('Decorative image', 'pojo-accessibility')} + + {__( + "(decorative images don't require descriptions)", + 'pojo-accessibility', + )} + + + ) : ( + + + {__('Alt text', 'pojo-accessibility')} + + + {data.attribute_value} + + + )} + + + ); +}; + +ManageAltText.propTypes = { + item: remediationItem, + current: PropTypes.number.isRequired, + openEdit: PropTypes.func.isRequired, +}; diff --git a/modules/scanner/assets/js/components/manage-color-contrast/color-data.js b/modules/scanner/assets/js/components/manage-color-contrast/color-data.js new file mode 100644 index 00000000..98708f12 --- /dev/null +++ b/modules/scanner/assets/js/components/manage-color-contrast/color-data.js @@ -0,0 +1,42 @@ +import Box from '@elementor/ui/Box'; +import Typography from '@elementor/ui/Typography'; +import { styled } from '@elementor/ui/styles'; +import { UnstableColorIndicator } from '@elementor/ui/unstable'; +import PropTypes from 'prop-types'; +import { SunIcon } from '@ea11y-apps/scanner/images'; +import { hexToHsl } from '@ea11y-apps/scanner/utils/convert-colors'; + +export const ColorData = ({ title, color, isActive }) => { + const hslColor = hexToHsl(color); + return ( + + + {title} + + + + {hslColor.l} + % + + + + ); +}; + +const StyledBox = styled(Box)` + color: ${({ isActive, theme }) => + isActive ? theme.palette.text.primary : theme.palette.text.disabled}; + + & svg * { + stroke: ${({ isActive, theme }) => + isActive + ? theme.palette.text.primary + : theme.palette.text.disabled}!important; + } +`; + +ColorData.propTypes = { + title: PropTypes.string.isRequired, + color: PropTypes.string.isRequired, + isActive: PropTypes.bool.isRequired, +}; diff --git a/modules/scanner/assets/js/components/manage-color-contrast/index.js b/modules/scanner/assets/js/components/manage-color-contrast/index.js new file mode 100644 index 00000000..2c5321ec --- /dev/null +++ b/modules/scanner/assets/js/components/manage-color-contrast/index.js @@ -0,0 +1,86 @@ +import Box from '@elementor/ui/Box'; +import Divider from '@elementor/ui/Divider'; +import PropTypes from 'prop-types'; +import { ColorData } from '@ea11y-apps/scanner/components/manage-color-contrast/color-data'; +import ManageFooterActions from '@ea11y-apps/scanner/components/manage-footer-actions'; +import { ManageItemHeader } from '@ea11y-apps/scanner/components/manage-item-header'; +import { BACKGROUND_ELEMENT_CLASS } from '@ea11y-apps/scanner/constants'; +import { + ManageColorAlert, + StyledBox, +} from '@ea11y-apps/scanner/styles/app.styles'; +import { remediationItem } from '@ea11y-apps/scanner/types/remediation-item'; +import { + focusOnElement, + removeExistingFocus, +} from '@ea11y-apps/scanner/utils/focus-on-element'; +import { useEffect } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +export const ManageColorContrast = ({ + item, + node, + color, + background, + current, + openEdit, +}) => { + useEffect(() => { + void (node ? focusOnElement(node) : removeExistingFocus()); + }, [current]); + + useEffect(() => { + if (background?.item) { + focusOnElement(background?.item, BACKGROUND_ELEMENT_CLASS); + } + }, [background?.item]); + + const global = item.global === '1'; + const isActive = global ? item.active_for_page === '1' : item.active === '1'; + + return ( + + + + + + + {color && ( + + )} + + {color && background && } + + {background && ( + + )} + + + + + + ); +}; + +ManageColorContrast.propTypes = { + item: remediationItem, + node: PropTypes.node.isRequired, + color: PropTypes.shape({ + item: PropTypes.node, + value: PropTypes.string, + }), + current: PropTypes.number.isRequired, + openEdit: PropTypes.func.isRequired, +}; diff --git a/modules/scanner/assets/js/components/manage-footer-actions/global/delete-global-item.js b/modules/scanner/assets/js/components/manage-footer-actions/global/delete-global-item.js new file mode 100644 index 00000000..2b602ad6 --- /dev/null +++ b/modules/scanner/assets/js/components/manage-footer-actions/global/delete-global-item.js @@ -0,0 +1,55 @@ +import TrashIcon from '@elementor/icons/TrashIcon'; +import Divider from '@elementor/ui/Divider'; +import IconButton from '@elementor/ui/IconButton'; + +import Tooltip from '@elementor/ui/Tooltip'; +import PropTypes from 'prop-types'; +import { DeleteGlobalRemediationModal } from '@ea11y-apps/scanner/components/remediation-confirmation-modal'; +import { useGlobalManageActions } from '@ea11y-apps/scanner/hooks/use-global-manage-actions'; +import { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +const DeleteGlobalItem = ({ id, rule }) => { + const { activeRequest, deleteGlobalRemediation } = useGlobalManageActions(); + const [showDeleteModal, setShowDeleteModal] = useState(false); + + const toggleDeleteModal = () => setShowDeleteModal(!showDeleteModal); + const onDeleteRemediation = async () => { + setShowDeleteModal(false); + await deleteGlobalRemediation(id, rule); + }; + + return ( + <> + + + + + + + + + ); +}; + +DeleteGlobalItem.propTypes = { + id: PropTypes.number.isRequired, + rule: PropTypes.string.isRequired, +}; + +export default DeleteGlobalItem; diff --git a/modules/scanner/assets/js/components/manage-footer-actions/global/manage-global-actions.js b/modules/scanner/assets/js/components/manage-footer-actions/global/manage-global-actions.js new file mode 100644 index 00000000..fa298a36 --- /dev/null +++ b/modules/scanner/assets/js/components/manage-footer-actions/global/manage-global-actions.js @@ -0,0 +1,97 @@ +import ChevronDownIcon from '@elementor/icons/ChevronDownIcon'; +import ReloadIcon from '@elementor/icons/ReloadIcon'; +import Box from '@elementor/ui/Box'; +import Button from '@elementor/ui/Button'; +import Menu from '@elementor/ui/Menu'; +import MenuItem from '@elementor/ui/MenuItem'; +import MenuItemText from '@elementor/ui/MenuItemText'; +import DeleteGlobalItem from '@ea11y-apps/scanner/components/manage-footer-actions/global/delete-global-item'; +import { useGlobalManageActions } from '@ea11y-apps/scanner/hooks/use-global-manage-actions'; +import { StyledBanIcon } from '@ea11y-apps/scanner/styles/app.styles'; +import { scannerItem } from '@ea11y-apps/scanner/types/scanner-item'; +import { useRef, useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +const ManageGlobalActions = ({ item }) => { + const [isOpened, setIsOpened] = useState(false); + const anchorEl = useRef(null); + + const isDisabled = item.active !== '1'; + const isExcluded = item.active_for_page !== '1'; + + const { + activeRequest, + updateGlobalRemediationForPage, + updateGlobalRemediationForAllPages, + } = useGlobalManageActions(); + + const handleOpen = () => { + setIsOpened(true); + }; + const handleClose = () => setIsOpened(false); + + const onUpdateForPage = () => { + handleClose(); + void updateGlobalRemediationForPage(item.id, isExcluded, item.rule); + }; + const onUpdateForAllPages = () => { + handleClose(); + void updateGlobalRemediationForAllPages(item.id, isDisabled, item.rule); + }; + + return ( + + {isDisabled && isExcluded && ( + + )} + + + + + {isExcluded + ? __('Enable on this page', 'pojo-accessibility') + : __('Disable on this page', 'pojo-accessibility')} + + + + + + {isDisabled + ? __('Enable across scans', 'pojo-accessibility') + : __('Disable across scans', 'pojo-accessibility')} + + + + + ); +}; + +ManageGlobalActions.propTypes = { + item: scannerItem, +}; + +export default ManageGlobalActions; diff --git a/modules/scanner/assets/js/components/manage-footer-actions/index.js b/modules/scanner/assets/js/components/manage-footer-actions/index.js new file mode 100644 index 00000000..d988d696 --- /dev/null +++ b/modules/scanner/assets/js/components/manage-footer-actions/index.js @@ -0,0 +1,12 @@ +import ManageGlobalActions from '@ea11y-apps/scanner/components/manage-footer-actions/global/manage-global-actions'; +import ManagePageActions from '@ea11y-apps/scanner/components/manage-footer-actions/page/manage-page-actions'; + +const ManageFooterActions = ({ item, isActive }) => { + return item.global === '1' ? ( + + ) : ( + + ); +}; + +export default ManageFooterActions; diff --git a/modules/scanner/assets/js/components/manage-footer-actions/page/manage-page-actions.js b/modules/scanner/assets/js/components/manage-footer-actions/page/manage-page-actions.js new file mode 100644 index 00000000..68769c2b --- /dev/null +++ b/modules/scanner/assets/js/components/manage-footer-actions/page/manage-page-actions.js @@ -0,0 +1,97 @@ +import ReloadIcon from '@elementor/icons/ReloadIcon'; +import TrashIcon from '@elementor/icons/TrashIcon'; +import Box from '@elementor/ui/Box'; +import Button from '@elementor/ui/Button'; +import Divider from '@elementor/ui/Divider'; +import IconButton from '@elementor/ui/IconButton'; +import Tooltip from '@elementor/ui/Tooltip'; +import PropTypes from 'prop-types'; +import { SetGlobal } from '@ea11y-apps/scanner/components/manage-footer-actions/page/set-global'; +import { DeletePageRemediationModal } from '@ea11y-apps/scanner/components/remediation-confirmation-modal'; +import { useManageActions } from '@ea11y-apps/scanner/hooks/use-manage-actions'; +import { StyledBanIcon } from '@ea11y-apps/scanner/styles/app.styles'; +import { remediationItem } from '@ea11y-apps/scanner/types/remediation-item'; +import { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +const ManagePageActions = ({ item, isActive }) => { + const { activeRequest, deleteRemediation, updateRemediation } = + useManageActions(item); + const [showDeleteModal, setShowDeleteModal] = useState(false); + + const toggleDeleteModal = () => setShowDeleteModal(!showDeleteModal); + const onDeleteRemediation = async () => { + setShowDeleteModal(false); + await deleteRemediation(); + }; + + const data = item?.content ? JSON.parse(item.content) : null; + + return ( + + {isActive ? ( + <> + {data?.find && } + + + ) : ( + + + + + + + + + + )} + + + ); +}; + +ManagePageActions.propTypes = { + item: remediationItem, + isActive: PropTypes.bool.isRequired, +}; + +export default ManagePageActions; diff --git a/modules/scanner/assets/js/components/manage-footer-actions/page/set-global-remediation-modal.js b/modules/scanner/assets/js/components/manage-footer-actions/page/set-global-remediation-modal.js new file mode 100644 index 00000000..1869bc91 --- /dev/null +++ b/modules/scanner/assets/js/components/manage-footer-actions/page/set-global-remediation-modal.js @@ -0,0 +1,45 @@ +import Typography from '@elementor/ui/Typography'; +import PropTypes from 'prop-types'; +import ConfirmDialog from '@ea11y-apps/global/components/confirm-dialog'; +import { __ } from '@wordpress/i18n'; + +export const SetGlobalRemediationModal = ({ + open, + hideConfirmation, + onApprove, +}) => { + return ( + + + {__( + 'This fix will apply to past and future scans, making the issue resolved.', + 'pojo-accessibility', + )} + + + ); +}; + +SetGlobalRemediationModal.propTypes = { + open: PropTypes.bool.isRequired, + hideConfirmation: PropTypes.func.isRequired, + onApprove: PropTypes.func.isRequired, +}; diff --git a/modules/scanner/assets/js/components/manage-footer-actions/page/set-global.js b/modules/scanner/assets/js/components/manage-footer-actions/page/set-global.js new file mode 100644 index 00000000..cece6da4 --- /dev/null +++ b/modules/scanner/assets/js/components/manage-footer-actions/page/set-global.js @@ -0,0 +1,177 @@ +import InfoCircleIcon from '@elementor/icons/InfoCircleIcon'; +import WorldIcon from '@elementor/icons/WorldIcon'; +import Box from '@elementor/ui/Box'; +import Button from '@elementor/ui/Button'; +import Infotip from '@elementor/ui/Infotip'; +import Switch from '@elementor/ui/Switch'; +import Typography from '@elementor/ui/Typography'; +import PropTypes from 'prop-types'; +import { ProCrownIcon } from '@ea11y/icons'; +import CrownFilled from '@ea11y-apps/global/icons/crown-filled'; +import { mixpanelEvents, mixpanelService } from '@ea11y-apps/global/services'; +import { SetGlobalRemediationModal } from '@ea11y-apps/scanner/components/manage-footer-actions/page/set-global-remediation-modal'; +import { IS_PRO_PLAN, UPGRADE_URL } from '@ea11y-apps/scanner/constants'; +import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; +import { useGlobalManageActions } from '@ea11y-apps/scanner/hooks/use-global-manage-actions'; +import infotipImageSrc from '@ea11y-apps/scanner/static/global-infotip-image.png'; +import { StyledProChip } from '@ea11y-apps/scanner/styles/app.styles'; +import { + InfotipBox, + InfotipImage, +} from '@ea11y-apps/scanner/styles/manual-fixes.styles'; +import { remediationItem } from '@ea11y-apps/scanner/types/remediation-item'; +import { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +export const SetGlobal = ({ + item, + onGlobalChange = null, + isChecked = false, +}) => { + const { openedBlock } = useScannerWizardContext(); + const { setRemediationAsGlobal } = useGlobalManageActions(); + const [showModal, setShowModal] = useState(false); + + const onShowModal = () => { + setShowModal(true); + mixpanelService.sendEvent(mixpanelEvents.popupButtonClicked, { + data: { + popupType: 'set_as_global_confirmation', + buttonName: 'Apply across scans', + }, + }); + }; + + const changeIsGlobal = (value) => { + if (onGlobalChange) { + onGlobalChange(value); + } + + mixpanelService.sendEvent(mixpanelEvents.markAsGlobalToggled, { + toggle_status: value ? 'on' : 'off', + source: 'assistant', + category_name: openedBlock, + issue_type: item.message, + rule_id: item.ruleId, + }); + }; + + const onHideModal = () => setShowModal(false); + const onSwitchChange = (event, value) => + onGlobalChange ? changeIsGlobal(value) : onShowModal(); + + const onSetAsGlobal = () => { + setShowModal(false); + void setRemediationAsGlobal(item.id); + }; + + const onUpgrade = () => { + mixpanelService.sendEvent(mixpanelEvents.upgradeButtonClicked, { + current_plan: window.ea11yScannerData?.planData?.plan?.name, + feature_locked: 'globals', + }); + }; + + return ( + + + + + + + {__('Apply across scans', 'pojo-accessibility')} + + {IS_PRO_PLAN ? ( + + + + + {__('Fix once, apply everywhere', 'pojo-accessibility')} + + + {__( + 'Apply this fix automatically to pages already scanned and to future scans.', + 'pojo-accessibility', + )} + + + + } + > + + + ) : ( + + + {__('Upgrade to unlock cross-scan fixes', 'pojo-accessibility')} + + + {__( + 'Cross-scan fixes let you resolve the same issue on all of your scanned pages with a click.', + 'pojo-accessibility', + )} + + + + + + } + > + } + size="small" + /> + + )} + + + ); +}; + +SetGlobal.propTypes = { + item: remediationItem, + onGlobalChange: PropTypes.func, + isChecked: PropTypes.bool, +}; diff --git a/modules/scanner/assets/js/components/manage-item-header/index.js b/modules/scanner/assets/js/components/manage-item-header/index.js new file mode 100644 index 00000000..3a1114a7 --- /dev/null +++ b/modules/scanner/assets/js/components/manage-item-header/index.js @@ -0,0 +1,73 @@ +import EditIcon from '@elementor/icons/EditIcon'; +import SettingsIcon from '@elementor/icons/SettingsIcon'; +import WorldIcon from '@elementor/icons/WorldIcon'; +import Box from '@elementor/ui/Box'; +import Chip from '@elementor/ui/Chip'; +import IconButton from '@elementor/ui/IconButton'; +import Tooltip from '@elementor/ui/Tooltip'; +import Typography from '@elementor/ui/Typography'; +import PropTypes from 'prop-types'; +import { + ItemHeader, + ItemTitle, +} from '@ea11y-apps/scanner/styles/manual-fixes.styles'; +import { __ } from '@wordpress/i18n'; + +export const ManageItemHeader = ({ isActive, openEdit, global }) => { + return ( + + + + + {isActive + ? __('Active fix', 'pojo-accessibility') + : __('Fix (disabled)', 'pojo-accessibility')} + + {global && ( + } + label={__('Cross-scan', 'pojo-accessibility')} + color="default" + variant="outlined" + size="tiny" + /> + )} + + + + {global ? ( + + + + ) : ( + + + + + + )} + + + ); +}; + +ManageItemHeader.propTypes = { + isActive: PropTypes.bool.isRequired, + openEdit: PropTypes.func.isRequired, + global: PropTypes.bool.isRequired, +}; diff --git a/modules/scanner/assets/js/components/manage-list/index.js b/modules/scanner/assets/js/components/manage-list/index.js deleted file mode 100644 index 34121e34..00000000 --- a/modules/scanner/assets/js/components/manage-list/index.js +++ /dev/null @@ -1,31 +0,0 @@ -import { ManageButton } from '@ea11y-apps/scanner/components/block-button/manage-button'; -import { BLOCK_TITLES, BLOCKS } from '@ea11y-apps/scanner/constants'; -import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; -import { StyledBlockButtonsBox } from '@ea11y-apps/scanner/styles/app.styles'; - -export const ManageList = () => { - const { sortedRemediation } = useScannerWizardContext(); - - return ( - - {Object.keys(sortedRemediation).flatMap((key) => { - if (sortedRemediation[key].length < 1) { - return []; - } - - const resolved = - sortedRemediation[key].filter((item) => Number(item.active)).length || - 0; - - return ( - - ); - })} - - ); -}; diff --git a/modules/scanner/assets/js/components/manage-remediation-buttons/disable-button.js b/modules/scanner/assets/js/components/manage-remediation-buttons/disable-button.js deleted file mode 100644 index f679f2ee..00000000 --- a/modules/scanner/assets/js/components/manage-remediation-buttons/disable-button.js +++ /dev/null @@ -1,29 +0,0 @@ -import BanIcon from '@elementor/icons/BanIcon'; -import Button from '@elementor/ui/Button'; -import IconButton from '@elementor/ui/IconButton'; -import { useManageActions } from '@ea11y-apps/scanner/hooks/use-manage-actions'; -import { __ } from '@wordpress/i18n'; - -export const DisableButton = ({ group }) => { - const { updateAllRemediationForPage } = useManageActions(); - return group ? ( - - - - ) : ( - - ); -}; diff --git a/modules/scanner/assets/js/components/manage-remediation-list/index.js b/modules/scanner/assets/js/components/manage-remediation-list/index.js new file mode 100644 index 00000000..c9be6aeb --- /dev/null +++ b/modules/scanner/assets/js/components/manage-remediation-list/index.js @@ -0,0 +1,43 @@ +import PropTypes from 'prop-types'; +import { ManageButton } from '@ea11y-apps/scanner/components/block-button/manage-button'; +import { BLOCK_TITLES, BLOCKS } from '@ea11y-apps/scanner/constants'; +import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; +import { StyledBlockButtonsBox } from '@ea11y-apps/scanner/styles/app.styles'; + +const ManageRemediationList = ({ global = false }) => { + const { sortedRemediation, sortedGlobalRemediation } = + useScannerWizardContext(); + + const remediations = global ? sortedGlobalRemediation : sortedRemediation; + + return ( + + {Object.keys(remediations).flatMap((key) => { + if (remediations[key].length < 1) { + return []; + } + + const resolved = remediations[key].filter( + ({ active, active_for_page: activeForPage }) => + global ? Number(activeForPage) : Number(active), + ).length; + + return ( + + ); + })} + + ); +}; + +ManageRemediationList.propTypes = { + global: PropTypes.bool, +}; + +export default ManageRemediationList; diff --git a/modules/scanner/assets/js/components/manage-remediation-main-controls/global/delete-global-button.js b/modules/scanner/assets/js/components/manage-remediation-main-controls/global/delete-global-button.js new file mode 100644 index 00000000..b5e26179 --- /dev/null +++ b/modules/scanner/assets/js/components/manage-remediation-main-controls/global/delete-global-button.js @@ -0,0 +1,59 @@ +import TrashIcon from '@elementor/icons/TrashIcon'; +import IconButton from '@elementor/ui/IconButton'; +import Tooltip from '@elementor/ui/Tooltip'; +import { mixpanelEvents, mixpanelService } from '@ea11y-apps/global/services'; +import { DeleteGlobalRemediationModal } from '@ea11y-apps/scanner/components/remediation-confirmation-modal'; +import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; +import { useGlobalManageActions } from '@ea11y-apps/scanner/hooks/use-global-manage-actions'; +import { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +const DeleteGlobalButton = () => { + const { globalRemediations } = useScannerWizardContext(); + const { deleteGlobalRemediations } = useGlobalManageActions(); + const [showDeleteModal, setShowDeleteModal] = useState(false); + + const onShowDeleteModal = () => { + setShowDeleteModal(true); + mixpanelService.sendEvent(mixpanelEvents.popupButtonClicked, { + data: { + popupType: 'global_delete_confirmation', + buttonName: 'Remove all', + }, + }); + }; + + const onHideDeleteModal = () => setShowDeleteModal(false); + + const onDeleteRemediation = async () => { + setShowDeleteModal(false); + await deleteGlobalRemediations(); + }; + + return ( + <> + + + + + + + + + ); +}; + +export default DeleteGlobalButton; diff --git a/modules/scanner/assets/js/components/manage-remediation-main-controls/global/global-remediation-control-menu.js b/modules/scanner/assets/js/components/manage-remediation-main-controls/global/global-remediation-control-menu.js new file mode 100644 index 00000000..f6169dcf --- /dev/null +++ b/modules/scanner/assets/js/components/manage-remediation-main-controls/global/global-remediation-control-menu.js @@ -0,0 +1,126 @@ +import ChevronDownIcon from '@elementor/icons/ChevronDownIcon'; +import ReloadIcon from '@elementor/icons/ReloadIcon'; +import Box from '@elementor/ui/Box'; +import Button from '@elementor/ui/Button'; +import Menu from '@elementor/ui/Menu'; +import MenuItem from '@elementor/ui/MenuItem'; +import MenuItemText from '@elementor/ui/MenuItemText'; +import PropTypes from 'prop-types'; +import { mixpanelEvents, mixpanelService } from '@ea11y-apps/global/services'; +import { + DisableGlobalRemediationModal, + EnableGlobalRemediationModal, +} from '@ea11y-apps/scanner/components/remediation-confirmation-modal'; +import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; +import { useGlobalManageActions } from '@ea11y-apps/scanner/hooks/use-global-manage-actions'; +import { StyledBanIcon } from '@ea11y-apps/scanner/styles/app.styles'; +import { useRef, useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +const GlobalRemediationControlMenu = ({ isAllExcluded, isAllDisabled }) => { + const { globalRemediations } = useScannerWizardContext(); + + const [isOpened, setIsOpened] = useState(false); + const anchorEl = useRef(null); + + const [showDisableModal, setShowDisableModal] = useState(false); + const [showEnableModal, setShowEnableModal] = useState(false); + + const { + updateAllGlobalRemediationForPage, + updateAllGlobalRemediationForAllPages, + } = useGlobalManageActions(); + + const handleOpen = () => { + setIsOpened(true); + }; + const handleClose = () => setIsOpened(false); + + const onUpdateStatus = () => { + void (isAllDisabled ? setShowEnableModal(true) : setShowDisableModal(true)); + mixpanelService.sendEvent(mixpanelEvents.popupButtonClicked, { + data: { + popupType: isAllDisabled + ? 'global_enable_confirmation' + : 'global_disable_confirmation', + buttonName: isAllDisabled + ? 'Enable across scans' + : 'Disable across scans', + }, + }); + }; + + const onUpdateForPage = () => { + void updateAllGlobalRemediationForPage(isAllExcluded); + }; + const onUpdateForAllPages = () => { + void updateAllGlobalRemediationForAllPages(isAllDisabled); + }; + + const toggleDisableModal = () => setShowDisableModal(!showDisableModal); + const toggleEnableModal = () => setShowDisableModal(!showDisableModal); + + return ( + + + + + + {isAllExcluded + ? __('Enable on this page', 'pojo-accessibility') + : __('Disable on this page', 'pojo-accessibility')} + + + + + + {isAllDisabled + ? __('Enable across scans', 'pojo-accessibility') + : __('Disable across scans', 'pojo-accessibility')} + + + + + + + ); +}; + +GlobalRemediationControlMenu.propTypes = { + isAllExcluded: PropTypes.bool.isRequired, + isAllDisabled: PropTypes.bool.isRequired, +}; + +export default GlobalRemediationControlMenu; diff --git a/modules/scanner/assets/js/components/manage-remediation-main-controls/global/manage-global-remediation-control.js b/modules/scanner/assets/js/components/manage-remediation-main-controls/global/manage-global-remediation-control.js new file mode 100644 index 00000000..9023e4f7 --- /dev/null +++ b/modules/scanner/assets/js/components/manage-remediation-main-controls/global/manage-global-remediation-control.js @@ -0,0 +1,38 @@ +import Box from '@elementor/ui/Box'; +import Divider from '@elementor/ui/Divider'; +import DeleteGlobalButton from '@ea11y-apps/scanner/components/manage-remediation-main-controls/global/delete-global-button'; +import GlobalRemediationControlMenu from '@ea11y-apps/scanner/components/manage-remediation-main-controls/global/global-remediation-control-menu'; +import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; + +const ManageGlobalRemediationControl = () => { + const { globalRemediations } = useScannerWizardContext(); + const count = globalRemediations?.length; + + const isAllExcluded = + count === + globalRemediations?.filter( + (remediation) => !Number(remediation.active_for_page), + )?.length; + + const isAllDisabled = + count === + globalRemediations?.filter((remediation) => !Number(remediation.active)) + ?.length; + + return ( + + {isAllExcluded && isAllDisabled && ( + <> + + + + )} + + + ); +}; + +export default ManageGlobalRemediationControl; diff --git a/modules/scanner/assets/js/components/manage-remediation-main-controls/index.js b/modules/scanner/assets/js/components/manage-remediation-main-controls/index.js new file mode 100644 index 00000000..5570b242 --- /dev/null +++ b/modules/scanner/assets/js/components/manage-remediation-main-controls/index.js @@ -0,0 +1,2 @@ +export { default as ManageRemediationControl } from './page/manage-remediation-control'; +export { default as ManageGlobalRemediationControl } from './global/manage-global-remediation-control'; diff --git a/modules/scanner/assets/js/components/manage-remediation-buttons/delete-button.js b/modules/scanner/assets/js/components/manage-remediation-main-controls/page/delete-button.js similarity index 52% rename from modules/scanner/assets/js/components/manage-remediation-buttons/delete-button.js rename to modules/scanner/assets/js/components/manage-remediation-main-controls/page/delete-button.js index 5d2e0213..83bb6a0e 100644 --- a/modules/scanner/assets/js/components/manage-remediation-buttons/delete-button.js +++ b/modules/scanner/assets/js/components/manage-remediation-main-controls/page/delete-button.js @@ -1,12 +1,14 @@ import TrashIcon from '@elementor/icons/TrashIcon'; -import Button from '@elementor/ui/Button'; import IconButton from '@elementor/ui/IconButton'; -import { DeleteRemediationModal } from '@ea11y-apps/scanner/components/delete-remediation-modal'; +import Tooltip from '@elementor/ui/Tooltip'; +import { DeletePageRemediationModal } from '@ea11y-apps/scanner/components/remediation-confirmation-modal'; +import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; import { useManageActions } from '@ea11y-apps/scanner/hooks/use-manage-actions'; import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -export const DeleteButton = ({ group }) => { +export const DeleteButton = () => { + const { remediations } = useScannerWizardContext(); const { deleteAllRemediationForPage } = useManageActions(); const [showDeleteModal, setShowDeleteModal] = useState(false); @@ -14,36 +16,29 @@ export const DeleteButton = ({ group }) => { const onDeleteRemediation = async () => { setShowDeleteModal(false); - await deleteAllRemediationForPage(group); + await deleteAllRemediationForPage(); }; return ( <> - {group ? ( - + + - ) : ( - - )} + - diff --git a/modules/scanner/assets/js/components/manage-remediation-main-controls/page/disable-button.js b/modules/scanner/assets/js/components/manage-remediation-main-controls/page/disable-button.js new file mode 100644 index 00000000..e077a7f7 --- /dev/null +++ b/modules/scanner/assets/js/components/manage-remediation-main-controls/page/disable-button.js @@ -0,0 +1,22 @@ +import Button from '@elementor/ui/Button'; +import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; +import { useManageActions } from '@ea11y-apps/scanner/hooks/use-manage-actions'; +import { StyledBanIcon } from '@ea11y-apps/scanner/styles/app.styles'; +import { __ } from '@wordpress/i18n'; + +export const DisableButton = () => { + const { remediations } = useScannerWizardContext(); + const { updateAllRemediationForPage } = useManageActions(); + return ( + + ); +}; diff --git a/modules/scanner/assets/js/components/manage-remediation-buttons/enable-button.js b/modules/scanner/assets/js/components/manage-remediation-main-controls/page/enable-button.js similarity index 58% rename from modules/scanner/assets/js/components/manage-remediation-buttons/enable-button.js rename to modules/scanner/assets/js/components/manage-remediation-main-controls/page/enable-button.js index dea1bc44..36717d31 100644 --- a/modules/scanner/assets/js/components/manage-remediation-buttons/enable-button.js +++ b/modules/scanner/assets/js/components/manage-remediation-main-controls/page/enable-button.js @@ -1,21 +1,11 @@ import ReloadIcon from '@elementor/icons/ReloadIcon'; import Button from '@elementor/ui/Button'; -import IconButton from '@elementor/ui/IconButton'; import { useManageActions } from '@ea11y-apps/scanner/hooks/use-manage-actions'; import { __ } from '@wordpress/i18n'; -export const EnableButton = ({ group }) => { +export const EnableButton = () => { const { updateAllRemediationForPage } = useManageActions(); - return group ? ( - - - - ) : ( + return ( ) : ( - - - + )} - + {isEdit ? ( + + ) : ( + + )} @@ -257,7 +276,7 @@ export const ResolveWithAi = ({ item, current }) => { ) : ( - - - - - - {isActive ? ( - - ) : ( - - )} - + )} - ); }; diff --git a/modules/scanner/assets/js/components/resolved-message/index.js b/modules/scanner/assets/js/components/resolved-message/index.js index 1f41e23c..287a00a9 100644 --- a/modules/scanner/assets/js/components/resolved-message/index.js +++ b/modules/scanner/assets/js/components/resolved-message/index.js @@ -2,6 +2,7 @@ import Button from '@elementor/ui/Button'; import Typography from '@elementor/ui/Typography'; import { BLOCKS, ROOT_ID } from '@ea11y-apps/scanner/constants'; import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; +import { useTabsContext } from '@ea11y-apps/scanner/context/tabs-context'; import { ResolvedImage } from '@ea11y-apps/scanner/images'; import { ResolvedButtonsBox, @@ -11,19 +12,18 @@ import { closeWidget } from '@ea11y-apps/scanner/utils/close-widget'; import { __ } from '@wordpress/i18n'; export const ResolvedMessage = () => { - const { remediations, setOpenedBlock, setIsManage } = - useScannerWizardContext(); + const { remediations } = useScannerWizardContext(); + const { tabsProps } = useTabsContext(); + + const handleChangeTab = (event) => { + tabsProps.onChange(event, BLOCKS.management); + }; const onClose = () => { const widget = document.getElementById(ROOT_ID); closeWidget(widget); }; - const showIssues = () => { - setIsManage(true); - setOpenedBlock(BLOCKS.management); - }; - return ( @@ -45,7 +45,7 @@ export const ResolvedMessage = () => { size="small" variant="outlined" color="secondary" - onClick={showIssues} + onClick={handleChangeTab} > {__('Review fixes', 'pojo-accessibility')} diff --git a/modules/scanner/assets/js/components/upgrade-info-tip/upgrade-content.js b/modules/scanner/assets/js/components/upgrade-info-tip/upgrade-content.js index 861ca084..98e2e87e 100644 --- a/modules/scanner/assets/js/components/upgrade-info-tip/upgrade-content.js +++ b/modules/scanner/assets/js/components/upgrade-info-tip/upgrade-content.js @@ -3,11 +3,11 @@ import Button from '@elementor/ui/Button'; import IconButton from '@elementor/ui/IconButton'; import Typography from '@elementor/ui/Typography'; import PropTypes from 'prop-types'; -import CrownFilled from '@ea11y/icons/crown-filled'; +import CrownFilled from '@ea11y-apps/global/icons/crown-filled'; import { mixpanelEvents, mixpanelService } from '@ea11y-apps/global/services'; import { COMPARE_PLAN_URL, - IS_AI_ENABLED, + IS_PRO_PLAN, UPGRADE_URL, } from '@ea11y-apps/scanner/constants'; import { UpgradeContentContainer } from '@ea11y-apps/scanner/styles/app.styles'; @@ -30,7 +30,7 @@ export const UpgradeContent = ({ closeUpgrade, isAlt = false }) => { - {IS_AI_ENABLED + {IS_PRO_PLAN ? __("You've reached the monthly limit", 'pojo-accessibility') : __('Resolve issues automatically with AI', 'pojo-accessibility')} @@ -46,7 +46,7 @@ export const UpgradeContent = ({ closeUpgrade, isAlt = false }) => { )} - {IS_AI_ENABLED + {IS_PRO_PLAN ? __( 'To work more magic with AI, upgrade your plan or wait until next month.', 'pojo-accessibility', @@ -61,13 +61,13 @@ export const UpgradeContent = ({ closeUpgrade, isAlt = false }) => { size="small" color="promotion" variant="contained" - href={IS_AI_ENABLED ? COMPARE_PLAN_URL : UPGRADE_URL} + href={IS_PRO_PLAN ? COMPARE_PLAN_URL : UPGRADE_URL} target="_blank" rel="noreferrer" - startIcon={!IS_AI_ENABLED ? : null} + startIcon={!IS_PRO_PLAN ? : null} onClick={onUpgrade} > - {IS_AI_ENABLED + {IS_PRO_PLAN ? __('Compare plans', 'pojo-accessibility') : __('Upgrade now', 'pojo-accessibility')} diff --git a/modules/scanner/assets/js/constants/index.js b/modules/scanner/assets/js/constants/index.js index c1c0d01b..96a366b8 100644 --- a/modules/scanner/assets/js/constants/index.js +++ b/modules/scanner/assets/js/constants/index.js @@ -26,7 +26,7 @@ export const PAGE_LIMIT_URL = 'https://go.elementor.com/acc-URL-limit-scanner'; export const isRTL = Boolean(window.ea11yScannerData?.isRTL); -export const IS_AI_ENABLED = !window.ea11yScannerData?.planData?.plan?.name +export const IS_PRO_PLAN = !window.ea11yScannerData?.planData?.plan?.name ?.toLowerCase() .includes('free'); export const AI_QUOTA_LIMIT = @@ -34,6 +34,8 @@ export const AI_QUOTA_LIMIT = window.ea11yScannerData?.planData?.aiCredits?.used > 0; +export const HIDE_UPGRADE_KEY = 'ea11y-hide-upgrade-alert'; + export const PAGE_PER_PLAN = window.ea11yScannerData?.planData?.scannedPages?.allowed; diff --git a/modules/scanner/assets/js/context/scanner-wizard-context.js b/modules/scanner/assets/js/context/scanner-wizard-context.js index 9497b3e8..52d844e2 100644 --- a/modules/scanner/assets/js/context/scanner-wizard-context.js +++ b/modules/scanner/assets/js/context/scanner-wizard-context.js @@ -1,398 +1,23 @@ -import { mixpanelEvents, mixpanelService } from '@ea11y-apps/global/services'; -import { APIScanner } from '@ea11y-apps/scanner/api/APIScanner'; -import { - BLOCKS, - INITIAL_SORTED_VIOLATIONS, - LEVEL_POTENTIAL, - LEVEL_VIOLATION, - MANAGE_URL_PARAM, - MANUAL_GROUPS, - RATIO_EXCLUDED, - RULE_TEXT_CONTRAST, -} from '@ea11y-apps/scanner/constants'; -import useScannerSettings from '@ea11y-apps/scanner/hooks/use-scanner-settings'; -import { runAllEa11yRules } from '@ea11y-apps/scanner/rules'; -import { scannerWizard } from '@ea11y-apps/scanner/services/scanner-wizard'; -import { - focusOnElement, - removeExistingFocus, -} from '@ea11y-apps/scanner/utils/focus-on-element'; -import { getElementByXPath } from '@ea11y-apps/scanner/utils/get-element-by-xpath'; -import { getPageHeadingsTree } from '@ea11y-apps/scanner/utils/page-headings'; -import { - sortRemediation, - sortViolations, -} from '@ea11y-apps/scanner/utils/sort-violations'; -import { - calculateStats, - validateHeadings, -} from '@ea11y-apps/scanner/utils/validate-headings'; -import { - createContext, - useContext, - useEffect, - useState, -} from '@wordpress/element'; +import useScannerWizardActions from '@ea11y-apps/scanner/hooks/scanner-context/useScannerWizardActions'; +import useScannerWizardEffects from '@ea11y-apps/scanner/hooks/scanner-context/useScannerWizardEffects'; +import useScannerWizardState from '@ea11y-apps/scanner/hooks/scanner-context/useScannerWizardState'; +import { createContext, useContext } from '@wordpress/element'; -export const ScannerWizardContext = createContext({ - results: {}, - remediations: [], - currentScanId: null, - resolved: 0, - openedBlock: '', - loading: null, - isError: false, - quotaExceeded: false, - isManage: false, - isChanged: false, - sortedViolations: INITIAL_SORTED_VIOLATIONS, - sortedRemediation: INITIAL_SORTED_VIOLATIONS, - altTextData: [], - manualData: {}, - remediationData: {}, - violation: null, - openIndex: null, - setSortedRemediation: () => {}, - setOpenedBlock: () => {}, - setResolved: () => {}, - getResults: () => {}, - setAltTextData: () => {}, - setManualData: () => {}, - setLoading: () => {}, - setRemediationData: () => {}, - updateRemediationList: () => {}, - setIsManage: () => {}, - setIsManageChanged: () => {}, - isResolved: () => {}, - handleOpen: () => {}, - setOpenIndex: () => {}, - runNewScan: () => {}, -}); +export const ScannerWizardContext = createContext(null); -export const ScannerWizardContextProvider = ({ children }) => { - const { dismissedHeadingIssues } = useScannerSettings(); +const ScannerWizardContextProvider = ({ children }) => { + const state = useScannerWizardState(); + const actions = useScannerWizardActions(state); + useScannerWizardEffects(state, actions); - const [results, setResults] = useState(); - const [remediations, setRemediations] = useState([]); - const [sortedViolations, setSortedViolations] = useState( - INITIAL_SORTED_VIOLATIONS, - ); - const [sortedRemediation, setSortedRemediation] = useState( - INITIAL_SORTED_VIOLATIONS, - ); - const [resolved, setResolved] = useState(0); - const [currentScanId, setCurrentScanId] = useState(null); - const [openedBlock, setOpenedBlock] = useState(BLOCKS.main); - const [loading, setLoading] = useState(true); - const [isError, setIsError] = useState(false); - const [quotaExceeded, setQuotaExceeded] = useState(false); - const [isManage, setIsManage] = useState(false); - const [isManageChanged, setIsManageChanged] = useState(false); - const [altTextData, setAltTextData] = useState([]); - const [manualData, setManualData] = useState(structuredClone(MANUAL_GROUPS)); - const [remediationData, setRemediationData] = useState( - structuredClone(MANUAL_GROUPS), - ); - const [openIndex, setOpenIndex] = useState(null); - const [violation, setViolation] = useState(null); - - useEffect(() => { - const items = isManage - ? sortedRemediation[openedBlock] - : sortedViolations[openedBlock]; - if ( - openIndex !== null && - sortedViolations[openedBlock]?.length && - openIndex < items?.length - ) { - const element = isManage - ? getElementByXPath( - JSON.parse(sortedRemediation[openedBlock][openIndex].content) - ?.xpath, - ) - : sortedViolations[openedBlock][openIndex].node; - focusOnElement(element); - } else { - removeExistingFocus(); - } - }, [openIndex]); - - useEffect(() => { - if ( - Object.keys(sortedRemediation).every( - (key) => sortedRemediation[key].length === 0, - ) - ) { - setRemediations([]); - } - }, [sortedRemediation]); - - useEffect(() => { - if (results?.summary?.counts) { - const total = Object.values(sortedViolations).reduce( - (sum, arr) => sum + arr.length, - 0, - ); - setViolation(total); - } - }, [sortedViolations, results]); - - const updateRemediationList = async () => { - try { - const items = await APIScanner.getRemediations( - window.ea11yScannerData?.pageData?.url, - ); - - const sorted = sortRemediation(items.data); - - setRemediations(items.data); - setSortedRemediation(sorted); - } catch (error) { - setIsError(true); - } - }; - - const handleOpen = (index, item) => (event, isExpanded) => { - setOpenIndex(isExpanded ? index : null); - if (!isManage) { - mixpanelService.sendEvent(mixpanelEvents.issueSelected, { - issue_type: item.message, - rule_id: item.ruleId, - wcag_level: item.reasonCategory.match(/\((AAA?|AA?|A)\)/)?.[1] || '', - category_name: openedBlock, - }); - } - }; - - const handleOpenBlock = (block) => { - setOpenedBlock(block); - setOpenIndex(null); - }; - - const registerPage = async (data, sorted) => { - try { - if (window?.ea11yScannerData?.pageData?.unregistered) { - await APIScanner.registerPage( - window?.ea11yScannerData?.pageData, - data.summary, - ); - window.ea11yScannerData.pageData.unregistered = false; - } - - setResults(data); - setSortedViolations(sorted); - setAltTextData([]); - setManualData(structuredClone(MANUAL_GROUPS)); - } catch (e) { - if (e?.message === 'Quota exceeded') { - setQuotaExceeded(true); - } - setIsError(true); - } - }; - - const addScanResults = async (data) => { - try { - const response = await APIScanner.addScanResults({ - url: window?.ea11yScannerData?.pageData?.url, - summary: data.summary, - }); - void APIScanner.triggerSave({ - object_id: window?.ea11yScannerData?.pageData?.object_id, - object_type: window?.ea11yScannerData?.pageData?.object_type, - }); - - setCurrentScanId(response.scanId); - } catch (e) { - console.error(e); - setIsError(true); - } - }; - - const isViolation = (item) => item.level === LEVEL_VIOLATION; - const isContrastViolation = (item) => - item.ruleId === RULE_TEXT_CONTRAST && - item.level === LEVEL_POTENTIAL && - Number(item.messageArgs[0]) !== RATIO_EXCLUDED; - - const getResults = async () => { - setLoading(true); - - try { - const url = new URL(window.location.href); - const data = await window.ace.check(document); - - const filtered = data.results.filter( - (item) => isViolation(item) || isContrastViolation(item), - ); - - const customResults = runAllEa11yRules(document); - - const filteredCustomResults = customResults.filter( - (item) => - item.level === 'violation' && - !dismissedHeadingIssues.includes(item.path.dom), - ); - - const allResults = [...filtered, ...filteredCustomResults]; - - data.results = allResults; - - if (data?.summary?.counts) { - data.summary.counts.issuesResolved = 0; - data.summary.counts.violation += filteredCustomResults.filter( - (item) => item.level === 'violation', - ).length; - data.summary.counts.recommendation = - (data.summary.counts.recommendation || 0) + - filteredCustomResults.filter( - (item) => item.level === 'recommendation', - ).length; - } - - const sorted = sortViolations(allResults); - - await registerPage(data, sorted); - await addScanResults(data); - - mixpanelService.sendEvent(mixpanelEvents.scanTriggered, { - page_url: window.ea11yScannerData?.pageData?.url, - issue_count: data.summary?.counts?.violation, - source: url.searchParams.get('open-ea11y-assistant-src'), - }); - - return data.summary; - } catch (error) { - setIsError(true); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - if (window.ea11yScannerData?.isConnected) { - //Wait for apply FE remediation - setTimeout(() => { - scannerWizard - .load() - .then(() => { - void getResults(); - }) - .catch(() => { - setIsError(true); - setLoading(false); - }); - }, 500); - void updateRemediationList(); - } else { - setLoading(false); - } - }, [window.ea11yScannerData?.isConnected]); - - useEffect(() => { - const params = new URLSearchParams(window.location.search); - if (params.get(MANAGE_URL_PARAM) === '1') { - setIsManage(true); - handleOpenBlock(BLOCKS.management); - } - }, [window.location.search]); - - const isResolved = (block) => { - const indexes = Array.from( - { - length: sortedViolations[block]?.length || 0, - }, - (_, i) => i, - ); - switch (block) { - case BLOCKS.altText: - return ( - (altTextData?.length === sortedViolations[block]?.length && - indexes.every((index) => index in altTextData) && - altTextData.every((data) => data?.resolved)) || - sortedViolations[block]?.length === 0 - ); - case BLOCKS.headingStructure: - const stats = getHeadingsStats(); - return stats.error === 0; - default: - return ( - (manualData[block]?.length === sortedViolations[block]?.length && - indexes.every((index) => index in manualData[block]) && - manualData[block].every((data) => data?.resolved)) || - sortedViolations[block]?.length === 0 - ); - } - }; - - const isChanged = - altTextData.length > 0 || - Object.keys(manualData).some( - (key) => manualData[key] && manualData[key].length > 0, - ) || - isManageChanged; - - const runNewScan = () => { - const url = new URL(window.location.href); - url.searchParams.delete('open-ea11y-assistant'); - url.searchParams.delete('open-ea11y-assistant-src'); - url.searchParams.append('open-ea11y-assistant-src', 'rescan_button'); - url.searchParams.append('open-ea11y-assistant', '1'); - - window.location.assign(url); - }; - - const getHeadingsStats = () => { - const updatedHeadings = validateHeadings( - getPageHeadingsTree(), - dismissedHeadingIssues, - ); - return calculateStats(updatedHeadings); - }; + const value = { ...state, ...actions }; return ( - + {children} ); }; -export const useScannerWizardContext = () => { - return useContext(ScannerWizardContext); -}; +export default ScannerWizardContextProvider; +export const useScannerWizardContext = () => useContext(ScannerWizardContext); diff --git a/modules/scanner/assets/js/context/tabs-context.js b/modules/scanner/assets/js/context/tabs-context.js new file mode 100644 index 00000000..e31ad768 --- /dev/null +++ b/modules/scanner/assets/js/context/tabs-context.js @@ -0,0 +1,49 @@ +import useTabs from '@elementor/ui/useTabs'; +import PropTypes from 'prop-types'; +import { mixpanelEvents, mixpanelService } from '@ea11y-apps/global/services'; +import { BLOCKS } from '@ea11y-apps/scanner/constants'; +import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; +import { getInitialTab } from '@ea11y-apps/scanner/utils/get-initial-tab'; +import { createContext, useContext } from '@wordpress/element'; + +export const TabsContext = createContext({}); + +export const TabsContextProvider = ({ children }) => { + const { setOpenedBlock, setIsManage } = useScannerWizardContext(); + const { getTabsProps, getTabProps, getTabPanelProps } = + useTabs(getInitialTab()); + + const tabsProps = getTabsProps(); + + const changeTab = (event, newValue) => { + setOpenedBlock(newValue); + setIsManage(newValue === BLOCKS.management); + tabsProps.onChange(event, newValue); + mixpanelService.sendEvent(mixpanelEvents.tabSelected, { + tab_name: newValue, + }); + }; + + return ( + + {children} + + ); +}; + +TabsContextProvider.propTypes = { + children: PropTypes.node, +}; + +export const useTabsContext = () => { + return useContext(TabsContext); +}; diff --git a/modules/scanner/assets/js/hooks/scanner-context/useScannerWizardActions.js b/modules/scanner/assets/js/hooks/scanner-context/useScannerWizardActions.js new file mode 100644 index 00000000..96be2c57 --- /dev/null +++ b/modules/scanner/assets/js/hooks/scanner-context/useScannerWizardActions.js @@ -0,0 +1,254 @@ +import { mixpanelEvents, mixpanelService } from '@ea11y-apps/global/services'; +import { APIScanner } from '@ea11y-apps/scanner/api/APIScanner'; +import { + BLOCKS, + LEVEL_POTENTIAL, + LEVEL_VIOLATION, + MANUAL_GROUPS, + RATIO_EXCLUDED, + RULE_TEXT_CONTRAST, +} from '@ea11y-apps/scanner/constants'; +import { runAllEa11yRules } from '@ea11y-apps/scanner/rules'; +import { getPageHeadingsTree } from '@ea11y-apps/scanner/utils/page-headings'; +import { + sortRemediation, + sortViolations, +} from '@ea11y-apps/scanner/utils/sort-violations'; +import { + calculateStats, + validateHeadings, +} from '@ea11y-apps/scanner/utils/validate-headings'; + +export default function useScannerWizardActions(state) { + const { + openedBlock, + isManage, + setResults, + setSortedViolations, + setAltTextData, + setManualData, + setCurrentScanId, + setIsError, + setQuotaExceeded, + setLoading, + setRemediations, + setSortedRemediation, + setGlobalRemediations, + setSortedGlobalRemediation, + setOpenIndex, + } = state; + + const updateRemediationList = async () => { + try { + const items = await APIScanner.getRemediations( + window.ea11yScannerData?.pageData?.url, + ); + const sorted = sortRemediation(items.data.page); + const sortedGlobal = sortRemediation(items.data.global); + setRemediations(items.data.page); + setSortedRemediation(sorted); + setGlobalRemediations(items.data.global); + setSortedGlobalRemediation(sortedGlobal); + } catch (error) { + setIsError(true); + } + }; + + const handleOpen = (index, item) => (event, isExpanded) => { + setOpenIndex(isExpanded ? index : null); + if (!isManage) { + mixpanelService.sendEvent(mixpanelEvents.issueSelected, { + issue_type: item.message, + rule_id: item.ruleId, + wcag_level: item.reasonCategory.match(/\((AAA?|AA?|A)\)/)?.[1] || '', + category_name: openedBlock, + }); + } + }; + + const handleOpenBlock = (block) => { + state.setOpenedBlock(block); + setOpenIndex(null); + }; + + const registerPage = async (data, sorted) => { + try { + if (window?.ea11yScannerData?.pageData?.unregistered) { + await APIScanner.registerPage( + window?.ea11yScannerData?.pageData, + data.summary, + ); + window.ea11yScannerData.pageData.unregistered = false; + runNewScan(); + } + + setResults(data); + setSortedViolations(sorted); + setAltTextData({ manage: [], main: [] }); + setManualData(structuredClone(MANUAL_GROUPS)); + } catch (e) { + if (e?.message === 'Quota exceeded') { + setQuotaExceeded(true); + } + setIsError(true); + } + }; + + const addScanResults = async (data) => { + try { + const response = await APIScanner.addScanResults({ + url: window?.ea11yScannerData?.pageData?.url, + summary: data.summary, + }); + void APIScanner.triggerSave({ + object_id: window?.ea11yScannerData?.pageData?.object_id, + object_type: window?.ea11yScannerData?.pageData?.object_type, + }); + + setCurrentScanId(response.scanId); + } catch (e) { + console.error(e); + setIsError(true); + } + }; + + const isViolation = (item) => item.level === LEVEL_VIOLATION; + const isContrastViolation = (item) => + item.ruleId === RULE_TEXT_CONTRAST && + item.level === LEVEL_POTENTIAL && + Number(item.messageArgs[0]) !== RATIO_EXCLUDED; + + const getResults = async () => { + setLoading(true); + + try { + const url = new URL(window.location.href); + const data = await window.ace.check(document); + + const filtered = data.results.filter( + (item) => isViolation(item) || isContrastViolation(item), + ); + + const customResults = runAllEa11yRules(document); + + const filteredCustomResults = customResults.filter( + (item) => + item.level === 'violation' && + !state.dismissedHeadingIssues?.includes(item.path.dom), + ); + + const allResults = [...filtered, ...filteredCustomResults]; + + data.results = allResults; + + if (data?.summary?.counts) { + data.summary.counts.issuesResolved = 0; + data.summary.counts.violation += filteredCustomResults.filter( + (item) => item.level === 'violation', + ).length; + data.summary.counts.recommendation = + (data.summary.counts.recommendation || 0) + + filteredCustomResults.filter( + (item) => item.level === 'recommendation', + ).length; + } + + const sorted = sortViolations(allResults); + + await registerPage(data, sorted); + await addScanResults(data); + + mixpanelService.sendEvent(mixpanelEvents.scanTriggered, { + page_url: window.ea11yScannerData?.pageData?.url, + issue_count: data.summary?.counts?.violation, + source: url.searchParams.get('open-ea11y-assistant-src'), + }); + + return data.summary; + } catch (error) { + setIsError(true); + } finally { + setLoading(false); + } + }; + + const runNewScan = () => { + const url = new URL(window.location.href); + + const keys = [...url.searchParams.keys()]; + for (const key of keys) { + url.searchParams.delete(key); + } + url.searchParams.append('open-ea11y-assistant-src', 'rescan_button'); + url.searchParams.append('open-ea11y-assistant', '1'); + + window.location.assign(url); + }; + + const getHeadingsStats = () => { + const updatedHeadings = validateHeadings( + getPageHeadingsTree(), + state.dismissedHeadingIssues, + ); + return calculateStats(updatedHeadings); + }; + + const isResolved = (block) => { + const type = state.isManage ? 'manage' : 'main'; + const { sortedViolations, altTextData, colorContrastData, manualData } = + state; + + const indexes = Array.from( + { length: sortedViolations[block]?.length || 0 }, + (_, i) => i, + ); + + switch (block) { + case BLOCKS.altText: + return ( + (altTextData?.[type]?.length === sortedViolations[block]?.length && + indexes.every((index) => index in altTextData.main) && + altTextData[type].every((data) => data?.resolved)) || + sortedViolations[block]?.length === 0 + ); + + case BLOCKS.colorContrast: + return ( + (colorContrastData?.[type]?.length === + sortedViolations[block]?.length && + indexes.every((index) => index in colorContrastData.main) && + colorContrastData[type].every((data) => data?.resolved)) || + sortedViolations[block]?.length === 0 + ); + + case BLOCKS.headingStructure: { + const updatedHeadings = validateHeadings( + getPageHeadingsTree(), + state.dismissedHeadingIssues, + ); + const stats = calculateStats(updatedHeadings); + return stats.error === 0; + } + + default: + return ( + (manualData[block]?.length === sortedViolations[block]?.length && + indexes.every((index) => index in manualData[block]) && + manualData[block].every((data) => data?.resolved)) || + sortedViolations[block]?.length === 0 + ); + } + }; + + return { + updateRemediationList, + handleOpen, + handleOpenBlock, + registerPage, + addScanResults, + getResults, + runNewScan, + getHeadingsStats, + isResolved, + }; +} diff --git a/modules/scanner/assets/js/hooks/scanner-context/useScannerWizardEffects.js b/modules/scanner/assets/js/hooks/scanner-context/useScannerWizardEffects.js new file mode 100644 index 00000000..237e4e50 --- /dev/null +++ b/modules/scanner/assets/js/hooks/scanner-context/useScannerWizardEffects.js @@ -0,0 +1,95 @@ +import { BLOCKS, MANAGE_URL_PARAM } from '@ea11y-apps/scanner/constants'; +import { scannerWizard } from '@ea11y-apps/scanner/services/scanner-wizard'; +import { + focusOnElement, + removeExistingFocus, +} from '@ea11y-apps/scanner/utils/focus-on-element'; +import { getElementByXPath } from '@ea11y-apps/scanner/utils/get-element-by-xpath'; +import { useEffect } from '@wordpress/element'; + +export default function useScannerWizardEffects(state, actions) { + const { + sortedViolations, + sortedRemediation, + openIndex, + openedBlock, + isManage, + setRemediations, + setViolation, + setIsError, + setLoading, + } = state; + + // Focus management when openIndex changes + useEffect(() => { + const items = isManage + ? sortedRemediation[openedBlock] + : sortedViolations[openedBlock]; + if ( + openIndex !== null && + sortedViolations[openedBlock]?.length && + openIndex < items?.length + ) { + const element = isManage + ? getElementByXPath( + JSON.parse(sortedRemediation[openedBlock][openIndex].content) + ?.xpath, + ) + : sortedViolations[openedBlock][openIndex].node; + focusOnElement(element); + } else { + removeExistingFocus(); + } + }, [openIndex]); + + // Clear remediations array when no remediation items left + useEffect(() => { + if ( + Object.keys(sortedRemediation).every( + (key) => sortedRemediation[key].length === 0, + ) + ) { + setRemediations([]); + } + }, [sortedRemediation]); + + // compute violation count + useEffect(() => { + if (state.results?.summary?.counts) { + const total = Object.values(sortedViolations).reduce( + (sum, arr) => sum + arr.length, + 0, + ); + setViolation(total); + } + }, [sortedViolations, state.results]); + + // initial load and scannerWizard logic + useEffect(() => { + if (window.ea11yScannerData?.isConnected) { + setTimeout(() => { + scannerWizard + .load() + .then(() => { + void actions.getResults(); + }) + .catch(() => { + setIsError(true); + setLoading(false); + }); + }, 500); + void actions.updateRemediationList(); + } else { + setLoading(false); + } + }, [window.ea11yScannerData?.isConnected]); + + // handle manage param on url change + useEffect(() => { + const params = new URLSearchParams(window.location.search); + if (params.get(MANAGE_URL_PARAM) === '1') { + state.setIsManage(true); + actions.handleOpenBlock(BLOCKS.management); + } + }, [window.location.search]); +} diff --git a/modules/scanner/assets/js/hooks/scanner-context/useScannerWizardState.js b/modules/scanner/assets/js/hooks/scanner-context/useScannerWizardState.js new file mode 100644 index 00000000..2c971bfd --- /dev/null +++ b/modules/scanner/assets/js/hooks/scanner-context/useScannerWizardState.js @@ -0,0 +1,90 @@ +import { + BLOCKS, + INITIAL_SORTED_VIOLATIONS, + MANUAL_GROUPS, +} from '@ea11y-apps/scanner/constants'; +import { useState } from '@wordpress/element'; + +const initialAltTextData = { manage: [], main: [] }; +const initialColorContrastData = { manage: [], main: [] }; + +export default function useScannerWizardState() { + const [results, setResults] = useState(); + const [remediations, setRemediations] = useState([]); + const [globalRemediations, setGlobalRemediations] = useState([]); + const [sortedViolations, setSortedViolations] = useState( + INITIAL_SORTED_VIOLATIONS, + ); + const [sortedRemediation, setSortedRemediation] = useState( + INITIAL_SORTED_VIOLATIONS, + ); + const [sortedGlobalRemediation, setSortedGlobalRemediation] = useState( + INITIAL_SORTED_VIOLATIONS, + ); + const [resolved, setResolved] = useState(0); + const [currentScanId, setCurrentScanId] = useState(null); + const [openedBlock, setOpenedBlock] = useState(BLOCKS.main); + const [loading, setLoading] = useState(true); + const [isError, setIsError] = useState(false); + const [quotaExceeded, setQuotaExceeded] = useState(false); + const [isManage, setIsManage] = useState(false); + const [isManageGlobal, setIsManageGlobal] = useState(false); + const [isManageChanged, setIsManageChanged] = useState(false); + const [altTextData, setAltTextData] = useState(initialAltTextData); + const [colorContrastData, setColorContrastData] = useState( + initialColorContrastData, + ); + const [manualData, setManualData] = useState(structuredClone(MANUAL_GROUPS)); + const [remediationData, setRemediationData] = useState( + structuredClone(MANUAL_GROUPS), + ); + const [openIndex, setOpenIndex] = useState(null); + const [violation, setViolation] = useState(null); + + return { + // state values + results, + remediations, + sortedViolations, + sortedRemediation, + resolved, + currentScanId, + openedBlock, + loading, + isError, + quotaExceeded, + isManage, + isManageGlobal, + isManageChanged, + altTextData, + manualData, + colorContrastData, + remediationData, + openIndex, + violation, + globalRemediations, + sortedGlobalRemediation, + // setters + setResults, + setRemediations, + setSortedViolations, + setSortedRemediation, + setResolved, + setCurrentScanId, + setOpenedBlock, + setLoading, + setIsError, + setQuotaExceeded, + setIsManage, + setIsManageGlobal, + setIsManageChanged, + setAltTextData, + setManualData, + setColorContrastData, + setRemediationData, + setOpenIndex, + setViolation, + setGlobalRemediations, + setSortedGlobalRemediation, + }; +} diff --git a/modules/scanner/assets/js/hooks/use-alt-text-form.js b/modules/scanner/assets/js/hooks/use-alt-text-form.js index 03adecd6..0e220aa0 100644 --- a/modules/scanner/assets/js/hooks/use-alt-text-form.js +++ b/modules/scanner/assets/js/hooks/use-alt-text-form.js @@ -6,12 +6,13 @@ import { BLOCKS } from '@ea11y-apps/scanner/constants'; import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; import { scannerItem } from '@ea11y-apps/scanner/types/scanner-item'; import { removeExistingFocus } from '@ea11y-apps/scanner/utils/focus-on-element'; +import { getOuterHtmlByXpath } from '@ea11y-apps/scanner/utils/get-outer-html-by-xpath'; import { splitDescriptions } from '@ea11y-apps/scanner/utils/split-ai-response'; import { convertSvgToPngBase64, svgNodeToPngBase64, } from '@ea11y-apps/scanner/utils/svg-to-png-base64'; -import { useEffect, useState } from '@wordpress/element'; +import { useEffect, useRef, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; export const useAltTextForm = ({ current, item }) => { @@ -24,6 +25,8 @@ export const useAltTextForm = ({ current, item }) => { isResolved, setOpenedBlock, updateRemediationList, + isManage, + setIsManageChanged, } = useScannerWizardContext(); const { error } = useToastNotification(); @@ -31,81 +34,111 @@ export const useAltTextForm = ({ current, item }) => { const [loading, setLoading] = useState(false); const [firstOpen, setFirstOpen] = useState(true); - const isSubmitDisabled = - (!altTextData?.[current]?.makeDecorative && - !altTextData?.[current]?.altText) || - altTextData?.[current]?.resolved || - loading; + const type = isManage ? 'manage' : 'main'; + const isGlobal = + altTextData?.[type]?.[current]?.isGlobal || item.global || false; + + const isGlobalRef = useRef(null); useEffect(() => { - if (!firstOpen && isResolved(BLOCKS.altText)) { + if (item?.node) { + isGlobalRef.current = isGlobal; + } + }, [current]); + + useEffect(() => { + if (isManage) { + updateData({ + makeDecorative: item.data.attribute_name === 'role', + altText: + item.data.attribute_name !== 'role' ? item.data.attribute_value : '', + isGlobal, + }); + } else { + updateData({ isGlobal }); + } + }, [isManage, current]); + + useEffect(() => { + if (!isManage && !firstOpen && isResolved(BLOCKS.altText)) { removeExistingFocus(); setOpenedBlock(BLOCKS.main); } setFirstOpen(false); - }, [altTextData]); + }, [isResolved(BLOCKS.altText)]); + + const setIsGlobal = (value) => { + updateData({ + isGlobal: value, + }); + }; const updateData = (data) => { - const updData = [...altTextData]; - if (altTextData?.[current]?.resolved && !data.resolved) { + const updData = [...altTextData?.[type]]; + if (altTextData?.[type]?.[current]?.resolved && !data.resolved) { setResolved(resolved - 1); } updData[current] = { - ...(altTextData?.[current] || {}), + ...(altTextData?.[type]?.[current] || {}), ...data, }; - setAltTextData(updData); + setAltTextData({ + ...altTextData, + [type]: updData, + }); }; const makeAttributeData = () => { - if (altTextData?.[current]?.makeDecorative) { - item.node.setAttribute('role', 'presentation'); + if (altTextData?.[type]?.[current]?.makeDecorative) { return { attribute_name: 'role', attribute_value: 'presentation', }; } + if (item.node.tagName === 'svg') { - item.node.setAttribute('aria-label', altTextData?.[current]?.altText); return { attribute_name: 'aria-label', - attribute_value: altTextData?.[current]?.altText, + attribute_value: altTextData?.[type]?.[current]?.altText, }; } - item.node.setAttribute('alt', altTextData?.[current]?.altText); return { attribute_name: 'alt', - attribute_value: altTextData?.[current]?.altText, + attribute_value: altTextData?.[type]?.[current]?.altText, }; }; const updateAltText = async () => { const match = item.node.className.toString().match(/wp-image-(\d+)/); - const altText = !altTextData?.[current]?.makeDecorative - ? altTextData?.[current]?.altText + const altText = !altTextData?.[type]?.[current]?.makeDecorative + ? altTextData?.[type]?.[current]?.altText : ''; + const find = item.snippet; try { if (match && item.node.tagName !== 'svg') { void APIScanner.submitAltText(item.node.src, altText); } - await APIScanner.submitRemediation({ + const response = await APIScanner.submitRemediation({ url: window?.ea11yScannerData?.pageData.url, remediation: { ...makeAttributeData(), action: 'add', xpath: item.path.dom, + find, category: item.reasonCategory.match(/\((AAA?|AA?|A)\)/)?.[1] || '', type: 'ATTRIBUTE', }, + global: isGlobal, rule: item.ruleId, group: BLOCKS.altText, - apiId: altTextData?.[current]?.apiId, + apiId: altTextData?.[type]?.[current]?.apiId, }); await APIScanner.resolveIssue(currentScanId); void updateRemediationList(); + return response.remediation; } catch (e) { console.warn(e); } @@ -135,31 +168,70 @@ export const useAltTextForm = ({ current, item }) => { const handleSubmit = async () => { try { setLoading(true); - const fixMethod = altTextData?.[current]?.apiId + const fixMethod = altTextData?.[type]?.[current]?.apiId ? 'AI alt-text' : 'Manual alt-text'; - await updateAltText(item); - if (!altTextData?.[current]?.resolved) { - updateData({ resolved: true }); + const remediation = await updateAltText(item); + if (!altTextData?.[type]?.[current]?.resolved) { + updateData({ + remediation, + resolved: true, + }); + isGlobalRef.current = isGlobal; setResolved(resolved + 1); } - if (altTextData?.[current]?.apiId) { + if (altTextData?.[type]?.[current]?.apiId) { mixpanelService.sendEvent(mixpanelEvents.aiSuggestionAccepted, { element_selector: item.path.dom, image_src: item.node?.src, - final_text: altTextData?.[current]?.altText, + final_text: altTextData?.[type]?.[current]?.altText, credit_used: 1, }); } mixpanelService.sendEvent(mixpanelEvents.applyFixButtonClicked, { - fix_method: altTextData?.[current]?.makeDecorative + fix_method: altTextData?.[type]?.[current]?.makeDecorative ? 'Mark as decorative' : fixMethod, issue_type: item.message, category_name: BLOCKS.altText, page_url: window.ea11yScannerData?.pageData?.url, + is_global: isGlobal ? 'yes' : 'no', + }); + } catch (e) { + console.error(e); + } finally { + setLoading(false); + } + }; + + const handleUpdate = async () => { + const find = getOuterHtmlByXpath( + item.path.dom, + item.data?.attribute_name + ? `${item.data.attribute_name}=" ${item.data.attribute_value}"` + : '', + ); + try { + setLoading(true); + const remediation = altTextData?.[type]?.[current]?.remediation; + const id = item.id || remediation.id; + const data = item.data || JSON.parse(remediation.content); + const strContent = JSON.stringify({ + ...data, + ...makeAttributeData(), + find, + }); + await APIScanner.updateRemediationContent({ + url: window?.ea11yScannerData?.pageData?.url, + id, + content: strContent, + global: isGlobal, }); + + isGlobalRef.current = isGlobal; + setIsManageChanged(true); + void updateRemediationList(); } catch (e) { console.error(e); } finally { @@ -212,33 +284,48 @@ export const useAltTextForm = ({ current, item }) => { }; const generateAltText = async () => { - if (altTextData?.[current]?.aiText?.length) { + if (altTextData?.[type]?.[current]?.aiText?.length) { const index = - altTextData?.[current]?.aiTextIndex + 1 < - altTextData?.[current]?.aiText?.length - ? altTextData?.[current]?.aiTextIndex + 1 + altTextData?.[type]?.[current]?.aiTextIndex + 1 < + altTextData?.[type]?.[current]?.aiText?.length + ? altTextData?.[type]?.[current]?.aiTextIndex + 1 : 0; updateData({ - altText: altTextData?.[current]?.aiText[index], + altText: altTextData?.[type]?.[current]?.aiText[index], aiTextIndex: index, resolved: false, }); - sendMixpanelEvent(altTextData?.[current]?.aiText[index]); + sendMixpanelEvent(altTextData?.[type]?.[current]?.aiText[index]); } else { await getAiText(); } }; + const attrData = makeAttributeData(); + + const isSubmitDisabled = isManage + ? attrData.attribute_value === item.data.attribute_value && + attrData.attribute_name === item.data.attribute_name && + isGlobal === item.global + : (!altTextData?.[type]?.[current]?.makeDecorative && + !altTextData?.[type]?.[current]?.altText) || + (altTextData?.[type]?.[current]?.resolved && + altTextData?.[type]?.[current]?.isGlobal === isGlobalRef.current) || + loading; + return { + isGlobal, + setIsGlobal, loadingAiText, - data: altTextData, + data: altTextData?.[type], isSubmitDisabled, loading, handleCheck, handleChange, handleSubmit, + handleUpdate, generateAltText, }; }; diff --git a/modules/scanner/assets/js/hooks/use-color-contrast-form.js b/modules/scanner/assets/js/hooks/use-color-contrast-form.js index a7161666..e5a7b87a 100644 --- a/modules/scanner/assets/js/hooks/use-color-contrast-form.js +++ b/modules/scanner/assets/js/hooks/use-color-contrast-form.js @@ -1,49 +1,79 @@ import getXPath from 'get-xpath'; import PropTypes from 'prop-types'; import { mixpanelEvents, mixpanelService } from '@ea11y-apps/global/services'; +import { + isValidCSS, + rgbOrRgbaToHex, +} from '@ea11y-apps/global/utils/color-contrast-helpers'; import { APIScanner } from '@ea11y-apps/scanner/api/APIScanner'; import { BACKGROUND_ELEMENT_CLASS, BLOCKS, - DATA_INITIAL_BG, - DATA_INITIAL_COLOR, } from '@ea11y-apps/scanner/constants'; import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; import { scannerItem } from '@ea11y-apps/scanner/types/scanner-item'; -import { rgbOrRgbaToHex } from '@ea11y-apps/scanner/utils/convert-colors'; +import { buildPathToParent } from '@ea11y-apps/scanner/utils/build-path-to-parent'; import { focusOnElement, removeExistingFocus, } from '@ea11y-apps/scanner/utils/focus-on-element'; import { getElementByXPath } from '@ea11y-apps/scanner/utils/get-element-by-xpath'; import { getElementCSSSelector } from '@ea11y-apps/scanner/utils/get-element-css-selector'; -import { useEffect, useState } from '@wordpress/element'; +import { getOuterHtmlByXpath } from '@ea11y-apps/scanner/utils/get-outer-html-by-xpath'; +import { useEffect, useRef, useState } from '@wordpress/element'; export const useColorContrastForm = ({ item, current, setCurrent }) => { const { - openedBlock, - manualData, + colorContrastData, resolved: resolvedBlock, setResolved, isResolved, + isManage, setOpenedBlock, - setManualData, + setColorContrastData, + setIsManageChanged, updateRemediationList, currentScanId, } = useScannerWizardContext(); + const type = isManage ? 'manage' : 'main'; + const [loading, setLoading] = useState(false); const [firstOpen, setFirstOpen] = useState(true); + const [parentChanged, setParentChanged] = useState(false); - const updateData = (data) => { - const existing = manualData[openedBlock]?.[current] || {}; - const updated = [...(manualData[openedBlock] || [])]; - updated[current] = { ...existing, ...data }; + const isGlobal = + colorContrastData?.[type]?.[current]?.isGlobal || item.global || false; + + const isGlobalRef = useRef(null); + + useEffect(() => { + if (item?.node) { + isGlobalRef.current = isGlobal; + } + }, [current]); + + const setIsGlobal = (value) => { + updateData({ + isGlobal: value, + }); + }; - setManualData({ - ...manualData, - [openedBlock]: updated, + const updateData = (data) => { + const updData = [...colorContrastData?.[type]]; + updData[current] = { + ...(colorContrastData?.[type]?.[current] || {}), + ...data, + }; + setColorContrastData({ + ...colorContrastData, + [type]: updData, }); + + if (data.color || data.background) { + const rule = buildCSSRule(updData[current]); + updateCSS(rule); + } }; const sendEvent = (method) => { @@ -53,21 +83,12 @@ export const useColorContrastForm = ({ item, current, setCurrent }) => { }; useEffect(() => { - if (!firstOpen && isResolved(BLOCKS.colorContrast)) { + if (!isManage && !firstOpen && isResolved(BLOCKS.colorContrast)) { removeExistingFocus(); setOpenedBlock(BLOCKS.main); } setFirstOpen(false); - }, [manualData]); - - useEffect(() => { - if (!item?.node?.getAttribute(DATA_INITIAL_COLOR)) { - const initialColor = - manualData[openedBlock]?.[current]?.color || item.messageArgs[3]; - item.node.setAttribute(DATA_INITIAL_COLOR, initialColor); - item.node.style.setProperty('color', initialColor, 'important'); - } - }, [item]); + }, [isResolved(BLOCKS.colorContrast)]); const { color = item.messageArgs[3] || @@ -75,13 +96,23 @@ export const useColorContrastForm = ({ item, current, setCurrent }) => { window.getComputedStyle(item.node).getPropertyValue('color'), ), background = item.messageArgs[4], - parents = [item.path.dom], + parents = item.isEdit + ? buildPathToParent(item.node, item.parentNode) + : [item.path.dom], resolved = false, - backgroundChanged = false, - } = manualData[openedBlock]?.[current] || {}; + backgroundChanged = item.isEdit, + } = colorContrastData[type]?.[current] || {}; + + useEffect(() => { + if (isManage && parentChanged) { + const styles = document.getElementById('ea11y-remediation-styles'); + if (styles) { + styles.innerHTML = styles.innerHTML.replace(item.data.rule, ''); + } + } + }, [parentChanged]); const changeColor = (updColor) => { - item.node?.style?.setProperty('color', updColor, 'important'); updateData({ color: updColor, resolved: false }); }; @@ -91,14 +122,6 @@ export const useColorContrastForm = ({ item, current, setCurrent }) => { return; } - if (!element.getAttribute(DATA_INITIAL_BG)) { - const initial = window - .getComputedStyle(element) - .getPropertyValue('background-color'); - element.setAttribute(DATA_INITIAL_BG, initial); - } - - element.style?.setProperty('background-color', updBackground, 'important'); updateData({ background: updBackground, resolved: false, @@ -106,27 +129,6 @@ export const useColorContrastForm = ({ item, current, setCurrent }) => { }); }; - const setParentBackground = (nextElement, element) => { - if (!nextElement) { - return; - } - - if (!nextElement.getAttribute(DATA_INITIAL_BG)) { - const initial = window - .getComputedStyle(nextElement) - .getPropertyValue('background-color'); - nextElement.setAttribute(DATA_INITIAL_BG, initial); - } - - element?.style?.setProperty( - 'background-color', - element?.getAttribute(DATA_INITIAL_BG), - 'important', - ); - - nextElement.style?.setProperty('background-color', background, 'important'); - }; - const setParentLarger = () => { const element = getElementByXPath(parents.at(-1)); const parent = element?.parentElement; @@ -137,12 +139,11 @@ export const useColorContrastForm = ({ item, current, setCurrent }) => { try { const xpath = getXPath(parent, { ignoreId: true }); focusOnElement(parent, BACKGROUND_ELEMENT_CLASS); - setParentBackground(parent, element); - updateData({ parents: [...parents, xpath], resolved: false, }); + setParentChanged(true); sendEvent('plus'); } catch (error) { console.warn('Failed to get XPath for parent element:', error); @@ -162,74 +163,38 @@ export const useColorContrastForm = ({ item, current, setCurrent }) => { removeExistingFocus(BACKGROUND_ELEMENT_CLASS); } - setParentBackground(nextElement); updateData({ parents: newParents, resolved: false }); + setParentChanged(true); sendEvent('minus'); }; const isValidHexColor = (str) => /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(str.trim()); - const isValidCSS = (cssText) => { - try { - // Basic checks for common malicious patterns - if (!cssText || typeof cssText !== 'string') { - return false; - } - - // Check for basic CSS structure and disallow dangerous patterns - const dangerousPatterns = [ - /@import/i, - /javascript:/i, - /expression\s*\(/i, - /behavior\s*:/i, - /binding\s*:/i, - /-moz-binding/i, - ]; - - if (dangerousPatterns.some((pattern) => pattern.test(cssText))) { - return false; - } - - // More comprehensive CSS structure validation - const cssRegex = /^[\s\S]*\{\s*[\s\S]+:\s*[\s\S]+;\s*\}[\s\S]*$/; - const hasBasicStructure = cssRegex.test( - cssText.replace(/\s+/g, ' ').trim(), - ); + const buildCSSRule = (data) => { + const currentParents = data.parents || parents; + const currentParent = + currentParents.length > 0 ? currentParents.at(-1) : item.path.dom; - // Additional validation: check for balanced braces - const openBraces = (cssText.match(/\{/g) || []).length; - const closeBraces = (cssText.match(/\}/g) || []).length; - - return hasBasicStructure && openBraces === closeBraces && openBraces > 0; - } catch (e) { - return false; - } - }; - - const buildCSSRule = () => { if ( - !isValidHexColor(color) || - (background && !isValidHexColor(background)) + (data.color && !isValidHexColor(data.color)) || + (data.background && !isValidHexColor(data.background)) ) { throw new Error('Invalid hex color input detected'); } try { - const colorSelector = getElementCSSSelector(item.path.dom); - const bgSelector = getElementCSSSelector( - parents.length > 0 ? parents.at(-1) : item.path.dom, - ); + const bgElement = getElementByXPath(currentParent); + const colorSelector = getElementCSSSelector(item.node); + const bgSelector = getElementCSSSelector(bgElement); - const colorRule = - color !== item.messageArgs[3] - ? `${colorSelector} {color: ${color} !important;}` - : ''; + const colorRule = data.color + ? `${colorSelector} {color: ${data.color} !important;}` + : ''; - const bgRule = - background && background !== item.messageArgs[4] - ? `${bgSelector} {background-color: ${background} !important;}` - : ''; + const bgRule = data.background + ? `${bgSelector} {background-color: ${data.background} !important;}` + : ''; const css = `${colorRule}${bgRule}`; @@ -240,33 +205,86 @@ export const useColorContrastForm = ({ item, current, setCurrent }) => { } }; + const updateCSS = (rule) => { + let styles = document.getElementById('ea11y-remediation-styles-edit'); + if (!styles) { + styles = document.createElement('style'); + styles.id = 'ea11y-remediation-styles-edit'; + document.body.appendChild(styles); + } + styles.innerHTML = rule; + }; + + const onUpdate = async () => { + const rule = buildCSSRule(colorContrastData?.[type]?.[current]); + const find = getOuterHtmlByXpath(item.path.dom); + const parentXPath = parents.length > 0 ? parents.at(-1) : null; + const parentFind = getOuterHtmlByXpath(parentXPath); + + try { + setLoading(true); + const remediation = colorContrastData?.[type]?.[current]?.remediation; + const id = item.id || remediation.id; + const data = item.data || JSON.parse(remediation.content); + const updContent = JSON.stringify({ + ...data, + rule, + find, + parentFind, + parentXPath, + }); + await APIScanner.updateRemediationContent({ + url: window?.ea11yScannerData?.pageData?.url, + id, + content: updContent, + global: isGlobal, + }); + + setIsManageChanged(true); + removeExistingFocus(); + setParentChanged(false); + updateCSS(rule); + isGlobalRef.current = isGlobal; + void updateRemediationList(); + } catch (e) { + console.error(e); + } finally { + setLoading(false); + } + }; + const onSubmit = async () => { + const parentXPath = parents.length > 0 ? parents.at(-1) : null; + const parentFind = getOuterHtmlByXpath(parentXPath); setLoading(true); try { - await APIScanner.submitRemediation({ + const rule = buildCSSRule(colorContrastData?.[type]?.[current]); + const response = await APIScanner.submitRemediation({ url: window?.ea11yScannerData?.pageData?.url, remediation: { - rule: buildCSSRule(), + rule, category: item.reasonCategory.match(/\((AAA?|AA?|A)\)/)?.[1] || '', type: 'STYLES', xpath: item.path.dom, + find: item.snippet, + parentFind, + parentXPath, }, + global: isGlobal, rule: item.ruleId, group: BLOCKS.colorContrast, }); await APIScanner.resolveIssue(currentScanId); - updateData({ resolved: true }); - - item.node?.removeAttribute(DATA_INITIAL_COLOR); - getElementByXPath( - parents.length > 0 ? parents.at(-1) : item.path.dom, - )?.removeAttribute(DATA_INITIAL_BG); + updateData({ remediation: response.remediation, resolved: true }); removeExistingFocus(); setCurrent(current + 1); setResolved(resolvedBlock + 1); + setParentChanged(false); + updateCSS(rule); + isGlobalRef.current = isGlobal; void updateRemediationList(); } catch (error) { console.error('Failed to submit remediation:', error); @@ -275,18 +293,27 @@ export const useColorContrastForm = ({ item, current, setCurrent }) => { } }; + const isDisabled = + resolved && + colorContrastData?.[type]?.[current]?.isGlobal === isGlobalRef.current; + return { + isGlobal, + setIsGlobal, color, background, parents, + isDisabled, resolved, backgroundChanged, + parentChanged, loading, changeColor, changeBackground, setParentLarger, setParentSmaller, onSubmit, + onUpdate, }; }; diff --git a/modules/scanner/assets/js/hooks/use-global-manage-actions.js b/modules/scanner/assets/js/hooks/use-global-manage-actions.js new file mode 100644 index 00000000..b624bd04 --- /dev/null +++ b/modules/scanner/assets/js/hooks/use-global-manage-actions.js @@ -0,0 +1,234 @@ +import { useToastNotification } from '@ea11y-apps/global/hooks'; +import { mixpanelEvents, mixpanelService } from '@ea11y-apps/global/services'; +import { APIScanner } from '@ea11y-apps/scanner/api/APIScanner'; +import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; +import { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +export const useGlobalManageActions = () => { + const { error } = useToastNotification(); + const { + openedBlock, + globalRemediations, + sortedGlobalRemediation, + setLoading, + updateRemediationList, + setIsManageChanged, + } = useScannerWizardContext(); + + const [activeRequest, setActiveRequest] = useState(false); + + const sendUpdateEvent = (active, group, context) => { + mixpanelService.sendEvent( + mixpanelEvents[active ? 'remediationEnabled' : 'remediationDisabled'], + { + action_type: active ? 'enable_all' : 'disable_all', + remediations_amount: group + ? sortedGlobalRemediation[group] + : globalRemediations?.length, + category: group || 'all', + context, + is_global: 'yes', + }, + ); + }; + + const sendSpecificEvent = (active, rule, context) => { + mixpanelService.sendEvent( + mixpanelEvents[active ? 'remediationEnabled' : 'remediationDisabled'], + { + action_type: active ? 'enable_specific' : 'disable_specific', + category_name: openedBlock, + issue_type: rule, + is_global: 'yes', + context, + }, + ); + }; + + const setRemediationAsGlobal = async (id) => { + try { + setLoading(true); + await APIScanner.setRemediationAsGlobal({ id }); + setIsManageChanged(true); + mixpanelService.sendEvent( + mixpanelEvents.applyGlobalFixConfirmationClicked, + ); + await updateRemediationList(); + } catch (e) { + console.error(e); + error(__('An error occurred.', 'pojo-accessibility')); + } finally { + setLoading(false); + } + }; + + const updateGlobalRemediationForPage = async (id, active, rule) => { + try { + setActiveRequest(true); + await APIScanner.updateGlobalRemediationForPage({ + url: window?.ea11yScannerData?.pageData?.url, + id, + active, + }); + setIsManageChanged(true); + await updateRemediationList(); + sendSpecificEvent(active, rule, 'current_page'); + } catch (e) { + console.error(e); + error(__('An error occurred.', 'pojo-accessibility')); + } finally { + setActiveRequest(false); + } + }; + + const updateGlobalRemediationForAllPages = async (id, active, rule) => { + try { + setActiveRequest(true); + await APIScanner.updateGlobalRemediationForAllPages({ + id, + active, + }); + setIsManageChanged(true); + await updateRemediationList(); + sendSpecificEvent(active, rule, 'all_pages'); + } catch (e) { + console.error(e); + error(__('An error occurred.', 'pojo-accessibility')); + } finally { + setActiveRequest(false); + } + }; + + const deleteGlobalRemediation = async (id, rule) => { + try { + setActiveRequest(true); + await APIScanner.deleteGlobalRemediation({ + id, + }); + + mixpanelService.sendEvent(mixpanelEvents.remediationRemoved, { + action_type: 'remove_specific', + category_name: openedBlock, + issue_type: rule, + is_global: 'yes', + }); + + await updateRemediationList(); + } catch (e) { + console.error(e); + error(__('An error occurred.', 'pojo-accessibility')); + } finally { + setActiveRequest(false); + } + }; + + const updateGlobalRemediationGroupForPage = async (active, group) => { + try { + setLoading(true); + await APIScanner.updateGlobalRemediationGroupForPage({ + url: window?.ea11yScannerData?.pageData?.url, + active, + group, + }); + setIsManageChanged(true); + await updateRemediationList(); + sendUpdateEvent(active, group, 'current_page'); + } catch (e) { + console.error(e); + error(__('An error occurred.', 'pojo-accessibility')); + } finally { + setLoading(false); + } + }; + + const updateGlobalRemediationGroupForAllPages = async (active, group) => { + try { + setLoading(true); + await APIScanner.updateGlobalRemediationGroupForAllPages({ + active, + group, + }); + setIsManageChanged(true); + await updateRemediationList(); + sendUpdateEvent(active, group, 'all_pages'); + } catch (e) { + console.error(e); + error(__('An error occurred.', 'pojo-accessibility')); + } finally { + setLoading(false); + } + }; + + const updateAllGlobalRemediationForPage = async (active) => { + try { + setLoading(true); + await APIScanner.updateAllGlobalRemediationForPage({ + url: window?.ea11yScannerData?.pageData?.url, + active, + }); + setIsManageChanged(true); + await updateRemediationList(); + sendUpdateEvent(active, 'all', 'current_page'); + } catch (e) { + console.error(e); + error(__('An error occurred.', 'pojo-accessibility')); + } finally { + setLoading(false); + } + }; + + const updateAllGlobalRemediationForAllPages = async (active) => { + try { + setLoading(true); + await APIScanner.updateAllGlobalRemediationForAllPages({ + active, + }); + setIsManageChanged(true); + await updateRemediationList(); + sendUpdateEvent(active, 'all', 'all_pages'); + } catch (e) { + console.error(e); + error(__('An error occurred.', 'pojo-accessibility')); + } finally { + setLoading(false); + } + }; + + const deleteGlobalRemediations = async (group) => { + try { + setLoading(true); + await APIScanner.deleteGlobalRemediations({ + group, + }); + + await updateRemediationList(); + mixpanelService.sendEvent(mixpanelEvents.remediationRemoved, { + action_type: 'remove_all', + remediations_amount: group + ? sortedGlobalRemediation[group] + : globalRemediations?.length, + category: group || 'all', + is_global: 'yes', + }); + } catch (e) { + console.error(e); + error(__('An error occurred.', 'pojo-accessibility')); + } finally { + setLoading(false); + } + }; + + return { + activeRequest, + setRemediationAsGlobal, + updateGlobalRemediationForPage, + updateGlobalRemediationForAllPages, + updateGlobalRemediationGroupForPage, + updateGlobalRemediationGroupForAllPages, + updateAllGlobalRemediationForPage, + updateAllGlobalRemediationForAllPages, + deleteGlobalRemediation, + deleteGlobalRemediations, + }; +}; diff --git a/modules/scanner/assets/js/hooks/use-manage-actions.js b/modules/scanner/assets/js/hooks/use-manage-actions.js index 9961ffdf..9ad2e8b9 100644 --- a/modules/scanner/assets/js/hooks/use-manage-actions.js +++ b/modules/scanner/assets/js/hooks/use-manage-actions.js @@ -2,6 +2,7 @@ import { useToastNotification } from '@ea11y-apps/global/hooks'; import { mixpanelEvents, mixpanelService } from '@ea11y-apps/global/services'; import { APIScanner } from '@ea11y-apps/scanner/api/APIScanner'; import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; +import { removeExistingFocus } from '@ea11y-apps/scanner/utils/focus-on-element'; import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; @@ -37,6 +38,7 @@ export const useManageActions = (current = null) => { ? sortedRemediation[group] : remediations?.length, category: group || 'all', + is_global: 'no', }, ); } catch (e) { @@ -55,12 +57,13 @@ export const useManageActions = (current = null) => { group, }); - await mixpanelService.sendEvent(mixpanelEvents.remediationRemoved, { + mixpanelService.sendEvent(mixpanelEvents.remediationRemoved, { action_type: 'remove_all', remediations_amount: group ? sortedRemediation[group] : remediations?.length, category: group || 'all', + is_global: 'no', }); if (group) { @@ -89,13 +92,7 @@ export const useManageActions = (current = null) => { active, id: current.id, }); - const updated = sortedRemediation[openedBlock].map((item) => - item.id === current.id ? { ...item, active } : item, - ); - setSortedRemediation({ - ...sortedRemediation, - [openedBlock]: updated, - }); + await updateRemediationList(); setIsManageChanged(true); mixpanelService.sendEvent( mixpanelEvents[active ? 'remediationEnabled' : 'remediationDisabled'], @@ -120,23 +117,19 @@ export const useManageActions = (current = null) => { url: window?.ea11yScannerData?.pageData?.url, id: current.id, }); - const updated = sortedRemediation[openedBlock].flatMap((item) => - item.id !== current.id ? item : [], - ); - setSortedRemediation({ - ...sortedRemediation, - [openedBlock]: updated, - }); + await updateRemediationList(); setIsManageChanged(true); mixpanelService.sendEvent(mixpanelEvents.remediationRemoved, { action_type: 'remove_specific', category_name: openedBlock, issue_type: current.rule, + is_global: 'no', }); } catch (e) { console.error(e); error(__('An error occurred.', 'pojo-accessibility')); } finally { + removeExistingFocus(); setActiveRequest(false); } }; diff --git a/modules/scanner/assets/js/hooks/use-manual-fix-form.js b/modules/scanner/assets/js/hooks/use-manual-fix-form.js index 2e5f2ca9..f4469aee 100644 --- a/modules/scanner/assets/js/hooks/use-manual-fix-form.js +++ b/modules/scanner/assets/js/hooks/use-manual-fix-form.js @@ -7,7 +7,7 @@ import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wiz import { scannerItem } from '@ea11y-apps/scanner/types/scanner-item'; import { removeExistingFocus } from '@ea11y-apps/scanner/utils/focus-on-element'; import { getElementContext } from '@ea11y-apps/scanner/utils/get-element-context'; -import { useEffect, useState } from '@wordpress/element'; +import { useEffect, useRef, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; export const useManualFixForm = ({ item, current }) => { @@ -29,13 +29,35 @@ export const useManualFixForm = ({ item, current }) => { const [resolving, setResolving] = useState(false); const [firstOpen, setFirstOpen] = useState(true); + const [manualEdit, setManualEdit] = useState(false); + const [aiSuggestion, setAiSuggestion] = useState(null); + + const isGlobal = + manualData[openedBlock][current]?.isGlobal || item.global || false; + + const isGlobalRef = useRef(null); + + useEffect(() => { + if (item?.node) { + isGlobalRef.current = isGlobal; + } + }, [current]); + + useEffect(() => { + setAiSuggestion({ + ...manualData[openedBlock][current]?.aiSuggestion, + submitted: false, + }); + setManualEdit(manualData[openedBlock][current]?.aiSuggestion?.snippet); + }, [manualData[openedBlock][current]?.aiSuggestion]); + useEffect(() => { if (!firstOpen && isResolved(openedBlock)) { removeExistingFocus(); setOpenedBlock(BLOCKS.main); } setFirstOpen(false); - }, [manualData]); + }, [isResolved(openedBlock)]); const updateData = (data) => { const updData = [...manualData[openedBlock]]; @@ -49,6 +71,12 @@ export const useManualFixForm = ({ item, current }) => { }); }; + const setIsGlobal = (value) => { + updateData({ + isGlobal: value, + }); + }; + const getAISuggestion = async () => { try { setAiResponseLoading(true); @@ -71,20 +99,58 @@ export const useManualFixForm = ({ item, current }) => { } }; - const markResolved = () => { - updateData({ resolved: true }); + const markResolved = (remediation) => { + updateData({ remediation, resolved: true, isGlobal }); setResolved(resolved + 1); setOpenIndex(current + 1); }; - const resolveIssue = async (manualEdit) => { + const handleUpdate = async () => { + setResolving(true); + try { + const content = JSON.parse( + manualData[openedBlock][current]?.remediation.content, + ); + const replace = + manualEdit || manualData[openedBlock][current]?.aiSuggestion.snippet; + const strContent = JSON.stringify({ + ...content, + replace, + }); + await APIScanner.updateRemediationContent({ + url: window?.ea11yScannerData?.pageData?.url, + id: manualData[openedBlock][current]?.remediation.id, + content: strContent, + global: isGlobal, + }); + mixpanelService.sendEvent(mixpanelEvents.applyFixButtonClicked, { + fix_method: 'manual', + issue_type: item.message, + snippet_content: replace, + category_name: openedBlock, + source: 'assistant', + page_url: window.ea11yScannerData?.pageData?.url, + is_global: isGlobal ? 'yes' : 'no', + }); + void updateRemediationList(); + isGlobalRef.current = isGlobal; + setOpenIndex(current + 1); + } catch (e) { + console.log(e); + error(__('An error occurred.', 'pojo-accessibility')); + } finally { + setResolving(false); + } + }; + + const handleSubmit = async () => { setResolving(true); try { const replace = manualEdit || manualData[openedBlock][current]?.aiSuggestion.snippet; - await APIScanner.submitRemediation({ + const response = await APIScanner.submitRemediation({ url: window?.ea11yScannerData?.pageData.url, remediation: { find: item.snippet, @@ -93,13 +159,14 @@ export const useManualFixForm = ({ item, current }) => { category: item.reasonCategory.match(/\((AAA?|AA?|A)\)/)?.[1] || '', type: 'REPLACE', }, + global: isGlobal, rule: item.ruleId, group: BLOCKS[openedBlock], apiId: manualData[openedBlock]?.[current]?.apiId, }); await APIScanner.resolveIssue(currentScanId); - - markResolved(); + isGlobalRef.current = isGlobal; + markResolved(response.remediation); mixpanelService.sendEvent(mixpanelEvents.applyFixButtonClicked, { fix_method: manualEdit ? 'manual' : 'AI', @@ -108,6 +175,7 @@ export const useManualFixForm = ({ item, current }) => { category_name: openedBlock, source: 'assistant', page_url: window.ea11yScannerData?.pageData?.url, + is_global: isGlobal ? 'yes' : 'no', }); void updateRemediationList(); @@ -119,12 +187,25 @@ export const useManualFixForm = ({ item, current }) => { } }; + const isSubmitDisabled = + manualData[openedBlock][current]?.resolved && + manualData[openedBlock][current].isGlobal === isGlobalRef.current; + return { aiResponseLoading, resolving, + isGlobal, + isSubmitDisabled, + setIsGlobal, + manualEdit, + setManualEdit, + aiSuggestion, + setAiSuggestion, markResolved, getAISuggestion, - resolveIssue, + resolveIssue: manualData[openedBlock][current]?.resolved + ? handleUpdate + : handleSubmit, }; }; diff --git a/modules/scanner/assets/js/hooks/use-scanner-settings.js b/modules/scanner/assets/js/hooks/use-scanner-settings.js index 4f7064bd..8830a7aa 100644 --- a/modules/scanner/assets/js/hooks/use-scanner-settings.js +++ b/modules/scanner/assets/js/hooks/use-scanner-settings.js @@ -1,21 +1,24 @@ -import * as z from 'zod'; +import { z } from 'zod'; const ScannerSettings = z.object({ wpRestNonce: z.string(), dashboardUrl: z.url(), scannerUrl: z.url(), - initialScanResult: z.object({ - url: z.url(), - counts: z.object({ - issuesResolved: z.int(), - manual: z.int(), - pass: z.int(), - potentialRecommendation: z.int(), - potentialViolation: z.int(), - recommendation: z.int(), - violation: z.int(), + initialScanResult: z.union([ + z.object({ + url: z.string().url(), + counts: z.object({ + issuesResolved: z.number().int(), + manual: z.number().int(), + pass: z.number().int(), + potentialRecommendation: z.number().int(), + potentialViolation: z.number().int(), + recommendation: z.number().int(), + violation: z.number().int(), + }), }), - }), + z.tuple([]), + ]), pageData: z.object({ entry_id: z.string(), object_id: z.int(), @@ -34,7 +37,7 @@ const useScannerSettings = () => { const validationResult = ScannerSettings.safeParse(window.ea11yScannerData); if (!validationResult.success) { - console.error( + console.warn( 'Ea11y scanner: Validation error of `window.ea11yScannerData`', validationResult.error.issues, ); diff --git a/modules/scanner/assets/js/images/empty-image.js b/modules/scanner/assets/js/images/empty-image.js new file mode 100644 index 00000000..e50192a0 --- /dev/null +++ b/modules/scanner/assets/js/images/empty-image.js @@ -0,0 +1,67 @@ +export const EmptyImage = () => { + return ( + + + + + + + + + + + + + + + + ); +}; diff --git a/modules/scanner/assets/js/images/index.js b/modules/scanner/assets/js/images/index.js index ef88965f..8e0a50e8 100644 --- a/modules/scanner/assets/js/images/index.js +++ b/modules/scanner/assets/js/images/index.js @@ -4,3 +4,4 @@ export { ErrorImage } from './error-image'; export { NotConnectedImage } from './not-connected-image'; export { SunIcon } from './sun-icon'; export { SunOffIcon } from './sun-off-icon'; +export { EmptyImage } from './empty-image'; diff --git a/modules/scanner/assets/js/index.js b/modules/scanner/assets/js/index.js index 3eb363de..51eddf41 100644 --- a/modules/scanner/assets/js/index.js +++ b/modules/scanner/assets/js/index.js @@ -17,14 +17,25 @@ import { TOP_BAR_LINK, } from '@ea11y-apps/scanner/constants'; import { HeadingStructureContextProvider } from '@ea11y-apps/scanner/context/heading-structure-context'; -import { ScannerWizardContextProvider } from '@ea11y-apps/scanner/context/scanner-wizard-context'; +import ScannerWizardContextProvider from '@ea11y-apps/scanner/context/scanner-wizard-context'; +import { TabsContextProvider } from '@ea11y-apps/scanner/context/tabs-context'; import { closeWidget } from '@ea11y-apps/scanner/utils/close-widget'; import { createRoot, Fragment, StrictMode } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -document.addEventListener('DOMContentLoaded', function () { +window.addEventListener('load', () => { const params = new URLSearchParams(window.location.search); + if ( + params.get(SCANNER_URL_PARAM) === '1' || + params.get(MANAGE_URL_PARAM) === '1' + ) { + setTimeout(() => { + initApp(); + }, 500); + } +}); +document.addEventListener('DOMContentLoaded', function () { document .querySelector(CLEAR_CACHE_LINK) ?.addEventListener('click', async (event) => { @@ -55,12 +66,6 @@ document.addEventListener('DOMContentLoaded', function () { } }); }); - if ( - params.get(SCANNER_URL_PARAM) === '1' || - params.get(MANAGE_URL_PARAM) === '1' - ) { - initApp(); - } }); const initApp = () => { @@ -112,9 +117,11 @@ const initApp = () => { - - - + + + + + diff --git a/modules/scanner/assets/js/layouts/index.js b/modules/scanner/assets/js/layouts/index.js index ac27eaba..c6c732f3 100644 --- a/modules/scanner/assets/js/layouts/index.js +++ b/modules/scanner/assets/js/layouts/index.js @@ -1,5 +1,9 @@ -export { MainLayout } from './main-layout'; -export { AltTextLayout } from './alt-text-layout'; -export { ManualLayout } from './manual-layout'; -export { ManageMainLayout } from './manage-main-layout'; -export { RemediationLayout } from './remediation-layout'; +export { MainLayout } from './scanner/main-layout'; +export { AltTextLayout } from './scanner/alt-text-layout'; +export { ManualLayout } from './scanner/manual-layout'; +export { ColorContrastLayout } from './scanner/color-contrast-layout'; +export { HeadingStructureLayout } from './scanner/heading-structure-layout'; +export { ManageMainLayout } from './management/manage-main-layout'; +export { ManageManualLayout } from './management/manage-manual-layout'; +export { ManageAltTextLayout } from './management/manage-alt-text-layout'; +export { ManageColorContrastLayout } from './management/manage-color-contrast-layout'; diff --git a/modules/scanner/assets/js/layouts/manage-main-layout.js b/modules/scanner/assets/js/layouts/manage-main-layout.js deleted file mode 100644 index 9e7af361..00000000 --- a/modules/scanner/assets/js/layouts/manage-main-layout.js +++ /dev/null @@ -1,47 +0,0 @@ -import Box from '@elementor/ui/Box'; -import CardContent from '@elementor/ui/CardContent'; -import Typography from '@elementor/ui/Typography'; -import { styled } from '@elementor/ui/styles'; -import { ManageList } from '@ea11y-apps/scanner/components/manage-list'; -import { ManageRemediationButtons } from '@ea11y-apps/scanner/components/manage-remediation-buttons'; -import { BLOCKS } from '@ea11y-apps/scanner/constants'; -import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; -import { useEffect } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; - -export const ManageMainLayout = () => { - const { remediations, setIsManage, setOpenedBlock } = - useScannerWizardContext(); - - useEffect(() => { - if (remediations?.length === 0) { - setIsManage(false); - setOpenedBlock(BLOCKS.main); - } - }, [remediations?.length]); - - return ( - - - - {__('All issues', 'pojo-accessibility')} - - - - - - ); -}; - -const ManageHeader = styled(Box)` - display: flex; - align-items: center; - justify-content: space-between; -`; - -const StyledContent = styled(CardContent)` - display: flex; - flex-direction: column; - gap: ${({ theme }) => theme.spacing(2)}; - padding: 0 ${({ theme }) => theme.spacing(2)}; -`; diff --git a/modules/scanner/assets/js/layouts/management/manage-alt-text-layout.js b/modules/scanner/assets/js/layouts/management/manage-alt-text-layout.js new file mode 100644 index 00000000..bb5c0703 --- /dev/null +++ b/modules/scanner/assets/js/layouts/management/manage-alt-text-layout.js @@ -0,0 +1,75 @@ +import { AltTextForm } from '@ea11y-apps/scanner/components/alt-text-form'; +import { FormNavigation } from '@ea11y-apps/scanner/components/form-navigation'; +import { ManageAltText } from '@ea11y-apps/scanner/components/manage-alt-text'; +import { BLOCKS } from '@ea11y-apps/scanner/constants'; +import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; +import { StyledContent } from '@ea11y-apps/scanner/styles/app.styles'; +import { getElementByXPath } from '@ea11y-apps/scanner/utils/get-element-by-xpath'; +import { useState } from '@wordpress/element'; + +export const ManageAltTextLayout = () => { + const { + sortedRemediation, + sortedGlobalRemediation, + isManageGlobal, + setOpenedBlock, + } = useScannerWizardContext(); + const [current, setCurrent] = useState(0); + const [isEdit, setIsEdit] = useState(false); + + const remediations = isManageGlobal + ? sortedGlobalRemediation + : sortedRemediation; + + const item = remediations[BLOCKS.altText][current]; + + // Prevent to render empty list + if (!item) { + void (remediations[BLOCKS.altText].length > 0 + ? setCurrent(0) + : setOpenedBlock(BLOCKS.management)); + + return null; + } + + const openEdit = () => { + setIsEdit(true); + }; + + const changeNavigation = (index) => { + if (index > remediations[BLOCKS.altText].length - 1) { + setCurrent(0); + } else { + setCurrent(index); + } + }; + + const data = JSON.parse(item?.content || ''); + const node = getElementByXPath(data?.xpath); + + return ( + + {isEdit ? ( + + ) : ( + + )} + + + ); +}; diff --git a/modules/scanner/assets/js/layouts/management/manage-color-contrast-layout.js b/modules/scanner/assets/js/layouts/management/manage-color-contrast-layout.js new file mode 100644 index 00000000..c2f8dabd --- /dev/null +++ b/modules/scanner/assets/js/layouts/management/manage-color-contrast-layout.js @@ -0,0 +1,98 @@ +import { getDataFromCss } from '@ea11y-apps/global/utils/color-contrast-helpers'; +import { ColorContrastForm } from '@ea11y-apps/scanner/components/color-contrast-form'; +import { FormNavigation } from '@ea11y-apps/scanner/components/form-navigation'; +import { ManageColorContrast } from '@ea11y-apps/scanner/components/manage-color-contrast'; +import { BLOCKS } from '@ea11y-apps/scanner/constants'; +import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; +import { StyledContent } from '@ea11y-apps/scanner/styles/app.styles'; +import { checkContrastAA } from '@ea11y-apps/scanner/utils/calc-color-ratio'; +import { getElementByXPath } from '@ea11y-apps/scanner/utils/get-element-by-xpath'; +import { useState } from '@wordpress/element'; + +export const ManageColorContrastLayout = () => { + const { + sortedRemediation, + sortedGlobalRemediation, + isManageGlobal, + setOpenedBlock, + } = useScannerWizardContext(); + + const [current, setCurrent] = useState(0); + const [isEdit, setIsEdit] = useState(false); + + const remediations = isManageGlobal + ? sortedGlobalRemediation + : sortedRemediation; + + const item = remediations[BLOCKS.colorContrast][current]; + + // Prevent to render empty list + if (!item) { + void (remediations[BLOCKS.colorContrast].length > 0 + ? setCurrent(0) + : setOpenedBlock(BLOCKS.management)); + + return null; + } + + const data = item?.content ? JSON.parse(item.content) : null; + const node = data?.xpath ? getElementByXPath(data.xpath) : null; + const cssData = data?.rule ? getDataFromCss(data.rule) : null; + + const openEdit = () => { + setIsEdit(true); + }; + + const changeNavigation = (index) => { + if (index > remediations[BLOCKS.colorContrast].length - 1) { + setCurrent(0); + } else { + setCurrent(index); + } + }; + + const colorData = node ? checkContrastAA(node) : null; + + return ( + + {isEdit ? ( + + ) : ( + + )} + + + ); +}; diff --git a/modules/scanner/assets/js/layouts/management/manage-main-layout.js b/modules/scanner/assets/js/layouts/management/manage-main-layout.js new file mode 100644 index 00000000..f3f731fa --- /dev/null +++ b/modules/scanner/assets/js/layouts/management/manage-main-layout.js @@ -0,0 +1,120 @@ +import Alert from '@elementor/ui/Alert'; +import Box from '@elementor/ui/Box'; +import Button from '@elementor/ui/Button'; +import CardContent from '@elementor/ui/CardContent'; +import Typography from '@elementor/ui/Typography'; +import { styled } from '@elementor/ui/styles'; +import CrownFilled from '@ea11y-apps/global/icons/crown-filled'; +import { mixpanelEvents, mixpanelService } from '@ea11y-apps/global/services'; +import { EmptyManageMessage } from '@ea11y-apps/scanner/components/empty-manage-message'; +import ManageRemediationList from '@ea11y-apps/scanner/components/manage-remediation-list'; +import { + ManageGlobalRemediationControl, + ManageRemediationControl, +} from '@ea11y-apps/scanner/components/manage-remediation-main-controls'; +import { + HIDE_UPGRADE_KEY, + IS_PRO_PLAN, + UPGRADE_URL, +} from '@ea11y-apps/scanner/constants'; +import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; +import { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +export const ManageMainLayout = () => { + const { remediations, globalRemediations } = useScannerWizardContext(); + const [showUpgradeAlert, setShowUpgradeAlert] = useState(true); + + const hideUpgradeAlert = () => { + window.localStorage.setItem(HIDE_UPGRADE_KEY, Date.now().toString()); + setShowUpgradeAlert(false); + }; + + const isShowUpgradeAlert = () => { + const time = window.localStorage.getItem(HIDE_UPGRADE_KEY); + return !time && showUpgradeAlert; + }; + + const onUpgrade = () => { + mixpanelService.sendEvent(mixpanelEvents.upgradeButtonClicked, { + current_plan: window.ea11yScannerData?.planData?.plan?.name, + feature_locked: 'global_banner', + }); + }; + + const showPromo = + (remediations.length > 0 || globalRemediations.length > 0) && + !IS_PRO_PLAN && + isShowUpgradeAlert(); + + return ( + + {remediations.length > 0 && ( + <> + + + {__('Fixes on this page', 'pojo-accessibility')} + + + + + + )} + + {globalRemediations.length > 0 && ( + <> + + + {__('Cross-page fixes', 'pojo-accessibility')} + + + + + + )} + + {remediations.length === 0 && globalRemediations.length === 0 && ( + + )} + {showPromo && ( + } + color="error" + onClose={hideUpgradeAlert} + sx={{ mt: 2 }} + > + + {__( + 'Resolve these issues on all of your scanned pages with a click.', + 'pojo-accessibility', + )} + + + + )} + + ); +}; + +const ManageHeader = styled(Box)` + display: flex; + align-items: center; + justify-content: space-between; +`; + +const StyledContent = styled(CardContent)` + display: flex; + flex-direction: column; + gap: ${({ theme }) => theme.spacing(2)}; + padding: 0 ${({ theme }) => theme.spacing(2)}; +`; diff --git a/modules/scanner/assets/js/layouts/remediation-layout.js b/modules/scanner/assets/js/layouts/management/manage-manual-layout.js similarity index 60% rename from modules/scanner/assets/js/layouts/remediation-layout.js rename to modules/scanner/assets/js/layouts/management/manage-manual-layout.js index 7fdb312e..0318e019 100644 --- a/modules/scanner/assets/js/layouts/remediation-layout.js +++ b/modules/scanner/assets/js/layouts/management/manage-manual-layout.js @@ -1,6 +1,8 @@ import CircleCheckFilledIcon from '@elementor/icons/CircleCheckFilledIcon'; +import WorldIcon from '@elementor/icons/WorldIcon'; import Box from '@elementor/ui/Box'; import Radio from '@elementor/ui/Radio'; +import Tooltip from '@elementor/ui/Tooltip'; import Typography from '@elementor/ui/Typography'; import { RemediationForm } from '@ea11y-apps/scanner/components/remediation-form'; import { BLOCKS } from '@ea11y-apps/scanner/constants'; @@ -11,25 +13,32 @@ import { StyledAccordionSummary, } from '@ea11y-apps/scanner/styles/manual-fixes.styles'; import { useEffect } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; -export const RemediationLayout = () => { +export const ManageManualLayout = () => { const { openIndex, handleOpen, openedBlock, sortedRemediation, + sortedGlobalRemediation, + isManageGlobal, setOpenedBlock, } = useScannerWizardContext(); + const remediations = isManageGlobal + ? sortedGlobalRemediation + : sortedRemediation; + useEffect(() => { - if (sortedRemediation[openedBlock]?.length === 0) { + if (remediations[openedBlock]?.length === 0) { setOpenedBlock(BLOCKS.management); } - }, [sortedRemediation[openedBlock]?.length]); + }, [remediations[openedBlock]?.length]); return ( - {sortedRemediation[openedBlock].map((item, index) => ( + {remediations[openedBlock].map((item, index) => ( { checked={Number(item.active)} role="presentation" /> - - {uxMessaging[item.rule]?.violationName ?? item.rule} - + + + {uxMessaging[item.rule]?.violationName ?? item.rule} + + {item.global === '1' && ( + + + + )} + diff --git a/modules/scanner/assets/js/layouts/alt-text-layout.js b/modules/scanner/assets/js/layouts/scanner/alt-text-layout.js similarity index 94% rename from modules/scanner/assets/js/layouts/alt-text-layout.js rename to modules/scanner/assets/js/layouts/scanner/alt-text-layout.js index c48d6167..06b0e55c 100644 --- a/modules/scanner/assets/js/layouts/alt-text-layout.js +++ b/modules/scanner/assets/js/layouts/scanner/alt-text-layout.js @@ -15,9 +15,9 @@ export const AltTextLayout = () => { const [current, setCurrent] = useState(0); const resolved = isResolved(BLOCKS.altText); + const item = sortedViolations.altText[current]; useEffect(() => { - const item = sortedViolations.altText[current]; if (!resolved && sortedViolations.altText.length) { focusOnElement(item.node); } else { @@ -43,7 +43,7 @@ export const AltTextLayout = () => { return ( diff --git a/modules/scanner/assets/js/layouts/color-contrast-layout.js b/modules/scanner/assets/js/layouts/scanner/color-contrast-layout.js similarity index 97% rename from modules/scanner/assets/js/layouts/color-contrast-layout.js rename to modules/scanner/assets/js/layouts/scanner/color-contrast-layout.js index 01fee795..d17fafd2 100644 --- a/modules/scanner/assets/js/layouts/color-contrast-layout.js +++ b/modules/scanner/assets/js/layouts/scanner/color-contrast-layout.js @@ -50,7 +50,7 @@ export const ColorContrastLayout = () => { return ( diff --git a/modules/scanner/assets/js/layouts/heading-structure-layout.js b/modules/scanner/assets/js/layouts/scanner/heading-structure-layout.js similarity index 100% rename from modules/scanner/assets/js/layouts/heading-structure-layout.js rename to modules/scanner/assets/js/layouts/scanner/heading-structure-layout.js diff --git a/modules/scanner/assets/js/layouts/main-layout.js b/modules/scanner/assets/js/layouts/scanner/main-layout.js similarity index 100% rename from modules/scanner/assets/js/layouts/main-layout.js rename to modules/scanner/assets/js/layouts/scanner/main-layout.js diff --git a/modules/scanner/assets/js/layouts/manual-layout.js b/modules/scanner/assets/js/layouts/scanner/manual-layout.js similarity index 78% rename from modules/scanner/assets/js/layouts/manual-layout.js rename to modules/scanner/assets/js/layouts/scanner/manual-layout.js index f9981ed9..b3626414 100644 --- a/modules/scanner/assets/js/layouts/manual-layout.js +++ b/modules/scanner/assets/js/layouts/scanner/manual-layout.js @@ -1,6 +1,8 @@ import CircleCheckFilledIcon from '@elementor/icons/CircleCheckFilledIcon'; +import WorldIcon from '@elementor/icons/WorldIcon'; import Box from '@elementor/ui/Box'; import Radio from '@elementor/ui/Radio'; +import Tooltip from '@elementor/ui/Tooltip'; import Typography from '@elementor/ui/Typography'; import { ManualFixForm } from '@ea11y-apps/scanner/components/manual-fix-form'; import { uxMessaging } from '@ea11y-apps/scanner/constants/ux-messaging'; @@ -44,9 +46,20 @@ export const ManualLayout = () => { aria-label={__('Resolved', 'pojo-accessibility')} /> - + {uxMessaging[item.ruleId]?.violationName ?? item.category} + {manualData[openedBlock][index]?.isGlobal && ( + + + + )} diff --git a/modules/scanner/assets/js/services/scanner-wizard.js b/modules/scanner/assets/js/services/scanner-wizard.js index 5a9f5940..1e30db51 100644 --- a/modules/scanner/assets/js/services/scanner-wizard.js +++ b/modules/scanner/assets/js/services/scanner-wizard.js @@ -1,8 +1,8 @@ const load = async () => { return new Promise((resolve, reject) => { - const { scannerUrl, planData } = window?.ea11yScannerData; + const { scannerUrl, planData, pageData } = window?.ea11yScannerData; - const scriptSrc = `${scannerUrl}?api_key=${planData?.public_api_key}`; + const scriptSrc = `${scannerUrl}?api_key=${planData?.public_api_key}&page_url=${pageData?.url}`; // Check if script already exists if (document.querySelector(`script[src="${scriptSrc}"]`)) { diff --git a/modules/scanner/assets/js/static/global-infotip-image.png b/modules/scanner/assets/js/static/global-infotip-image.png new file mode 100644 index 0000000000000000000000000000000000000000..4eaeb76b22e8f724763ac84923587610c77e8dc3 GIT binary patch literal 94429 zcmV)OK(@b$P)4}7k=K>&?rQI*&Dfp+FdmKoh#7$7kc1&U-cNhqRo&&yb@AeaJrS9=x_Y{%r>iE<%vW`)ywGJeW&+Mr=wpxcI-fD!Q6Gn&tZDr+;{q0uV23&7h|9K=1F|y zKR<)gN?RUN*KfCp+L;G{vBxnm{Q|lRV>mo|6w|FPI;upQ?uqKAR}oz_JL+1N0`0C) z@8;SUBD2>$P|2$Jk@xPy_T6icspWKERlfjizw#Pv-E%e0o}I?O`lWx5=A7DRt%^6j zdmpx5xe0^)4fOZ-V`yjygMH>q+3TkS1S(ceLseM+|znN-xH4|u51ZovkpIS%nje1qT*Q*(7w5{4trCLR! z(LhzbSJk?zDkD@IM3vf6L4~$gZKh+bsC8;3x}7#Utrq5IX3=UjF+aDUmUVnT(pCRa zSCLg}=+K9*I!~P@hWgf^y`awRf-2il9ZI*Oj#r(>j_P03eL|=<>pO9yicVGF;OIGQ z+dhOX8`tP_G&`~$`}givC%lHizCr4p@QJ_MhohrwajESy|Nc$bzOfG%V}JAc7x2&i z&9~J#t>|-3=T^{X_nrQlbS>y>ik4-%j5@RWZ!UFt>d1xd(1odkDmNW`?p^rr<{$lC z_yw%IT@SXN*P(&HmJ_BP`umH5{pFVXk40s}{sr5oT@baO>eatD;fRH|)z;_tW45E; zwEndi;Bl45CeoADW4)DGU#-#f~esXTX$k)+d2g-HOwz`F*P%XQ)8zw zGdqKpf(YUYQK=%U_o2~O#X$c6HgDO8pZWQB;)E#)!3#2kFIK8TYYb< z@2xgHywJYY`gRAcj@0iR)m8$(4wX~in@t60ZK;p9tIBp-ZB|h6HQo)EWCJ*V0C4#j;@SY%W*>3Uh+c> zFW*KHwN1atxLnkJkshxFBociPb023JUcXyfO*^PmiQ*ksy54;^>rIR4vr}Qr`mP2g z2Um_D&B=c0s@LUW{G4!5Dq3DzY`v7v#jeNFO1o@~^+K}RrvO3U)lgT^QqvFcO1aTE z00lzj6>*dKQna>Yu9bU zJKp>Km}#EF_x-?)Si62it3IRvk+Lv9-^77K2e5Wz0UOq@!?1#nKDEB0R2XQ{A;XXo zB;In@tr+O5;n}Y|h=F<=BvMqRxo?TC0UOwkOepoK)#;kfS1JS)9Swy-RCW0lflsE6 zgH$GZC~m5Cjk;EjM3+=7g2%3YTGu@T?W3kt5G~JH=z_H1)z$m$bj>}W(>1?;0y#xm zs*M?@CZ$%XNMq8ANhwohGXrh$05qJOPm+->qVf?_p&8YR)bUWIr zVKwz$QTuHxHLEo3DIX|sd9%(pEDLBG$!jSXB+eeEt#SKpORaMAE&z6<1U-%_b73wY*J96x&l8p;(lG&HCHt53V@stTfp2D{k2`F0#Xeg=mRpTN$Y zgeGg0d#_Ks*2v9eJQV`Bz4bNdU(=5#KJ{(vluhMwBR3tvh|ut(?pXAM4K0>c41AF~ zm9_1CpImLQeL=I0zIsi$DTOJg?spaHN(r3Eou~HQQm(qX>%&QrvcGFms^~?(cXHX2 z3PuW-cHv0_r5y!VnNpCZW)x&8^-3sm%BYQWe^B)kf(>omiaqc8A#B=tEygFt)QMB> z!pT|u;LbJJGQ0+Zjed2m3?Nj<)z_LsdwvclM+dO~)HbYaGaW4bJu;@*2*ymr*wU7B z5BcEbJe@cWHL> z;l}kDa1SkfTVeN|2e}x!!Z!AlF;&b#q5>#iF6p3(l&Jtl>2iCrm`lz;Q&6tB`V?BW z`mC3VSK8vX7$lVtRoZ1`^kAse4$*C?PIwa4*`uhOc@*{4()v%G-?g(*}D(l zdEg$5O{~Y(O06DGKxe3mVemykP29 zOjFTzbe)iDCE`~3`dPov-G6#h5id6?5c7P2wHq<$gw{!`wuQENH0#>3Sx~|ppgCUj zK*eb;UB|%>W}OG9IF?D~;!oW2==4P2?ZqxahbM$v?!LRaoa%)YZ@m<{G!Dv@EiG?F z=cLFUx9d9U^}4#bOWd$;AC4V6rORNwQm6(7Rej3ESgTR^u z-@(;GUFBkwx{VrqR9jkJH6rk_564RN=_=P91(1_E*41j0iQ8(_O+Ye*(35MiDr+$D zU|qd$sxDLKr>lE8<<4Aa8dy^x)mCmbQmW>g9Su|T5^8@l<6X4o+d77x(@TP15@3?Xcwp_6dqTWz$$O`JUx^_1b z2u_^o$G6V>2&P&WH!$ULFw@@)$3)hf$9sNT&VzwP3@#u!AmN|g<_UDs-&5K?-eHbp zB8gybLk>jNGwdH8yi&%LDMlhjF>}HTU(eg^_V3s68rZ&=8`1j~VU=J36e&;M1&IV0 zZP}{XpU5dqsk_eXGw;IV7N7~fd?*v3DN?^ziS>e|I3BEof+FMB*JAN@rImJBn7S6H z*St)_che+O5sQ4l0L*4<;{ zmuNJdR-B;KwU$zW+$b$ku|ecr6E{P!M~71t)V-J-3%~^Ck_#RnQw9+z zIC#*1H%lNIdMvbPgt1{!vCk_AB#vry|Bnzy#NBjeUnX!|x<+u=%lr|9c1A!&BQnjX zw@57GeTJ2(d>e#w;T1(+ft?#5_KqP^yCPR$(uG%fBbKH#^;+h(7mP5Gxa9{W20n*4lP~f;8z1P*p z)9!S<%FqC`=Ye9ls0_tGNiD;f&WPZN;#b;wWM4-ER#R=~`B2QVP{-G966MB%Hh(F8~x*RSath#xtz1MkLnw4I(wii#q%P z*r`8!_&*Y00^5i53wRg6KfA~*;r|WwySn|cIE9x(j|j)kVa>r`_!{>?A&yGMfQBW+ zK&4i!QpEw~g0J~v;LRgd zY#eN0or0)A<)*BtbJJL}26F_d%6HnHoKddGwsuSE+*GP9>+5q^H73kxN8h^(rn$J~ zv`T~lTmhzH5E52}>P8Q=v71XPhTw3)wg&b+W9)O6s+{2N*OCp z60632R|HQ*YF^p-=Z#-$yao8=w{HVZAGilsZQ6dpqcCM6} z3B@uk3=Rz`6{(@O*tvZher(gb@a+e`ha<-i>6}SYDLcD&>{Z;AX-F9wl-Ry`C(e$a zM5Ef!_hSlBZ}j!44pSkHf~dFs$SwHX*c8s5JB4)(x>;BB2@!;!Al=#3 z>}!<9fL9nbLdGNT@)nkLSyC(qOhTkOn%gH#F!tuzq7Jb$HKmPR9v zs{QIiKSX;@U88Lsi$g)x{a3t2-3VJ6u7*dpDK%wAyRn=*1du-5!G$`c=I^-*AZkj* z(dvS_w^CquMPwSNC|^@^egTwinjne-!Yhp$-tpEuasQ(aP@ zLn%&Mwrs@Y#I#b!MzLk%X4EOZN_CF)dJVF#uApoXcf9LHeEz?DQz=Lb7_6Ce1o{b` z0= zIB;eT&z)^yUV&TxKtri!eHcACfyoz7;=29}*Kew0eXW5Z<$|oK_Ezit3bN|z)ld-I zuUw|ENjsIRaekK4m7}Tt8|?4b%SjO<2kXY#gi;WcI!J)H;!Vc=eDc8oyUsK(gIW`$ z9TDn*RTPnVnaZb!;);~pj&<*@&*vt3ekr!#o{KkOyJqbSz?i!7B>R0u4CDS0{_yO% z39(;r5C{gF_KRTK7P|137W0%GbfgyK0f}$En)<<|2H2-%N-MtRdYwC$6RNb*E*~>~ z0jb%C&#DJ&5ZkR&CoEe-m?eW_wLm(Oq7U{g32=*VOAv==;HM^--?av*W;Vt z{f2^tr`1hgV)w4Ab-gv^3f#MQCypLHg>w^=SiepInNp|dMF2waLL*x@v;xeML--tfN&WlT*iZC{0Qspbn@*DFSTLO==Tm7^gPtDVe$o^c3_^M@6X< zD0h|SFPM?qRi&=Y%r$jV(n`I9b?UoRG&C&d@uramb}E?4)cIRKq}HpQE@aTjHy2tn z7@F&102G_m((z`Lmr5teRNw?9+V!oi>r@L;=b38i0Q;@=ar9SInRY)`zU5UX5rxwj zldSW%f4<=M${4Y5fyqT~`-&rJ%3cQu+xqRp&bW%?R4SWLAllZs%%sch#Om>Fmux>p z>XY-hvBq-gdt0tW=;%yKJq}Kzq|0fwRw{-dKmRlTTnna|VqyJ25wXT2GTgH*Qjh z+V*BnVoOH`_d}tn>wR!YVV82T-E#9a3M}eqw#mgci-%u$4i7#0B;NSOTe17(V`HVN6{W817!^Y&$TXwyDz+Eu==@kF-xaB1 zls#N3rHbaJ>t;3{lmfXawSkVKGNbM2J52*KJUpn=6Wp+)fvXfSO-(m-ZYfHlzR*-# ziY^IMI5)49J{n=D_D9J(`?{o{QSPWI3@F7(=oASGD*M!a`hWqN8_T=b#ZQ6}DP*<+2btB3h zw-ax=^=4fAx;^;z{SV`wuYLs+JXdXXGOF)w-6G%2&9i1;wPZ$iL-I02x2DOkz1r81mVcl>8 z>((}ug4V!>^%e9fx80zUmnu~PD7C%lYM7$*39W8Zx%!%zQ|jMBcMda{!$N%?80w-v zthO6c@I*PWXv85+YD^$Y%_Z1Ve~le|V_02g`(GC{&H8?C7GOfGkMNrc_rdnjNP^Cp zMB>3Ehc3*Q=H|O|+j^DDsZVyLhpD)k{*%nF_GR4uMGh3XP{Z0GgP6nDW`eiWewX1` zk%&TMK8=Civ`44Ec>EKhKuG7pD-H@y>&2?D^!&M3@rcFJxTPvnX{D`5qw9ER7dp57 zzp?SeJ(zpp(@H@(u2ms&cV)W6hE`MB1WFy#TV;X(geqYE3H_*TTx1*8_G5nhtezm3 z)>RbL3~t$iHN)#M&^LgQwHxsCv-|P+&wd&|_(MO4t=so%HAiO(*Ym&td#~7odF8hH z_I=+(f8Wy>QmWKopHiHvW)kCVTdu&=)L8{H3Re1*d#p;M^`v^uW9`TQu6fgSIQ`8> zuw|$NlLbm@*diby4`dg{Q6lVPyiWXV`e5Dl5M;GGCQzJIqi(aQpr)dgqDoeSNo!yx zC+-{Q*XN{4X&Y3f?RJLw>VkS#W%LBcUEQ5lYld4js#l}_7 z^TB2bp`~5ECSU@~htS@5JJvMEF?9MqoE&{oXW-TY)`S5KN~9@hl$w+#pAuY$V3KW0 z>uI7o>k3C&?FAhxrSnLQPh#%u1RCqsVDmNmuzqASZn)v zRY8>5o&bWnlh<5z13Il19)A3O^fmf%;|(_{=;+sJ47TswglC?aL`}KBDhf2}3ZSTc z6qrs*(d%yCjZZ%KJl40TL0JzfwnNePxT)oj6!1yZab)lfCUk8;p z8hr|^>ISN^%*+8;F;lo?*7axlREgIN9YA*a?Rt8h)nEpJBx1QJ4X+6xCEa=sD(-LR zmP_XL0+?%y?Nfw&Pl4SWZ8=vp;FDXjikQ$7L6oo8LmYFf*#goYkXCPfT4|S`sq0p`xkP6HeMi5D*1<1eR=Jl(`Ue#!%wuw)qo+2Zt6Q^; zG!t|YD73roPH#Yi2f_9!g@s0~>GFn(v|=@T?ko;J`~bFIbv@Q>*^1isIx0%lx%XS& zLsqThs(r6hkfDI3Z2*Egf*Y>60jJKK!r_xgl#6TxSMRxwqFi)5(e>9}iw7UN5B<9v zdgnCEKU45kY1h#=IEdH1Uy`qm**gQZT3x+g_Ftv+D*=a`^`N=YKoX%@Q z;6pJ~dYT(`DBT76z3aT@8r(>w!rYv>XX;UOQmat}LZa)Y$Tg$LNOUlks| zMJgy+(REf@X_udM(U^P@Yma{#Yajd9=pOwVn#yI?Y;|#Laz?2jRrFQOG!zssM8G87 zH7=n6#)t-9Kk)Uo6iASJt0PU$C}|&1^;9QHl{xzKqZmE(JQ@Q7*tK;RZhphf_}1Ou z!lCD%)V&8~`KFf{eOJXh-trDmtoe(FU&Q3}S-rPvMoLo?S6;1Lb)%zdA8oiH?*um0 zdJTJT*n;^rBkGt;C!(ir0qlHrVNM!~!RlHsgYFu$kghI6%51$jz!s@2K$Sl6l>DQFe_UeT^h z3Wlb@_RIzc3F~{LyinWgXR#VA z6I(XzR$y`vGs=ZlA$3P9EM46#Xg7(p%7N@gVG;CXK0UR7HET`sOt0?4{az?}Wd$eq zzo@}u+rHOg$EIz-?ZB76dJo>y--jK$_o3Zx86`$lT2~Nt$M?Mxe|PVf@cfI<;O5u9 zQLpRk8_>V6Lkm^R%+Ba+-uj+S5JhgX{sCNh+a8>_`w{f5F*k2Lj!qaC9RX3ry79U; zL%BXK8=pdTQ2U+dgy2cq>0Bt&fQfI(Oy7~&+zxh1EIQVTP1u>)r*n#TsuCFKt)F#F>=&h!pyM0NX0bXfP=qi+X&!*}20wF>$w=DA--TTeA04K6Q@ydm&2dT}=N-m|EU~ncS^3Wv% za3xHMcp4j4g1vE8ih{^5L`f+w%SkV$I$pmivO0puQ&(m>l?(s=a_pN%{1i_MQBb~M zWiNo@+xzPyDd!+r%4D5%H~CO5fKt57fRb6K!O708P7hm}Nm+`az!#WJF z->M+0traM;%^Nmi)B4Rge)=TVA3caYJNKGrd{xhtS6_ww&pnPETNN}>a!zu65um6| z_g=jnPv842M%r^2s2LEXcqpSp!NfYzZ#6TpM2d$_Z6FM|7&l+n#;d>tyH_(4WZu=G z(#@ zqwMN0?cwusGrDG^eUJ1T4M&o>za+drQ-rvp1W}k93*i2<*rjIwl``2jdh^f{mKk7^ zOd)f1!v0-D>A({nX%bk|pL3HQ2N;4oHtCMR?nfjpzyz*5vB*?ngLOW>1ncGMb2Q84 zIz-Z6IPt5|ILM^#r{#9v(G78ae!duAk;=C0q|(*sH??J_jg zS@}n(Nr>T-pbJ$vQRj7_B=y z+o>ElGNq?K>99d5-L8xh%W7px{eSN0aU6a23H0~%W9PP=*mKQZeEBP1!u;H{_XW`1 zqOFv$Yp=eRrnSR?qc31)Zc_iY$W_kn~UL~zU*|Y^lX)>w;_g1)3R9Mp_WTJw|DJ|G>XYo?CKt0x07v^@=)9zE@G>)d&IRjhSU(*~=_Nj} z+9^>%Pzvb@BL9~4An|)}=KB9+AMrB5+Q13Ql;LR({H_G=b@Kg>uS$frMGzE9=F}&r zmIa^-$8U-UI&y#%aT6=!m@R3XSoCx1=8F?%I*>Be$0`Px-%;4_SXJhSQ(C(ax9EkK z9=)S(g1zb!JwCs*y^EKZT#_djb0c)OLqy9DNj>VQ#f+s zkWzpa%X-E3MlfE1l7Ds`n$UB3lm3IZ1tj8I;ywuIhqmqQVyB9TUr{6^%r=>^!oo=p%xB%xA^Iwtr-6FnVFu@;IIml#4 zI7L`!a#eDV606h0mwv^W%7sr*6|NWCyyR$tBIX4d9x`M%;w;6tEvhwYFFR0?K$ef! z27^olr7D6*vr$lzT&LeFfVVivR~8&P_pzpys#L4P(%3M*d^v3~c=8rNx_HIQVWP)- z@mF?%rt8g5Laeq6q_kCS?REPwv+GAun>~*Hnf+*2htb_|E2bw;pi!w|SV2w4-uD{n zH9&#TnV}_mqY-&kH~h#xCKEV4{P;Qbo0qxNDD>unx!sMPzVCndJ}gX6V_9lMm`)Q`hQ_G5Z_QrAa$nMT$RfToa}pPSQa zl?mvk+2bnMvTY5fL`{z>Y;`i798)LORArhrd1luFRf~efyYO+0WG?XBD-;A^9g_n} zyOSuBdD|*Z78VJBba1&criDTyoIE{)ZQJ^EY>$rZwH;5F{Z34WXQ1Vo`@e&m-+33-uUU^%=c+EPkR8c$;C8QKuLF(!e zZvE?qaCUS8eQVknq|tH0P@ELfj&d_88fYY3yKQn_(Fi&>8nA1X2b~kks`lo-*0J$R zHYEZKZ0TuZbURGWDGg&(hUNg&DG5Mxy&$Wb^U`Unv)syT$E@Df_l`7imDD!vBDCt* zH|K9xDq0n*!8l%I_FZ@U5=bYl77>NmzlKOuEQDm4K@oCYMOX)?&*UC>jS48E_JOHU zjE^KDr~+@E`^PCxqDZk~VvbX}B%*esU|Uv{vs$hlncJ}|DK&cr1uAtn64hMk*tVf(uE80lY!9qRL@p?>Yk8&YZ% zVTu-FvdniB67U^CE3n zzd;9v>rC8$j%(VuVJqtOew;jYLV?qqjtinmeb;SRgOg{*t((iHsId_rYu5JROtXcl zd4c&hP1OReHc{J}3T|4~eb*wFoCP}V>a!}5kgOkZ{f;n-N4sMZWl9S;E}lyIxGxiH zN)SbEzP68>GVVK4%xJRU&Vo{!l#JG;B%}mT%5_Id)`9}9h530j)%t^T>+oRnW~>@x zu}3m23Tlj9$ma}B^Dnvku}d7uA%`If02xCI0$2%F*r`~)fGr;N4ZaD|@0%+~e0#yv zF0XV6)&^-4YRXVg+7I#Xt~*@}p2GQ){zNzzXJ#r{yoXA`sdqVEHY{`RPJQw@qp#ci zhRT-f>u)HC+N9K`Ih;IkL~Y+fRaEr!D6_L&<=UIk0HWiEwDJQC5B6!1>BOAGL{mK& zwqQU?t)|z!f~18u&3y+w58fPoZ*??Cxs)q3nvSf~vG>X(U{(W?Jyz*P=2~!ab&z7! zQuQ{~j!2E7dn^H8ySbnNsYS4)9$G117EJ>)Ki|}^>FHVQpB}+qHUDSKVhF3nJT?VM z-@e&P5(Bn;VBe*@(2nT3v0^(d( z;Eq+YeTF_I7Pq8dC)iSf;zxexYW9E%bAFtP8V-snOKI?PQ9wEO#fO*qV+7bLc3=4% z4J+J=g2eM96M@H`coLue{O55gttW0Qg^m`fF9xRzsB0053X-z>wT5fU{Logx#Zua< z%d#qD-v-S0?Leo#4y|?90o%S0vqM*6zPF8Wa6bRn3Bg*KeT+f6Lb8F=>P3jW@rv>#Q^{LfrYque(Uo8cG8l=?m%&Oy$Ps+zGoQn`bK|%arq8y!8ZXuU7Fm|A_dhBbUE2PGCw{P8 zS`w?`dMWMIW!>5u^%_P?Ua7x}&O9nJM^PXBD&`mFG!X0^YT()N1r0egq3@c0prJk= z*TB*>i3JsybVLW=yZ;RCymO0A4%yW?penjvf8U|b&yLFLs?T%dV;DXBBG&J{TB$Ik z%*^SjPP9vmlqdxzG&kPhz#5F7JB#VbbIRq{Q0mw``upn`ADu^EwXWxsqY-NwQl{q6 zSCeRTNOcmJSfC%P31xR9=47TmX9|62JM~|+YjQxfgo*2N@iAta7O8{hGij74ig)Tr zo1y^lu~W{a*U?EaD<++To?owm1!<gx@zy|zBdwiEtPMOvl*>;lQ7e#!=wfO$h)LtRx>u9Wl~ z7N4jo`E?-!Iadyh`qX7)*bNO`g@OW)C3EMBp5XrU&;A@|Mn`eUhz7+_^K#dlavOaYh57Yle2i>fpNU~O`B2eGdFqo2i4?(PWLGl9DeR;3~$-0_R~=J zqgf1y0UJbZQUFlk_L?Yo7GO`H`wg1V9vlu@+iDBjDlN8*pfTX#g)SR(t zJ&KTgj7nqE0pBwV!c5XvH8D@JVgRm_by4e@nAmE^j5KtauxY=YZl*!1r4w30kF=_0 zrUN&-usMsgT4wqZf~^XHlrY_JwMr8e!zSD9D!6Q$iH2)+<<4v4NNWe4sJ|VD*S|mQ7n#pbBm(C6&<7EQ;b(r4@b4y(F?`Us8v&;WEM%#MWz4zZ99hKLk4PS(g(n#i zl@K2M*&RPVFc~rfa81pFp`$t9iUh^N^LT(U2b8Xe2=B?>16}#51DRFRv;}D_Pr5AZ zFsLnA<{*lzN-~OhGNc&bDIJ}+QmvOt=e~dM{s-{lp~IM+ozt^~UkZyUI=_s6TlT*K zzkd09kFw0uYo1E%B%jU+?xks6MxeCPR56P{-ZPq|*UR`U9=7(cE}d8^kNM;zak0N{c2JHW|nX?#^NaPY=Hj$JV?7 z?Q8(6-TdEN+VdN^@MH)c*PSp^W2pbP!#e$CWbd^Oo>F!I>*q74_%J|8i^+JC=!+S3 zEh6PAg)crnFjcS$D)nlTVEb@C6Is*_dvLx6ejL7xgHrh4STvPO5k&cY$>LN8d>PLR z6%;A-P@Lqd6BN>scr!Dz_|jLurbpk=x_Z5VOJV0{mkY%v0hYRUDW#()7Fqnk{@8h? z9P9#=dr7La(pJ2w>$;*`XxIK6HYnMoIei@KHtfTW)+Dl81t*R_g^BrT4IaZ3L#2Lq zv$E@N;2bog?yObXa zS{mIlxQyf~Mg-v@+W?qNwF0;HVE ztJ@Mp|IC|u$@%1Z7 zyjs09KjxPmH;Q+;xr=P%fb;;KiM4g+g= zn6`%UNoABbN(azqT0yC5rK_X2p*q{rO*l>)sWvFFsZpItp5N`kKBn)<${_@!vzyI=R?}$pxMM{nG8t_Z}A= z3Y4f^-R}88`jMQ`(~^^SSJ`z_+N<2i?NJ>lrn2?bAW1udD4F*4~69(in3#|-V=yO#BA_PL$imngXV?1>ZD)Yq?- zs;a#w(FnHLg(>B_JA?V@d8I5>FgrPcsfh{9jE#dd^X}9ABCNZXV z$tl383gb4SUAMb7J9<}IkP?2PVTzH`aZ#i~Y4|c~QpExkB{m?jDbx8xZCM%$fpapFk}HH zy{o5cR9G&+q%2>?-Q&%OQ5hi0t3-m6kU0hqT`fYcjjk5NQr%(@VP6;v~~ zL*^avb+WdGJ?Kw4fLeUCohU5BPiB_!q?~bFRETsh=$TijahE1PFHb-73=SMTq-O!A zA3i0p8yL7W0Hxb=zU4hgYEi%{GIs%j$)HQyB>pbK3imVPVkgwje| zHI~&hOp&{4cKEgERtC^t8Nx{CF*L`IVPs@4)(_OMk)}nNJEKSGJ#^?f1tMLnR}gV} zzJ+go_monY1a7!)ol=0LP9)fMzn-qNogy$jK92s$aRW3;v7(^z*4!+nrq1Eyfg@J;V0s@3TJnW0}PO^sSrk0PY}RdsdzwVKfJ z`4u6`2P|&^R02#RazVweFRA~44i?-e8I?m0baEz;C|2xCB8!E(We$wO-%%8T64GYy zW0QtKx=~#g+S~($1$W=ycyJOq70s;SNki>9vK&)5l}qlth~hx$czF-Sf+3)(KBsosCtMPU4H7 z{bStojj!UDKUe}(X{*c>0d%YC-ayGE$!FJIKy}Nl*tzunVCvjC9hBatxFH2B zb2F0|J8~Mw51l~0QB^Lq4$d4pje{?aVticU)aY5A--%+F`e{U6W~N!uFhuS+VMf!{ zD#q=j=dDw))U>lrk#a;aQ0A^^Vzn|0O{6?!!oqu3j?%<5{Lvt0m?aUbW_UAV7gc#_rl8E@ke^nbubH4ceoQ?Z|oJ;*eG*?gc~v zv2rCyAfYztku9zp39N{Ca^su|#y4R5b#80-K+o5j0ZM|YQ;5%q2(9vEY+Xha3P&ec zJC+=jlP1!PVJl4A61!%QQ=i<~kie>YVsIdO^yo1>@#NFyoMp<}tbXW&Lqk|MvJRKp zmXA?2!edeYA6p;)bY)z`UL>iasjn*=1SqTv@A$F(_%|Q^MI1bSME&3|O%Q%*rCoY6 zzlGZTX-w{T54tq5o9#g6=1-!A3XUK78f0q$14@CRNbRPACjy(gB8WNlb9v^4QH+hv zPfWAq@wF@R4I9;0Vv8gPGRaMfIM`Z z!j2Tnta_x~f*PD?G+pLhbu?0s)FuN@b)`ntl=5VNjZ~|m4)s-O!l_H}%25`tjufKI zsxbkta*i;woORX?A?yS@22D6o0MUM>1vTC;_);kZ>9QQCq<^ILE;j#QE+a+DT?1x9 zWXXvvKpFxVnRCQAL?msLVL1UL){|lWdxIEu(dV3olY3im-AOm^(+0 zJpMSEO*2Y_q~%(zuKe|b7#@D*6{w8tmW0&6IfVt9SA=Nq|R-<3)$>(Og{caT(S2J*wkrZ|AG5(dS+g! zJ4)SFZmfP`rd1g|JB?3&aSpHBw*gmQxdyd>67CM zp2!{7!Q9-mf*vO2@CaP>KHHWUs6xjz5e!i*6eYo=JXQoh74@*FWiNs?3R_TRXd*ZkfIrF6Oi0jts*w;jQlj;&H)MD95PB?=O6D1f5eRCR(U4OLa`hOFw8 z3_|CV>L|=C4W`q#fD;o2EZvAm%qqW8b2V^niSO2_NgS9&(qJS! z<=PS&7D0_S=i!J7q=zic{EIf}X7Gd|7RVwHEX*hsj>UeBM&YH_E6HYBKDg<^U&Tvg z!X(oSjXi&0`bx9(c)okz{pJachS;i$wZE@{Aq7uELoeUFOR~o@Q9vyhd-l`6cK)xw z)H;~*B-sSQ*60a|rgfl;Ua9xW?I7?<+py|E{{CG%@$R>7#cHveW)^fRgEa~$TC$C41t=}`I~q`1Jo)4pp4&fxeF~zs zZB_T3Y6s&})0mv?;OuA8~)cN|X!8 zxZTV%dDmt-H;~lDf(E9p4l)meMY_6f3WBd%c&aOvsa7?C;`B~#JLkS5aB4J+5=D29 zjHa)#043V;RS!@iVkPEkij=8zn zhyVO${r+mOYj+LdJqi>*@_#>r&)j>mXSpA|^M3s7Pkt4bgKfYBuGo1Bx7_$V{_XGm z91b5{Y4e>ab!YGj_1X#U3=x$+4DEd<`Zw-FW%NGGO`pYJV-~yC4k?gmVWF**s^$zb zJ^HPKJ_RFl^Bp|!#A(!?9~_q^=bwv)(JRe$OKC>WyrINRb(>6AmSj(gOR;Q zBbI)mTeYP!p4Kue=#kw0U-e_7M6%&MBr_TzQC{}I3yD|4y67vuTC-2@B!V3VZ>}6 zGJCu7p<0t$NqRS+1sm2#0Yr*V03Amz3|cq@W{8w5-z*^_FbYXLSxa@$Jveg;$h;+%CLFP}?+|9w+DNwq$=oUW|*WW#!;=FForGl04F6dLvCF?{*}PFI@xcSH#%NoCkMNE7X5dO}@F zd?|EZCUXzg`J`-~CjJ}N#yS`>a;a79s4e>4wxyl(4t#`?*`!u6uC;E*WD=(`G_^~H zimd}WNIkQ$HT9~Aq0+(P)l7pEy=xVU5)jvDAdc(c&Hl|pyXp8z$!c{KKviDF3Y2>w zcFK&fDv482EIhfQdPsj(tgv`E5e|ytLHxSk@O`~S9(Ut+qN(fay_^I1{8C6RFH0l* zftv$XphBk-iCnvNdmQB?VBzy`^1g||RJD;J^UZkE~O2^vm;vwAg7B30#n)5n5W~1=JDyjIEL$9H;nDO`?39s zeoRfYF}ybXHip+#@%7K1!JY5kq~1^C_P4IrWw!6WaDd&l^E6hMk!wtWf{rzkm>ayt zWDBot#M(_SU}N_%#wO3{cqRJn%nK8|t$$Tr=){*z+R_d1G0rqy7b!w6i@5CAhhCTm z9h&>DF3fyt1TUF1lh5inC^#q+`bI$6rz?77UR_t#A(g8w_3R<8^X*c+IX#Ybtt*6r z#uY(n1LJ2c}9^GF}cI%36vsNZ_26hubE~7DLuQIcM5a~QJUKffA#1s>z z{`I5k{05=EP%aGZ7twG#5w{2N_pDGcVTzQiLPV?JH88G*F(~rK6vv3fbqL2WA{-TK zBcFDLGnjWf@|`m4*`qJ!vP;W_JPCZ;7^qxlXaWdVj|-2VmlyZ%_oG;78Oh6ieSN4m z8rU#0g5|QM!jCK{oWBH&iGnhcZSG+Qh^Avn>x=&M5lhJ2hAmY1UK4sr^u&KA%JqO3W8MbYMTP4 zik&DJA#o?c6m;BIhAvh~r}C1G-+05l6L9V}$;B1#ZUdjX#eI-Q>2rAFl3t3akloxW zT_Nx~5Re0s)H2%u$)Cz2S&8G9$Q5zwkSp#Fs0*ZOoM1{CFV7wk*j{9SF~QU_iTGG- z-C3{Ece$N1_gt(#nI{si7KF24_GF+5%!Mabh5AIs&)>1*C*aS6{c+dz(Ax&q3=Lq} zMj!GMdY+f`7!-d-?i;)IF7Q4S{*#ejJZwUhiYqDyTsP@52{zc8O1id6<2K9 zjr9X-^e8&rAv!lp#*!wg9&k>nRnuLHD6+xa6*Cbq!Bb7xv;%-vxOA>4?Z(UOh+~su zoa(3G@QRLUs;c%>Oh)laW-^IuNHQ=~uNfB}x$Ed(g7r`l2*KQU(l=5%oLeQv6{zFKj(az-KDiYdIc9Q~3=a0=l`>hNLir0q z%806^`;e3m7DimSb^HR0Lck?kF7oP>SD*Y3Da1XwydBZ?{|d^McRw_$(-$meH{EoT zcGWo--uvJGe!p&fd|bm7y{|<3>^F-?;obYa*IiH?)O9xwjyH=UVRY%}N z%kF%47%@lgJ6iU0zqDH`Q=~-E=L@xOefLJJdQ$*JwA0%3CgsR#u*8z>i42=`f==Ba{vp^--EekOT$oBY3qDYok|DIu5zJi5b9_QsoD4#0wxDgJJ zW#+eQ9)!uYCY1Cf>78oSaZDA{G22d?(hpGGmea7X(AKU+f+EtCs5E`1eN(_VWf`xj zcT%(nq)4G6cyehHXkR*srZk_?oI`u^VN^!0RgHi7(#2fP#`G1-#^z=lxzgx-+1N7! zHCAJ?{c}oF3JJMy9b@8N+ji-n=pkN>5_s*yGvReMvV6A zM?B=|eThgA@}J^Uvv?k;rJDB>f~!-AzDJFeVY}62hmRa_=%D|lAAYrJM&VHsxaBYo zKNqqM8NY1Y&qT)b6Mc|LX^Eu+4F5ZGiZT;9rtPrxM;CAac7gVM4&V^-qv8%s~mw9 zkC9rcQQMw*eivRHrhXmDr8l?vZKw_GgxuLdzXGf7;CAIOt6*wiD=KII3)VLJ(N|Fq zbepb0edCP~O0hy?1Y^&BNpo4jlURehNmPHlqFl`AhZu06L~0w1a4 zlgywHmq3|1wVHw{X%&JBWlopI1xG*YYA4eQmawr^nR~hyMxmn5<{nznYCes;^KKLw zTrFMY=9?HjglF#k->_!Gwb*w1KSFindc5k)(}-Y_eFmHX$R!nVejsupj{F3I1LQ8UB)u7pmQ0w1qJ(!in+|^V2KzUq&Z-VDvS4D zn8tuF3yl#`z;wJ0tV$8fU&0(BIf!`B@M2+MYu+Lo*H3?<6Z`^{YRfXh!e_{^@|FkUJWDire@pti!UA3 z$Gw>S+u!^9T7{w!Zx?FXWp^INxlnkTo$bS?zVKFjko(oUbm1{Gr8UUF+g@MzL;14JlGJ zI|YmJ`x6CK^fu0Y6sEsWz@;7{X?dHJF3=1O6g*x*Tbj<2+<~>a0i{YMvm^DoO7W`1 z$3_9;!i?^7t`vR0?RIctY98Ho3ujM1jrP4C#je}_5h~l?4qz2^HcK$WP|~}_@_liD z8SlDv-}Sp2%_f3!B=}TqnfESmnn~*p`{arVBJhgDoaE0W5d)BeskEOE{GY^y7XZ+F z=5E9=P>?Z=5I+3I)gkRM^b#Q=1!}Plz85^@t3~8vHLos0Ah|1l8T5$DV37;&#EEe8 z$gIy^0Tj9KmJOgJE?BvfdCA9rE`ln(s_0K7i@!*I(IF^GMf*%dE`1ZGd$5q^DYPTm zMnd-dfxS{jN({Yj+qd$Rqzf^ECxS(KKc7*arN8{!AJEXUYsV`)>TPzeq2rziqF%iw z;&=B|xwg8s0r;89WE;(+_hR;`kD)Vr6dQERlTn&#H2yqQt{ChAK04k(*B1jznZE z*x7b=qxaR=}!iH-83`1Ln?(`66@yFA3a zLEmE?P55m+A;IQzf{U^MAY<^7h=ms<5S5B8?33{ShLt?F+P7b;V*#q%yV;L`b4&8; zU9#3>Y#E0)eqQ(?8Mx&8lcA*|?n0wSyEx}TUU4ddsC0y#J7*4*96Y7&nbqXJbH_I| zJ)>_BQg-xpX?*t?`uiK_1=OY5h176fWy#@q^y`U>6_IyZ$i+O#^y*?3xY?9;7pEQu zEzJ*=G8DTDS-7X$>jZ78^m!tH))w!i+MAFPu~Gq7*;%l9NX;|HL1lJN5{M zWJ@VPQU`Mrb{hCm?zWCeJn5#@%G3DAI*mz*^c6yoka*8i~Q`sg@#+sye1 zNkQFtf{dqoNtn@ph+KG!<$dDI^EskiwWbNhpcND;Pk5^UqN4Mle#sr{!mHSa9=d_l zC|j&U0yqxOFW?4DP zrnJ&7)LN4VarD05$HK&6o!EE#+V!Z+9mTrNB&v0xTy`*{51b8RyzD~+_%u}gp%foD6@Fez*78qu**&{HJqZQIu6(mzl$!Q@u4 zui8`vW;!f8F$gKt_6F6lb<+xpxoW9)QsDZ=H8tFI?GO%3j$=lF-fVLo&BI?s>xD02 zcz8P+>#s-u_BWxn`F0&Iw(3j<)hKlM7D%M@0oma;9M2vXlpQPigw2_%#jF5!A zG}Vep7i84+roEYDqN0Dl#t~25iB+IP2>8uR0i!l#CQErmnwTm*M_I2sL_wxv&x zz5{G|NHg+It5?R?Bnj+nFWyz^RfpP0?G{yRT|a~yx6I&M$J#oWohC}AyT<6`F*Ih5 zqN9Lnpl=g~w%v~YZEr+%csH`X4XXZHQO~<#%xC;&53Lyk7Ll>kL>7=d%ODFX4jho! zK|_+c28HYY5i3!G&rw&T+Qb8p951*4Zf>sM@Wmn*=zO@pj5Je@ zdwFu1f0%NK=Z-P~H6#JgIg_wGD+#RBUE;+RruX2J!8q=g{s7o8*~B z>M2*DFhTP(b7wI9;%6aV_^g7q5d~4(&=}r<0R>a__4|+wY=da5RqZ9HS}Cfx^bg9K zS2;W(+(VtkVbSL4nqpZ!R>=Utd=#Enn)ttoSR&XzR$7Tv9I)+D5YikW!8NhaUm)3s zKXSmi+`KDMB>kVrZwFQ`Y$Kx6;y@AUdTY7iEk!X-OBtm{7BZ3tkYa-|SbartQ7hBj zak!@}4Njy+Wfe2!K_-^p^|Zv(Cez~Xe2YR(@?N<3f=SFlkIb**DG@kaxgWPoE>5#X z7BS*+>MSbK-^sRp_ z`qo~B>bfhHNHu&J1C{V!KuUpXBP4CAw+~MLY^V1)TFM zRq=P1p`5D+l6C8=K~acc6V?@FDWlVF`n)L1wm&SHpL}_dewGDrW&LZpq-$vN8kY85DRpKd8Nh%$pOEOd;z zq=Be|emmWZ$u1s}ZnkYKqrwzDwF|k|x|DVx>*#Fax+A2#P+h4(McN61E8R@H3ad7e zCV^7cH2|ka?a}DJF1h47Cf`)osQcYkTLYAK1qz#wN;P1pzlI^z?z0_pJ8}0Sx((~Y zqkD6=W6z7bXG6mpeQqg8n{UlROr3x{{B1kYaXw)@fz#NTGdMdow(OvIA;KN!n1pGPO3lUR6d_LPi|teP{Nfh5OBO5! zN;2dZ#>Hzl*D5CcpmnYnd)=w!(^F%3>bYleu{J#L@=WMlT4}GGDIl01!`Negg3%Yh ztn(suEC}gau0)}dOo7(_{n;7(Pp?xDwWSYzS%47dc<*R`N=3WQ+DaK}DrKjxh^Z^g zXsCuFC`xHS)38)C>s^pC4sEcVjagtOjMCub#+Spm4}D)`W1T3cNe68WROTKcjoXeu zPHm?Z>dd+dNo}G@h6(T*H4IcG&bH0D;clmJ%4Fyq-5ljY=3v2`19BZsqjmf~OrQ83 z`k(%Dtljkv4D9(4<^H<@mxC(BqI(jx#VSvk`;w|EhLD1@El59xCwSmDkceCmAsEVk zcR&>O!6qMOPy!hkAvzYXB(b^~r=<`1al1mIRW~Z!agpGo4Be(e`1i$gSJ9hFm3T(a`W3G}Tkw@e{{iF?Xd$Tl)m& z+;9HnKeEUwX<^2l(7Av^ke|6;rp8AxH9duk zwe6c;&G~^!TXjb6x`SWAp|AZa&b;^qv=t2yH|T8Ox`2!o2i+-|PNbp$WKKcRUp+N} zLqr>uDk>CvqyVX=6989rY?PUX#l$X=LPajVx>AxT?Ll3EQ$wjeb;{+GnU0dcrqym+ z7kn6CV^R=w?c~BnwsPqSbc-?*8$}RC524L=OR4y61B42k=(AZL;4G_}x$~UvLUBtJ zTQ#8CCBa7b=1!aeClkP~hqA!++Gu>8Wt6k%<719;k#9jZ9r4Ic9%O;AyW1t4QO*TP#OWk^02dDLx;b z5HY<5H;n5XUm1vaxy_xMwZp?&_I5}-KR1U*9(q7^{$qIE_4}}XWJyPU0c?pd6A^gm&0^jW12E!YPn>$34OgS%I|j=?>^l6%c8nQnFQUmhIA zFT80BZrDtt>T15@(KyyI?!A`U2c-+p=?3&vE16D5(4Z{h!T^^0VUz2vqu{KqW0|Nf z<7(5Q5g#EhOnefDB2v04UDM-A8ll?>_bffK%vL~~=y~^4+ZN_qCY=Ippo7x8h@3R` zy!bv#hWl6)d@SP*HS~R5b88|s3T86p&fEkRUi>1)kA55L_x>P;uKo#R{hM$hcE_z7 zv0Rq2j$}|0r4V3SgiFMo3wUVZ6}x-8Kcg((**VuZ=-8{yE*GIs5CNvBOc(LK+%+Mi zW(g014%IT&u}K;CNnUdDcxEA4g!V+e4=c}lDc_I^#- ztW0XX|ClS&_%ZI^NsZ0?R*_*VvL(6DeZ|SuR8-fJTFl$;(6| z;`FOt*U+S=r&6#qs9a@zO6j4_tX3zGsptfmHOe@inSnJFYotRO)H`JiryNj{Vu~nO zn{qsL!~IGtRisc!=n;96Vw|j!Md=PYt+uVJ>yLvb7_K)Ox~=A(*TsSY!g-<`cOR1H ze|R_G8M=LGPLf~8&zXq@BQ;7XZR-5Zv?i4z^|v^pRKWRTcO&=&&u`0jFUqSc8EbR? zQCE0a$t=x?Xng=1;Z+zzRvKn(*o9{lMovY2JD=x}^NUV;A(`T%n<{f4#slCTJh^&Y z0K80&EXnj1x+B_Y@6Y>6O+@}xl>8XL&Lb_#sJ8}NGe&vJe@l9b)QnzihmWXZm{|!zk>PZ z(k^6qqe4z9p<^aoF@L?dCn1=Nxve@0EK@N4&;HO(JbP%m=LET>m3CQLw~glM2XOZB zKft+(V)s*b?#ZN9U&t*+ zu}|%ma+lR<8#8K;+;#+1nf_eS0oMdbcg+4Akqx8sqe5WC z8+T`3edU$v>B|I?lZ4$>Pj>BA6Gx65#zCb(?cKE<%V8G;p7tF3d`dE!A;p$Slq6{{ z+Vr<$ub^o661i;WUZRzOot$WuUi4iK>vrxt6W17aq;Ir3%lp!w?9OK&`GtMjmG>84 zK8n&xyWFiac>w30{R>PUxfcthICV19dBa5q!zwdHX=mJHl)E1rS6jGu*@s}Y?ScZS zzk2utPE4-HkG*chjEs|JN|#E7lqmZ{&P?z)#YmYrCTK;8Y9ZL7pz;=rSt7Tb&id3A zb`BV+-ESJ2XtbP8JJ2yol5MA%C)0!u(EXGE&1E!SP-+w@PjtU^XMhy0ma6~c_`HHZ zSrBT%J53pVcClDR9+{#r7amup&%IqY-=4?m{hvZ>_9WK5_J2WjxOi%#-}t$!^@H59 znFS=_l__+GS?cK{b!wjfrEQ#E=|A-}dc#niX23M+ixyIu)xW zDF}qygX=KdKKuZ9`{O;R0PQmeDtO;bTQFQzDv?rjNEuo~IhwjAP`r|b>6I!vhmx@F z6=?&)O*U{+i!@+m6nI|MsSyaUDmJwN)!k~gb)2I+4&7H|!e-MpaaR<;URR2fAZVMJ zL;uhk)cYD*nO;*J#?-{LauHV08LDE(t~J^X$pguqV&p*69CO#hkndX=WIFJQ2GYpT zk2&SW-`eP$(5S-f4Ej&}DK>2Vr^xy@>&=GL1KXXqZN$6Zx^;O1$8#1y$o0FaP{HGm zOlM+&*d2gRiX@`DwaD$o_2D7`5l%sl1%fKdNBL<$wgL;+cl;+&)R~FsVV>=Vv4<#7 z$Lu->aDrhZW0ruH0$dpiOlg+uvTEA8^N zH>=k_M`=s5{{^pZ6N@Ml&J7kRRH?)S({W3_j(_676t)Q8MG7OVGy^&_PC!I$I)4PZMV$C2di4GG{A*P~*GCp9Ootn72l^#&3U#h0DPrXLlo!|H$!)=e~-MRB( zpX2JXpLpMUF)=ZL`|f`L?Pd$jg*oilekHEI;Rc*Obs86M^1`{(JzO{q$iYwgLzDLM z5HeyX{zZnNc;Nz;h!icuLhOk`a)$Da2`ZGmVvS($>SXB?)PlL$870xLeBdsvw9-m@ zSXwWH!y^wJU*;63MA>nm#uhUb zN?1iRR+VJBAK#|990#%dL%g*8PFUsY_3-CJAh1b=-Fo%C$`z+xf#m7vySyteG{_~= zikF+cl9U&zQmddpEDE|`vt|wUDR&*kKE3{pZ&Gf%-B_5P!@@!n%VA3fD8ZE>c7gah zWK`IzuUyGWE;_+L$^lR)%L;3#vPzN^xj(h!S1fD{frxQG0G7b)%#3!6mR4G6rInU9 z;nfkQzeua}*rcUyy`vk+R3LZz=Al0(E+`d8MD!r;6sOSPgyLZf-Y}80Tjb%37s&!_ z@w!a|ld`;(0^uPX)|3zub)jB|oP3zUSQ#)w&*`GN)X4^YaTjspduR4;R3lOy)EV1dDqBj~(}L z%QiL3U3rnRl-08&yI^_UZvVnTSNzLLq6#3}>ikTNqBV04?dh`+?OA;dQIKfg;4o%R zy`V?glvY}4rIi+&2NbrzyX))%4^GTg$J51Rk^EyFVUbeA!h3+6Kob0G>>)jZsPIHC zETu&X5qk7(Y4^eRE!;jt^t_*gDmNn0c4PtSz=VMKSi#^j>Py)&+U(#=@N@_n^O%LY`6Ty?DqYcX5ta8%K~SQ|s$s+A5R>nV zciKU@N^?PZ$xm=KE38=!mO|l)lcz8>J*||dIsIxjn`VgZGP$5GggqI?#@h$%4(4IW zT|6>+s)!cZ%FU0)j7oq^U^;Dxjf?6w=Wz6~ui^Bw50mtSv584cof|_-LFVN2jNU)x zk(pNKu&MOt+1WYFDEDVp0#s?Gl~!8btZRbYZO;AVzB_Xdw3$e~0pPdz2r>jycigI= zXbD7l1-Z9D#9ju$D@I6lAUz+5=ujec$b*xJ*9qqbvHXEJ0cXNYh~ul`?CAV^94nSA z5OLj$=7Cx)nm;z<{s|KX#?2SPpC;epe2mn}wG=KJ^#*2C=RdE&si~kq0~Dn-XfMw& zd9EV$v;?3d*Bz^bD^!sSj=A&%lb<5C65&T_{>I_&ufN%yxhE6)FnaiLyzuSMp*b^& z!GU264h~`6#;us0o5s1bXHhwE0>_V^PyjlEiOES!&owbVqrhserIe^ua>0~VT4|*% zw~4r0@QFU{ZIkkRWllMQg$8$XmU@;NmQ~E7RSvAez9iRqBIU>Q3&O`!#mpv(stf%@ zvO8YRPws0An&5YK+_nimUa6qVNMpWC>uo9z5g(6<^-Y6NT)jyQl;5;R%oA@w9DHsn z>vox}mu|y8@Z_2+-AvAsZX}JaU%w6u^Ya>@$aUAU3e<7{l)hM&l9AiPOa4dTebY=} z(zz{w@MK_hPmaQVwsJ=69HP9lPKJPV2T>Fx{=#=Yi$}ly35;ymgbf=uBCAy~+iIe# z&J}GivVH>wht}Y#YxgPDX$DhMQ#f?=7@j+L9OEw@RtgkKE3LHBN=wXp<6!Q&LO2gz z=%X_uNm*qmi@alG8ARgRGB@j39 z#Kayf89n?9_7LxPD3To@&PT=1q1(R^0EAZ?c%XYwp+r6p)Z#fLVpmnKv#mTFxjBGI^!^9AMRJ~(5|X$q1i$$ z>kap%l~!75uUfMdVR(V%@$=KC1km7clHO6HlmdQP01*`R=OV5l3jcAN2}BB&Z*p3N zeYlUhA0Z+YOYn_k{Jk&+*B?Iv$s!YRUzxfFqqza?DXHQ-#+8*c|C8B~@H=#Sic+_S z{jrA01gKV@-G19G`1(EH(DOmHX;$$L%`Cn=02O=i*j1dI&)j7q(G=eO7d|4m>~`w1hPy5f2`mZl<8$#}d@c;vza?%w#+}sX|Q*7-{&!Yb`(!N{XCAI8q*``+8wDU1D1N? zV7rBe%C2jSUe>ig+=~4bi*A_kvAdctn$lh|d-31>29EvZe?V!aT}s>Wv%i9?e&gR^ zc`QF_-oQ|bPxd`hqC@~M4k=*Mm(UUyogS#iTi!chItbI9XD~^Ctk6iQ>vS%itCk^iE z26xQ}4B=*FUnLh!X_t*1`orHU;iA@)6$KCfFFc>)o;Jzvk z4@~Jn!GY5<7#oqL){^A$Px%jluX(c zHx#TN6vcUADWab{D;h@h(YHS9YdhvU6QI>ABgo)g?12Sjea!W@wfLWGYp z()V@uU4|V-kQac7_e}8VafJWOw`Fxkj3t;y#MXoST)uJKcENEtkq3f#&%oljLtCRD z@}hXCqSOV&CX+a*A{QT`n5os~kx9={e}6x2zwK5WJ9->4!?KuVk$8bKQH*F2(1T60 zLhmkTlZ20F@>gw-y4*epj-L7MXYlaXKY^2HMlmxpha)G?Vti&69R*Kq@{8NpFGu}V z{bVF{Y*RH8{s+;umt|Lmi&jTVc~HaB@?Vu!+7fO0*~k3nih`%x{+|a?T4|TU9{hW^ zp*=IHKTkjR7}nkP{kRwl$*P^@AK1O>Bse_L7wO|p9fdjYVckZg1~~5T2$m7X`1&f0UjScmG< zn^0By_R7uaSFNI2sVIP|DzMbPJ+u{QwLA1KG$0zFQi7`T;&mvofglwM zrIq%wSaoO^rImK6tTMF51JrU^1V+LQ!hv^tnL@mvu*oj{BQYFcMc?gq$vC)B9os(} z=Z09iQFn7jMZzx+Ea2K6X;A_>RY@<-M2;WKZdjXH8)b4lVR{(5&V!s_ik3SKrg6Mh zVb5c=m#Z#)=j%efGE|1(x-(O{$jD9SPA*niC)n$pINPy(8*aMsHTcFizlCM-SsLCz zZyhk*jSLrv-zd34q`PdxLjSY@KG%?vpXa9W?0sLxW8eG?PMti7iJ57Po}0w@#2lI( z8d2A=K~-UxEB$V2>bQwSw zN7daHq#%Ca?nuKfC7u8At)#@w*fRe0E9L{LraVz^W>aEpca zdx1Yf2iJxp2@wHVPL=|%2NAJI4_To)pB_O3%VFIm2r+{FfWkE=xUi~CGFP1dC;spwXfDj->2H4yPk-yPn4X%z)Z79N z9Y2ln=>;q_7j!aAQ(5d^)}s_8x$P7fRrN1GU%=WGr+x_?`ayKsIu@!bD*A_%*{hc_ za?mJ}TD@iu+PLycE3LHBN=wb$zcULV1}!*RCMOS=-RBR$iD8nRwh)3Od}Z`j?Ugk1d}$2g*eFw7BpreQ z6F3PAcEDH_cK#8Hz?FM;4MXMW1b027v_pv1jZ3^Brvm_>P_P+ZQbU0zl5uogM!3VE z*ESaOF-W1xtdaz z>gw;Vcu1!$p!5aRY7GrVReiNO25Rbkupj*jocad`F*LjegM-7Ush6nKLG$0WnhR>* z3#tt>n46kW5I2X?N-M3j(qaohlX?O~REB5b&JD{)3^Z;dcYBZBWHy&m6r^pG89%{< zts-+N2c$`^83Y#*gcanolnVrqlbqV5WZ)*k_65hxk4?iPOVX@cA_5S4<;kjSac>#+ z%g<+&NR!)QL-UK4IVFQP-houtOC|}ZtpXn&2PU#K1q8QRt%{2_u0ZvmsBmGk@6!HY zxF~2VXe&@T`0$r-;K9GcxpPW!I&li8$IfDbCPAhNfF-3TXsee|52byIaCN$4)j@UY z0<9^RT%T5!x@goZ3YHW=sY9>VDp)(%z+nFXh7~Yvx#BA9dhMIA{)+1`uz441eS->~ zs-SG-RA;B%LUUdzU-L6qm>$Rc)Hp7cjgODxkw+fUpYOQij`Nm1c<`Wpzxn2yv2NYP zW))tV(f;VQXU`s#R$6JVVsq}UR5h~y@`q|Z0?j>Kn^%~36a_HDcNQFPGOEkD^Ca8I z%~;`+b|OHGkiutAH8^*)R%|8y=PDf zA4$bO_bwk=nDceoRkGm3taOW0!wbJGEwgO<1AHL4M30vYu0DFnEsTjBP0(&r%v0OOi3{VpOW;~Rb->KVO=$=k6@n!N zQS}-I)oZA)iVbV~uzCGjY~Qj4*WdDHy#4*ZiMN07-{R^!{vOs}bu;?bj3BGp5qI_n znq^hg`-d>FZZpZd*sSqnEi<$d_Eeb~hc^~G=XQ!MM#f(Lg-!dp} zRBPAK(;~)!Hn{9ygXSa_rq1HP1E0gmXYRwvQ>St0*b$7+&MIJ%F!AApT3xGl#coL{ zV{p`yR;mlPO7Ntv;6`02*PO&)L%~yjAJ+C)Ftm0pwkmjf?VUe^owxl6Dg;aH$B$Xx&2ao)E3&`@BZ%Z>UwUv=_aq3 zQCsLmZF8#FrBcUUEk>O?o&T*{x8mxnuhuVu8`tT7>s#N#Ti)^(U!Ka*-(+ZLNSCE@ zRGya8XHqbK`?r5vZ}Zl-z7_Z0d#_&RfQdf8@WKoFyIV%v(lRQ4IU5}d9s5uH)KB4W z|MqY7F}k+Xxo}47=sKq3`J2D_8-2_Uq-YtPyV4SqVDU*o$nddDBI$oDxKLkUQU*u^ zG=ilsfP*2c#0U{jIee7c&i}pp^ef;Ae*L5t_9B5I!u0GnHV2qZ3_`@Ie`=otPhehkp=Y}lp zr5SbV)cODL5C5r)_8*)l2mdP;dxykm2f}GMB5p_~MJ0nVHeYzC}Gu5m32fpkt-uBD?CUt0JHx zbD-(YS81t9cp*h3LXLHN`7)li1bDzXmz>gIaL*<_>1B94_sW&u58JP=FMfBGW{4rn z?=0DO{6VV#fi;8(^)kTVjKLvJrgcVOhX8~bqa|6?77+)L_ln&)ze_5GsZh?;)eSyN zevGRapm@KIf0JxUx6{J-k*9Frd!NSfL(eI2I)>5lS~Xy zNqSh)BCa%lw~V4P(%&iZ=29|ESw2WR33LF>^EYa^Qg(3nCQm074af%F;b>MM1gN1`8Ql03Soa*La z(5Y=snRDyuyHlu2fa+}mA_|rcicJhOmJBSNb9kHo`}S+wwr$&5t<_j;FE=!Tg7K14%}5Jh9JZ@6u|G*tlz4b7noVvLfE zp22D{**9Ts&RnN82;bup7D+WTf0AS>N=_1XlK;l@7{@e96-+VsH5ZKNsnEIB?Njf} z4n#QeZ|%<3cnf~e1Ly!M_Wj@1w}S_~GT=~ora#8TU+5#P;xdX&DEiNo+u#0dgTB09 zF8<22fRU>E15E0+eX%$$;jtE*W=x52jZ41@h7G3+IbLbw-uAuR#OPdiizxq}p{`8a9!+UJv;#P0(!fAZh? z3Qa}kkDATXBIr1;Q-hJJvrn<$2-5R~P`jSbPiPmGC@hpO-BQ2u zczr)?8^NKheC=5jns*2hg2Bi4qLjOobsxu+nOhKMR49v>JCoW7wG%e|MfBe!ZHz{j zs6=IfITWfgDi)7W*ngPGF<)zj&e;;7;g~fuTdkKp4y?CvVW=sc8z9~j(Gt64s#RH& zZV(w9lGeoi(~c+`NRt+qt~L&-I|Hw};iPq65DgPmry**Ve|M@_*17mxLA89M%=@*2 zW>Hkw8%Cb|0}4&_!6{G31FiIUl#{=`Xv52?rOWB;*O@(_4U*yOid>K=lfr~!zq#!9 zJTlze<~q%}U)X;<_|^a8&xIzQ!8IC>U-I|d0s0Hp0XUfks971laDk@$gtQZj+p-EX z5)Co|BAz>bu=+@O_NWhHrJ0T7uOI6-?%zEk-IfXX@p&%)&iN3%jPB>=b>72QGSf`S3gzXIdld`?RRNSHKN*%j(c*flGQ5Al5gK9MhZLb`X$_Kh-p}*8@QOr-^~x^<(_Kc_Cq?2 z#%jh&6$w8sx4OOy-LNEyeRzRe8qD(qMWY&T5T|&L8)hAgS2_LiJk`CkyU%Z(yf4Z> zxkQ0TC_Gh9L|`=thR zZTcZVWSXnY98mccci<(Htli#qy1@O)vg`eeysNb*)B^VcQXU?UKAT>J%Izledf zLUf8lPR5k^R<`?x7Zbd$axU7Ox1IMI0xA6T#_STo|dM$;m4$FH50hh7!G#TD@ z7qi9E1Ox=b6<>(6d;UyKvB1H@gWjqKPV{YTY&?!*v&h8)5xTuEf|v5*58FfNUkh0E zqm%WGC#aSbV6GJ>tW|<;k#tV>u)P>XaPER2VU;qr0YzMtRH(nXG}Lo9etdJv@(V&9 zh{Welxg&qRx#2Q8z!*7>Ag$Z#;NbPf1YetYrlgxjc#6Rsz6{3HQBhWORU?zX%&Lx^ zM!s@_nmc9vCmX5qt|xO~CqQVWn>84#myZ2iqpqtW?3XSWp7G&RGdUeMEXLAdn zX1-vmRg~72eYqs!0T}Bp+%5B-^4{~qL}lK#`0WcBy>#*KcwN2H@McLgr$OvU&EFGg^l~aZ;aac|?>rCj6reC0Wramj3rG!_d-XjX*V8qLOF%OO!l)h@xa`;{kz~d^Qnt6KS z5c4|^FU*J}t~&$2)R!JMH+%TXv38%;b@OmIuC*YliNCi%MH}Obd^e*dA{8dRqx3qz z@2<#zyMLd`;r4%*Mi-4atM4;`W@PnwnnR9U(5vJ;go8A)SWBhjSQB3v^hYqlQms4b zIF(^IlZ56(o$E`Ib4OA|aW`s=JGDF$-0=GvHEHe$d?WH3vL*cSd!ZH~>;QCZKQcEYNTFLhS4il#3yzR|lo z(YiTSZ+?#^{m9B90ji^8*}J^XD1lVi4R8CkyMFJ48hpnNc2v{j2z>cT-yP|9SmE$* z&XLsa>G*LwRpYH=Pv*>6qT??A$INW$nLSb|(}a#ZTektQ7m%AA&HfEdB2N) zE7IW-M6OZG>zGm!kehPup^x^s2`c*I_N8jt%G%pxz(n3u;qrC9v+zXVp4034-K7UH zdr;xfO_6JcUo>91P4Q^MF8fzvw*Tw*rEmM_fzZi8*YFR=qgkXKFUeP<;3FngqX-TM zhZJsefzMf6ox>k49NHQJHWWv4{^8AeHbnyB;eKIe#R=`7knO5HpO9Rb>{CucSMwEM z{mEg6CdU1EDt~g_x%*F{AyoC!w`9%f?i9p8k-Z%k0Pi_PponTrjDJoo$$QtaBp3$>WJQoTjKvq9z1Due>xS%Iq%WA%>v zUy>@ZY;bWFh7qod1jmsE<8As9@9yPnJA8hiuYqSS0P^FC1QId+?LnjNDi20X#}f!Q zI9E`)N8pL=U|ru&E_AhP0D10A5*cL8XNl;|Aer?N*o)1dBYNx7Og>AO{yE1wM4mEF_ zN%q=A(UsoTPY?Rmh%H7tI^crC)44OSAh=Z3xa80!V150*>8yUck}|ZH_}XBzVkTAy z{SqV)Lq**>otJ+0APkuatpvW5&0}&(qoP&j>{+zob>a&OfX2DXWCkB(Gg1g8C1HiiB&kkQq-iM78OyfG-U?mOyroIm3%|zI;e%J zE@V+EBV@u7wG4<`VuU1Pxtt(&9dU7nRsFNmb}7btb#0OHa>=`BKTwO^KO1(7dE6VrC=`m%;z|;P#y4I`=;Ry4oo@PCh?=Obs z%_7Y_qIZ4%mE_$4rZyulWar@R=nQ0++RrKi)U>OCLe%HQ&nnKNTdszTs^EWxQDq6E z`v2$5vu4%IBrr_*hv-ASgZT2p*ZV>x!U-h0>WuTfY=?|xHMrAzUqr{K@z&A&MUuwH zD`yx9*@W7obah6Ow;7n49GD$>QE6;l6Fh(>755E?{Bx>cUoh9|B?G!R-PjSv#IdBZ z2`7!K$$h?d2&^hSd3lS0=g!8$H~Bt$i+mxh3vWH3&ngZJ7Zg#iX}k(t5f{-{d8KsC z{6+K{)!8zndrW~P?!&rjOIL+ZY}!#0!r|9cua1wTPt5j@MRDmMv)cOY@v5{d?WSzg z;X3Glo=ot0Z7_X&&ydKEV0+%=6hm8d_ii3>95L(aw5JYrb z3my;MPSSJ;1lhx+y0|Xtz5&;&ORqBFe=T#obIhY@m~|FU`L=6z?cVF(=pOv_gggM< zxv#T>764JK>ppwvfT!)fmEG`E_Vur$iyVdcG}uJe3=%!6ZfY=J)UUz7)A|aq$T~!{ zM2YhFVD7s~@wa9@<_z#0zS3~U8+qZ1FW5)c>7aH+{0N_dk^kiMwq0`LGpUZL#kerp z4n7|Is40AR2H<%FFkw>0H9YdWc5FpGO(nyVA+fAUM_yCh5BuZVDxa1R)M1o)CX$je zw-CwfTJ~zf=;8I~ydXWIzuNnq1B|RFyt;&?2oDe!i+N2839&$Bs_+-iG%GfrKOx;G0TZON0uMZFG zL1;4~4+2T#q7fk*H_5}s75+?FQ?q=b=|8{Ed1su(ZumSHpa#98E}nss5$p1njtbxqg~g{CS;>ih-YU4*%Nnt4We+X7)r1@eaLhAY{wwh zewK)pW1t6@{3^n%~I6Wj3ta+z!f{y}mVs z!3`P(VSc0QEFX+@6>C%PHNd$(=l@y;9k0Vg zg{WC>TzACf*ey_C%$H?V1cKQ)Y$^RoB*@q!zXelz_^DK^JlYjbvz6q}T~lk=ZZhF~Gv-)|u(+hb9xe+R-! zV~3HCamyhj27E?Fr(c2~-8QWoF~9UoqF-*2xmX+7-?-NWOE#eFQ7xcvc`64G0n zuhqT)CnFPs|2p%m%E59KJ|EEYh`e7Lizl!HUX+Cv1VW)<6NnLnYE04aP-Lb~o$e7t z%`13|L+n?&u7FQJPV~;k8J=GUqRD1SbEDWB-67(VuKpUjEU3Ty3Fs?Mj?5}&Bv%Ts z!*fvCKt9kFco?tZc9z9Uhtvu#nK15ivSLbwh$Ghc(|(8e1M@W5$coAd=;Aw!LKw~< zN9r2OBb^~sf=%hWp1%3}8~Gfc!djlA!y3(P>Gg&rKI_5~9aFp^UOl>)ZuT@q6cc8q zVeP18iIOani|maj(BeV~g4Ic|R+Kj>7XAuxyR!0n@9KjN?#U#1cNvHouE+KT74vag zsq~c3c_rd3GfqZghYqo#bK_*QiVX~Z&md1cmMeB@p^z-}F>WM=eh;@_g%ht4|Mst< z^MS_uYOR!V_@}W=gJuE_Uzn(nY8X8~EZSE@jY;znw>Gk$Jr9+cS(XYl2mT-*?oFF0wzZv)cP){0 zB@q;-6PdpLS<=~w6Z(u136V~>>9F-AEBRh2(ZswS7k2T|MYY57NB=5=!1!~O@C&yz zwtTkk+QtNi?8}uSH&NQp+u2Xt5UM<2pR7Lv+b9M%87z+RM4V9*v{N{c8u}=5U!V6k#bYjc_ z@mc+aXY|L_Or%ikfvB=J6UQ`XB|T5kgbtQgo6iTmsa;Z_CdbL)d=tGGRQ-~b?D2FE zn@&bKp?I_r(2xy6H0ZU(JKeQZCOY8pd_1%J?XtVn>Ic8eD5=kehCcn0Wc@swn(!n3 zr{oZJmD+Lh>bsid=M{*qg6xyb`+e{;>U4*QWhw4xRCyU}AZyA+px)uCrb0a4x-G*Q zPGV4vW7Gqoon;d6@3CoG1zH971;5>ku?pg>YErQzuR`HDr(t?J0}%%HYj{y+9pNA- zMK|1RCrHU%_(hcAlIeuy6{e#PkhHf@v-wR%wMUKJP$+}$q#U(37#i)(EE=CHM|!jI z92+H3*rSoJ>OWuWMF}u@HeH8M*OqR<6Ce%K)5{EiO#F^py!7cyzid7D{i(U65B`0B z+OFTe>bX-z)yl|u<*oNaRq7TwpC$juimF6zsnT@Wd32g9_elDpZ8{iY_p1VhB7<(h`hN@uCOUB536&PRCe%XX1Jr^1vL3H zgxFK(3~n7Ythv9x0W>}&c9n9O7^XrwgzO~Z1VY2l4X||RpW*Gdq8v$YgIt+p{(1G$ z=lcHnVd`=AGoAj~J9N(nvWswFA&qxIa_}drG8OpiKS*2$2RJ-b=W7Ei&C(U)y znvNd16dnHF#{El|M!@u@e4lU}J~45)m?^OriB8ptpXIOEj163?S7ZKvYZFu4rOX9kwgge z%Nkjo$|5e>dZ_a;zuE#uvI*LFmIe!*?i? zN=0t?jYcX~Rvi8DK@^u^zh^d}V(z|X%1t*i5CVt5-8!CNA`iKHU(l7Mmp<3HaL?jd zhfuJ=!*^fOd1HixkX;w*j~qrXMDNLF2kr%HHy@Jg`(ig;(>09GS>#^FTS}wYOOzTl zR5SsC#mEx8ffixUkj!M+`t!GOe|K!4JyUFlX^YN+lR$k+B=P1I#^X;T zA0mgIF^byDYw?VwYA6%w-S0Q^A0Pi@Ee z&O}oE^3ae;t1^*P&HI~Vz{U_df0rV*St@uZNMF$JVESeN?D@&!6|Aa3F+T& z>@W$bHmw?Xs@8BgwnaW7J-7Wv9}J?u-!3TnSJ!>nM15-Qb*$FlI`qKr`?G?=L)5JS z3MzMPxLksOn0xoES`a@?*qcm6Rqpr??Dk!13i_1v4glo@56hR-xAI zQx<~q`=k;~G$A~p`O%lgfi>%#%u1}CU8J-aslWt(+6x}G#;kPhanWeWC5vt8fg z0bl#_-p4+l$&n{9F)#^49~QNOSGV5+0QC(+)cl3q?oXMI6YJ06+xnWFy%*h!4o_(l zY0NJ0!LVSDYiEv@0|iE?x~tydRiWF7%&+~9!*gk_u&r`jjKClm%~(0wv|k{$e5MiT zY1u)3t|n4vK%s_+KRVd)y;_UN zklb$k#|{NgOHm0HJXtK0M0=VXWj46eY)Oy z{6_Q52+im}6KSXowK^qQ;A)67d60!@I!kej<`%5X*lQ3YYK|@?&(IF?wIf@LHF1O zmUvv+zR!b!<<9N?GqV-zc~%%(_w$3j)XLbPXJe5?lD}TK8(!wbQ7wZ@k$Rb#9O{mY9qiuk|HWm>GyhH! zsBkn0lhP}ufnag}_amv^Wal)n;&KPgn3G;9= zTj_sATBq8z?1g6LY`Bwra1NMc>`lpV1ge;E_)c~(FDx1UVODtgUa5_$usx*EBSI0u zCucyR8=t9uKVPfYvv7zdW3o2Uc6!}KoB1R7krTz_jUdZDv$))b7JQkM9k_f$St4CA zZkcd2IV=k8h~;=y-ENgjD2i8LvR$y|Z(Czk1G!|q;*HV<~YXT z2=37!$(U-7T22$z#*5J5^@<^3cp%cz{nnFunL?s{Lq}X#&O!ssw68+aYw2mRP+kzd z&imS%rxwzb4(v_jGjs8WkRhzYhZTeGeF=J%Ish3zaO5~8>0S2A|xtkH9=?U!v=W4DBp@dugs z&97B&wcP_&IUhc;8!?nk~PXr1* zXL}HQ*f;XDBjhOe7#P^$gF|ktBZ=*A_@A)O&rg6vF!f;3`>_5~n57btc$ef=ue3Je z9*!hLe|2~SH}OPzWg6abofPI!S>_J!hnXjxGi6U0tS-W7&bpev+1!uV9~N%QR3!E- zvDL~4K4eSB)3U$a**qSuVSDo#8nnMMg;h}zoQ%L5Y3|!E+BZ`9RQ`yHVAd?pO>3w6 z&9Yfo&YFGkvC}8uLO7xOF*}pxfuH4Vmrw*QejF8lMdI7KF=2^E-1<~P7c_yjaHXb3 zRXo@L6jQ~XREHB_a^w=~rEiw7z`%6JB`Q<{EC+CRBrBz0@zq^$;R^^9I zj1AgQws!0z-k|e(v-`8c(P1h@UPj9o&2!X3Kj(2oesGbyrXd7;loDDQ7n%Y?j0cjJ zuTPGG+@G&{xt^5rvE8h{;!xu~*oOOkxV9?a2`c;=NBYg;E}~^sI_VCd&|!WnvG8z` zQe=Olxc0((+FfHMVl{JP01AOjsBbL*Ha)=bN(r!lT=$K6!gNuXuO{Lr)fuko@fLnJ z8Th#ay*K4Q?qtJfboy?tlzf>knt1ElU?OEW)ROPX*p6?pVukjN^o%+u0&l;Z$mHfo zsQNgtPYUkri4#$w(aqvYO}NMIOnzLX zy&j&Bjpg~W3LyvFEA{m=6o-O0D3=IX8u~#SX4H;Ut0z{gDLQV*&VLh`#aP=ilF)xV zDh`@ZhNB3h*KGP<#((xAzy;&GekhGZ9;3#;cOe}{sA0bdghSoks%d>|XHMM${cJbS%01I~^H=H};d1Kdi-y?Jd znlVPKC4~mmO))KBt})~zp_cUtnD6HUl@qwhnG+!+!c`+&GGnFJ&ul0ght(Nd%L=O# zdZnHE`FVXU5h1b{fk@hf&^l`)M6g#TPpO}VV_cXD8mD34DQraroE;KcmzL z!9T7XM?&%4dHoN!{I4{XzntKjmK3Oi>-XV8>;{X&Ry&HcUe&^N#nCOWH42M zG5ei2F+32jp*9^#KQyY5Pm(X=)-BUCwag|pQ(2`%^0yL^x&(HK=#Ro&>&5)C+DhHX}fmnul?)3(>dY;&+`s( zH87R)Y?-NhEsKn%{}qh$mF_h7ZNi-0%BH13kk~?*A;_@|-lQQMs>rz>ttk+S`=PWu{qv(m(q6)!tX@;Zm$8QsZ zPue-By5m%5gkgcxz8P2C4Xuwao~Y8D+)XX^?#NTFZz(^H3#SFJv$lc=)HBO6WnEJiv*Dx)~Yc4EoLbyuA zx0=tXo~|eeQ_yTWn=ZI$vby}+g*AL6grY`Zc5R5TfH85?9csJj6cB%+fE>l09!q)6 zQBk*g>00S)y-bSip%TPz!?_oxt`DVWmj8m%XHH@7@ByX|zNv!!H#C{*N+*+k*_TER zJ+`C8GTDc=yIXgYgTeamc7&LKB>v3KksJmV`{YboL|jZLy2I65GUFET2WD%7o$@LFis@{w%YJ&OmznvvA5B((0 zK-j^o<(ucY7BR&{)q!?-f;4W3=Ry8@wgM$nW-0}>wa5C~%L@!UqN`5L&t;yAq);Z! zl8pp<2@}CPub2k(?#6Flo~~doyybtChb%ImJzGdmHmk^7W(karzml$MoM30)SR40X zN&x3ES_yo!kQV0?!>#LQ{ug5?ck}4Lcs1R=iHDH=Ce}OVB8jAYzILl2O|>|1CNup7 zzWy8Rp}g+{y%hvS6@MpC87-h>M}GyG1hn!G!Cnu=ckrU0G!lGXl*Y3DxOt4>Kw?Li z_s4hY=R;w0M)t(Oc?}u!co5xeQ7Slu7cPR*ZVSPNG}tf5sYDSyLwjVaP&!@@5YmoDkuh-dv#j`k6;gmz*THAMUG~%Gc$4zO+U19AsDS?; zEax`c>8I26t*M{t$1!!T8ifw+-L?+S0Mv{QBcl5DGLDirb|e-Eu2}K(e6JCiLkyKFuS%kfc_r(Vip8=p%UU_KLUZJQOo4bO|GM&R z!INhS2Y3MtMHS4eY|!bf-|BjOP=4>nx)k#%r_g-Nlc6aRW++X@wzs9b{v_!1uf8;*<0><-_br<{4i zX+ymNB{$`=diiCwc`_5NsQ-U}g(Ezg247Hh8ppCO$t>y9T%99^GlA;>R5UBLJa2TD zz;nn2yza;z#=##cv>Dc%QdDq?ymR_Q#WYz2=_HvIhjoHn7^S)LWt>DYMp&!oAz znc=^0EAyX~e{)|0-+#zc@f(!wA;9ZG_=m)zXlk;E4&X~VpULEc**zj34DCRP5;|Hd z_qPKk9sME8)E+DvQXVSZ92rUCl>RdIl>0fO0W8d+fzUkToMho$_C8~C4nN=QsrG*? zcQ%ZNE1539qMrzGS<<0h#{~hd;Ml-kdLiiBu76lja`1gA0U_bQ7BJx9jkSFvEExdW zkI(XAUyI6eduug);{~dHlvu&oPH!|(fg=NNqQI?8pSSX$IfW5J7a%U+wG!}a1|9f0 zi~f#r*>%PS`p*g-3_LTw8tmSFckVeBy&CvX1+=T{a2&m)` zn0?o@G9_g1w}=!tcg>7tv24KnK5310JoL_;Bq5uNZV?F%R{1vg!(j%oYyUu6{Iy4B zz|VIOgoW@c8^CPect1h-u7z59a$DDZ=k+Y<-zv`2-!(@@$7`9LF#uCQ#^L4oq96d3Pl161$ zKD2!q6MN+BOL+7fBlS7CYDwzF9u@^%5BZzk1G76X^4~!-pCV!pRcgBzE`aX3^uO=n zfNS)7zyF$4ilw7LH?s~;mz%+cl~Nk3y}iAY_H4udJH5f*479}kr;lq&5)rPd(AX&k zW>tJnGYRptD49p&)@2Rdmrs&}#7!QD#IrmW16%$$YqG$Tv zL}?PX$#06x$R+Iq#B~dF9YuZt`l-SxPRFxJPsdACK8i??$Iy)b**uRoyn0k}_aLfa zN;+^M?bz}Exvv+3#tUsbC<&<4sTwFtt;9d}#p@)aw#08@)qH)aLW-|?SZYhVwmv_e z0&J;Dh4NF^?z{Wmx(!yrMLuE?y4G#!u?>S`cs+qju6%|das$$H4i`Qn!=4SdrpW54j%NkIX z#nh2)@pg2gm%`{Mar+#>vmmA6i*q@V?xL?d%ks`n9~; z2u_IC^!1T1n*^|eop*w!+04Qf6ZxdV)nYRi?@_27H>1QsIafhX-6!M(vxk4{O|RMm zOqQDW^FsZU{{KEiU*aDQ;xDbVA}D!Iygp3_Nv zObO#^8k2N+u2lY^)4k74$olmIbT@P6!CgNwZzCk%!bWCG?cuaLMv#>M{w{MPvoSJ8 zS2`oRe*M@H)P0x+f426R|9*5loAZNn?Da1>xY0vbw^!iavT+V9%`Rd~;)aGpY`xfA zbMm_?MV{G4UUkBrz09wWVm>X_q~r7yV+ekN6|s>9p+3_Cv8d_QWojAor?K@ZrX;f8 zYWX=MIdg&{H}^k4H9l4&f3ZIL%R=l`EHn5S&|eWuI|GE(6LPmeQW8 zU_q1mg@zri=^Kkc2h&O``8yew0(MD}hWGk4J->|!mD_q~``q7S`)o5O8^@tXGFoLN zOP^;|5w&#F`Row@c9f5zgZk0UwtIv8b0#|vPnFKRAG$;C@4TcyvHOAc z7IQ=Lb61r=KUywLwm07v7tnVjKc5Z#jh)`vt?+%llqdlKrmDbU@g{A~@3ngdpqgFzkSzL0 zFij#_GvhHAT*}v!6x{eY1|R+t{p&m*zWOv_{*%q;hT7HX9MQ@o z1S;-Y-o1X#)WnNNz3(=276!6$H;11@=0sD% z=kGpQCSzabjogUs`fm7Hm_ftI8aYWl8_U_4bR70Lr#e9QoqvhJjb?7}rhcL|n3nt+ zvE3X)#P)s@>CJs&@c?Izkg78T87H+w^M2lYOv%Wg4iuthTo`2~*UmF|t`}GZaT^is zx9fRGc&ZpHYs3|E`~?X+{sh7sI(*K$Jh)5aFsi^v+%s{xj|<|HXS%Ux7{?nd^Hr0p9E;dj6$eNZ6*{=PR6`sTokh2WSic8WR$8w6*?#!-?d{%tR{Ji*KgK@wmv54MdZ>NaeBVI=J^m@; zoICAbHdAr%;yKK?142?}Q1Er(B zcnIweTnDz^eb3`jsf?>G*sDQl`O746Y&XHT5%O+$x=;b@Xv`Rezd4o?s-MaR6<=T2VTmhn3j zgL=Dn0AYcfyH_~gI|MQl4Vm4d&wR2e@L-g#A!a)>Fxl02-^(}T6jc^EYG0&gjP>zVlH@n?;3fGodXrBG__0Y#*dZ{7L90ZoD55n<2A zL;aK0IEiQHRZ9@c=Sy{iUS(>QnlQ&xJ~Z*Ck#@3hR!LV}e{7#kCd-r@o8#D?P;Hoe zg>68C$JvCNnHzI6=&{p%ob&m8JxI0)>8>bsRmf9=vVkBYt}I`6471i_y-Qj_GVF13 zEwA8H1t&udf?bsURg}4x%>9_oc=}V9O91O+!Qy%gIcnaa?x*Mm*Bd{pzrF%F!iL|p zQDya{HprWV0`HBq{CM0{4-;e9nXw+@>k^~o^?^APEIOr89a%REM2XfH>*>)1DeZcnhqN&fj9q|qN5`3XkGxuks!4QthCU`6fca>a%ed65oXkK%`)BbBC+ z$EsHhh-dl?eUXbFd0)8{4fMXZ*@DE2q5I?fXV-I=QES4A`e$=3`~H0H6aedA;&>ok z%$oiSVQUvTdnIPb>|{Bcf@EBl;Br3lYlT3?_DlZu_>ycLf}Tw(ra-FwO?BUEwJ)RG zy70p!Y?&6u%YWLjL&&Mud#YdzRltRVX}l?D#B|v7eaUpUGE?aKh`(f6Msq#$_xCZsUxG&H<~?F==!ve&4VE`WF|OFE?W~S!?s>8pn%V{8v#;KF-Uyh5*~mgd__r^O11@R6k>oeqHqKbX@f6XB8G@We37gxP921SPP}jRZROE`!jZZQPDzi1|1E zMvE^0MVaa_e9*;^sgqL55tqh z6T`+3p4^(-oL+Cwv{I>V54UMP%| z@O5K`GXAXOXNH%9k@!3{eP^z!$J0 zj@aE*vqi1_OV-Vs|4PlyGrlO@7Ti|bfp3XFUUj-Ly{yw^Cn><^Oa$c)jd?QEyv}(! z3CH!TA*l7}8+0%!qVNbjLP1lHW+o`A^ltmC3!emT%Ec&*@e2x4f3~m>-g7(8y}V}#SDpVe`6BmBz8W-xWg-Z32E zI(#^t>1hA=I(Y$#hs4OYRzlw}FyCIZF^q0cTm$6OBF=#>vUQ$CwY61?e2s(e!zS`K zqoMDhyZ(=9Rv-Yh#=4cQz!vBOxbvYVwBJ4b{_WzQV2-OlG&>a2(D5#fujM70rey1EIp?^AQr5mj(Ptf&`wA!yn%)O5d&Y0Dno^>(-L1 zpCNYHl4ico6&jarF}u&i%#~GUHsJ-`MdyGvBU%FE5@eQJG^Dh4b zfkcQBqxvXwf5>_=-{80UNqObh0ULjBdx?b2*Bz2n3hdo?X4rsTxE#-4 zsJSL6aY*f^NCmOVy!u$K3+oZzjuva;bXdJ?SCniCc^5AJbF+Gqn#!klhtA#h7zs}c zr|HHQpPvFrv4uEI=@6m!&6DblbhmiueFMqeewS;*`XSNaQuZMa}szL zEYt}QWNn0FKpEEpjT(&{uQe53rd`GM=Lsfp@Y5b1R-np zar46K`~3~yR#8!0Q7lC~#MwuHb>neYR~_@~~WEnS_N85EDX^Lf~tCr7oI+ zontz8omeWBEdL{pK?hPMZ6n9GjiAQwni*~BE@P9fuKQM60xSksZO0FS)wUsAq$ZIT zBq#ctV&!NfDAzk}(V=+FDi)v(L2$wxE9gC_3qukjRLaz}Z(7iM2z7aXqth)Rp=ROg zcywl$m%m3YxxaL{`t7MN#*6yQZ7EYPs;wD#!(D0V&(alHN7*v6{k*vyT04ogkrLx# z?Mc*y)w%Cw)qD^jfIlCG0neAy9h*+7xy zr+9esmU#}MutVE@MzWMf+y>A5sAGuG} z@srA^wuKK(xNUSu;o7g<9d#KJlCH|A4bf?T_d!@FQogZ*fxm zEh{Bd_qYa;MJQa@P=u6KDH2f=3TeM23W5qnYg#`X@`DPVY8lJon$y|~xX*!)YYtby zzn?Y088KO2$Oj4fi$lwukdSCwqVKkVzrh1S`hpztq$3V65Op?wo&&uifD5=J$?b{cK3K&zv#b}C5qUADc|WaeHDWNmCq&Y zMYe*i*#PcCyYA!O=kHM8xeh%M8r--1XYQpXS0DV}MeTi6QX506!qs=T{z8{KD?_CH z`){w|3^Xj~w%_)@H)*o8>eJ&QSG7sLph92IP$YI&Mww*5nRreGV4konHaj zl*`L_*rSnk0tfKEoV{F`9jN_;KiEt7X>q_9o`k}2t#>(6c^W>)R&8sKK7KAPtIwWJgrX}r)EEK;xhlL(jgCXIv-}2CTjGV4|0+U+pFGL(YtmM!(Hp#jV_3 zYVvmkWA__c&&^{o`ASyFC88QN;w9us+J?odTH1^85wZLw#Cz&7ppb^VI1T0|tT}-2 zUrMJ;5fa-Ze1*x5-MVaz?hLo$|1-z~EsCYnCxC`cLFvCS zFYj=Y_ghum0E`Enr&WS-v3q<-oPU0SQZ0s&Q3G69EVT9Kw}M`xPbN&CQ@NVRfft;f zAN3#jr00r-FlUBVcuND3OWIwxt;;5c5({8Xf>PHX!z_(-;Y*g~OF&UDA3gb%pP3pY zqWtl`RR)v1t~<}sG%Z5#AU2t4f=_BalF?|_@0|$}M(9kq<=nvMgXoZ$90*Sxv#ch!qmo6jZj5_q5G z*2?3aBUDdNdf^-?xq^nXW8c;L$98>@jlLFzdga4k9CaP_2HlifijZF+F$6)4kFsVd zze|*%>SWLoW2h8L@G!xJi2n~5LFc}wHW48RFj42sWM!0x1;smrkt`qOk_RCldNEnB zUIKsy2Z#o}Cc0~SDeC~+W}8fwMnGl*X!&KY3SyxmWOdUktxN>%4Cv3rZry=u7%N!W zu<@4Yl>$}-K)?^;c2$0&5nt=#?DgMxxq69=ED?GyCy7kN?%Jl_6FJhVr*m)cW$+R? z^hz>N^3ekAA30?n03EY-A)UG9wRGbHuiNl~@&lvfe!vuJinZYwI%U&8T!N?Xa#Juf zP16)O!Ra=fx|>?lB-06Uj{K14zDA#rFP15?Pd$VjDk@L@?QuD;fm$2(FA8wZ+2`0v z>&#_8Ex;zRI>#>WZ$Ei^Io^KC_C7GeSeFeFPx1OxI@-rHn4H^_>H#*%>5n_Tdp+wx zbaBj9(@UnOT=DEphSk)wk?oNC2K&cPJNj??EDgfBwVn~5UQ{>QwR`OQGJ^>qYLgerOKa_zxK@> z5J{bg5;6FH@-9EGQ%b?+H<)7WBGKP=Ix2OheP9!g)PLr30;RtJ+x)M=ot%i+D1 zjv1b@>>QgP7Dsxx6ZV@Jg^Kqhc#7$zUm|eA1pdi_iU>tOH3gn9ax?|JrmpoO zVYyD5)&_lZrtHhJW*cjkJVUSmD?G6ox#SI=NkPs7q_J#1ttE`%@ER5K*TBChUo&-9 zZ;1pbs#7Z&Li;rw48jiMdcbi%Vi(MID^vB;#{JOFW$hP6lH{IDja5lN7G-a>mnvzr zh(iBE&!C)=I_7MLT;xU7Rj)5ne~p*az+@(^mjf3J`#>#!H??(!xEIu_G1EKNsk*$U zl%S3z5fUcZ&Ojn7gG5nS_B|SG;kA=R>fdxOV5b3{3MNR;C5<{#7{(CqHoijRbd98k?jZ+VpAYn90bln3~ zkxwt;G<6&w5_yACccZ+;Ipkki{F<+9TB}Z|)riDk`v(|u_cL94?KNdhPyJ>c zmv7r;%F}jY+Va#?-ueBBPduv}Z{A$j1wQb=o|Z0vjas8{ebs#mvRN91K%)qUH?KZl z+`=Gr(yl#LKZ_i2D0$fEn*`7cb(+{k= zAkdY)S%*<6s5dcDtZP$pZH1@Maq~n_}mVPt;>3<0oi*%9L6QijyZIM~M{q9?sNr^fDmlX}kTW z9)J-&yVu=`M&L>QV!Bn}DPHT1fmP!o$~y(XVqclcv+6#Um#0l+(hjHa)PCqdhcQ`h zNC8VwaMKrP>(;IG#V>xb{N-=h<+VHIck32<;JzhbDBpHVw0G~`^4480JEz&b`wqI} zj@{*ZaAm#JGS9Y?w$aHaZ)=eYl7~7qJR+R4apAgbv@3bD`H*1R(9RhwvPQg<)!U^3 zzS(!;xvZxQGAoeWTt3L0Q|IHod-j$x@;b7!GSEV^2w-gh zok+d3jimk{KT?%7MZ6dv>c{mY&(EcWblOj-BTKuH7m;sjTSH|45Eqe8^;z^ccz=L5 z$4WGbDiErmp+=o5co`gkGN59Ss8YfpzwA7>-;JA{ufa ztJrxWkJsg#nJ5n3-cyI9vpk14^&BKFUd|Ma#!P*hiuh!jGQL6rqGEbUD-_p59Zo>E zr<=U?MBdQOej;yZ*b=wtAI+^tYSbH@vVA*Uef2eT)6F;0*=L=t4kj;z&VVV?_U$Lr z9ZRs3fmQ}g+fF)}uDkYH+P3ZFvfd{zpw4fiWdM~O=_z?l6gxzq$FX%0;9)&C$&+_V z3Vh79q+tBUPMWd)ir^$qwajL0Yresy1B=FaP1x2Ffy)3XFCgE&`*u2Osjpn;2Or#1 zvOT!>K{{#MNhRYo*Ir$}jYn30*KehNlE*f->oSM-^Rb`-^h0oMQ5Nn?oeAm!ZgA8; z$jOK*EAE&{+fnZ4wRc}_>0~3#jZ)Vh4zdQSIeNC;v~wXti>{EK18C6cId}_A0EfI2 z&j6a5B3ZW75RlO-&L}4fSgM-W=uJzD^{K~$-Zy|{QLuW2RUW5)7I_93r>xgR9*W?q zfil^dT)~xU2+$5*XBCLX?l?>Xll487*))aO3j6Lf?a3eIiafC17J-5(ybPk& zWMfx0oS=LozdB&I9&-#`bkRj+XL__77hUvJA^4|(>GTmo}`?Td@u`>80y@Ba#={SFy*kHN^njhCFcxY@cid%U1Vwn zb!l?uk1wCo%3dQ`pXAiVsR14-BUvLsI7;u42TYYm{(sH#F4L~1HLJuh)-PclLFZZ* zdCN=^f-ghmFtCpc$a`Z)>>x&lF{;c7rk)0jG__9_wXF-d0j;9)!L~b)=#{)(CZDUR z(M;Up0vBP!x$LJz{viWF6gq6i&595(0eB+>TiRaG?@D37zcrf5Cyra+&Wms2Nx5w* zk8KTF?Ke!gz+5U&f^sr;v^B~42Mz*}H4&_-wD!g5m8i(4pwZYM_JOxg+3VUD2I zizvds2D|(zS#p%_}k;#n{Md!`6%P6=vgm)PmXwfBV`Yai^#ctcP^#o zR9bz7zXK@M*rJUTcPPL_%OF`)3DZVQ4u~Ph5DTXDfjqS?#vA!M5R+(RBN+ZI=~d@6 zvOi*zd_HdX7Hy3US$$g?G0KCJFZc`rCSsHaaJmBBFdsFCM(lx9i0Ha}z>YHE~b-@T!Ys?HW7281+ibz36Z4-+s*=!nX9_bjuI{_a7 z0iql7c!W0d&S2+8gUY z9&o!G(yLn8g0YgnO|ghbjGdlM4AEi2^Y#x)h*R3qPZ^oCW3x;3U(o}e$MGlaO-{tB zw|!nj9`6~l#rtAFQn-)D)BFz?ia-*7<-X9oCTe38!2%jwp$MendMC*z_cP%hHL~fZl{<&JqhlunRN5aRPro)Q%BDI?g9B-*I|kV@P=I!f?aQi158_N- z>vJ;wCsV7kjRAFZ6314IsA?U`{!UqlL3cK$j>4EznpdCAQ*oK;9>j`q2lb6$XvcUZI+i$0BC!b7T zxbeoa_9@dYJD4p}|zS zDHuL+yln50HFpHS!6GYY_*FNF^egWQ3R^M*L46?n@lSl5wr<`^r|&quT+6!13&~~C z{pRIwS=Z6{Fq5q4QF#U74OFL=3`nlFzx7Qm0Qec00st%P026-l#vEh)C~qQ*f-zxq zOq5ai55B*ER~ArK?O83u&ifA`ajZ-|h*TGEk*Eq>8;EECO2I?n0~G~m+hr16W#*cgylyOR1 z#=VLZ0F~#5H(3iw&K8XFwO{=d43R~;njRGM5YVW?RZ(VuFkgbfk76ZXZO=9KANUn zw6!p~@gq+fNjAG=><{g?1u}Vb$JmIheY=;m^>noI7#57pWs@h#!+;?~{8xZ6IS^-M zYkzM;A(LCw-q4kxn)Y}O{h8}F2CJNV&!DIX0a(>~VA@(>ojxHDBO!=8tZCJg<9fhz zran#eISl=aetUaBVVoPUgB3P1WT3qxl=XZbXeRSV7m(!Q8 z`IMTU5_x#wK3dyyj2d+(UsCHyxI!xP%x{(DG&^_hEPEa0Z{20Uli#|_0Aa_@9R)yT zx^dTybn@~zJC?1-Y%M@3Q_kB2E!Q%SEn9XJIc~r0_QGrDGQyQ3PTMmW+pa!kQN7y@-^&pq#4+Ijj8+Ir(II%Zj) ztdkpe?J9N2JGT$RfRBfcxBXhF5@pe7joxVvIf;{EeJ>#O>XD*{uyZ$ACv_5!z)}WE z1=M(HB!MCFp)f7E9r{;|Cmcg@(YbyOtfU#NZ3~)#SMX(XO_IgqYpuVmDcEsx=4-7} zh!3g&rq#B!4YM_l0-QC}icMwJX+z^FZ4!CZri=CW>4qjRVIO4=h}aswO<{uUji=Rv zT)$1GIS1ZxZzv({^3a6PN+M3x0d;I0O;PWm)aPhDL+g_4%%#C+kmPuc6W3_4vb63G z`576J*v1CTcqCc5rw?S*SpyY~d^GsIr)?q|!;#D}RF&vJq$3MdtcuR9mu99utpZX# z!;HTOu&}Sn@zhgs?|91b3a$5)jo_{a^m=m2+ktZa(0%vN&2RZFy7`Y@PhY+FZbb?b z1mAnxP4w{}{w6y4+rEcR`-lI8j(W`Fz+$8pqE|~?ezPOWz~<6RpQ8i#+rRzWml330 zv||YrGPucL2_1Vj`1$*I*#Pg{v9sA=?izs9B;2|440m77^OZ9S@L~l=Ju8f6w%Bip zo=ANd?~k{#WPHRNaZc*M60`?<1X}W+v14aB-Ld@L(dwRoBWhLB@>uv0KXrC3J%dcu zfSXQOuLFpJ2oj?%cnJ5zfyz3VMkBr(61Yl4M^C2 zP_@5mbY&RyZ9D-;_=gp85h8sfRe0f)9b0>_MV*6hHf)9H*e&Y(v+ zt!%XA6@lJKS2+EaTnodHPod- zbZPhBysiA5_77jN{Cl}Q9h+37Z)IVb0PF+1VvMT3-)hF%LIC2vOZ^* zX#`L(Kf5HVzg`r|A+Z(|b(Wpa2uN0A07H(?&a2O5~dqgbU0 zFfipL{L^;?6GY6xkdRzsTE{OP7KDbN_of|QKUfi{H@x8u1sG+@py~Yc&o5IguYUEb zUAniv^{w>W=RQ{f)AiS1Pw#lgJLvnr|NH5bQ%)K2+`fG~z34?RqFZmhm2SA<2721l zonqut|&*CKzRIE^+1rrgtXr#$2r<%}|*95=n?H|cY~{x53zYR=5? zn8A}p!u9^mZ+Si4|M@HF2|x2Iv}x;c&5}voEQ+9jf+f_?1U{T(dSldgo9qvi9m}^i z_5m!CmB~b-x;eh$^bp*TIc!OzyNS0bOXU%aBK}I`@dUB_2L@rjB2Ec{L z9mi?*c;B=VfP{RQHcywW)la-o`$N!Wbsw6J2C2E}0eC5YhQ8hE$X*&9VG6=j7cC8w z8%NfCM7|2QQPt)HjgQEJb#K*beOb0Hz;%EO-Gx;i>)#CJ%0c@_ql>NqYI~7&jrdH1 z{aUkB1#5ZO)r8Lw@?m*ma}wl{0S?)yLq^_;q#BY2u}z6V0DeB~<(n92q{QeI<{!PDzs z|N3&C!Bz%E85~6bl|j{qKm1|(qd)qiaxVj~M-q+uTUs|wq^X6jl6SrKSp}ZfOS39{ z54>erZ~L1+qtE~P&)0PAUvXOUZ-P4ncQW1ep}(Rp{O4bxC;aTMDv)S}Drm~nbf3NQ zv&+7+wX99LZF!zkopbKF1svu3xgX`VODAtTncnrTchRvh+L&XsM7>8E$^EW(zpL!Np6i#d z^Oei!Ud@vipl@lq6FF+J0oW6H zM;UZD1@JlQCn2;9#7sSKi*gW=2X9(RjYi>zgpr1+NEp^p98xQ!ZjjmJS0GXk+sdZ~ zC|LL5s=i0oLye;iCG`;8rZ{+xpo4WrMb7b|O!BJE2*4BbC=ZU#hIQ;3H?Y(Mmur@l z?LASQ<|7AaTwai|Cc#y3YUd}U!iqn5`JsbVZ zFaJIgDH`>SfFK6vFm){+$27gap2L{px9YD~$NabF<{Rj9|Gq|^)cERga)63Mez62u z!rT7pZS=)I`vWI;wex2BW&Gt_G8VnQ+s?k_OvA^wZ?yA?CGfq9cYpjS)GZ(P5 zq75@;0F)DCr<>nWyz;86O8qmqI&TR&_dM`Gxt=Klq?{`+$j>^=EOyLfVPXQ6)u=SgFjsKtv%&Su#@RKhsB{yaaj+{hqgx$*eZoX5$GE=V&*oPX35V^!-s1Rb%qrzpXWrEN+@y}m;o6%n|w5sjkHp(Rx3r5p;B4D8~s-Y!$v z#?t{6EJHFT0aHmqRV)j^vee7*j}-`YN#p^Ew6`2P2}AzT$Ak%l=>dUw4QiuoqtZkW zMUqLPGIZF}sz?Z?Nr#}aIbxKj++;d0Qhw4s+5(WCsEb&-FsED@%iMVFaJ)>Te(W!VN|n>y>ye)h9P zw;7Zo4cw2ix+%QSA0=EbfAJ<~aE;8uKIXe#N+&<}#dO6#|9*=#M>&=^9{Tb< zbld-Y8$I?P{D_k!{|cBc5y&>3l&7YU5O7OB+Y!>_tqTxzuhRL3bcA@L_~miE zs}n~*!9KI9!|Gwk#{(0haE?HPY|Rh^)q;%VK(op%d(SpMK8{8C?T99%C)`HLPXK!P zT|QVASn)#|gB!1o5u&K=o#0VYgdO*3op}t+g`aRq?57nobvM#dAhF$wh&Z3($+tHz0qAC zy{sNYV<98FT|eQH=g_w2zNFn`GwKOVMzunBeem51pbixs4iU?l4kmHWYwjm2Rt!;s zV}raDVZQ_Wva8E!E_uN13tTwHD5VidDRC_x-_utzxK5JGBx{1w*|C)en~p;CnWxED z3Qfw#q{VS7x~(8JG5TZzEzo#vj*8-CanYFu7~Orj8SIV#S{I!v^~UsJZB$&3M`PKI zACtdX>kOh2b?WX+9ZhvbHkp%@3U!V7QtNS`Sd=kIUun5EZDb0n@=fj+rz!-sCJm#F z#PW(e!qF4zuL8=&L9KI~R$1k^W-xU4(S28a(kz2^n_wzO-6@4@kbcSQzR!J@W}4}s zk~))v!78-~4(r2sz25$<@9&d$$Mrxq;mq4jl#erqhYn_mFmN~eB78KOMHMQgHeEhw z0!thnumCajhY%^QWw9?eNrZ$nD&(R$SV2}uH9`ppY8(z^yQ#b)!szHCwG)6cL^O^Y zF@Rl$5LD_#n7k>Pj#vS}aRMYPD*#4J%j}*V6Es0`1v2%}7HdAsh=36FBFgQ)8nK?U zxA}JiRV#s*R7O!K(~&~Gh#b<94i;xR_;laZpX^-i^rMx1%iqfzaqhk43pCSA2aOJq zZL5CNKuUGi2td38#E6T+F1GbB+#D+_Ur9sttuf$_EAdzXfeFS4!Wh5{042Z+0d)Ah z3Yc8l1tD8GERp{I^kBJLWi= zX{JXeAviI}GX_RvAg5eOZn7^CJCLMnq?$`|phy5qbE*~S)CK6lF)DZAYaXuoy9Sa3 z0I{3=0tpl05*h$YQ;WdKz*DkxBUh4~xRH%xCdD&^!^(dHFajC93=s=N@ZqmIeS>`X zm&ix4YN3hn1+{#yk0q>s@U&#cw*3z5_m}GBkvDpy?3l8%jYEtsO(Pw=2JCe^d(*aVR3eInK z9x8G^3L_QZbp`kkCaKdL-s!PF=7wVA7o!U`ij>-K-A2K{R{aMZI+F(qsmWAD^fG#p z(D^lJmZl;`$v_$SP+WT?Uw^V z<3Eh@(x^^ed=HG}(hxw8B%gvdOjf@Lu2S;F>NWk5ko52$*n@7Dkq3A?( z_#-IP2}YlKi|}WfX`{65TVFyqz4!!_Xpg#0nXVFYE9ZWj< z)SYy~#m}LGL*dPawzYxHKr#AkvdO08 z+*}CKSbB!3IO==z(L=c`hXH;Ll&~)dfr9%G6#FvEwd4U<3ZSj$(lNRx$fNQ~dPBEh z2q4Qv@RD6iiKf&+9TCU@X~|C}yXklgIk1=%bcgGq@VhCj)oGE+Rgn_}1FIH_V|0mK z=Nsh+=?`C84`jw|A7+|qBXrE!7j9@8Z|9HwB3<-f|DI-=>1#!&{m@U*!6E@D8Uhf< zO92gO^n>lZ+(cM`zycRS<1YSCMlwgP99lU8CWE)k(cc;%z|W(w2TCpBpn-;tKh9vX=UofCbN$SxiSo7p#Cb)xSoi8kf;1(TT3_y69tAcnuH;lXfB!FDewT< z$g%cqD@xPF{JrTum5Eh02kHQD;hT~)`fVRtU$qc=Dv}~mm+450bP_*bD;@Ai2++jo zxv0B25;fEQPzFz@{opIsO_!roSHJp4X{MPTZpx9TGk@-N%YJ(B08}M!F9)*qNrgCt zxCt4M!WdApD1m^O#=sTDWR6rFy*gAj45v{I2NejX+Xh#JU+0TJ!xqZ3e)CUdgnHKU zLS6moNOKMxO(ksdrNB!TZ9@{som4L)b^u}xpl|?Nt|%vqEy|a&*#nt|^lJH4cSB^x z2!0UuQD}d=?r{%XC(wPuC?qg!hnqwJy?vfmIe(0}5XozhseOb|5M?cEk*rf&=JG+ZDGZehHk zf|Ta;b^49l`lXfXW*UM4+a`z|Sa1M_Lz%IzY|~~jfkv(L z+&+(jOt3DdB`*lB;Tp-&6INg+-V1vm8KjU4$z2d|!i*9Z$VZD6bTscbm>ZT)n{WUt96UA3 zDfRTP@y9p!416L9ps65+{EqHermHVZNje}j0pzIjHk(5^sRK;76OrnJ7i&)j87BNK z0KN*E#?WZ=ex0`c7)+KvC2}N8n>UnC{dPHLu^9_b{}lzPl|jR3ST-X)75weV5(^KE8?s?!@fphQ_U zQm05`@Dbh*Q^^Jn*Jb*x3B)?(L=(=L8raiA5YWE)=B5|10%S6q49JJwbmY+P48$>D zr0I024d|KiQt;WL0;w97uk|M)T$>dEA(rn0E*{KPkV;Lr?R?~48^1ww7zk9MEp4wH zU#3+sB?I-y%dLUxMVR8-c6jbVRgcFQd?DIC+(Fc;$Xt)+2-QscN(Dsy&b#+1id3Ul zZ=J!@Oot8Wn^Fm!sUil8UvL0Atd5k)t;Jy6@V206{Ag40HF7#nG_wT?ndA#8PJf*97zEg)Lm~; z{!B9+DCH=WM56Bc=w)SX)K~Aly8x_>ZhbJ*OpioT$JPG9NQF$FSaih5MMcP*tUAwu za4Cz6#uyU;E#rF9I#z{|r z08v?CicY^}Lmj`HxAj?nt`MgqfMnFf++2_US3Hc$R?}dN|A+VG%8)jn#h?;}hMP1?>cv=)|Ac+v6 z#)aZk{Quw<=P!~WWY&euW2o{L7U0w z5>18&$LVoLZcCGcP9j7LKoNCN;NeKCbhc{!*i19cG}BB*H9}!)nd05inf`;IA{qGc zVJ#BpSdLh6fIZj(aFM?$AEf{X|40^I13&_RK;gGWGuU5O`K-Q!M>&v&Mtb754TA$c zO(p@SBA*AM6?|%B4f4tdcril97=eTO8>rHPN9&>`YN+67R1N!lO@f(*rtWNE zJ?s5(B#(3|(Bed-u^{hVS#^?vYzWSAno%xjH(*(JXJs=;Av}kvDLTfEg=*+bOS!dZ z3<#4DaoVu}uMSo_tVpIkrLIGptHg-i44h_~X{MPrND$^wzn{?Wa7aEzHOaj1$d9uE z1HYdV@*4SmA}-4{Mu$WN9oC791k{!=swWh8{%WZ;m!q+{vF zpCV#`;LeyJfR*~>TLxv(@ZnXG}D-Tydq3Nu}m+?cg{TM zh~Nfe27S;)5CutY+7A6%&qr~`opLyi{1mLT-|8EZi+)ViXOaX3uuXXpmQ8jMqbZI> zoI0d&#Uv_IqGNJG25wF706ik$<-W7W01JY)ju;kvkxEJvw~&`^VA>Rofg0-xHf`$r z411&~Q7xu19)#n`j|um#_ZCKbj)v=W8w1VjL0JsC9nOS$S%00_Of$_i(@aR!S6B$Z zz2e?H9S2XQD~=#)B?&8Nk&s4GAmcXHjG+kUl8|R~>bAF1yob@86mG_J_Sv(rFSbLH z3F=BdN&;@z0kkx(MmK)ffGCHWbpcG%mH2fB63K(Lm~TB`)Vr4dhEBVeR)v1Sff1|} zBAmkWWqEAUlUEeU)`bYNtG{@gz^e}|{{p5!w#dTr5^fIMuuM?fPT2P#uqneas>K}$FVrTOqnzX5DCh^7|?>ZEB4w>3SZNC3jv3mpBX zpRIwq1Dmm{_S1o*dnjy*pdq=s^vSO&<3Ru_8MvVyMj#i#>-s`%!*Ts%GHJUst^|B& z+R}EqN~89@J!n7&ARXcwzjWZ3E^ls{0#B>Izy;pv;@OuJVqd;=iJB6jssw@n@nTJDg|J1SPg6TnZtQ@BtrA&sXjkSl{ z{E|W$+FTq!)mWFlPc;4wxr~Dxz+c-P8V~d^_>MG^(aAsz`K8`8l1oSyi4U9BXl?7! zv}x-WI_lJuiH~{|L6;m6kA-P%PXRM2jKWht_1LoFHVraOY3lIzV&0r~Q|~RYUQ`MJ zK?Db}flARSQACsxL~&0`hZ*&N66!DNCT}D<*v42h%{0@)OqXB&CHhzY@_#PD(+wVM zkasRhsr?h26+SRla|=7fv`~jG(*TwUrT}`RFWbZ<0Cp-<<8FEEjjH_hdzLM>1CP;h zC|hbtkc3B>TFrFpfp+XU(#V6kIyM>gTCR&Z;JW4o9WXmRlCFt@qGW4vf;JX-LLz+@DyVeC^-8@77u8X|%o2!W683{wD4%rsuu zEp?2o3F&M-k_o0gG8w=^z`<-$o%GOx9=hkg<=_3Z_lvjFqqZGKo6k6vhNB;CbtMEZ zth})QOCQPn-5yh+ZUXlb0gurDAZHU2+3+U^3hSj!Q$PuURYbDuy@`iusfi%Uv!*#R zXi9%_F9NH*d-u}j&6{banPz(U>Hqw#zh8ob4;{VZF?96lkI{4$=QLcRn}Hh2 z6;xqBNr0T~CN9w>hI|o&2s}wrC`7DM0NwJt$uo#(a{|G3@~~TF->_aocow+=WcoFj z+PePw>*<6OPAGrt(Oq}lResk?8}$<-b1@qAzylA^QAZs$N10zc{{gun;SN`o~ z#SXpb%pLTb&wq9SQ8Udn)5A}XJNoGIcj0lz(u+>nPVcyTOBeGI5~s^rwINr``J*2>t7S{jcf76HlbaKmPIM_o+{PD*eG9{K3k!@|W}EYe@g} zpZ?QQ=C^&@x9RUc`)B`*Zn)uwazEGgJHF#P=;wd_=UqP0?eG5X?-npO(@ZlxJoGCK zJZ1Vf&v^#D@kQS@gQuBhdW6unM?H#Odg^28h1<86^9Oc)iT2)ndpYid7h+28(Xiek=iZcFCsjBW5Joh$IPYN5=Rf~>de3{_LwDbO z_vl^*L>Vl-`qi&4=im5^-&p=W@rh5+KmN!6xPT)8GWl21=6sSiU(eTG_Oh2L-<&@K zH_=5-^B@1?f28L~HidVGySq1Zy-9ryOq}MXvUAuPCop;_z=bwK*z2F5epx^qf-ztmD zZ@lrwa-Pfm5C7pmlzh3I-}#;2DSyBH+rM4FUeRWV7$Wu7XT`K}vHKLoNG0Z3wP?QA zr=qM=Ywo6G>-E9>d+%pIPS^hGPnT}^s1r}2vwz|Bqk6=(dXet9Lr#$5;w0rmN z<=-82?X}mGsn=Z2mMvSBec)T^o$q`{NuSRj_qfN={rBHr^5#5W_`(GpEh>a{6noxrV;!o4$$eU-HdD<-D0kPJivS*A_XS@{|k9`6|jLVv*8*jU~Tc z5BH_;<>$!P(EMf6ePd#;6@6+jj{Ev6){j7tm z%X7{?yURbyhifQ%>Co=fJhfl^@@uazb@-w4&Z2+ujBliwW}4}dOW%6ZiFD0<_t7Ws zxtAXJ+|6|Cg=bQ=LX~u`OJ77#yfnGs+s}~6O9$o*6g)*=)2`QdG1}6kb}b{Dl{=BI z`o&{>`>vjo7nc3pqE`Ii&E$1Srs)t6Wjf=GGw9X2Cts7le7?4} zR(^B6{_}tS&&4_AYx%zX_VUU7crWHf8Sq-$HaKHHHbd+^2`cQt>x7L~-*VYCNZ(G@3pII*%@Z|>qInRJG z*Yl<&XuRpBFO<66dFNePM!vsec|WJU^Uga;9h{8p+g92wi!yZTB4ec2wzuB)75c!3 z^O#ICc$#UZ!-Vp5-j=2P`Rd(YUjE%Tx{nyC02((5)&mv^p%eL767Z2X`f!C*yOu)P z*_@RK)JCEYaSMZZohUvXMlj_8(;>!Ag)%tG04V?U==XpB_e&%vgQuJ(pZ7?>QRXKU zqdl?)YsZcq)Q{4Q^NB-HzbW$cB0y)7PM6si|J%fMj5e!M6!z!Qn)C*mLvs9BCBF?ZXZ2>i}H-pTP}M21hlLGLFK1*Na~~D(m#tPk!Pj zexm&5>0h}|VT|c@Xmiw)uSKp(vQ4*JRxL~T00j@>q_slK$vqiMHjkD}d66X`U)@E6}oV1vXa3Z(|1rlw?i zQKxZ%en(Y2Mw}O?P7ZbSX`^q^<@-79Dxeif40c|}lEHkRe^2j!|NH4r{^U=}-|@#EPp^5+Yt*sgVtje>j_s4DfQ0{-zx?GA8Myf3 zi=7NppsqLF7pKVL3k9pD@1{Uw?|fCc5umLC`&>Y#4Xgq>srJAj)`NV=mNy`a3?Ipd zaaRE!?b}Gs=@YXXthjqj56n)U=P?N&VF13d4fKoV=yR?Nww<3T)KS}MndZ9d>l%vu*4<1q&2;$CQ#Nm*5AhN} z-F`Q1-ti62(BQZyI7XYookXKc0hrY1x5~*)2QrO)1cz-5CLI&=$|*csJAqHX9!ME- z6o-)xDTtEo3NpCK04V~gd`+hA^1ZweJ+EWRKqgXN-yuAar2ZLrW#IJFKmF70x)6c^ zSqFuHC`YY+?bm*-M4Ixts2q*TZ}9!)gky%De=>(lMp+Tb_C6Nj!cV3`!2lmai|uWOvB0s_g$@K=CO(^UaUx!y8fiS+wl zl27DZV;Ab_11|Ca)ajh8t{!Atc?7%&WE<&{s1qftpm+fQH$Giz9Rn{pzV7q)mkZ}V zcKxkOW}0cHM-H8~`RHP&7JDD;+{Fj27=<$M6YT;1BB%J!$O4!KvA+ROwc}0Hq4k{K zI8sH%kqCg3U6ZGFYIBjYxb#p`UOb+GQGP>C)-h$0MdY%;Jg_Bas~Z%Zq9AblWqY@r<&lI8Pa!cG_uWI`O8PZYtAQ z8CW5Yd@cW>EJCk*hDXKk}3`a(zm!Oc;Th(j`uD z3gF2luMNYvE$}?cInf7WohHWwlLKWg9UuaHfS7(zp>9}t(}WHLaUoy1JQ`da4hJ8J zqh9?}@*oH!K{@{-IpJO1xRZv)^`DAbBCSE6nd-AqUg7=a`yMK?KYq*cG}BBo9X^z| zHYl_VItYyc5pJ$ -LE4`>$3)_>i@n?6NW@I*ej(r67-&!FKwJntE^dVK-sxJ!AB zOc~-jcnB&3B{aY}DwLy1Im(o`A;@4UuQAF~Z5b$KV1#enWiXUMRZg4VxD(KXZ}R1> z4}^LV8v!#SM~)`F=}m8P*MIN_f6(#BQ*}A|lz~(J&1pXHfe+AUKJ%FZs00{fkbs~9 zH{wJfB637Ir<~MA0o1Brn$Sm-A)ZgIpNe^UAP(=zBJ@oeSZY*>6lAuPB*O#&$H4wt zKG?H&FCBgK(GAeC-t!LzL=bLL=Z#ZhFyExSWH2bkNq$gc1j~5DSWV?$rt|8@!3?lU zo~&EwGd2jJuA*C_PCD`c3iCE$ANa}x#kh#BP?n;@qmI%A;w~blasm*}H|6u|aF1O1 zq%z?2rA7%yYsAt)v@kQxG}FUPXaC4Mch}9w_ZEuc>?oi=V7{%6?eiRHIJFR`Rh7OdAz<-E}A3bo0%$`RL6hX9fp* z_GCbHRM~Uv{PQoMJC|urJmJLh23dal?1BrPRHB8sEkF0U&(Vn|oVv6q>61H1oh8nOt8j*Pcf-U>^ za-8IBdBfTKd7}Jw;+bbYQQuO`JWoCKw4$S8nP+4@Iqenmf<3CQ{`JLZc zUhmG^AmrEB^OPRu&FQ}H`@XM4rSe)Qj6khB&D3O*DAuZ|R0M*($W*VsQ^&FFsc6!w z{HyX{WDeIrNd&6FgT|+JL)q6zLbD4>dTsA=zWwBFCl^p~-F4TN^9&Adx#gBJwff+L zdrKdA@YKBp9OU-OujxPd;Dhv)<=@?R-=zSzNl)a$#K}D+nS9(muaTw#zi={K)0+GQ z9F0#-z1D`gl-VYK1=bNxd}ST24L&{Q6rJV$tF~_0T4cNBmM_tXOYoR=aLh3@odsKz zZQHen5Rh)9kp@A!yFpq?>FzG+?rx-e=#(6~OFD*-2I(5Q-|>FF?+?Is&6&qK*S-#& zapK6l*Xz>sd>0Piy-yfvSMff(RIZ~m1anPbJ(BJ=jvzC}&*8($Q`{>!d*S zbsWahPl@G-w<$J!prqP;PP!Zq?6vwB1tBNGNI84z_;JEe)n^j@I{10dM(a0O{-acl zbDeoKvQAUm9qp3bj`>9YhpCD2G994q8;R4{10*ctI|d)+kG~lfX~&+)(QC)Y)?OaQbdVcL9HE3%?DZ_agH_qaT}PnCoO*`i7xWG8mn9Muo*2$B%hpx~hjxVe^Pq%v~h3fNt@ir^U_l^)wY+4BB zRxskqyMB!wHtr~F_C&i676E-Skvb3T%q?MFVtUb<*4lr?#y9GBT(x9WO={k5A&Qmi zHw`bQidQKpdF-Acq{m;LU3x2z}t|6U4$CWrHpKFdD^) z7nunwD3sytru{BcE0ss%mCN@hp6j!-Ss$YvnXr4@7AUhLHO`m|5*H z<7a%g>sRBy*uQ-0?c4S29I9IrP>4OEI)_f}aA7?kIWpnSpD~ba?Ta{F7eh{W`|oD@ z6U!zf(nnqY?MA1pm2(1cogemR#^XnW0MP`t^q=v(XQ&T@68%_)=j4>%!Pc9C5}E-_ z6{rbAknNWhC25q2lufM{qhPem`!3?pWS#!J;y?*w+BW2^f?l1)|GXpJzq6~g*){(i zyVDDM1ao7p=$C%bIu*|C4Af`0SgkW|;RG>QUlXbg@7qBA;Jk=rw%IduRO?bt9wwb+Tk3gW3yBeKT)PmOJ;-giN41--}ufZ^L-9@5}dQep? ztY7CdY5+7#?FHkHK^LCKnDYv|e>cmq)MAKGT}OJR5~L~HVjSBimzf-Ga1Hs373y2a#*fimvq@RLyN-+`n+#FvCf-?2>QHA9Chf3S(T5|LKD zmHO`IZ&~CD*nnW;7j6W$C@g;)X_w7bGPs8&92e%rIGoYs+eG+W%IYZLn?kw|q2i7q zcakj)R;C!X3NW!66sC+7!Ghk-cVnxT>oEYGne&3m9Sj{QGyAGHcXrjh~aw4-zQGz`}DRrK^u$?QO} zlE1a1y5V|3jdGIGtcnMGPDF8XvL-OU|0kaD)t@Y4+w#p=a+vyvRtxt5&vl7*rC!@s zf|=h?qwYWgi2`kHJfxUK6jfHk4@iKtKUe!Qu!@SwH-2pK2W)4kal@@x-$H-MnZZszNuW_8j5H?yremO(&=zTZ)vn!oo6$UJ>sOnH*Bc#nG z)I8%Ol;1hifFAM%z5<5U{5}Omo;tp|U2H?)eK#I~S=a>2gvcs@43#OSj|A-py}#o< zx9Kgbf-E3aIs?%EozPRH8dy~p4y^Txe!(H~na)_H z&6C2&!B|}O03e`LQmv#W`bFlSwH{g|$6Fly8&`qYKOrkpiyOrUV>B-D(T|R@uODku z3NlcOMi@RT@mwX@A}eR>PkmSB12OFH_w7d@e}wIciEo$TmJ{ z#TE%m!kA6-lIkbZV~Ob>6{Gp+3GDbX{bKe^vJwRbN~ zv*<<945wkLZwJCr9!jao+BPHWVJ8fLulxXY+Zf^c$$y{4)fWB<7H6jiK`mxhCuye$-HNEWa7c{?{dd$%JR9gq3E73i#X z#GOV(@Ot|Uw&t>ibTC*I6}N6YYAa*}bD znz1YMHyo*vm+k&jFRpv3T4_=<0`4dDCt0ljh8z4HKo}Fs?)VT!F=Uu)$IX)2X z6}O#Ox88g9i3KgartC$5@MhaJ?L~RJ)#mDp0jPXtjqT)y9F;IE3P z5Ug1UVYIT6cp#~y%BsftRDa7GE@o$JrAkOtw1mr#9NMb3?k z#D9%?v200L*V9zllwF7s#23*?;Bu@WRv=G&nxfLkr*XGF$=8{-P5CR^WOICoJ-3;n z4=?|dY_2A~22qq!^bjVEL0>K33{R2SHNK%bsYuJ5CHAsGxoy`*BP>+f;#l6Fb%N;Y!0w2`(u}(0Za`=79 zOEbDq^rBTLVBV52OV!9zrj`%|AOq>uD_@(&)S?_vxlk0E-Dj&!&%c6T?d&})Y7R(> zb-r>6SN2423JmqluU+@JS) zh-ynWG@mi;6Uv(D3;Z}?0$z*9VIF5SO5 zyE~J*2nO>IN&Ok&)XOG{y7LC#m1LL8reB=GG%5T?R`;L3=zyMf&Wl(_6=AXC{D-a% zi1nY}3U>0IUH3}Ouu+-dnN3gZb%TQDltp|AxmNT<=3wvG4M(slP-z_4_jx;Zm)@lo>6+Pwup{#ikR|X1qxpy7ICBBbyc}f=9 zP6(uonNcg%QIyr&zx3B{fPc9`!d6jQ&#@GCjO?M^YG&8?g)+Hc&vIGL3T^xjhYxGw z^`t>#h$M7z1??i&TCR5+Z(Me7&F8?m*R4+nGo{%_uKfuz0QJcHCQgC$#4-iX_%irI zj+~h@u}iP%IXV+;fTMVm?rNfUMY$TWBV2ruv(uN~Qcrc+Mi;O{bN$Ep>K_LW zE=vAP6OC-3tuyb#Fz+tiIAz|W8E8uRD((5X;Ms>2&*yqqO9mgsO^OE>E!Ydm-zrqf zhRWE2j$gd2xdVnU28}(Us{;^)Zs5wV0OL)+oy(aC9R(8OL&;@Wy(Vj=XJ-;*^wGMV zh!}Par-|1u!eC?AQ>{{<*eQy;!OM|WyQ_omvCd)Jly40z@VK@+cO9DfKmG6hA>G%P za!RlXQb^DlVc&V93EB!HKCn{uCOXAXJZvGL8N)4Y!89DG#_$sK?irRr(NbnJ&P1{< ztWq5!n?8b}(BHck=)tf$Pj{aweH(ofhmue*&1nOES}j`Y`vwhkpqJXWl(U!X@WT%O zD@50M6Eg7Eq`7nT46huYr-39F&M%A7qdA2V5V%=pIR@ zO1&gw#Z<{;e&L>*DZO(oZ{U{n9j0>)k;`DtxuJvDysk|Bs}^$R;i=`zZ(d-GuBA2q zi4EVBb&rRIy?cT@U$&R+U!Q>z(lIc}=(H-U#6`PLB1BxqyD>FVC0=C`q8MX3XYb&$ zeHNH}S=rx*exC)FLod~XOKhdOek2j%!1u}spVC$ zud|`}lb25JehaCK*DnlMd_=^b@VJp{y?6 z&HNtj3{~OL)Kt?otah3hdKTn428&Ksgc5~c2OEFdr`lHD8xpmxkN-Npd$o%c6tlCD zaX8M|ZSr+$xc6aiRBHM0zOd^)4XpFM$Ifz2)zhfar;z$Cl=#P*td_o;8Q)}pfP8w-=2*4hl3%?YBoJU; zPTVBaq`Qpf`WeHW2}ZRuZLAqVOV3TC{H}fdr3RM&s_@B2Xnzd^ubm;kXr)BuH5q+! zbrbJAJoOPCC1Ge4|v?IZpRZe1KENJ#;=zY=B`g2{MuiAj~3TB z=eiQ#NHnB~HZK2mf0GvPR*>A`0nhTI3!lOP9I*=D*9~+IIEqnyrKXsy8GS)tS8GoS z$oe*=C={}Uhn}lof;%X%kLlZ2N2p*>9D8ivpF$<=Ii?7_qTHif<4D6Q19#mvy^|fS z+;OT-nYL?L)AZx$Ns1pO`*IWrZIi3Z^}_AuuVGsMqkFjJ2lL+?#!9V8^{#SZ&vAo} zNPFKWaxcOaDk&5oW@zdAFRXRMJ!5bFhg0jKbbfPJhpluX{g>l*K9@8EEUQm2Am8QJ zbbT)?7%9)G(yK|K+Absa-;0nTD_}GWNb)WKw3{UFN=^rZ#8)^QjX*ibW!&ki28tHN z=`KGQE@83Ji#q+&mUgT|DLww?oYUeUQu4f>urZ)>^wHri0UV$DQk&)pHatEK*2nDBs?F)+mUJ8^3Kza~QN zrTgqiYcjHB_;@ER*8`LW}dg_;MOM6?;zunBR{H0scJf^k%q+ahi?Q z2A(nG+Z0x9=vNl@{4_7}-7!p1m>T_t)0qtxCSpqI#R3ZO{7EmLjArRF)g#0xej||! zF%&&G)iMx)E6YHC&tM6=pL04*IZ;W8&jjJ^;)O|9a8g$VpRyPf#GzJD$reDpzMVbH1gy#=(s4|6E_a7 z5(+SG%OCFUcZJydT{+C^k+(5P7HMxUKAg2ZOI$BJoy|0mQs5u>lh=DZ$wYQr-N1>I8TEI=12$p;}tafRdd&)e)96MNcm^t(JeK>Xr6 z{F?peJdoLs3~^nSH^woa5Fq|w?H#Rl@%>`nG@V1W4YAf`>sGFJdj2&+4$;J6Zo{*{ zhv*xr&oer}{S)4s#0O2vWS!Lzp3m<*T~?o$1UBT~{+3<88M@^2g&j<_=l?>FikkE7W+GmSs7IgN_ye z)vzVR9G1^|ieEd?%;x z?i{?!c#H445?b>zP0rEkz+3zk7vd^(o2VA`8tm(MaC z8}`&b4~O0b4LlarP(I)b=)QDcR0E_aRgi%sX-M(}BckXnIK4Vp{w0`ZeXjTBx!yxV z5$VE}zuM{Ut`=ZY8`p7znDuYw;7{LCHZTY%7jnXDb4)a{{9`AJCB*LhUw?7VCCXR5 zkr1ZjsKrH@j?E-PZx=(esyRh>8;$Th#Ci&^MdOxY7}UG$e)jKKUwz)kX{sW8RHLBk zw%^bxZHm%I&EGl&|6v8?QQ01p`B%VQpr^3Eq0R!2QW?O^a8)k_sG`5@-wEb8UKi;} zeoB9X^M}-=?uk?l?D}+J!sf~=Lz_4){vqhTnmEtr@qN_p4nt0R-QAEv%vY?QaQlji ze_cPy?=$%~lIxpfCs*#x9?{BlklWfLLh3JlQ%h^C%1$g?0uZcraJg{g*9l&0VC>i2 zKB!Z}x5OBTVc+v)^M!XJwO1`SXk7G5p^~Rp4WclRUd9Qcui*VU7@sN|qtwoHIJ|zD z>pS*#$MU3~E&>

&bCZqRg)o&yf&8vF&S@$u=ilItxkpmd5<{aS!w*Cy~7eDD>R&!oT?0Xr4i4>lTl~ysIHz4fy+9UgRo1`W3RFM)nJB_Z~?ITh=_ob%qC~#}d)= z$SDd!>;)az=Zyyk#oE>dF{)dK$8lnNJi3sZ1ym7~-xBtq$z6xHMhXlY0NuxS&Axjg zY{)`Oj*~We`Q4&Q_RuerJk1OYq%zhCMm8u3sPI21v&;E>4LY=Ciy~0nwt&L5hj$`) z*rDT@aRq5_u*(x!AwV{J5`+Ro@J<067`=8#A4sq~bzy5HK08>?`4Sg2_#p$yS=ukn ze~?odn-xIBfG3+9NDy;9Q%^=&p$ernp;b}b=E0$R|F(J6qsWf2`L&j1*WFTkPgA>J zDn25xG2Bh5wL;#Mu%Ri(e(TthFG~XJ@?e&mY+H84fqhLi-KpD{54ddD{PcivCf%6H-i9XEICc>Yd8slT_L#XKD`ozdU~>9= zMuH|;lu+yjomO}q!*j=I$;10whqtE%?dDhh$+-m&CFBRe1fsqypSshOH+(W|{rFnk zxH-Bz_nds;)fGb+@F|pmIz>5szI@9OzA<6>mhty&Z+^|K_K9!Bld2$xESxM=Zf+PB3sFs81%lp{%*881Vb+8E1(|z-*hLHET6+1tFsHKWXdUDnq z4B|OqrTBaGxo;baFRpyZsf!WaLsHY<=ahP|>&#kzCT=9lxs?!sGPIk@GYT@iIX8$h zk^t)k?P3kjTSQSiPHYaT_OmXTcqhFnX@**dvEvxXz9xIZ*ruv*r*j$boL!>}uS~C- z64HzUq`8w4@mxXgoeS5$?KE=&O}|P$p>r*(6SP|@2GOem=i)1m^KX9;%>r+PJF^$P zVRD@M>g^b(gde^}gUhQJ0UQ))kthoeF0@C!t6JtKrb%`Bx=l?0TW=+O!nDmWnak@^dzLt$!x|J*ak!}$~8T=7wP(bZ--Z|Ji z-1_me+B%V#Fh18!{nj7+d#rumnfRpqaD7Non?{O!~k z*l}r{2nW?>l5wZ_1WsTOg+b$nK-9r$l#GG*WHs_ zUW{?y^{&63mE~;g;s8RAW45;+d_P87n}OOn(9#L&_I>j?A#M*E?+z&Tp2XXj7oSpM zKKbWZ;&i>cm;HEm$PJ?wMSpVIsNg9v0ryTty!Pz?@0tL-E-EnT!Ib0n$ncQ*TEZF( zCx@pzdUs11VcY`G2#vRrogGvprqD4M#1DkznIHN$(~#k)o0}n3y(i_V`x4$}lyzt$ z;VB;+{d&AH=hl7u1+;hS-@4`Fxnd86ytl*dQZf&b@~L`;HvJ@yr38(yr57=8=z~Kn zo?rb-4J@Cq_R8e_yp`$CuNoPHQp9>V{N9wmq*Qh=w7{GG6oHF*S+mbS>4p!G+2u

&i|iU#I+ef36jNX46YPE$iSlrF!z>fyxOdB7miKc1 z`CI`j+mD&v-Z4_mAkqh5M)f3iin3?bNi6J%rswoBaSf;J1xxBb>x51{IF_7w~S#wh5O_sv9I z`_H?~v1RvM;~#3$XPX^-<-SXB&4CF?|6-NBJovnqc5wU@AWN8U`YJO0y*0ac$_D%} zsXJ-VMGXEvoqOJSNs*`m`hg(;ZwkBZuQLUhX9 z;K96C3YG4Mt#LyRSfxL=AC-sws$o*HhJqFA^@>Yho_5ug`19RV)Le(x?q(d`j-akX z3z5*zUDx9LuLC=kxSrN5gb~)!4I~5d*d>89l1OFqf)~8Tdy5%rYxmiSq`lk-nT}G_ zR@Uus-Hv}`GKX+t`=TTEk)ZUYEFV#7;Jk}4DE;&E+K)LXC9cW;wD6|&(0(vM4z%dn z$LGJ=*XIc?t<~cHZ3kVkjWY~Xk?Tl`ifDV5@)!xDal6OW24exVaFMMlx{&kn>qT7u zf(EgDVc0a;wl3o3UP0x=zZlGS_&xG#js%ynUu*iweWzM}m>nEGC$g55dW)$do0OBo zv|Urk6eI{kTJnlpo^SJJOGS(~Sj>>Wh1hncAd~lH3dR82$wfs@7F`KSK=G}`w_##OQ2lG9iMi4yS&5}|&1@V)suYxNf zF&azEMP>iNw5Nx+H=4+paoc{uMn~EhfqC?72oYLJp|9~wtHhsRFf7q~sgBnddxO-z z<)U-tI3JB(3yDDb%s7vh8omjB9Q%lZ!?p}!Kw)QNqQ(r#4s$3XGw*q+O9 zAN#;(V}tx_!zBA-F@he2NsgSMTAqsCRxMC5;N7gJTG%sQ{p7D7|9mnE95?hbdDOFR z6wxyl{H%TFvMZl=8J5|p!zG6^`DR15kJ!y;=CEsd**fA;ZhG>rF3aVo`}x>Z?w#5D#0a2CA005=p6GaSFWV2dW&YS%I+gB;%%uK(CcKiDd4Kko=%f7p24$5!w)=BvId zg#OANvxD295uZpg(;Ky{^Bp6{Rbt8Qb-WBxTTvSob-7*9B=hASeU+bSFu7>KRiMI| ztRKz)2z=<1;Pmu!jJt=bepbjPgG07we)KT(Q@?iCqBP1OF808U{(zVg2N2{m1$W`A zm)^VvydF)l6bWIRG#xgl7(RZMduKX<^&XP^Nb@2?Pe`62X#?g_3@nlDEK}dh5D$ss zoQ1j_S&K$)ht^|q{`*ie{V_wy+a#~a!>29;I^~kl#{!9ZVftD(sD{>(&Lz$wY_pG= zOZI+VufW7m0@yh)cRm#!ib3n^N*D>coy#k-@O$DokW&_@4Jt)QauuPcTnm_E5sc4^ z3(R2mKw*)|hcH5O+ShUE`<~Qh=wYN<-;&oYbnf&C;!AG{(%>~=D{j^)ZV4#b{Wa&%W`J~Sagc~gUFzDyWHTw z?>P^skw>KrRYkV`J`1dyYfGoG&v)_2(P zIh&-!SlC0MUMUc5#%%ISfzLWJ@N@f#_CN2OIln2V40NU*21Ks<7a?M!w|*4z`S1K` zxp^J-_vYz5MRO=^KWi=3w{5Q?W@UUk3i8MnI)3*P;W^-fbWn?YCJ z?Vmi09zz?N!sdex-%l9k zhj_F2Nhs6?zWozniOe7VGP1SK?6D{R_2=~KQippN(Een5wL9pWz7jjleaqM7$PR+Q zqiVM(+#)3>Qrt5Y>&+-U(}l{WXP&OhqU&(~dn2zgH8Od+?AEpp%1E2tlE}KYlW+3# z)qj}}xN{rubi?nmqgKZ5Jp9MdsnqAeBof~xpJ=D(?b!4AHPUolQtO$SMvg2Vc;`Jc zlWHtb(iOwL-3b%ZsUbgR6qfC1czT_3e~mnA7xNADj(#+S!`vguppASuYV&=_DnI#D zSzx@u3h783$x|C=Vb}3CdPyR`FVcR}xrwuqjN6gnJdMQmfJ79LEz~wg3!1#byGa~g z+nmQ@+2pio!O8M;ndvkSi1Gfjxnq_bILO%*FVVf|5(}||#S~|n(hYQ8b00{jile*s zjQPE;W*ouWUlXs@KC8`nsOA0WeTw=GppQL&I_#*r3_Lc^+BSy_D*HkHruogpm{Kka zj6q*(%bu~hf$Bn=LGx?}k}wJ(oaJ!X9oQqw2k=?$TS5WF0|&c3TJhglSWHRZi)wrq zh$BCqeo1vl-KTme$aV6~-n|R>%qOZcj9E-mMNmw37)vC&m6&TnxVJ(nDFb7Px%hF6 zcqTPlT!^&L=r=}BJ{ql~U1R<8l61rlCD0}ReIP@XVow)+4^D8uCVIPb zV;3@~C%j+hZ(O{+BW*J=Zrv_*l}BzK$M-(iy`Zr(oz8-qveJS`)h|Mc|7K^f=(0O? zy@gc>+Hdx+gbB*Rdx=7!>4&Y!aYbrMt4gKWUdVO^uQ|>u4A5shb&ju*Bg;Ni^(9%3 z%BAJ9xb|k#t8E?Y_;edFlz*nezM{4<(G#p!<2s_Q-;~1Rec%&x*8J{Jc}`rrJewoi zTiKAhc$VcO<-UK7)@+n}>%Zzts&9{~G|Ffk2XB3FzZL%Tk_;!d2M1Eukd#`Wge&lx z&(4p?a-$IUDWEhToZXE#roAE736#V|r~##N!Zu1aBQqg3IM4?P6IrJOW)`E0_XLl? zkFl~D<-5p)FHU{YBS>(X|FMT3f1N9BINoyq0e&9WJOwqv1F(w4L!nOa%>-PiNqgX? zA{2_mzKTg$oQGPBAqUUScA&oj+ItL8AHrOUL}tYb(eXDI6J8wmZH!xjVRsgtIf_^9 z#yZM|Zc?}L^M7gfSt5>p>LgT0j#06m)8-Ya_&>4w5jfzH{@AW}eawj02`V|2uWN)? zjJGk2bk50)HHr44yX0f;HVlVP-M|&0Cb43K9h_{9HtC6nr{l_md^Cz3QQ4)kps2VC zt#4at3X%Sk%}Qffsyw4YSIDi06&Q)gaXT;`aCkJ=khUtQI5Pnm8oA`rrasoDr2RqC z)jc#R#(%vrC$mW~hr6Ge_Xs0c(0?-^+&Ay4QW&Nw3a18#%J6n%;WVS@zyL$9_!l1M zK*wK#l#@uDOx^Mki5C8v$r{b}jg32eT#OGsfa1NO!ipyUOIS~_B7v4rCNzovcQ{8j z74M>4m$Ux|MwUQqbjSMibUt-~-X+^pF@PnK=nef5{D~VitJupkT-X%dW4UJ-?RbxQ zL~*s`4fB05M14!Ao5V**XgfsEbeUBS!FP{uvI+dpcuHZ~MX@DSmpuR5m~d;)t|OdS z6pD4Ij*rZ;UAJ;57wsqu)&u}ibK_Pz+Gs^ZBuEVcCaG0JFwT6Eb+aFBqvOBT0b?DO z&4>^Hs3>q>{g5r0$^tpA(2vSm`P2Ag=>V`j*o3rKTmX}t-BIw0Ufc|3v@`oA4xPa_ z`56=9DxBB<{ z?%OL|X?jTY_a&~!4~^cr3=amj0x-TrdXku0?13cp>7Xux#gBLivn!(HAWmuxHFSKj zio{`P98eQ_DVE_oS50n4=6F6hseF_LM4h!T?jUR%J)5@FOJlKr*GRF#R=?%n>TW6T zvlpUsTZ8?Mi}DI2qzIXEe2$vU)UAl^{7-IazVWBViEMr2fE`3X3f~=B-m-1N{#}K+ zjSIZZAqWZ;htH75LeBrAodig*6`?zS*w$Bp&7@{&xnYXlLyH6$;tLRjALqp8udJxJ zS(hrk(~!u)p7}5Xk3B4Y8mVRGz>XO}uXF)l`!&rgrSy;+uN*t=HyV7HvB^LU@m{^3 zrWn>ph#YGz0O_PS%)Z#RHc1tpDs{N{^i;R`-m ztF&)xMR9!%u?~1$l?R(E+0Sg7m^TTQGbL-*GTX8Q;p`|+jLlgO{2K9Xw3I6776olk z<>Nd3BozS;>FU;g#Aw)eF3~j=CoxRNnZKfD)unUbneHM_4(*{)2BnLXskHtTKDpC5~H zpJgD<8)BqCc08I7pwB6A`hAF$9tfr&8dTp{G+`mIip3ka5sjl)im`tufvS8QMr+E9 zYh(pz*!=^?$(tdM6alX>Lz&I*=yb=_OqfWT6xMTP+j`b{wE`D%)+V*Mgg^Yf zl#0ayTul{nWzldeMU9_;vzk$dG^UWE z9BYnKosCzmCjVWQSs@Oa3pNXag;T$17(7MFpfVMK(6OAGbq(@#;f|eEz>Dgn6B$qD^Lu_7(&mL_$5Skw|;&ydk5wxU(8k1;g=01+)%`)cPoHaFA zv95(b-K=Mh&Rx&U=^^hqKc!x~RlI-1Y)m{zet*1gCb!fgTFVeB%)|M@al41i>`Y$s zva*8mz;a{g7U7@XX48w@ja zWw;X~n5RiRp+#Cm>dmx8K|gsuqOhdP!OdVsAX!5DVv&?w zz9XPXkdpGDV&q^*FK`9pv*H~9zoVW;OVoVFnXetCJ~BEGEdTWv1^e4>?{2b< z{pW9UY|lx1!LqT3eR)*m7!B?tZ2)PS#4uIGDy!- zXALYo(#3`+#?y)u@7jQJqh)NPN9-%qBq7Rb?4;E*v_WI6%_#xx^MztJ<_I{Dzr?q6 zR(Z~~p(m*K*qoJ3;pbk9XOGlNg}O_cMQqDQKgIR1W8ie-!&E!14-kG=H>4c+p0p6rca2KAYhD`bW34|#t721KV!(;cR*}u`UMci?U zW+z-;vD!!Mtx@$Z)9}`>$?)&l=wl0_(E*@ip;P`4q%~oVHbE=YYi*NFlkG;$Gy#swV_A zO`jy4u*h%a)0r$y43;h8m7^qs2iZVA$B-koF=6jh`AeYq?()nbG|Q$-RxxeEy=WHF z(VP-p5Rb_8WRln>;&-G|z-Eq6+LlY2iiR)Us?=7c8S z>jXmS@)^4VdGOr67K~^~t56~{SY0R*W#>*WV56cdP)S29&~<@VBYc@hn5d=^PUQUP z>oJ~R$g0Xr{%+=fg%=S@*!9eEIr@XKZ&G@g6Q^imyo`h&Vap%IS*wWCdp%wD8DZoZ zcY^g25j{>72(TT21MKC%QmB3}tUL1f+9~nE3x~%pzlG z4n^cy$t~xJMY*9ADWQKNloL^3Mgp)CU(7J@0Z7_ckaf{9K zyBU9(R*UsT!SJc=*pd6H`oAm2xiB@=@prgX z?klDEFbMRgNS0XFs|*38p0LXbO6Q!(F3*wef^EaXBjTXN-%EvFc?V4OWAigw`58$e=6Ff&%moA zw+H_Gz$MSfJ{AiytJSqV7VJ!wZl5Q1NYM%)f=E-ucw+OLBNo%|CbsYri&##5L{Fn2 z_=PB1ewoA(rIcr^Js%n)V88iw*XnfUF|bJhuuhw7t1&}S(WSxDXwXdSI8>{Y5kDLn zG)jFOYfBO_!gXNBUjKo!!B2bRB4$TK10cuR_0^VmW_&Z0fH0IC29qEGyrSkFzSJ0L zb&M^!_6C^g_Blr!S6}ab{98H;y-GE{rHqKUuC})DfREh@|H-BYZw0=E7Q%0Z*uJxL z)J=M*-=ca~VAEi9jPUVg$@&J(%VP1u17Amcs+I<__KXzSr*;D2tQ%h-^p!yqPM} zD6itbIK+8CLde*hDS*j^00v9BEaJzSywPHk;uWZ?(-BqwB4G!-Ge2)cLu5OUA>kL0 zVd0;lb2N_i-^2k`S`3;U(Zer&P_DfDrawwGS+kJwUKB$zU!AV6a!#gL$Emb~GtC4y zHs8@#QV`7?5L00b$eqsSNO}BYHv?+pmARpoS{rR?`lYiCMONRBPjm^vI(Lq8f-lg6^Q{lZ{p29 z``(g=fXmV>Eke24v3G+Z=@a7t&zqevNZ-RyxzONYbJ!9rbcc`GWU{_u2rcQePPSwN z-tj6@Hhq6+_YjB^Fy}k#>iF12pzF$=<2)H>Jx=W3s4>(Y=O8UGf5sTu2K!p&N4dh* zr`-zqos>zcM;zwh$%RY?W>=KT`Qu8=OS4?PTBtHPjwxLvNzeIOqw7fIu_naj!ZGcL zVQ|T#GQH@5DeSbrAr`+@cttq}m<%tNY^Dw0^!t9-SB3T!`~&l%sZlexHd#vRo0*tU zT+Od4LaV(99IIpZleU0(GRDANKBl#B*+BaC~ zpm)a={q$N^E;i@Po$!V(4=zTMP z`i%;s%ihC*3>f5dQ|YM_ymsyH_l&&acJ8}(yB^a4bT(NYJO zV)$P+yLs-}p>*v$<*o`}k8es4^IW{e%rIQ)ZS!QGZ#J*zucyavBrhS>OI)a6V9K1D zNQ!3}56~&?C@Su6xn(EKDy#G6hs?R3!1tH;-sbCkdN+fWy>mTNo|aS+6yQe+n5VsA z$FU+5-WHv7(pi?=8Ts(LyMZ={9}Sm5agSvW=sxw;nu;!HBx*q;8(V27vWA*P$wfnG zxQ4Znk~%j95V4dJIptu-00A{L>DBoyD*3Ho>A;nm4SvMI9r!$E;XtcRcq?jrlNNs+ z<+GHAFmQ3IdJ@0;$Z$k_jIIZ@IM|cOX_C>van`#uLoTk6nt)vVJME0u-VnQFR%bXj zY{RY9l(l3{WC^+Uq!@onfCa55^DvOd?E^CscPAA=!Tf|-R#jA`cJ&|ya7Ggo(v#x) zOjkO%$TI>li%9`XuKjomx;@jI_J8@!wHHt5MNXuYFnZLVph@YXNn6hqzl^mhWBhC3 zXjm2YS=^tt7Vg;Qt`*vbq`*X4tB;Tv6&&`S)e9h4hjnB%AHVn)0`MaUX%joFN zTbu?iKTh2Xc`3&Sls}>TvJ;nyQuDkd${jAl{mef!`K1PKnbL*|y<o7=fLRCgzYsvhi^~v3MIyt@9^tPjF+_8n}E)Z(h-G+6x$WKKSdyqp^KE**=i2(I* zKll-Wx}y1+^FP;5W#u9e!T6cKC2X!B4yUL~U$a|oG)QxJ8WS)2ZI6?~KL=o8az|3f z-zbtqFECKQ%Us0q+N|QUpOL-UR!W~;{|=PWDX#JTQ%e1CQ2JrcOJrHS%$pzY-Peqj z{|nD6Fw|oyQ*gZ|09(eW*=j$Yb)-s-JRrmJy`tbjtg6Ju zQv&ZR=)ZqE8c9&1~93hh8LakH=a8{pf-A?(*;Hr<_0^q}7z8 zPZvD(Y-Pfz1mKAe(2S0rr8ik7*H5)iFnQMf`XpD?kS2A$g=ZNPO;H3<=m4a%+EY1p z3~iB)g90dMMGgm${U1U0FRlrha@<%Zf$JI9 z*>?mc)G2jkL)lXr-?PIMBNtxx5l7HrNf2L2_4T5Wd?p*FwZ4V? z2ggf{4ncph{=<&H_E}w74p9xR9|lH1oJl_z%zj}x_Nno>v)@_=h7Jz0sujrh?QJzo zCPo22yq$b*YtL53;HH|BTdyTXmBHM&))=f!#LO>fERQ<))&TYrgIY|ql>ll<%eRM< zrb8_nfI({@4=e}JJuRmPnU2{qbo&}Z7jRJpwxtRP3S@s8%_}q95A<0U|?_7^x7yPA&& zYaE6ra)7bR3mKhc7^hi#}Qa6ga*UFL0 zQ|p2I9R+^W>O-V!2$>Nq*>55gH3SSA1MB1Vn9#!pl7$!}{RB2fOVo!Qq2oS&4c&9+ zy);ER`h;Jf$GWr;;bdgzjBD#*ynqm2!f3)5hyiTAuglxs{bvPChk5cpwFVaEscPc(MQq3@4~I|;CzOn0&T_)R#{>pW2?4s6t!K3TWAjscz2 zkwKCKhR3Q8`%~=Xwt~b_itfx>SD9UMQPl3q!Hh$}0O5~c~z!M`=mniG+^v8b#T`>WmPJhB<%i{5# z?we}N=4oA+NFTz;02{us&c3HaPnx1Cq(TM~R7Vujj^{BpKo})32gVKE9qu$sJ1Z*pnB@qB1{2ibkzMV<*$aNZ~S5B8d(MZ=m5Bqv+ zJqOSO*3tI|--f=Ova@r20AwNHiTAPqwoem7zd~ZwRa#)X8+btgDx0OEPO;CKWn%r4 z{}TB=6Z`X^5u3)l{h_S#B$rT!L0`5zyxjL-bpZL%l$5EnPMhFVo9R7+kM?anlBfl% z4o72<>jLo_9T}YL+7Gn8vgs%(-(J5f;1ll!-O7&aV$`rs))$6xJ0?;&M6U_jAhrqW zi==I`j+3QVa6!JG7_gyWd&vP%Tcb)L5k>!C>BLSFmTOK5Yaqr8!Gqkhr5RT91qdRC z`5L-kVieH~3BI8!g(Un<6Q>(YWMBQdmL3*Ed0}% z00%Q9O;K%{LZ1R#`y|LO<@zUmeA;4He(%W#<<%~Xj+9I`9C&uU=C>&w;}c~x1n-~v zy{r^u2K}<_RfGe_>LN#R9gfxC^z8@D_?mx8C#+fPV+>(e?sYnl%j&_SvQa-;R%fk) z)S)FHA6XhdWn(>R#Aa~DwBOIMKYICMU4;L*T-NIr8H4fT+8@Rag>ry@94oFgCI#kh z4B`Dm@ZtsVXxGHJufa676y?T$$ZOw@BF7i&HT6P>Zu%-xHT~<`hCpRY2sZ{X1 z-1NW}qo8EV%&L8WNKdJkTqlat@TEM^lj*{j>SITeu->WrdL`e!Wb37TiR4_Y<3~gz z+uRbfvkBNnk^A)3x(?3zKEob(^@8+jhCTy4laH?B3k;PX1?L^*V9y_~v9)cIUdd)B zEZ?{-wB161(ibWt6of;bqzz(yve|-U=?6j^%dfP!AB8-t4KNY0~0caY7 zkOV2At2BOQ@bR9Ub2qP59Cjqe-J6roZw{mWq4U9OcutUB{t%FOp5nNC1jL@!lNxam z5P_BfI?IQk1|tzZLgLF4$D04kj-;2~_zw;Y#BB)70b>-8pxmg>@gLUFu2VN15q0W0 zZX@H{Z@1ca@a;T|_VndMotJ62MbbJW?+D;re}THyH833XL|xfcJI=s}wIV3XuFGdh zDkvE0`V%&gz^Y@A-AZbGgtQDBZ4;&w z0yoZ7UvT2UPwZnv`YET@7vk>3u`T42G^l?AMvZ0a_aj}KCgVr=V*cWfT0aW))@>>{ z+7Z(6t=}ocH5=6IFB$j(zehG@Tg#NOrQoTeouEI->#exefk1YvssRqEw>W9%hA5%I zUO`UxkVjNpKm&9mty_>s+H_PWxSz-umC)!znjjz{D*+e+!d+6bx*%77#iS4&*>_~2 z&&arHX&317bv!2WEt;I%$+aUnIkf+<=}g+frYcFnLz>_tWee!H?v`3#t$qCX6OX0i zP8{#O_RMd)I0$Lj0p+X@E5Qz-&!%2o$v_ockh4Yd2`#cYZnY|coeg*af8}o#`KNZ1 zz$%`C(ZWtM&hPn*&ld{JK4uWn)wiA0PzCL;$M>ng^FgcnC>a**^*UW^7B;D+0>Ge^JMg?ATos z`TjC6X@408;mPW}YaXjNx>syBG%g0@V2m-lK7)XZV2ZLDVC(ySTnbOqZECr2LAZY~ z8l22%G;spN_bsi7ub0%>$&#QwiLn3T)N4~r1AU;^6vl2lWcco%2TvxS04qj6*>u6$ z*IK!vw`N<2Ex#0MeJUx?Lj9l&3Z_6|91#Z$O9wXKHe`ox#tx8(>o>h6&$JyUxNgoe z+7eG3-)t1;$D=C10XU6zeGomf$v?2yA=am=Dv?bmVmgz-M=a8~Cdr=|h~!YVTxIu26SqoMgH z(Y!^evF55CJAsE0ArC_mN@3rhv2~q=5j$TqS>$OInL|yZb2K*j*e_}|5h6gc z{OqnlJ;AsUjTaBI$}edJ{Pm(9P4o!5mU7OPVJ}^5k4L zB{dmH^U2hPK7lxA&Mtil17Q@2=tNLF@IvQBp*Nb&?HKtWNb>ES|0I%QhS1ss>*S0E zTUvC`-jQOP8(6cxiuEV=r+^^zA2e+Fw??sidrA5VqHJtQv=D)k^)uAX7+7TzpeXhs z|1Q9yEf$~;)}9r>4wO&U1J%iezKpVYkeJ50)=fG|L7+JLaoZ+8CIJ}X1COnlskd=l zDIj9&KVnoxEdnrlV6w{Nqg2=?K3xDpWV*JITPjWXZ7l`Txblc0q5oiRF34Efyj49tTlm+ zA=SfpOv)M^X+Ot1*#sMcbmhZVxN9G%k7cT<71i~u=<%&sgC91`&4k=|^Y)7K53%zM!` zq7>>C>qf3jii5h{(;>&CkreAjEsPx&@cMe2o)Y%vpcAVfkAT&b{@whxBTS!0{2d={ zJB)T9-vL03=x*rr0h{7cr_6m|8{0tANF-4rQ3Je1I^YJA(s%XkH|5+E1Fc$yj`jE0 zrR#wAPr#U9SR2ObB~m~+0MJ24K;ZDT4}(v_ex^Nk;RJy3FZr52T3U^cNPV04=VnxK ztl)T8U+?<5AMf2h?CK}_Y|&9_fVs(89bLzB5W(Arq!dX#C zg=H((i|k1HL`t1R(r-aeVlK4rI6u|$EUfp}d)P*fz0+%*ks#;no!-GFGK}5I7Y23I zS5c&>A79Y7EPgC_2EM-&1+|+WN#w?^S7jQ5vE);-W0A@R&^hXK^wCF^-wQ4}r}dvI zur+g#*V$JP7PPN=#X1`UiYKw}1Qn_M)JUIaU&sNd=-{UY<#fc#nxHy{s$CC&nqr~y zwkcf#{y05Vxr?J0*?jsGc-M0u3G=dgBbGwBq)tB8*PkQ!AgCup-AU!eCr7Vu(z;2$ z@skyib#c;q32$@sR)3SpCdozd#q<3aA%~pdZg?Jms1~9po56 z15+H*ApA&X>@!v_Zkn2$%susE^fc!iU?V>Pot3AG;PfLKY)aYk*J@;g6|8?E>zBIs zk9333_gvlJmIA=`_KG==G;$cy4+r*Ro}vB;tHr2u(t2u(wQT|z`xiLJVa)(jCfjEV z`cbOWNkz5=jmAIr79QdlZrX*YaDs8GMiOj+81T`Hh3y1}xSG8L`alO;>k8wI%t=MN zmlkL91cJn{UU^xPLcvz`v2!c7Tv zjJA5ZaRPk=MufE?tDe$GF)&W?e)1y78F%l4o%4}_PH99doOy2$RCW3YOd35ESSRNO zj8f=?$r*E#1M_>{DnS~*`L|dkC!07<9L!cOTE@8QKXqgv8h7I~l1a+(!^-qK230A5 z!xVJq^&`LbHb*RHEH_3#5O`tK#X71lC(4(+6;hr1{$q8ED<7`d_9%0$bb%rWUxxX7A+7fC_E;-N$jJ(L_zlO*}^ zPts;KPV*0Jm~H(Nma+`x`L&U1HarbaRLCeXjGZVf6CgVg) z;=m|bTN9))9k64ryb_k5&?^yWw7ua+T`y++HEA8pC4$`%9VRl{HL46Rr|vE(Q+Xx( z0?QQrI!(a8H0pa4QbtfmUPL{&aZgTlqO;gmgnDi?cyPk*I67t||D_z@kLPJ!3Dwnn1*x^#)ZjN zo~0LO=0SnUh*MTru7OA^*RFX$V^NZEn~6T8y--KOo1MW)p)TagL~!bXg>Pd7#?Fd_ zI>pGTH!?(4R|NLH|HM9JWhcsc)IpW@jl^L;;wy6MB{#pRZ2FFVwwfm<^*~Zy;@%~`-EfD zWMJ0~?Yi{~Y-zLD<^$1s_i%vuD!>a+PN@f$+1}DU{>0;3K3V6;IQ&TCo@mRd=H*c1 zMAKcz_+g!$X=vlq_wOk_Db!sTkvj%mMfIoj;pXOSvN0hH84@MTo_4M_^GO!KC zI^vNmXeF^7BH-z@nFotB;zI#|#D3ChN*ztYUzWjd@QCu@vFOY;fh~8t{m@T&+Czy% zv^>DIzU%6jIJ9R2DT~yVFTuHc8gzz8jHHCg+aKg7V&Coya?Vemmf1TZUxw%`2-z-1 znd_U1iF({>or&}bSo@W4Tk8|aIj8?BCdZis?3QO`i8DRV|v@K&@pJWW0@Red1C3M;njw$iJgz zXhQa^UmVK**MQORt?`6j6?&exr;L%MX1;h&60n$W5=p_;h7Ru_!C~ zI-r3RAEzH9HfyWV9Qovouo-NVc3-wQ_vYlJ3M1>Lg|{}q1^OddD=ij_vC$O|G1aY- zx6}&qLo(Dm@|EklCQIZdu#ceKG%DWidMDq?l|Q!W$wYCySws-mjMaY8>6>gt;5wSn z`8%#x)Rn^3#usGv=rGn6QW`OAAnUKX^j=7VIM@2~=gC;WhG!2b`wiW@ssP!LJf7zj& zR8Ofr$cbc0b0-IY5)|%CFt!6$tZN{phQg-@Kwcw4;2s=qY71qU zoT&@?r?CY=P(vJw@@?-`&U>9}5k#M>Wux{hQ-iY_RAF2DnK0XrMBB^Nsj%twr zAW40b_5kK;%{lkh+{+#Opwx4TfsOTs*sQ{J7;uzv01Tor;q-`4`>u)B2^bK*A zB1Mec`*K_VOGK%6lVDoTifYr{wTwlNug7%AO>ZY@qfxQmzSlCM5aV$cZNsP^)2M~X zblEE&KrUD9>lNtRc#T8`w)T`J#dgH@PB8XVEVw5p*xHV#9BX~qRS|932tciJrmLUE zeVSE{Z6hE=c&$2@Yf3Y|CAap8i55z?<*8_+>nyTY^ zX;)pFq9|XFPp^;m+PvQvd-@rd5zDe)Ira{jN0#50>lf>f^)a-yKisoWYBlTk&+ucb zE~z+L%?E}tiFu1A4>%=x3ka>XvA`|abi!}s)8Z4m$wi@LZcm;;Z4L-fL`t3Xq8>4N zGCj&sCSf7JfOx!fL{U=s;H?K+y`F8rAY5~aMzxd6##ikB!pc^OhZtdl^g$N=3yF2C zF?n1}s5=7^u8d9xOk``#G@_7F06FZ1400t%g2ZGCto_4?IWs+PGMnrszm5nNi3#q- z=r5T=)`y}nWt}?5ZC(+(0Ltx#IO3KxG2I3NMA13?rq-9o zL%OEWHBhUZ+?3Owa^St_$9kYM(agwdS$Kx80*z*qd+cFPsj0o zyf+m!iXfyHrNU!OHw850IO?=VJwUpVzR)WZ@5R0-z-OE8&B%(4U{_xcB+OFd0#7snjYC4vG%)Q zoX_0&F&Uoea)6S-AOr8%&Zq`!H{*ezjlIV-JqD#$XGrd1 z0zD+?a0*r!9g^@#3Kks@A~Mh)9J@tVtj{?~LYuprNt`ADph{TGn6O`v$v|X!OceAg z<&Ai?TPeBrCUD5u2jsW*AB#j;`&pFO`W{(lN*!_9^@R!V?%9w|c06Y1ZHlrQM@ucO zSp*sWsg22zZ6fNAb9XH{{(2?!mng6GLHS#dEG18)4wO$DL@8Z`?Y^`HfFsR@?-%uj6D1c2yy7G0%I=v1Hvg_5M#0!%WQtW?I4}d?65nOy_uSM z1;fnbDl5ja=`Pnk<`JOm9;j9y0+pZ0?^pjw2n`ZyIsSGqO@V%3kzf=uP&4mYsExbzLp8~ za7_#7QLCN^KqCMKlrJg%qwpL-G7u0D__iLqA!>3Z{RLUsShr9FQGh~N53d)3bgAv$ zAEu2u`ba2T>W_aNF93eA6%y)5l>kzXh$ z-`x;^ntDp^Ic5`R?7*k^ZiJ=)PCTD-;5}J;0BNIT_JCNgte#9g9#iG_=~scZRTSF`k9+0gy$Dnn_`xq4N0Fj@VqgVut&**08O7PgdnjKlvqqTA zvC9BPETAltmr4x)x2In~b!>PEG5}N6N6;&zOPyaP>e@2TBM~A1bqpy?H=um&cWCCH z84L7ju>RuDI8?y;C&~uXh&1JpLXp~!Y3y*4)kQeUHVxiU3tukNq8(O*-G-UR(gmIb zPs!L{ydLWL0=*qpJ(++APNM8mKL+OFP!fEU%>V@1XKu*XMSvz=V7TYd%)$zlwyx<| z3qr?j0@p)ZkJsvm{fV(CjPfRhA2L2v2gwNtW5sLV!S>KFvF&>R0fAi{zSwjoQv@tT z_{ExN`jZU@>bV8~FldEBcX5R^5lp8py^YS~{?G^rF~x@s?Xn|VRNF}PDF-bvbOXCg zUhYWTNrQZH0{1cD5d$7i(a80fF8*#597TsUMH_Lz>qS+fgN@gw(jz}9V-;Ay^gS6R z?JD3#6SDQ%1Nluw*?RTByz(0(OtFmh>a`xN()Sc!k!fF|aI1ma`hCQnPJ8W?Lcetq z(0DY~kw4WYkw>Bsg(Cw}RYof%MhDPHuobPq0YQ{MWqr0cqf`fS00y^vrFu(#^KA)_HNV>5ucEA*5RoSgH*VQJ>Sps%g?a{Go z13xBW8%%UeSPG9M^e`}wOXxhMui|;taFQc1>e$j?Hbx0dvgl1$XHU_k$Mut zBK?k@u?-Wgw$nN7x)G?J1DbMvs{p|&=P^Z?Z>o$bxu(F(6abk@7y0$dpK|V4RaK`5e#8@k_Pi2fBcPvb%{Lo1+4KtA0k`&OGj)g}*VcamA^jTI3KH5vy@Z%}|WXlOS9Q7ETN&8Z84>#+nj2a5QMU ziP22r2S;KRlN9?KsmP<6NZUB)knAln(TQYm5rq*bU_Bn?(ehc3Rrd{WAaCnm@HQ3) zkU$LMgj7dfE^ga7*i@hZA=CpoU`iA!hCvjSulw*tu(pY`as=JOoR`xjxsEFR&*Dxt zMWW8I6%%`Cax9^)Z2g*j^+v}iPp~u{P)zES#}pzKAeV6@LY2uajb=xiLdHzMgp7x( zQtrzIZ9o*QNbBox&QNv=`9yx(Ssd&L^oy>Yq}chOJc{Lzf=#)G8MR7IcYb=8KrZQf zj@YMlbxrZN*C$M(6z(}00$(>)8OH_GhhPzTN!M}KQ-EU?pjrhArpj6`U$5L%{9?pt zBfx12fc4VGJUtyme!Xj}09cRjdO)pLhX{CLo{jLv^Y!Z1Ye&>ulm)2=(2#deUp;

!=q$#4j;xq=oD}5b`$Gc+ z1o8;qqIY7D&*Vt(c@V5)&bV-1pz@CEjj&QyZ}R}Ge2tSBUs$!Gz($=>$B!vsAp?Qj zaWm4X`Uw+t z7QwT0tYiaKY)(YeS%!r?D0Cfb0vw(wbOEaa`FycnIF_VxNu>-ml=`7w80?>uDu9yK zw>hCSz>~-UAlHYJ=OYbfF}kE_r%j9>&k0&E5v&_)Nz*u|_7fQgXmC^mPz;CS8T250 zWqhe5X)L#tiFv9%K+w=Xu>3^6-*}-Jv)5y0VgPQTJ8SyXVX2p(Kc}uTJ!O9DehV); z;Z;xny?j&DbKFx-9*!+avHMe4s zn24sFBxF<2gw9od6B#djl2!cE0d_;%7?`n++*zC{GK*pFIOZ%@*@lX}@)+b4|EtEY z-BjPq;!u)0M`wGf7M-R6;+0JXnG-#MBV(ufYmkB%-BdDS}c6|D2D)2<`MAiW&)=f z>y!{ZFgY-RpAr&&@~IpT!X)~Uy27N+fuvV(M*$?LfU)l60O(?5TM|W3NuWv`BlUtU z5RVT~sNEEVh%n0cNheJkv;LYzx2c=Hh#epBs0ZPxRT=sa1v`TmKyIh=MS?VNuUB9S zmq#bizfm1wVCL&C0)@xDNY7LxVAa$As_Q*|@%~2mPUVk0RynH(Vq%`DdiHcM74hkT zuPK>(*H_ga`Np(Uyfy;*R<%h4)9dB!0l!u46Lr?pVNa&jfY|b+(a4}6e@#COu`S2x zrU;#;{jxfFl(Pp+@yUuM4M%VRQ{-gEG?|A1=Zc;X>M3O0a!@C>{#5>=z!OA^1U?iJ|0h{i!!bU+G6@% z+VvtT8_mB8yhfR)K-zl0m~R!}+-Un_zV!f8Oy9d6qe!b{>ZOmm>9zg1T$Cj~b^AJu z<(2wO%T*7t4J~ht#w;j4Oi0=q>5&DxY4m!#rdxR9AOfD!p8aiqyJq0siA zPu6qxX_5$BFS4_o%(9 z5f1pQlxXBkT_MosfEQX>zU(O{?*%j2q>tbltWh?|u%(JSzo}8=dIMpwx4H5x4Nd}2?jD9LmjT+Gi2qiE!0+jsL30}U~pG-z?4k@jk z>PS$87&7J57Rn<()x7{9Eoa=G5-D3OykjTTseKMaWVa(y15IN56Uw$?{7ZTO0Nlbe z0-O|hu}^PxZN_wg<$!EKhtl3sM>YIf7V4nZ-5dupA~nR+8>d;v137SFPJ_Ju$n)2G z@l8U@u7Jx#3gd8r>(G2=bcC^NECc&KX@5(WpHZLk06^f75{TO@ZfZlaayE4ppY?%{R69}d=iWS z$C=$2V=uas5kV{JDS>i|4gz2;VNk{p=H>7I3qVkUi_tHj-2eap07*qoM6N<$f)K;tA^-pY literal 0 HcmV?d00001 diff --git a/modules/scanner/assets/js/styles/app.styles.js b/modules/scanner/assets/js/styles/app.styles.js index 42d8e8d1..4e4c15f1 100644 --- a/modules/scanner/assets/js/styles/app.styles.js +++ b/modules/scanner/assets/js/styles/app.styles.js @@ -1,11 +1,15 @@ +import BanIcon from '@elementor/icons/BanIcon'; import Alert from '@elementor/ui/Alert'; import Box from '@elementor/ui/Box'; import Button from '@elementor/ui/Button'; import Card from '@elementor/ui/Card'; import CardContent from '@elementor/ui/CardContent'; +import Chip from '@elementor/ui/Chip'; import MenuItemText from '@elementor/ui/MenuItemText'; import Paper from '@elementor/ui/Paper'; import Skeleton from '@elementor/ui/Skeleton'; +import TabPanel from '@elementor/ui/TabPanel'; +import Tabs from '@elementor/ui/Tabs'; import Typography from '@elementor/ui/Typography'; import { styled } from '@elementor/ui/styles'; import { ColorPickerStyles } from '@ea11y-apps/scanner/styles/react-colourful.styles'; @@ -21,6 +25,21 @@ export const AppContainer = styled(Paper)` ${ColorPickerStyles} `; +export const AppsTabs = styled(Tabs)` + & .MuiTabs-indicator { + background-color: ${({ theme }) => theme.palette.info.main}; + } + + & .MuiTab-root.Mui-selected { + color: ${({ theme }) => theme.palette.info.main}; + font-weight: 400; + } +`; + +export const AppsTabPanel = styled(TabPanel)` + padding: 0; +`; + export const StyledStatsBlock = styled(Card)` margin: ${({ theme }) => `${theme.spacing(3)} ${theme.spacing(2)}`}; padding: ${({ theme }) => theme.spacing(2)}; @@ -73,7 +92,6 @@ export const StyledSkeleton = styled(Skeleton)` `; const disabledState = ` - opacity: .7; cursor: not-allowed; & * { pointer-events: none; @@ -83,15 +101,27 @@ const disabledState = ` export const StyledAlert = styled(Alert)` align-items: center; /* @noflip */ - padding-right: ${({ theme }) => theme.spacing(0.5)}; - /* @noflip */ direction: ltr; & .MuiAlert-icon, & .MuiAlert-content { padding-top: 0; display: flex; } +`; + +export const AlertWithDisabledState = styled(Alert)` ${({ disabled }) => (disabled ? disabledState : '')} + ${({ disabled, theme }) => + disabled + ? ` + & * { + color: ${theme.palette.text.disabled}; + } + & svg { + fill: ${theme.palette.text.disabled}; + } + ` + : ''} `; export const StateContainer = styled(Box)` @@ -153,8 +183,7 @@ export const ManageButtonWrap = styled(Box)` &:hover, &:focus .MuiPaper-root, &:focus-visible .MuiPaper-root { - background-color: ${({ theme, disabled }) => - !disabled ? theme.palette.action.hover : 'transparent'}; + background-color: ${({ theme }) => theme.palette.action.hover}; } `; @@ -170,12 +199,6 @@ export const ActionButton = styled(Button)` } `; -export const ManageButtonGroup = styled(Box)` - display: flex; - align-items: center; - gap: ${({ theme }) => theme.spacing(0.5)}; -`; - export const UpgradeContentContainer = styled(Box)` display: flex; justify-content: space-between; @@ -199,8 +222,32 @@ export const DisabledMenuItemText = styled(MenuItemText)` color: ${({ theme }) => theme.palette.text.disabled}; `; +export const StyledBanIcon = styled(BanIcon)` + transform: rotate(-45deg); +`; + export const StyledBox = styled(Box)` display: flex; flex-direction: column; gap: ${({ theme }) => theme.spacing(3)}; `; + +export const ManageColorAlert = styled(Alert)` + padding: ${({ theme }) => `${theme.spacing(1)} ${theme.spacing(1.5)};`}; + & .MuiAlert-content { + padding-top: 0; + } + & .MuiAlert-content > div { + display: block; + } +`; + +export const StyledProChip = styled(Chip)` + margin-left: ${({ theme }) => theme.spacing(1)}; + height: 26px; + width: 26px; + + .MuiChip-label { + padding: 0; + } +`; diff --git a/modules/scanner/assets/js/styles/manual-fixes.styles.js b/modules/scanner/assets/js/styles/manual-fixes.styles.js index 31807c9c..2759fe8b 100644 --- a/modules/scanner/assets/js/styles/manual-fixes.styles.js +++ b/modules/scanner/assets/js/styles/manual-fixes.styles.js @@ -37,7 +37,6 @@ export const StyledAccordionDetails = styled(AccordionDetails)` `; export const StyledSnippet = styled(Typography)` - width: 335px; word-break: break-word; `; @@ -47,20 +46,25 @@ export const InfotipBox = styled(Box)` white-space: normal; `; +export const InfotipImage = styled('img')` + max-width: 304px; + height: auto; +`; + export const InfotipFooter = styled(Box)` display: flex; justify-content: flex-end; margin-top: ${({ theme }) => theme.spacing(2)}; `; -export const AIHeader = styled(Box)` +export const ItemHeader = styled(Box)` display: flex; justify-content: space-between; margin-bottom: ${({ theme }) => theme.spacing(1)}; - min-height: 32px; + min-height: ${({ theme }) => theme.spacing(4)}; `; -export const AITitle = styled(Box)` +export const ItemTitle = styled(Box)` display: flex; gap: ${({ theme }) => theme.spacing(1)}; align-items: center; @@ -71,3 +75,11 @@ export const ManualTextField = styled(TextField)` font-size: 14px; } `; + +export const TitleBox = styled(Box)` + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: ${({ theme }) => theme.spacing(0.5)}; + gap: ${({ theme }) => theme.spacing(0.5)}; +`; diff --git a/modules/scanner/assets/js/types/scanner-item.js b/modules/scanner/assets/js/types/scanner-item.js index 323893b8..3d9a8fcd 100644 --- a/modules/scanner/assets/js/types/scanner-item.js +++ b/modules/scanner/assets/js/types/scanner-item.js @@ -23,4 +23,8 @@ export const scannerItem = PropTypes.shape({ category: PropTypes.string.isRequired, level: PropTypes.string.isRequired, node: PropTypes.object, + parentNode: PropTypes.object, + isPotential: PropTypes.bool, + isEdit: PropTypes.bool, + global: PropTypes.bool, }); diff --git a/modules/scanner/assets/js/utils/build-path-to-parent.js b/modules/scanner/assets/js/utils/build-path-to-parent.js new file mode 100644 index 00000000..62d1501a --- /dev/null +++ b/modules/scanner/assets/js/utils/build-path-to-parent.js @@ -0,0 +1,26 @@ +import getXPath from 'get-xpath'; + +/** + * Builds an array of elements from a child up to a specific parent node. + * @param {Element} itemNode - Starting (child) element. + * @param {Element} parentNode - Ancestor element to stop at. + * @return {string[]} Array of elements from item → parent (inclusive) + */ +export const buildPathToParent = (itemNode, parentNode) => { + const path = []; + + if (!parentNode) { + return [getXPath(itemNode, { ignoreId: true })]; + } + + let current = itemNode; + while (current) { + path.push(getXPath(current, { ignoreId: true })); + if (current === parentNode) { + break; + } + current = current.parentElement; + } + + return path; +}; diff --git a/modules/scanner/assets/js/utils/calc-color-ratio.js b/modules/scanner/assets/js/utils/calc-color-ratio.js index 38859936..c4a83ed4 100644 --- a/modules/scanner/assets/js/utils/calc-color-ratio.js +++ b/modules/scanner/assets/js/utils/calc-color-ratio.js @@ -34,6 +34,7 @@ export const checkContrastAA = (el) => { //some exception occurred, or not able to get color combo for some reason throw new Error('unable to get color combo for element: ' + el.nodeName); } + const fg = colorCombo.fg; const bg = colorCombo.bg; const ratio = fg.contrastRatio(bg); @@ -43,5 +44,6 @@ export const checkContrastAA = (el) => { ratio: +ratio.toFixed(2), largeText: large, passesAA, + isPotential: colorCombo.hasBGImage || colorCombo.hasGradient, }; }; diff --git a/modules/scanner/assets/js/utils/convert-colors.js b/modules/scanner/assets/js/utils/convert-colors.js index f0edad97..666ecbc5 100644 --- a/modules/scanner/assets/js/utils/convert-colors.js +++ b/modules/scanner/assets/js/utils/convert-colors.js @@ -9,36 +9,6 @@ export const expandHex = (hex) => { return `#${hex}`; }; -export const rgbOrRgbaToHex = (color) => { - const match = color.match( - /rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/i, - ); - if (!match) { - return null; - } // Not an RGB or RGBA string - - const r = parseInt(match[1]).toString(16).padStart(2, '0'); - const g = parseInt(match[2]).toString(16).padStart(2, '0'); - const b = parseInt(match[3]).toString(16).padStart(2, '0'); - - // If alpha present and less than 1, include it - if (match[4] !== undefined && parseFloat(match[4]) < 1) { - const a = Math.round(parseFloat(match[4]) * 255) - .toString(16) - .padStart(2, '0'); - return `#${r}${g}${b}${a}`.toUpperCase(); // 8-digit hex with alpha - } - - return `#${r}${g}${b}`.toUpperCase(); // 6-digit hex -}; - -export const hexToRGB = (hex) => { - hex = expandHex(hex).replace(/^#/, ''); - const num = parseInt(hex, 16); - // eslint-disable-next-line no-bitwise - return [(num >> 16) & 255, (num >> 8) & 255, num & 255]; -}; - export const hexToHsl = (hex) => { hex = expandHex(hex).replace(/^#/, ''); diff --git a/modules/scanner/assets/js/utils/focus-on-element.js b/modules/scanner/assets/js/utils/focus-on-element.js index 0305f8bd..a3a0ddb5 100644 --- a/modules/scanner/assets/js/utils/focus-on-element.js +++ b/modules/scanner/assets/js/utils/focus-on-element.js @@ -2,8 +2,6 @@ import { BACKGROUND_ELEMENT_CLASS, COLOR_ELEMENT_CLASS, CURRENT_ELEMENT_CLASS, - DATA_INITIAL_BG, - DATA_INITIAL_COLOR, } from '@ea11y-apps/scanner/constants'; export const focusOnElement = (element, queryClass = null) => { @@ -16,20 +14,6 @@ export const focusOnElement = (element, queryClass = null) => { } }; -export const resetStyles = (element) => { - const bg = element.getAttribute(DATA_INITIAL_BG); - const color = element.getAttribute(DATA_INITIAL_COLOR); - - if (bg && element?.style) { - element.style['background-color'] = bg; - element.removeAttribute(DATA_INITIAL_BG); - } - if (color && element?.style) { - element.style.color = color; - element.removeAttribute(DATA_INITIAL_COLOR); - } -}; - export const removeExistingFocus = (queryClass = null) => { document .querySelectorAll( @@ -43,6 +27,5 @@ export const removeExistingFocus = (queryClass = null) => { COLOR_ELEMENT_CLASS, BACKGROUND_ELEMENT_CLASS, ); - resetStyles(element); }); }; diff --git a/modules/scanner/assets/js/utils/get-element-css-selector.js b/modules/scanner/assets/js/utils/get-element-css-selector.js index e2fd0457..2b3a2d88 100644 --- a/modules/scanner/assets/js/utils/get-element-css-selector.js +++ b/modules/scanner/assets/js/utils/get-element-css-selector.js @@ -3,10 +3,8 @@ import { COLOR_ELEMENT_CLASS, CURRENT_ELEMENT_CLASS, } from '@ea11y-apps/scanner/constants'; -import { getElementByXPath } from '@ea11y-apps/scanner/utils/get-element-by-xpath'; -export const getElementCSSSelector = (xpath) => { - let element = getElementByXPath(xpath); +export const getElementCSSSelector = (element) => { if (!element || !(element instanceof Element)) { return null; } diff --git a/modules/scanner/assets/js/utils/get-initial-tab.js b/modules/scanner/assets/js/utils/get-initial-tab.js new file mode 100644 index 00000000..54186437 --- /dev/null +++ b/modules/scanner/assets/js/utils/get-initial-tab.js @@ -0,0 +1,7 @@ +import { BLOCKS, MANAGE_URL_PARAM } from '@ea11y-apps/scanner/constants'; + +export const getInitialTab = () => { + const params = new URLSearchParams(window.location.search); + const isInitManage = params.get(MANAGE_URL_PARAM) === '1'; + return isInitManage ? BLOCKS.management : BLOCKS.main; +}; diff --git a/modules/scanner/assets/js/utils/get-outer-html-by-xpath.js b/modules/scanner/assets/js/utils/get-outer-html-by-xpath.js new file mode 100644 index 00000000..00be9c10 --- /dev/null +++ b/modules/scanner/assets/js/utils/get-outer-html-by-xpath.js @@ -0,0 +1,44 @@ +import { + BACKGROUND_ELEMENT_CLASS, + COLOR_ELEMENT_CLASS, + CURRENT_ELEMENT_CLASS, +} from '@ea11y-apps/scanner/constants'; +import { getElementByXPath } from '@ea11y-apps/scanner/utils/get-element-by-xpath'; + +/** + * Return outer html for el. Should be used only for global remediation. + * @param {string|null} xpath + * @param {string|null} attr + * @return {string} element outer html with removed classes + */ +export const getOuterHtmlByXpath = (xpath, attr = null) => { + if (!xpath) { + return ''; + } + const element = getElementByXPath(xpath); + if (!element || element.nodeType !== Node.ELEMENT_NODE) { + return ''; + } + + // Define ignored class constants (with dots) + const ignoredClasses = new Set([ + CURRENT_ELEMENT_CLASS, + COLOR_ELEMENT_CLASS, + BACKGROUND_ELEMENT_CLASS, + ]); + + // Clone the node so we don’t modify the real DOM + const clone = element.cloneNode(true); + let html = clone.outerHTML; + + ignoredClasses.forEach((item) => { + html = html.replaceAll(` ${item}`, ''); + html = html.replaceAll(item, ''); + html = html.replaceAll('class=""', ''); + if (attr) { + html = html.replaceAll(attr, ''); + } + }); + + return html; +}; diff --git a/modules/scanner/assets/js/utils/validate-headings.js b/modules/scanner/assets/js/utils/validate-headings.js index 253aed22..2b0c29ed 100644 --- a/modules/scanner/assets/js/utils/validate-headings.js +++ b/modules/scanner/assets/js/utils/validate-headings.js @@ -1,6 +1,6 @@ import { - toFlatTree, getHeadingXpath, + toFlatTree, } from '@ea11y-apps/scanner/utils/page-headings'; import { EA11Y_RULES } from '../rules'; import { HEADING_STATUS } from '../types/heading'; @@ -48,7 +48,7 @@ export const validateHeadings = (headingTree, dismissedHeadingIssues) => { */ const validateHierarchy = ( headings, - dismissedHeadingIssues, + dismissedHeadingIssues = [], parentLevel = 0, ) => { let previousLevel = parentLevel; diff --git a/modules/scanner/rest/scanner-results.php b/modules/scanner/rest/scanner-results.php index c86cbd56..70f955d9 100644 --- a/modules/scanner/rest/scanner-results.php +++ b/modules/scanner/rest/scanner-results.php @@ -44,7 +44,7 @@ public function GET() { foreach ( $pages_scanned as $page ) { $scans = Scan_Entry::get_scans( $page->url ); - $page_remediation_count = Remediation_Entry::get_page_remediations( $page->url, true ); + $page_remediation_count = Remediation_Entry::get_page_remediations_count( $page->url ); $scans = array_map(function( $scan ) { return [ @@ -71,7 +71,7 @@ public function GET() { 'page_url' => $page->url, 'scans' => $scans, 'last_scan' => $scans[0]['date'] ?: $page->created_at, - 'remediation_count' => isset( $page_remediation_count[0] ) ? $page_remediation_count[0]->total : 0, + 'remediation_count' => $page_remediation_count, 'issues_total' => $scans[0]['issues_total'], 'issues_fixed' => $scans[0]['issues_fixed'], ]; diff --git a/modules/settings/assets/js/app.js b/modules/settings/assets/js/app.js index f85b9a3c..18e406ec 100644 --- a/modules/settings/assets/js/app.js +++ b/modules/settings/assets/js/app.js @@ -6,10 +6,9 @@ import { styled, ThemeProvider } from '@elementor/ui/styles'; import { ConnectModal, MenuItems, - Notifications, + OnboardingModal, PostConnectModal, UrlMismatchModal, - OnboardingModal, } from '@ea11y/components'; import { useNotificationSettings, @@ -17,6 +16,7 @@ import { useSettings, } from '@ea11y/hooks'; import { Sidebar, TopBar } from '@ea11y/layouts'; +import Notifications from '@ea11y-apps/global/components/notifications'; import { mixpanelEvents, mixpanelService } from '@ea11y-apps/global/services'; import { useEffect } from '@wordpress/element'; import { usePluginSettingsContext } from './contexts/plugin-settings'; diff --git a/modules/settings/assets/js/components/analytics/charts-list.js b/modules/settings/assets/js/components/analytics/charts-list.js index 6ae1aa8f..22356e56 100644 --- a/modules/settings/assets/js/components/analytics/charts-list.js +++ b/modules/settings/assets/js/components/analytics/charts-list.js @@ -1,4 +1,5 @@ -import { ChevronDownIcon, InfoCircleFilledIcon } from '@elementor/icons'; +import ChevronDownIcon from '@elementor/icons/ChevronDownIcon'; +import InfoCircleFilledIcon from '@elementor/icons/InfoCircleFilledIcon'; import Alert from '@elementor/ui/Alert'; import AlertTitle from '@elementor/ui/AlertTitle'; import Box from '@elementor/ui/Box'; diff --git a/modules/settings/assets/js/components/analytics/charts/line-chart.js b/modules/settings/assets/js/components/analytics/charts/line-chart.js index 5af4439d..908f552f 100644 --- a/modules/settings/assets/js/components/analytics/charts/line-chart.js +++ b/modules/settings/assets/js/components/analytics/charts/line-chart.js @@ -1,7 +1,7 @@ -import { useTheme } from '@elementor/ui'; import Card from '@elementor/ui/Card'; import CardHeader from '@elementor/ui/CardHeader'; import Typography from '@elementor/ui/Typography'; +import { useTheme } from '@elementor/ui/styles'; import { LineChart as MuiLineChart } from '@mui/x-charts/LineChart'; import { LineChartTitle } from '@ea11y/components/analytics/components/line-chart-title'; import { LineTooltip } from '@ea11y/components/analytics/components/line-tooltip'; diff --git a/modules/settings/assets/js/components/capabilities-item/pro-item-infotip.js b/modules/settings/assets/js/components/capabilities-item/pro-item-infotip.js index 504e3283..e41dea33 100644 --- a/modules/settings/assets/js/components/capabilities-item/pro-item-infotip.js +++ b/modules/settings/assets/js/components/capabilities-item/pro-item-infotip.js @@ -1,4 +1,4 @@ -import { CrownIcon } from '@elementor/icons'; +import CrownIcon from '@elementor/icons/CrownIcon'; import Button from '@elementor/ui/Button'; import Card from '@elementor/ui/Card'; import CardActions from '@elementor/ui/CardActions'; diff --git a/modules/settings/assets/js/components/connect-modal/check-icon.js b/modules/settings/assets/js/components/connect-modal/check-icon.js index e261224d..649c1307 100644 --- a/modules/settings/assets/js/components/connect-modal/check-icon.js +++ b/modules/settings/assets/js/components/connect-modal/check-icon.js @@ -1,4 +1,4 @@ -import { CircleCheckFilledIcon } from '@elementor/icons'; +import CircleCheckFilledIcon from '@elementor/icons/CircleCheckFilledIcon'; import ListItemIcon from '@elementor/ui/ListItemIcon'; const ConnectModalCheckIcon = () => { diff --git a/modules/settings/assets/js/components/copy-link/index.js b/modules/settings/assets/js/components/copy-link/index.js index 88a06c51..9401eb9a 100644 --- a/modules/settings/assets/js/components/copy-link/index.js +++ b/modules/settings/assets/js/components/copy-link/index.js @@ -1,4 +1,4 @@ -import { LinkIcon } from '@elementor/icons'; +import LinkIcon from '@elementor/icons/LinkIcon'; import IconButton from '@elementor/ui/IconButton'; import Tooltip from '@elementor/ui/Tooltip'; import clipboardCopy from 'clipboard-copy'; diff --git a/modules/settings/assets/js/components/edit-link/index.js b/modules/settings/assets/js/components/edit-link/index.js index 4533ecfd..8cef2de5 100644 --- a/modules/settings/assets/js/components/edit-link/index.js +++ b/modules/settings/assets/js/components/edit-link/index.js @@ -1,4 +1,4 @@ -import { EditIcon } from '@elementor/icons'; +import EditIcon from '@elementor/icons/EditIcon'; import IconButton from '@elementor/ui/IconButton'; import Tooltip from '@elementor/ui/Tooltip'; import { useSettings } from '@ea11y/hooks'; diff --git a/modules/settings/assets/js/components/index.js b/modules/settings/assets/js/components/index.js index 700f2914..bb83e824 100644 --- a/modules/settings/assets/js/components/index.js +++ b/modules/settings/assets/js/components/index.js @@ -1,5 +1,4 @@ export { default as ConnectModal } from './connect-modal'; -export { default as Notifications } from '../../../../../assets/dev/js/components/notifications'; export { default as MyAccountMenu } from './my-account-menu'; export { default as PopupMenu } from './my-account-menu/popup-menu'; export { default as SidebarAppBar } from './sidebar-app-bar'; diff --git a/modules/settings/assets/js/components/media-uploader/media-uploader.js b/modules/settings/assets/js/components/media-uploader/media-uploader.js index 73cf46af..2637916c 100644 --- a/modules/settings/assets/js/components/media-uploader/media-uploader.js +++ b/modules/settings/assets/js/components/media-uploader/media-uploader.js @@ -1,4 +1,4 @@ -import { UploadIcon } from '@elementor/icons'; +import UploadIcon from '@elementor/icons/UploadIcon'; import Button from '@elementor/ui/Button'; import { ConfirmDialog } from '@ea11y/components'; import { useIconDesign, useStorage } from '@ea11y/hooks'; diff --git a/modules/settings/assets/js/components/my-account-menu/index.js b/modules/settings/assets/js/components/my-account-menu/index.js index 10977dbe..90343dfb 100644 --- a/modules/settings/assets/js/components/my-account-menu/index.js +++ b/modules/settings/assets/js/components/my-account-menu/index.js @@ -1,10 +1,8 @@ -import { - ChevronDownIcon, - ChevronUpIcon, - ExternalLinkIcon, - HelpIcon, - UserIcon, -} from '@elementor/icons'; +import ChevronDownIcon from '@elementor/icons/ChevronDownIcon'; +import ChevronUpIcon from '@elementor/icons/ChevronUpIcon'; +import ExternalLinkIcon from '@elementor/icons/ExternalLinkIcon'; +import HelpIcon from '@elementor/icons/HelpIcon'; +import UserIcon from '@elementor/icons/UserIcon'; import List from '@elementor/ui/List'; import ListItemButton from '@elementor/ui/ListItemButton'; import ListItemIcon from '@elementor/ui/ListItemIcon'; diff --git a/modules/settings/assets/js/components/my-account-menu/popup-menu.js b/modules/settings/assets/js/components/my-account-menu/popup-menu.js index 56ea2847..8c5e799a 100644 --- a/modules/settings/assets/js/components/my-account-menu/popup-menu.js +++ b/modules/settings/assets/js/components/my-account-menu/popup-menu.js @@ -1,8 +1,6 @@ -import { - CalendarDollarIcon, - ExternalLinkIcon, - UserIcon, -} from '@elementor/icons'; +import CalendarDollarIcon from '@elementor/icons/CalendarDollarIcon'; +import ExternalLinkIcon from '@elementor/icons/ExternalLinkIcon'; +import UserIcon from '@elementor/icons/UserIcon'; import Avatar from '@elementor/ui/Avatar'; import Box from '@elementor/ui/Box'; import Chip from '@elementor/ui/Chip'; diff --git a/modules/settings/assets/js/components/quota-bar/index.js b/modules/settings/assets/js/components/quota-bar/index.js index 6e4c25c5..8d203467 100644 --- a/modules/settings/assets/js/components/quota-bar/index.js +++ b/modules/settings/assets/js/components/quota-bar/index.js @@ -1,9 +1,10 @@ -import { InfoCircleIcon, LockFilledIcon } from '@elementor/icons'; -import { styled } from '@elementor/ui'; +import InfoCircleIcon from '@elementor/icons/InfoCircleIcon'; +import LockFilledIcon from '@elementor/icons/LockFilledIcon'; import Box from '@elementor/ui/Box'; import Infotip from '@elementor/ui/Infotip'; import LinearProgress from '@elementor/ui/LinearProgress'; import Typography from '@elementor/ui/Typography'; +import { styled } from '@elementor/ui/styles'; import { QuotaBarData } from '@ea11y/components/quota-bar/data'; import { formatPlanValue } from '../../utils/index'; diff --git a/modules/settings/assets/js/components/quota-bar/quota-bar-group.js b/modules/settings/assets/js/components/quota-bar/quota-bar-group.js index fefb9c3b..3de43fa6 100644 --- a/modules/settings/assets/js/components/quota-bar/quota-bar-group.js +++ b/modules/settings/assets/js/components/quota-bar/quota-bar-group.js @@ -1,4 +1,5 @@ -import { ChevronUpIcon, CrownIcon } from '@elementor/icons'; +import ChevronUpIcon from '@elementor/icons/ChevronUpIcon'; +import CrownIcon from '@elementor/icons/CrownIcon'; import Box from '@elementor/ui/Box'; import Button from '@elementor/ui/Button'; import Card from '@elementor/ui/Card'; diff --git a/modules/settings/assets/js/components/quota-bar/quota-indicator.js b/modules/settings/assets/js/components/quota-bar/quota-indicator.js index 1918c46f..7cad8430 100644 --- a/modules/settings/assets/js/components/quota-bar/quota-indicator.js +++ b/modules/settings/assets/js/components/quota-bar/quota-indicator.js @@ -1,7 +1,5 @@ -import { - AlertOctagonFilledIcon, - AlertTriangleFilledIcon, -} from '@elementor/icons'; +import AlertOctagonFilledIcon from '@elementor/icons/AlertOctagonFilledIcon'; +import AlertTriangleFilledIcon from '@elementor/icons/AlertTriangleFilledIcon'; import Tooltip from '@elementor/ui/Tooltip'; import { useSettings } from '@ea11y/hooks'; import { __ } from '@wordpress/i18n'; diff --git a/modules/settings/assets/js/components/sidebar-menu/menu-item.js b/modules/settings/assets/js/components/sidebar-menu/menu-item.js index 204d0a26..641b1cf4 100644 --- a/modules/settings/assets/js/components/sidebar-menu/menu-item.js +++ b/modules/settings/assets/js/components/sidebar-menu/menu-item.js @@ -9,7 +9,7 @@ import ListSubheader from '@elementor/ui/ListSubheader'; import Tooltip from '@elementor/ui/Tooltip'; import { styled } from '@elementor/ui/styles'; import { useSettings } from '@ea11y/hooks'; -import CrownFilled from '@ea11y/icons/crown-filled'; +import CrownFilled from '@ea11y-apps/global/icons/crown-filled'; import { mixpanelEvents, mixpanelService } from '@ea11y-apps/global/services'; import { Fragment, useState } from '@wordpress/element'; diff --git a/modules/settings/assets/js/components/sidebar-menu/menu.js b/modules/settings/assets/js/components/sidebar-menu/menu.js index 528bd422..f34bb772 100644 --- a/modules/settings/assets/js/components/sidebar-menu/menu.js +++ b/modules/settings/assets/js/components/sidebar-menu/menu.js @@ -1,11 +1,13 @@ -import { ChecklistIcon, PagesIcon, SettingsIcon } from '@elementor/icons'; -import { WidgetIcon, AnalyticsIcon } from '@ea11y/icons'; +import ChecklistIcon from '@elementor/icons/ChecklistIcon'; +import PagesIcon from '@elementor/icons/PagesIcon'; +import SettingsIcon from '@elementor/icons/SettingsIcon'; +import { AnalyticsIcon, WidgetIcon } from '@ea11y/icons'; import { + AccessibilityAssistant, AccessibilityStatement, - Menu, - IconSettings, Analytics, - AccessibilityAssistant, + IconSettings, + Menu, } from '@ea11y/pages'; import { __ } from '@wordpress/i18n'; import { AccessibilityAssistantContextProvider } from '../../contexts/accessibility-assistant-context'; diff --git a/modules/settings/assets/js/components/sidebar-menu/tooltips/accessibility-statement.js b/modules/settings/assets/js/components/sidebar-menu/tooltips/accessibility-statement.js index e954ce47..48118ac6 100644 --- a/modules/settings/assets/js/components/sidebar-menu/tooltips/accessibility-statement.js +++ b/modules/settings/assets/js/components/sidebar-menu/tooltips/accessibility-statement.js @@ -1,4 +1,4 @@ -import { InfoCircleIcon } from '@elementor/icons'; +import InfoCircleIcon from '@elementor/icons/InfoCircleIcon'; import Button from '@elementor/ui/Button'; import Card from '@elementor/ui/Card'; import CardActions from '@elementor/ui/CardActions'; diff --git a/modules/settings/assets/js/components/sidebar-menu/tooltips/analytics.js b/modules/settings/assets/js/components/sidebar-menu/tooltips/analytics.js index 9a015be8..c7877962 100644 --- a/modules/settings/assets/js/components/sidebar-menu/tooltips/analytics.js +++ b/modules/settings/assets/js/components/sidebar-menu/tooltips/analytics.js @@ -1,4 +1,4 @@ -import { InfoCircleIcon } from '@elementor/icons'; +import InfoCircleIcon from '@elementor/icons/InfoCircleIcon'; import Button from '@elementor/ui/Button'; import Card from '@elementor/ui/Card'; import CardActions from '@elementor/ui/CardActions'; diff --git a/modules/settings/assets/js/components/sitemap-settings/index.js b/modules/settings/assets/js/components/sitemap-settings/index.js index a8a7eb64..d95426f8 100644 --- a/modules/settings/assets/js/components/sitemap-settings/index.js +++ b/modules/settings/assets/js/components/sitemap-settings/index.js @@ -1,4 +1,4 @@ -import { InfoCircleIcon } from '@elementor/icons'; +import InfoCircleIcon from '@elementor/icons/InfoCircleIcon'; import Accordion from '@elementor/ui/Accordion'; import AccordionDetails from '@elementor/ui/AccordionDetails'; import AccordionSummary from '@elementor/ui/AccordionSummary'; diff --git a/modules/settings/assets/js/components/skip-to-content-settings/index.js b/modules/settings/assets/js/components/skip-to-content-settings/index.js index e7bb8e9b..e9b573d3 100644 --- a/modules/settings/assets/js/components/skip-to-content-settings/index.js +++ b/modules/settings/assets/js/components/skip-to-content-settings/index.js @@ -1,4 +1,4 @@ -import { InfoCircleIcon } from '@elementor/icons'; +import InfoCircleIcon from '@elementor/icons/InfoCircleIcon'; import Box from '@elementor/ui/Box'; import Card from '@elementor/ui/Card'; import FormLabel from '@elementor/ui/FormLabel'; diff --git a/modules/settings/assets/js/components/upgrade-modal/index.js b/modules/settings/assets/js/components/upgrade-modal/index.js index 649bf3e7..41e82d7c 100644 --- a/modules/settings/assets/js/components/upgrade-modal/index.js +++ b/modules/settings/assets/js/components/upgrade-modal/index.js @@ -4,9 +4,9 @@ import Modal from '@elementor/ui/Modal'; import Paper from '@elementor/ui/Paper'; import Typography from '@elementor/ui/Typography'; import { styled } from '@elementor/ui/styles'; -import CrownFilled from '@ea11y/icons/crown-filled'; import { StyledContainer } from '@ea11y/pages/pages.styles'; import { GOLINKS } from '@ea11y-apps/global/constants'; +import CrownFilled from '@ea11y-apps/global/icons/crown-filled'; import { mixpanelEvents, mixpanelService } from '@ea11y-apps/global/services'; import { __ } from '@wordpress/i18n'; import imageUrl from '../../../img/upgrade.png'; diff --git a/modules/settings/assets/js/layouts/position-settings.js b/modules/settings/assets/js/layouts/position-settings.js index e8172071..b894057e 100644 --- a/modules/settings/assets/js/layouts/position-settings.js +++ b/modules/settings/assets/js/layouts/position-settings.js @@ -1,4 +1,5 @@ -import { DesktopIcon, MobileIcon } from '@elementor/icons'; +import DesktopIcon from '@elementor/icons/DesktopIcon'; +import MobileIcon from '@elementor/icons/MobileIcon'; import Box from '@elementor/ui/Box'; import Grid from '@elementor/ui/Grid'; import Tab from '@elementor/ui/Tab'; diff --git a/modules/settings/assets/js/layouts/quota-bar.js b/modules/settings/assets/js/layouts/quota-bar.js index 50e1f323..aabe6291 100644 --- a/modules/settings/assets/js/layouts/quota-bar.js +++ b/modules/settings/assets/js/layouts/quota-bar.js @@ -1,4 +1,4 @@ -import { CalendarDollarIcon } from '@elementor/icons'; +import CalendarDollarIcon from '@elementor/icons/CalendarDollarIcon'; import Box from '@elementor/ui/Box'; import IconButton from '@elementor/ui/IconButton'; import Skeleton from '@elementor/ui/Skeleton'; diff --git a/modules/settings/assets/js/layouts/sidebar.js b/modules/settings/assets/js/layouts/sidebar.js index 4307497f..fe364ef5 100644 --- a/modules/settings/assets/js/layouts/sidebar.js +++ b/modules/settings/assets/js/layouts/sidebar.js @@ -1,4 +1,4 @@ -import { ChevronLeftIcon } from '@elementor/icons'; +import ChevronLeftIcon from '@elementor/icons/ChevronLeftIcon'; import Box from '@elementor/ui/Box'; import Divider from '@elementor/ui/Divider'; import Drawer from '@elementor/ui/Drawer'; diff --git a/modules/settings/assets/js/layouts/top-bar-menu.js b/modules/settings/assets/js/layouts/top-bar-menu.js index 7bc68e0a..dbbab7ce 100644 --- a/modules/settings/assets/js/layouts/top-bar-menu.js +++ b/modules/settings/assets/js/layouts/top-bar-menu.js @@ -1,4 +1,6 @@ -import { HelpIcon, UserIcon, PointFilledIcon } from '@elementor/icons'; +import HelpIcon from '@elementor/icons/HelpIcon'; +import PointFilledIcon from '@elementor/icons/PointFilledIcon'; +import UserIcon from '@elementor/icons/UserIcon'; import Box from '@elementor/ui/Box'; import Button from '@elementor/ui/Button'; import Divider from '@elementor/ui/Divider'; diff --git a/modules/settings/assets/js/pages/accessibility-statement.js b/modules/settings/assets/js/pages/accessibility-statement.js index a97091b8..23a3ec5f 100644 --- a/modules/settings/assets/js/pages/accessibility-statement.js +++ b/modules/settings/assets/js/pages/accessibility-statement.js @@ -1,4 +1,4 @@ -import { ChevronLeftIcon } from '@elementor/icons'; +import ChevronLeftIcon from '@elementor/icons/ChevronLeftIcon'; import Box from '@elementor/ui/Box'; import FormControl from '@elementor/ui/FormControl'; import FormLabel from '@elementor/ui/FormLabel'; diff --git a/modules/settings/assets/js/pages/assistant/stats/index.js b/modules/settings/assets/js/pages/assistant/stats/index.js index c4a9e607..3a19b3f8 100644 --- a/modules/settings/assets/js/pages/assistant/stats/index.js +++ b/modules/settings/assets/js/pages/assistant/stats/index.js @@ -56,7 +56,7 @@ const StyledStatsContainer = styled(Box)` display: grid; grid-template-columns: 1fr 1fr 2fr; - grid-template-rows: 104px 176px; + grid-template-rows: 104px 206px; grid-column-gap: ${({ theme }) => theme.spacing(2)}; grid-row-gap: ${({ theme }) => theme.spacing(2)}; diff --git a/modules/settings/banners/bf-sale-2025-banner.php b/modules/settings/banners/bf-sale-2025-banner.php new file mode 100644 index 00000000..1d6edda9 --- /dev/null +++ b/modules/settings/banners/bf-sale-2025-banner.php @@ -0,0 +1,172 @@ += $sale_start_time && $now_time <= $sale_end_time; + } + + public static function user_viewed_banner(): bool { + return Pointers::is_dismissed( self::BANNER_POINTER_NAME ); + } + + /** + * Get banner markup + * @throws Throwable + */ + public static function get_banner( string $link ) { + if ( ! self::is_sale_time() || self::user_viewed_banner() ) { + return; + } + + $img = plugins_url( '/images/bf-2025-banner.png', __FILE__ ); + $url = admin_url( 'admin-ajax.php' ); + $nonce = wp_create_nonce( self::POINTER_NONCE_KEY ); + ?> + +

+
+ Elementor black friday sale banner + + + Get discount + + + +
+
+ + + + + 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 PGs*dY+kS>4`tpAOqrMXz literal 0 HcmV?d00001 diff --git a/modules/settings/module.php b/modules/settings/module.php index 7a5b4deb..4c1ab0da 100644 --- a/modules/settings/module.php +++ b/modules/settings/module.php @@ -19,6 +19,7 @@ }; use EA11y\Modules\Settings\Banners\Elementor_Birthday_Banner; use EA11y\Modules\Settings\Banners\Onboarding_Banner; +use EA11y\Modules\Settings\Banners\BF_Sale_2025_Banner; use EA11y\Modules\Settings\Classes\Settings; use EA11y\Modules\Widget\Module as WidgetModule; use EA11y\Modules\WhatsNew\Module as WhatsNewModule; @@ -49,6 +50,7 @@ public static function component_list(): array { public function render_app() { ?> +

diff --git a/package-lock.json b/package-lock.json index bb086466..5a94203e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { "name": "pojo-accessibility", - "version": "3.8.1", + "version": "3.9.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pojo-accessibility", - "version": "3.8.1", + "version": "3.9.0", "dependencies": { "@elementor/design-tokens": "^1.1.4", "@elementor/icons": "^1.46.0", - "@elementor/ui": "^1.36.5", + "@elementor/ui": "^1.36.14", "@emotion/cache": "^11.14.0", "@emotion/react": "^11.14.0", "@mui/x-charts": "^7.27.0", @@ -32,6 +32,7 @@ "html-react-parser": "^5.2.2", "husky": "^9.1.6", "mixpanel-browser": "^2.58.0", + "postcss": "^8.5.6", "prop-types": "^15.8.1", "react-colorful": "^5.6.1", "react-content-loader": "^7.0.2", @@ -1963,40 +1964,40 @@ "dev": true }, "node_modules/@cacheable/memoize": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@cacheable/memoize/-/memoize-2.0.1.tgz", - "integrity": "sha512-WBLH37SynkCa39S6IrTSMQF3Wdv4/51WxuU5TuCNEqZcLgLGHme8NUxRTcDIO8ZZFXlslWbh9BD3DllixgPg6Q==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@cacheable/memoize/-/memoize-2.0.3.tgz", + "integrity": "sha512-hl9wfQgpiydhQEIv7fkjEzTGE+tcosCXLKFDO707wYJ/78FVOlowb36djex5GdbSyeHnG62pomYLMuV/OT8Pbw==", "dev": true, "dependencies": { - "@cacheable/utils": "^2.0.1" + "@cacheable/utils": "^2.0.3" } }, "node_modules/@cacheable/memory": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.0.1.tgz", - "integrity": "sha512-Ufc7iQnRKFC8gjZVGOTOsMwM/vZtmsw3LafvctVXPm835ElgK3DpMe1U5i9sd6OieSkyJhXbAT2Q2FosXBBbAQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.0.3.tgz", + "integrity": "sha512-R3UKy/CKOyb1LZG/VRCTMcpiMDyLH7SH3JrraRdK6kf3GweWCOU3sgvE13W3TiDRbxnDKylzKJvhUAvWl9LQOA==", "dev": true, "dependencies": { - "@cacheable/memoize": "^2.0.1", - "@cacheable/utils": "^2.0.1", - "@keyv/bigmap": "^1.0.0", - "hookified": "^1.12.0", - "keyv": "^5.5.1" + "@cacheable/memoize": "^2.0.3", + "@cacheable/utils": "^2.0.3", + "@keyv/bigmap": "^1.0.2", + "hookified": "^1.12.1", + "keyv": "^5.5.3" } }, "node_modules/@cacheable/memory/node_modules/keyv": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.1.tgz", - "integrity": "sha512-eF3cHZ40bVsjdlRi/RvKAuB0+B61Q1xWvohnrJrnaQslM3h1n79IV+mc9EGag4nrA9ZOlNyr3TUzW5c8uy8vNA==", + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.3.tgz", + "integrity": "sha512-h0Un1ieD+HUrzBH6dJXhod3ifSghk5Hw/2Y4/KHBziPlZecrFyE9YOTPU6eOs0V9pYl8gOs86fkr/KN8lUX39A==", "dev": true, "dependencies": { "@keyv/serialize": "^1.1.1" } }, "node_modules/@cacheable/utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.0.1.tgz", - "integrity": "sha512-sxHjO6wKn4/0wHCFYbh6tljj+ciP9BKgyBi09NLsor3sN+nu/Rt3FwLw6bYp7bp8usHpmcwUozrB/u4RuSw/eg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-m7Rce68cMHlAUjvWBy9Ru1Nmw5gU0SjGGtQDdhpe6E0xnbcvrIY0Epy//JU1VYYBUTzrG9jvgmTauULGKzOkWA==", "dev": true }, "node_modules/@csstools/css-parser-algorithms": { @@ -2164,9 +2165,9 @@ } }, "node_modules/@elementor/ui": { - "version": "1.36.5", - "resolved": "https://registry.npmjs.org/@elementor/ui/-/ui-1.36.5.tgz", - "integrity": "sha512-QpnLrps5uC3Pago6JNXH67g0fhqYINKtetgMz2jZiRDzySLdZMXV5BUwLM4ak8wUkEE2e3IB0gEoZfMX5b6SNQ==", + "version": "1.36.14", + "resolved": "https://registry.npmjs.org/@elementor/ui/-/ui-1.36.14.tgz", + "integrity": "sha512-oC6wcme3O7ybmD3WeQizwTA5aPBqR86LeHJcfE8XJKOaaKU/jxpn+iT6H+ogR5KSgqyD7QveSUTHiwcmTpNXIA==", "dependencies": { "@dnd-kit/core": "6.3.1", "@dnd-kit/modifiers": "9.0.0", @@ -3801,15 +3802,15 @@ } }, "node_modules/@keyv/bigmap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.0.0.tgz", - "integrity": "sha512-N2UsRSXlWwbvYKdFVS7sKqj6oXGegELh+zr9VripWDc8grsq8KBNp8JHI+9AQuUEFiM1S7+tm6lLp/lmbBCqCw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.0.2.tgz", + "integrity": "sha512-KR03xkEZlAZNF4IxXgVXb+uNIVNvwdh8UwI0cnc7WI6a+aQcDp8GL80qVfeB4E5NpsKJzou5jU0r6yLSSbMOtA==", "dev": true, "dependencies": { - "hookified": "^1.10.0" + "hookified": "^1.12.1" }, "engines": { - "node": ">= 20" + "node": ">= 18" } }, "node_modules/@keyv/serialize": { @@ -5102,13 +5103,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.55.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.0.tgz", - "integrity": "sha512-04IXzPwHrW69XusN/SIdDdKZBzMfOT9UNT/YiJit/xpy2VuAoB8NHc8Aplb96zsWDddLnbkPL3TsmrS04ZU2xQ==", + "version": "1.55.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.1.tgz", + "integrity": "sha512-IVAh/nOJaw6W9g+RJVlIQJ6gSiER+ae6mKQ5CX1bERzQgbC1VSeBlwdvczT7pxb0GWiyrxH4TGKbMfDb4Sq/ig==", "dev": true, "peer": true, "dependencies": { - "playwright": "1.55.0" + "playwright": "1.55.1" }, "bin": { "playwright": "cli.js" @@ -7234,13 +7235,13 @@ } }, "node_modules/@wordpress/a11y": { - "version": "4.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/a11y/-/a11y-4.30.0.tgz", - "integrity": "sha512-FpKsURiJmlQXyNCYJXiPxdJLsfcVhDSvH4/+kjEsVLOYyqD+vcb9P/oYDmor4XNIIkMT0/Lna9OnwvwCZB8owQ==", + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/a11y/-/a11y-4.32.0.tgz", + "integrity": "sha512-FNoyQUO1wAf768MX2vMNNk1Il3bi/A7c1s9WKSaufwEZEViXjWeqqb9GO6stWkur4UP9MRcv8IpWoLXi1BePHA==", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/dom-ready": "^4.30.0", - "@wordpress/i18n": "^6.3.0" + "@wordpress/dom-ready": "^4.32.0", + "@wordpress/i18n": "^6.5.0" }, "engines": { "node": ">=18.12.0", @@ -7259,13 +7260,13 @@ } }, "node_modules/@wordpress/a11y/node_modules/@wordpress/i18n": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.3.0.tgz", - "integrity": "sha512-5Dw5JOWlF8JM0hOZnli7MbVshHi9wFPrBkB4CeeJm//arGGYYxw5IYaPJN5jnlMxFAVtCOaXgrxGJoC9mxQBng==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.5.0.tgz", + "integrity": "sha512-VJ4QwkgKaVBk2+u6NYTMJ4jc/fave0Q2DAmoTN1AoSaHnK1Yuq9qJtBHAdkLUo7bBpRORBTl8OFFJTFLxgc9eA==", "dependencies": { "@babel/runtime": "7.25.7", "@tannin/sprintf": "^1.3.2", - "@wordpress/hooks": "^4.30.0", + "@wordpress/hooks": "^4.32.0", "gettext-parser": "^1.3.1", "memize": "^2.1.0", "tannin": "^1.2.0" @@ -7279,13 +7280,13 @@ } }, "node_modules/@wordpress/api-fetch": { - "version": "7.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-7.30.0.tgz", - "integrity": "sha512-KwI+ENWAd350Y5qd/Ok1bbSplDP4HFf9E/yh8QnRkhtsx4xAmFP5prNkPXktqK00NGPU8rRcTKzMdtrgQ2M/pA==", + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-7.32.0.tgz", + "integrity": "sha512-kTufX1lhb7AG7J3KMoDOKO9IKWVwWemf/TqaqiRYNC06uxXPl/VPBJC6AzInirsNw0BZknssje+g7Fc6WbrBFA==", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/i18n": "^6.3.0", - "@wordpress/url": "^4.30.0" + "@wordpress/i18n": "^6.5.0", + "@wordpress/url": "^4.32.0" }, "engines": { "node": ">=18.12.0", @@ -7304,13 +7305,13 @@ } }, "node_modules/@wordpress/api-fetch/node_modules/@wordpress/i18n": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.3.0.tgz", - "integrity": "sha512-5Dw5JOWlF8JM0hOZnli7MbVshHi9wFPrBkB4CeeJm//arGGYYxw5IYaPJN5jnlMxFAVtCOaXgrxGJoC9mxQBng==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.5.0.tgz", + "integrity": "sha512-VJ4QwkgKaVBk2+u6NYTMJ4jc/fave0Q2DAmoTN1AoSaHnK1Yuq9qJtBHAdkLUo7bBpRORBTl8OFFJTFLxgc9eA==", "dependencies": { "@babel/runtime": "7.25.7", "@tannin/sprintf": "^1.3.2", - "@wordpress/hooks": "^4.30.0", + "@wordpress/hooks": "^4.32.0", "gettext-parser": "^1.3.1", "memize": "^2.1.0", "tannin": "^1.2.0" @@ -7324,9 +7325,9 @@ } }, "node_modules/@wordpress/autop": { - "version": "4.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/autop/-/autop-4.30.0.tgz", - "integrity": "sha512-0VGvgPdmbJDdVUgSYfwgM6aiBI/3G5bAtksQfue3HvNMMmZvsudopPgeOXH2S8jjkbbPLr3AIbx6lFSk4Autqw==", + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/autop/-/autop-4.32.0.tgz", + "integrity": "sha512-JD1JmCE2gEWBikF9zHCFb8j6Az6AdXeuZjg3Ewyk9vlAfU8bADRXEwVslFM0p8UD/TtK3Zzw7Rj1B0B4Lf8W6g==", "dependencies": { "@babel/runtime": "7.25.7" }, @@ -7347,9 +7348,9 @@ } }, "node_modules/@wordpress/babel-preset-default": { - "version": "8.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/babel-preset-default/-/babel-preset-default-8.30.0.tgz", - "integrity": "sha512-DUEAseIg3Xqa4MroaFQEob4TYTGJv0zKRLsDrLHAgQCTtC4PcvUqU0gM7JZjG3zo20G9R5YCBNzx1353qd1t7Q==", + "version": "8.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/babel-preset-default/-/babel-preset-default-8.32.0.tgz", + "integrity": "sha512-K6sbRuLpLQWDnIhg0FRWuHOV68BMsrPrNeMNt1TcFDOMCqozI/mnfwCbejfIO7ZPqJtbfucnh1OQ9EkMWPibew==", "dev": true, "dependencies": { "@babel/core": "7.25.7", @@ -7358,8 +7359,8 @@ "@babel/preset-env": "7.25.7", "@babel/preset-typescript": "7.25.7", "@babel/runtime": "7.25.7", - "@wordpress/browserslist-config": "^6.30.0", - "@wordpress/warning": "^3.30.0", + "@wordpress/browserslist-config": "^6.32.0", + "@wordpress/warning": "^3.32.0", "browserslist": "^4.21.10", "core-js": "^3.31.0", "react": "^18.3.0" @@ -7412,9 +7413,9 @@ } }, "node_modules/@wordpress/base-styles": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@wordpress/base-styles/-/base-styles-6.6.0.tgz", - "integrity": "sha512-72OqyskL5E8V7trJYt4xlNbOfgAtAFfpwa+8TR+wLwqIYxhFjsWmkZsT3aknE6cyfRAmKZgSUfEZr8zH6Skwyw==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@wordpress/base-styles/-/base-styles-6.8.0.tgz", + "integrity": "sha512-x3LCQ4DuIOg58LyQRZtI6shmNKCk2zuKGwIEMH7h7MMri/Q95ehR6Sub8dKiUL4AHktdlweouJwbHaqrXPkd0Q==", "dev": true, "engines": { "node": ">=18.12.0", @@ -7422,9 +7423,9 @@ } }, "node_modules/@wordpress/blob": { - "version": "4.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/blob/-/blob-4.30.0.tgz", - "integrity": "sha512-kiGJFVNM8snw8r45s5cAv+qDaxOdeMWpNRB0scBFiV09yTLJtjtk7tJxVz0FfUHCX5cBA7NhwaOf+m8O4T2xZQ==", + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/blob/-/blob-4.32.0.tgz", + "integrity": "sha512-LcQMY5Rj0OczNnBU+w+kvJJ9htsOChpoq7uMdTMqz7Oj8QmC+laIkpkX9Ds72Vkck1uaPDpFofw6wcC5KY3h+A==", "dependencies": { "@babel/runtime": "7.25.7" }, @@ -7529,9 +7530,9 @@ } }, "node_modules/@wordpress/block-serialization-default-parser": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/block-serialization-default-parser/-/block-serialization-default-parser-5.30.0.tgz", - "integrity": "sha512-7bCeSqlkzkisn4MsVpkpNM8OsM8Orkulkg2lL6TKH0bYnDGZLYFmz2Jv5PC2MfUf7g0F1lkXu078Rg11s8aQjQ==", + "version": "5.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/block-serialization-default-parser/-/block-serialization-default-parser-5.32.0.tgz", + "integrity": "sha512-Rim243Fc2snGskZiKuBgi25MJQ9u81ngdMo7w1VZ7r/uqd6KrRr8CC1sY8hwop7ritCeGMFTEDAiD6ARuycmNw==", "dependencies": { "@babel/runtime": "7.25.7" }, @@ -7604,9 +7605,9 @@ } }, "node_modules/@wordpress/browserslist-config": { - "version": "6.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-6.30.0.tgz", - "integrity": "sha512-CjirkPIkMf72VQcKmhmQZUJGHHFEt80ITZVgnxEtyswWA6QPRXIwFhQOAElmfhWg2wS6pCncyg6k7DfgYX3bOg==", + "version": "6.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-6.32.0.tgz", + "integrity": "sha512-8NemosyPrey1yswd6LH8vX9mcRF6Xmqt2mKUCspnmEX4KvayyezmtJPh2EQo3GfySVHGWePzb0yfMYEqDdsC0Q==", "dev": true, "engines": { "node": ">=18.12.0", @@ -7614,18 +7615,18 @@ } }, "node_modules/@wordpress/commands": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/commands/-/commands-1.30.0.tgz", - "integrity": "sha512-1ErowcNAfn0sC6/KkFQje/F+pR1PGsoSbtxk8fxg2ZbDiINZnEMy/sc5yin+7nVLiC4QncAKnwdysbqnBAHb5Q==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/commands/-/commands-1.32.0.tgz", + "integrity": "sha512-csVqkLoGw73i4plSMVx1t5pXFdFk8D9vJQJRzJWtdWiy8aMM+/1Yx3YetTAY/YD62mYOGk4FtkkhF/3ijaQUqQ==", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/components": "^30.3.0", - "@wordpress/data": "^10.30.0", - "@wordpress/element": "^6.30.0", - "@wordpress/i18n": "^6.3.0", - "@wordpress/icons": "^10.30.0", - "@wordpress/keyboard-shortcuts": "^5.30.0", - "@wordpress/private-apis": "^1.30.0", + "@wordpress/components": "^30.5.0", + "@wordpress/data": "^10.32.0", + "@wordpress/element": "^6.32.0", + "@wordpress/i18n": "^6.5.0", + "@wordpress/icons": "^10.32.0", + "@wordpress/keyboard-shortcuts": "^5.32.0", + "@wordpress/private-apis": "^1.32.0", "clsx": "^2.1.1", "cmdk": "^1.0.0" }, @@ -7667,9 +7668,9 @@ "integrity": "sha512-SaEcbgQscHtGJ1QL+ajgDTmmqU2f6T+00jZRcFlVHUW2Asivc84LNUev/UQFyu117AsdyrtI+qpwLvgjJXJxmw==" }, "node_modules/@wordpress/commands/node_modules/@wordpress/components": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-30.3.0.tgz", - "integrity": "sha512-tXe6ucxRThjMPYCk1ZZHAnL0MQqFpCq/PwME/ypBf5dOE3GHKMKXVTvgsJv5bHH6dbJoElkDWRm3ZwqwwQ5CVg==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-30.5.0.tgz", + "integrity": "sha512-LIu96PI14RpwABd5iDyTI8OxlDVEbDfX/6UUctTKsSqfJWTAynIL/K/JIHhxxI2wYbtut9yu8nugwFCPdconvA==", "dependencies": { "@ariakit/react": "^0.4.15", "@babel/runtime": "7.25.7", @@ -7683,23 +7684,23 @@ "@types/gradient-parser": "1.1.0", "@types/highlight-words-core": "1.2.1", "@use-gesture/react": "^10.3.1", - "@wordpress/a11y": "^4.30.0", - "@wordpress/compose": "^7.30.0", - "@wordpress/date": "^5.30.0", - "@wordpress/deprecated": "^4.30.0", - "@wordpress/dom": "^4.30.0", - "@wordpress/element": "^6.30.0", - "@wordpress/escape-html": "^3.30.0", - "@wordpress/hooks": "^4.30.0", - "@wordpress/html-entities": "^4.30.0", - "@wordpress/i18n": "^6.3.0", - "@wordpress/icons": "^10.30.0", - "@wordpress/is-shallow-equal": "^5.30.0", - "@wordpress/keycodes": "^4.30.0", - "@wordpress/primitives": "^4.30.0", - "@wordpress/private-apis": "^1.30.0", - "@wordpress/rich-text": "^7.30.0", - "@wordpress/warning": "^3.30.0", + "@wordpress/a11y": "^4.32.0", + "@wordpress/compose": "^7.32.0", + "@wordpress/date": "^5.32.0", + "@wordpress/deprecated": "^4.32.0", + "@wordpress/dom": "^4.32.0", + "@wordpress/element": "^6.32.0", + "@wordpress/escape-html": "^3.32.0", + "@wordpress/hooks": "^4.32.0", + "@wordpress/html-entities": "^4.32.0", + "@wordpress/i18n": "^6.5.0", + "@wordpress/icons": "^10.32.0", + "@wordpress/is-shallow-equal": "^5.32.0", + "@wordpress/keycodes": "^4.32.0", + "@wordpress/primitives": "^4.32.0", + "@wordpress/private-apis": "^1.32.0", + "@wordpress/rich-text": "^7.32.0", + "@wordpress/warning": "^3.32.0", "change-case": "^4.1.2", "clsx": "^2.1.1", "colord": "^2.7.0", @@ -7728,13 +7729,13 @@ } }, "node_modules/@wordpress/commands/node_modules/@wordpress/i18n": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.3.0.tgz", - "integrity": "sha512-5Dw5JOWlF8JM0hOZnli7MbVshHi9wFPrBkB4CeeJm//arGGYYxw5IYaPJN5jnlMxFAVtCOaXgrxGJoC9mxQBng==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.5.0.tgz", + "integrity": "sha512-VJ4QwkgKaVBk2+u6NYTMJ4jc/fave0Q2DAmoTN1AoSaHnK1Yuq9qJtBHAdkLUo7bBpRORBTl8OFFJTFLxgc9eA==", "dependencies": { "@babel/runtime": "7.25.7", "@tannin/sprintf": "^1.3.2", - "@wordpress/hooks": "^4.30.0", + "@wordpress/hooks": "^4.32.0", "gettext-parser": "^1.3.1", "memize": "^2.1.0", "tannin": "^1.2.0" @@ -7843,19 +7844,19 @@ } }, "node_modules/@wordpress/compose": { - "version": "7.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-7.30.0.tgz", - "integrity": "sha512-2OkdbhGiNWI4A8VXrawZcnqU8dSty3B3KvSLV1YK/TktW4qCGDNvp4W++NO5BY0d/zaSMgDUJryxyk3ejKTfzA==", + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-7.32.0.tgz", + "integrity": "sha512-y4StIlClJiijBHduZ6Bx0tfFarsNi6hc+mvPk2ENIfNNLHf0P90f97XjbvUUr0U1J92x7silHliQfdF0ygbFQg==", "dependencies": { "@babel/runtime": "7.25.7", "@types/mousetrap": "^1.6.8", - "@wordpress/deprecated": "^4.30.0", - "@wordpress/dom": "^4.30.0", - "@wordpress/element": "^6.30.0", - "@wordpress/is-shallow-equal": "^5.30.0", - "@wordpress/keycodes": "^4.30.0", - "@wordpress/priority-queue": "^3.30.0", - "@wordpress/undo-manager": "^1.30.0", + "@wordpress/deprecated": "^4.32.0", + "@wordpress/dom": "^4.32.0", + "@wordpress/element": "^6.32.0", + "@wordpress/is-shallow-equal": "^5.32.0", + "@wordpress/keycodes": "^4.32.0", + "@wordpress/priority-queue": "^3.32.0", + "@wordpress/undo-manager": "^1.32.0", "change-case": "^4.1.2", "clipboard": "^2.0.11", "mousetrap": "^1.6.5", @@ -7881,27 +7882,27 @@ } }, "node_modules/@wordpress/core-data": { - "version": "7.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/core-data/-/core-data-7.30.0.tgz", - "integrity": "sha512-mElhJ3Oj/W65bmZhpZP8d4T9pIKrdXwiVqsUQGbfdVyQxgFIarjB7AL8SyvMkqX0MzBghZL5rAoNfvMg8CTRvQ==", + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/core-data/-/core-data-7.32.0.tgz", + "integrity": "sha512-cHcEhoucu5YhPTpo2I+NVlxINPMTnh8cXcfrBmrqAnwFA5MttMhF8av9lnz6k3ieY9eB99rY+BDcE09+jkRl7w==", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "^7.30.0", - "@wordpress/block-editor": "^15.3.0", - "@wordpress/blocks": "^15.3.0", - "@wordpress/compose": "^7.30.0", - "@wordpress/data": "^10.30.0", - "@wordpress/deprecated": "^4.30.0", - "@wordpress/element": "^6.30.0", - "@wordpress/html-entities": "^4.30.0", - "@wordpress/i18n": "^6.3.0", - "@wordpress/is-shallow-equal": "^5.30.0", - "@wordpress/private-apis": "^1.30.0", - "@wordpress/rich-text": "^7.30.0", - "@wordpress/sync": "^1.30.0", - "@wordpress/undo-manager": "^1.30.0", - "@wordpress/url": "^4.30.0", - "@wordpress/warning": "^3.30.0", + "@wordpress/api-fetch": "^7.32.0", + "@wordpress/block-editor": "^15.5.0", + "@wordpress/blocks": "^15.5.0", + "@wordpress/compose": "^7.32.0", + "@wordpress/data": "^10.32.0", + "@wordpress/deprecated": "^4.32.0", + "@wordpress/element": "^6.32.0", + "@wordpress/html-entities": "^4.32.0", + "@wordpress/i18n": "^6.5.0", + "@wordpress/is-shallow-equal": "^5.32.0", + "@wordpress/private-apis": "^1.32.0", + "@wordpress/rich-text": "^7.32.0", + "@wordpress/sync": "^1.32.0", + "@wordpress/undo-manager": "^1.32.0", + "@wordpress/url": "^4.32.0", + "@wordpress/warning": "^3.32.0", "change-case": "^4.1.2", "equivalent-key-map": "^0.2.2", "fast-deep-equal": "^3.1.3", @@ -7946,46 +7947,46 @@ "integrity": "sha512-SaEcbgQscHtGJ1QL+ajgDTmmqU2f6T+00jZRcFlVHUW2Asivc84LNUev/UQFyu117AsdyrtI+qpwLvgjJXJxmw==" }, "node_modules/@wordpress/core-data/node_modules/@wordpress/block-editor": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/@wordpress/block-editor/-/block-editor-15.3.0.tgz", - "integrity": "sha512-10ZtT4QupAMQCAVEP6uA5nB+vI19WJzZuqGTpQtIDygcD/L8xQ+k5/yHVnhM3Q1W++lfQJ3wTsIS5XdAZYceIw==", + "version": "15.5.0", + "resolved": "https://registry.npmjs.org/@wordpress/block-editor/-/block-editor-15.5.0.tgz", + "integrity": "sha512-wyqhR7kE7vknEfxdHw5LIJrk7jR3I4WtO31TLKaIIK7gEaUS1eTAH6RNNu8UxJGhXP7PL7ogOb7q2bhd6eEmGA==", "dependencies": { "@babel/runtime": "7.25.7", "@emotion/react": "^11.7.1", "@emotion/styled": "^11.6.0", "@react-spring/web": "^9.4.5", - "@wordpress/a11y": "^4.30.0", - "@wordpress/api-fetch": "^7.30.0", - "@wordpress/blob": "^4.30.0", - "@wordpress/block-serialization-default-parser": "^5.30.0", - "@wordpress/blocks": "^15.3.0", - "@wordpress/commands": "^1.30.0", - "@wordpress/components": "^30.3.0", - "@wordpress/compose": "^7.30.0", - "@wordpress/data": "^10.30.0", - "@wordpress/date": "^5.30.0", - "@wordpress/deprecated": "^4.30.0", - "@wordpress/dom": "^4.30.0", - "@wordpress/element": "^6.30.0", - "@wordpress/escape-html": "^3.30.0", - "@wordpress/hooks": "^4.30.0", - "@wordpress/html-entities": "^4.30.0", - "@wordpress/i18n": "^6.3.0", - "@wordpress/icons": "^10.30.0", - "@wordpress/is-shallow-equal": "^5.30.0", - "@wordpress/keyboard-shortcuts": "^5.30.0", - "@wordpress/keycodes": "^4.30.0", - "@wordpress/notices": "^5.30.0", - "@wordpress/preferences": "^4.30.0", - "@wordpress/priority-queue": "^3.30.0", - "@wordpress/private-apis": "^1.30.0", - "@wordpress/rich-text": "^7.30.0", - "@wordpress/style-engine": "^2.30.0", - "@wordpress/token-list": "^3.30.0", - "@wordpress/upload-media": "^0.15.0", - "@wordpress/url": "^4.30.0", - "@wordpress/warning": "^3.30.0", - "@wordpress/wordcount": "^4.30.0", + "@wordpress/a11y": "^4.32.0", + "@wordpress/api-fetch": "^7.32.0", + "@wordpress/blob": "^4.32.0", + "@wordpress/block-serialization-default-parser": "^5.32.0", + "@wordpress/blocks": "^15.5.0", + "@wordpress/commands": "^1.32.0", + "@wordpress/components": "^30.5.0", + "@wordpress/compose": "^7.32.0", + "@wordpress/data": "^10.32.0", + "@wordpress/date": "^5.32.0", + "@wordpress/deprecated": "^4.32.0", + "@wordpress/dom": "^4.32.0", + "@wordpress/element": "^6.32.0", + "@wordpress/escape-html": "^3.32.0", + "@wordpress/hooks": "^4.32.0", + "@wordpress/html-entities": "^4.32.0", + "@wordpress/i18n": "^6.5.0", + "@wordpress/icons": "^10.32.0", + "@wordpress/is-shallow-equal": "^5.32.0", + "@wordpress/keyboard-shortcuts": "^5.32.0", + "@wordpress/keycodes": "^4.32.0", + "@wordpress/notices": "^5.32.0", + "@wordpress/preferences": "^4.32.0", + "@wordpress/priority-queue": "^3.32.0", + "@wordpress/private-apis": "^1.32.0", + "@wordpress/rich-text": "^7.32.0", + "@wordpress/style-engine": "^2.32.0", + "@wordpress/token-list": "^3.32.0", + "@wordpress/upload-media": "^0.17.0", + "@wordpress/url": "^4.32.0", + "@wordpress/warning": "^3.32.0", + "@wordpress/wordcount": "^4.32.0", "change-case": "^4.1.2", "clsx": "^2.1.1", "colord": "^2.7.0", @@ -8011,26 +8012,26 @@ } }, "node_modules/@wordpress/core-data/node_modules/@wordpress/blocks": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/@wordpress/blocks/-/blocks-15.3.0.tgz", - "integrity": "sha512-/ojVeDNhWzfA6fAKcF/CO1JFF1SiXcvcLemRrVq4oScC2EJh78Ft7PDbI+dnll+4u0wJnJ9+VUTuAKEBnmjioA==", + "version": "15.5.0", + "resolved": "https://registry.npmjs.org/@wordpress/blocks/-/blocks-15.5.0.tgz", + "integrity": "sha512-RxJGBtjgyjDd79H4TEbGfy6N6JU1KLMyWzgjzrmScoZghFOBeWI4qHvqud9pr01A3i9IGhQ72j0QvvgQdtWhhA==", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/autop": "^4.30.0", - "@wordpress/blob": "^4.30.0", - "@wordpress/block-serialization-default-parser": "^5.30.0", - "@wordpress/data": "^10.30.0", - "@wordpress/deprecated": "^4.30.0", - "@wordpress/dom": "^4.30.0", - "@wordpress/element": "^6.30.0", - "@wordpress/hooks": "^4.30.0", - "@wordpress/html-entities": "^4.30.0", - "@wordpress/i18n": "^6.3.0", - "@wordpress/is-shallow-equal": "^5.30.0", - "@wordpress/private-apis": "^1.30.0", - "@wordpress/rich-text": "^7.30.0", - "@wordpress/shortcode": "^4.30.0", - "@wordpress/warning": "^3.30.0", + "@wordpress/autop": "^4.32.0", + "@wordpress/blob": "^4.32.0", + "@wordpress/block-serialization-default-parser": "^5.32.0", + "@wordpress/data": "^10.32.0", + "@wordpress/deprecated": "^4.32.0", + "@wordpress/dom": "^4.32.0", + "@wordpress/element": "^6.32.0", + "@wordpress/hooks": "^4.32.0", + "@wordpress/html-entities": "^4.32.0", + "@wordpress/i18n": "^6.5.0", + "@wordpress/is-shallow-equal": "^5.32.0", + "@wordpress/private-apis": "^1.32.0", + "@wordpress/rich-text": "^7.32.0", + "@wordpress/shortcode": "^4.32.0", + "@wordpress/warning": "^3.32.0", "change-case": "^4.1.2", "colord": "^2.7.0", "fast-deep-equal": "^3.1.3", @@ -8052,9 +8053,9 @@ } }, "node_modules/@wordpress/core-data/node_modules/@wordpress/components": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-30.3.0.tgz", - "integrity": "sha512-tXe6ucxRThjMPYCk1ZZHAnL0MQqFpCq/PwME/ypBf5dOE3GHKMKXVTvgsJv5bHH6dbJoElkDWRm3ZwqwwQ5CVg==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-30.5.0.tgz", + "integrity": "sha512-LIu96PI14RpwABd5iDyTI8OxlDVEbDfX/6UUctTKsSqfJWTAynIL/K/JIHhxxI2wYbtut9yu8nugwFCPdconvA==", "dependencies": { "@ariakit/react": "^0.4.15", "@babel/runtime": "7.25.7", @@ -8068,23 +8069,23 @@ "@types/gradient-parser": "1.1.0", "@types/highlight-words-core": "1.2.1", "@use-gesture/react": "^10.3.1", - "@wordpress/a11y": "^4.30.0", - "@wordpress/compose": "^7.30.0", - "@wordpress/date": "^5.30.0", - "@wordpress/deprecated": "^4.30.0", - "@wordpress/dom": "^4.30.0", - "@wordpress/element": "^6.30.0", - "@wordpress/escape-html": "^3.30.0", - "@wordpress/hooks": "^4.30.0", - "@wordpress/html-entities": "^4.30.0", - "@wordpress/i18n": "^6.3.0", - "@wordpress/icons": "^10.30.0", - "@wordpress/is-shallow-equal": "^5.30.0", - "@wordpress/keycodes": "^4.30.0", - "@wordpress/primitives": "^4.30.0", - "@wordpress/private-apis": "^1.30.0", - "@wordpress/rich-text": "^7.30.0", - "@wordpress/warning": "^3.30.0", + "@wordpress/a11y": "^4.32.0", + "@wordpress/compose": "^7.32.0", + "@wordpress/date": "^5.32.0", + "@wordpress/deprecated": "^4.32.0", + "@wordpress/dom": "^4.32.0", + "@wordpress/element": "^6.32.0", + "@wordpress/escape-html": "^3.32.0", + "@wordpress/hooks": "^4.32.0", + "@wordpress/html-entities": "^4.32.0", + "@wordpress/i18n": "^6.5.0", + "@wordpress/icons": "^10.32.0", + "@wordpress/is-shallow-equal": "^5.32.0", + "@wordpress/keycodes": "^4.32.0", + "@wordpress/primitives": "^4.32.0", + "@wordpress/private-apis": "^1.32.0", + "@wordpress/rich-text": "^7.32.0", + "@wordpress/warning": "^3.32.0", "change-case": "^4.1.2", "clsx": "^2.1.1", "colord": "^2.7.0", @@ -8113,13 +8114,13 @@ } }, "node_modules/@wordpress/core-data/node_modules/@wordpress/i18n": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.3.0.tgz", - "integrity": "sha512-5Dw5JOWlF8JM0hOZnli7MbVshHi9wFPrBkB4CeeJm//arGGYYxw5IYaPJN5jnlMxFAVtCOaXgrxGJoC9mxQBng==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.5.0.tgz", + "integrity": "sha512-VJ4QwkgKaVBk2+u6NYTMJ4jc/fave0Q2DAmoTN1AoSaHnK1Yuq9qJtBHAdkLUo7bBpRORBTl8OFFJTFLxgc9eA==", "dependencies": { "@babel/runtime": "7.25.7", "@tannin/sprintf": "^1.3.2", - "@wordpress/hooks": "^4.30.0", + "@wordpress/hooks": "^4.32.0", "gettext-parser": "^1.3.1", "memize": "^2.1.0", "tannin": "^1.2.0" @@ -8133,20 +8134,20 @@ } }, "node_modules/@wordpress/core-data/node_modules/@wordpress/upload-media": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@wordpress/upload-media/-/upload-media-0.15.0.tgz", - "integrity": "sha512-bIQKxGczbI3DMam1+lLuQiQdP+quen1OATIoVJzW1v/pYfcumZUueOQTmkhFPB/8RpxnGVyFJonTyqMWg49vjw==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@wordpress/upload-media/-/upload-media-0.17.0.tgz", + "integrity": "sha512-AyU/AQOIwjgWiCcI6K36up87RNXSjvIac8oqEgd/U29Srk7aD51griD43tbRB1T3xy9TMoyASiBrAgT1w33lHQ==", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "^7.30.0", - "@wordpress/blob": "^4.30.0", - "@wordpress/compose": "^7.30.0", - "@wordpress/data": "^10.30.0", - "@wordpress/element": "^6.30.0", - "@wordpress/i18n": "^6.3.0", - "@wordpress/preferences": "^4.30.0", - "@wordpress/private-apis": "^1.30.0", - "@wordpress/url": "^4.30.0", + "@wordpress/api-fetch": "^7.32.0", + "@wordpress/blob": "^4.32.0", + "@wordpress/compose": "^7.32.0", + "@wordpress/data": "^10.32.0", + "@wordpress/element": "^6.32.0", + "@wordpress/i18n": "^6.5.0", + "@wordpress/preferences": "^4.32.0", + "@wordpress/private-apis": "^1.32.0", + "@wordpress/url": "^4.32.0", "uuid": "^9.0.1" }, "engines": { @@ -8175,18 +8176,18 @@ } }, "node_modules/@wordpress/data": { - "version": "10.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-10.30.0.tgz", - "integrity": "sha512-lHvL78H6JI56sFVyLRuk+fJTIQ7XKiy14d0/kH2rWStmqKDQnniUXM4kVT3MUUOKtL0Mh59CVoiA9J4dlxJ6Ww==", + "version": "10.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-10.32.0.tgz", + "integrity": "sha512-7lReC2/qVxlQVYVlqIYfZ9Irbzo6W30iuiD67xaXqVxiD9BA8CePY2dTBpCsykBkczoT0ryerVp648SvY82R9Q==", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/compose": "^7.30.0", - "@wordpress/deprecated": "^4.30.0", - "@wordpress/element": "^6.30.0", - "@wordpress/is-shallow-equal": "^5.30.0", - "@wordpress/priority-queue": "^3.30.0", - "@wordpress/private-apis": "^1.30.0", - "@wordpress/redux-routine": "^5.30.0", + "@wordpress/compose": "^7.32.0", + "@wordpress/deprecated": "^4.32.0", + "@wordpress/element": "^6.32.0", + "@wordpress/is-shallow-equal": "^5.32.0", + "@wordpress/priority-queue": "^3.32.0", + "@wordpress/private-apis": "^1.32.0", + "@wordpress/redux-routine": "^5.32.0", "deepmerge": "^4.3.0", "equivalent-key-map": "^0.2.2", "is-plain-object": "^5.0.0", @@ -8215,12 +8216,12 @@ } }, "node_modules/@wordpress/date": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/date/-/date-5.30.0.tgz", - "integrity": "sha512-btCp3y33ykgZbOkbbbjK+PXXFNo2k3QJhh45e3NQpdSAQV4sFpsqhzRRu58ou/AeBdZRXMIzbhTpT2KcKUcIgw==", + "version": "5.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/date/-/date-5.32.0.tgz", + "integrity": "sha512-hWmsDHzzmhbWAwWzBM042eItGor1up9tV0nEvjn1qdERoI/MS3+78d8vF40sMdWnXLNcPTCR+JHjD6kqJVmuXQ==", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/deprecated": "^4.30.0", + "@wordpress/deprecated": "^4.32.0", "moment": "^2.29.4", "moment-timezone": "^0.5.40" }, @@ -8241,9 +8242,9 @@ } }, "node_modules/@wordpress/dependency-extraction-webpack-plugin": { - "version": "6.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-6.30.0.tgz", - "integrity": "sha512-sms4yRJriS8vzlwbYHII/xqI64oSY5ALbfQy6HJBSCfLJCNxVOzC/2fCrhdV9ghd8nK3NMAJKhTCe09oMPCnIw==", + "version": "6.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-6.32.0.tgz", + "integrity": "sha512-05XLzLx+cTXgid6/qI6XzBclNvkLIi9F0oLAn4htL1ExIXLfc9yBSNcNkVVP7Tr5pmpMq0NUxfM58jqxxmFfCQ==", "dev": true, "dependencies": { "json2php": "^0.0.7" @@ -8263,12 +8264,12 @@ "dev": true }, "node_modules/@wordpress/deprecated": { - "version": "4.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-4.30.0.tgz", - "integrity": "sha512-QfzUOCANmcrdsViEpdqh4eD4OTkG2ZGpWDuA7lSJvnVB4rcupzHpDsukmKtz6gr/AZnBDXYBoHLUwZ44G4bx0g==", + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-4.32.0.tgz", + "integrity": "sha512-HfHXUWfe/lyXTvJLWjpMJ90+XzmC2l/9vcp05n2tD+nsxwF5nS0Hjf+38pQtFPBcw3d1bbzMTNahDjtNBLvKTQ==", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/hooks": "^4.30.0" + "@wordpress/hooks": "^4.32.0" }, "engines": { "node": ">=18.12.0", @@ -8287,12 +8288,12 @@ } }, "node_modules/@wordpress/dom": { - "version": "4.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-4.30.0.tgz", - "integrity": "sha512-t+Q4ahm0joocIle1Wb9BpwykXjItk3xsAfIj8yNiCehBRC8z0b9uJHZLzcvs5iP4iDfRooDejAHDeNY26yPP2Q==", + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-4.32.0.tgz", + "integrity": "sha512-TphAq3bE34R5O0qW2q1SSBGdqfjTtHQSxzjKc0ufvTJf1nVZkJpCOqAP0Bue48AwfFYQSagdD3RgqYjcPPEMYg==", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/deprecated": "^4.30.0" + "@wordpress/deprecated": "^4.32.0" }, "engines": { "node": ">=18.12.0", @@ -8300,9 +8301,9 @@ } }, "node_modules/@wordpress/dom-ready": { - "version": "4.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/dom-ready/-/dom-ready-4.30.0.tgz", - "integrity": "sha512-hD5mU0KkgNN4DzBDbBLfRKfZyja4CePK+nRlVTFn8Yo3dGdMAlOjJGaJS1szz7XflDv/ekngj72KoqvJGrrPmg==", + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/dom-ready/-/dom-ready-4.32.0.tgz", + "integrity": "sha512-Ru+gF3J37wiz33yqVoSmwPmc5afvGyujxyLvkGI0N4Y6EBMUmEJbC6QUbTOVld8RANQ0Bqu1btXMZfFYEY9PIQ==", "dependencies": { "@babel/runtime": "7.25.7" }, @@ -8334,9 +8335,9 @@ } }, "node_modules/@wordpress/e2e-test-utils-playwright": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-1.30.0.tgz", - "integrity": "sha512-KN/q6359nlb+zh/eamQD0gBgi1616Px7v+03+Hz8HqKUPKozUab1ogxr6Ew751LCYGuh204eG7ImYVM6Aqta0Q==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-1.32.0.tgz", + "integrity": "sha512-w1hE7xDB06SQDfwtbggyU2xTyQ++GyU2wwEc648LL6OYLh7wlDuEzP/7XCrYbxA/GPGFDKCOL3uhU163F2N/Dw==", "dev": true, "dependencies": { "change-case": "^4.1.2", @@ -8355,14 +8356,14 @@ } }, "node_modules/@wordpress/element": { - "version": "6.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-6.30.0.tgz", - "integrity": "sha512-hPbnaPcnD1pbLqCmrjNnuFuAIAmyB8JPmD2GpywOXnWaHbtEVX0GaC6iPQSIHShnPqoUBESXXwS3Z8KFz8MBHA==", + "version": "6.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-6.32.0.tgz", + "integrity": "sha512-W/Bw6HXzRBJgYYUdoUBUvtjXNWh8dVK8aqFsqpnEJTAiXdU8Ii0wBQ+E49bI/08yGCwsaXrLbQLXqtAiV6leMw==", "dependencies": { "@babel/runtime": "7.25.7", "@types/react": "^18.2.79", "@types/react-dom": "^18.2.25", - "@wordpress/escape-html": "^3.30.0", + "@wordpress/escape-html": "^3.32.0", "change-case": "^4.1.2", "is-plain-object": "^5.0.0", "react": "^18.3.0", @@ -8412,9 +8413,9 @@ } }, "node_modules/@wordpress/escape-html": { - "version": "3.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-3.30.0.tgz", - "integrity": "sha512-qlyyPKiize/uEyqL1nRMce9GuReZyOum81tOTR14YAngvyRIGz3K3hGwWWf9VrITZrjwuZgqMJc2udSC1uKmKg==", + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-3.32.0.tgz", + "integrity": "sha512-pT5wZmg9ob/u8RuSXgfZv8Kfd8zpvtBcCdcFE/UHasjtxJSecxDHFb0uI4eXQrSiTrsthbDZDlK/GIAagmt75Q==", "dependencies": { "@babel/runtime": "7.25.7" }, @@ -8490,9 +8491,9 @@ } }, "node_modules/@wordpress/hooks": { - "version": "4.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-4.30.0.tgz", - "integrity": "sha512-uo3H4KneQSVxFU98jvbw1jN3aYQ/1fPTGqDXZh4NyZx5qras1hE3QhtQU0cO2A8o5/ZNbk/Oek06TnhwsXQPBQ==", + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-4.32.0.tgz", + "integrity": "sha512-aXCLsuOQJiVJDrVKV4MjGYeU2Nv8+pg2KSAzANs7OGXIl714Q968t5qODJiJ6ADsng3FnQ0pATVYBGBTGlW6Gg==", "dependencies": { "@babel/runtime": "7.25.7" }, @@ -8513,9 +8514,9 @@ } }, "node_modules/@wordpress/html-entities": { - "version": "4.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/html-entities/-/html-entities-4.30.0.tgz", - "integrity": "sha512-SlJ9HM3LYO5cDzLHAP5UytbnI3u2N770JwJkLK1mfCoEIjxa+3cl9faI2ClJp1DvIzGCE4f5LliHdit/D+pARg==", + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/html-entities/-/html-entities-4.32.0.tgz", + "integrity": "sha512-IHHxBeMIQR7/+Fq27eWEzOuBi10guTRBNVZUrdk32ZyJL2ISpVYwMiHOwKBH6J/67ayBSon23gUEWBqUE6bC9g==", "dependencies": { "@babel/runtime": "7.25.7" }, @@ -8567,13 +8568,13 @@ } }, "node_modules/@wordpress/icons": { - "version": "10.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-10.30.0.tgz", - "integrity": "sha512-PT3VGWafx/+XnlftBK8uyrm8XSsFyOJPOnl6v87Om2/I5ihv5+UeCwMLyZP2j2axdSzhcRtkEyBnN7yiOcgBvA==", + "version": "10.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-10.32.0.tgz", + "integrity": "sha512-1WvJdT361X1LnetYBpBWUjAVXZzl+pBdIwHbYRAp8ej47EI/igPmNxmq81nFd40s8fer/9qtipielcqSI6H2rA==", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/element": "^6.30.0", - "@wordpress/primitives": "^4.30.0" + "@wordpress/element": "^6.32.0", + "@wordpress/primitives": "^4.32.0" }, "engines": { "node": ">=18.12.0", @@ -8592,9 +8593,9 @@ } }, "node_modules/@wordpress/is-shallow-equal": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-5.30.0.tgz", - "integrity": "sha512-Q9xoIrxxsNuiaftBciAsHmUU3IVa7V1Es0pzKmwDa3ZFBFeBn3dgrY6iWzeQ4Fe4pBaiG3NagH0qE5Iz3PuG2g==", + "version": "5.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-5.32.0.tgz", + "integrity": "sha512-YabJ43zv30CU8kPhTrWQZhlatwO/fBo78/HvEU40CSGCRf+j9XKu9ZUidj3xDKgzLEkDCOKmj0vUY0+NyBbKzA==", "dependencies": { "@babel/runtime": "7.25.7" }, @@ -8615,9 +8616,9 @@ } }, "node_modules/@wordpress/jest-console": { - "version": "8.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/jest-console/-/jest-console-8.30.0.tgz", - "integrity": "sha512-Vw7iBqsueb9jCy6RnnjixjLosm+fMi+3iMQDiBB5Pw/yUpr0PUBCR11oQCE/nO3wDl7OOA5Nq3v2qo/wxLBLMg==", + "version": "8.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/jest-console/-/jest-console-8.32.0.tgz", + "integrity": "sha512-Sq4Ss0gJNmLzpJ9K/5Nto/FTV5L7ALvmdfA7b8JpmuKh32CETN4uIGDSfqS2a5Pfcg4KepwnL6jF9790uM54QA==", "dev": true, "dependencies": { "@babel/runtime": "7.25.7", @@ -8644,12 +8645,12 @@ } }, "node_modules/@wordpress/jest-preset-default": { - "version": "12.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/jest-preset-default/-/jest-preset-default-12.30.0.tgz", - "integrity": "sha512-nxldSo9luQBfjIFF08hcVT1pEVABe213qBxYWCXMCx3+SPsENRF6pU/yoRb0i7nz6yrd/j5oD92EXXnbQoN8eA==", + "version": "12.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/jest-preset-default/-/jest-preset-default-12.32.0.tgz", + "integrity": "sha512-Y8X+0KCjUiB1MxxwridiY0Ku3iJVOTJSsutSReu+rM/hS/1azlBMctC0bqUyC0mQyBqPF0SNlpHjYdsAoVEznQ==", "dev": true, "dependencies": { - "@wordpress/jest-console": "^8.30.0", + "@wordpress/jest-console": "^8.32.0", "babel-jest": "29.7.0" }, "engines": { @@ -8662,14 +8663,14 @@ } }, "node_modules/@wordpress/keyboard-shortcuts": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/keyboard-shortcuts/-/keyboard-shortcuts-5.30.0.tgz", - "integrity": "sha512-X1LGeoqyWFH1lGXUjJ+QUXEBnf5+ty3iO9a5MQecCpSF94ZajPAcN/17LnaftftqF81PantfG2TkXNQ7HydnTg==", + "version": "5.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/keyboard-shortcuts/-/keyboard-shortcuts-5.32.0.tgz", + "integrity": "sha512-s33V1wNmlXTaUmVC8NOX06a7vU4UUuiwqaDeKZO6V3Y9U28b8NAr2tJeY8oFfctPX5aRWeOaYfAXqTMI4oU9KA==", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/data": "^10.30.0", - "@wordpress/element": "^6.30.0", - "@wordpress/keycodes": "^4.30.0" + "@wordpress/data": "^10.32.0", + "@wordpress/element": "^6.32.0", + "@wordpress/keycodes": "^4.32.0" }, "engines": { "node": ">=18.12.0", @@ -8691,12 +8692,12 @@ } }, "node_modules/@wordpress/keycodes": { - "version": "4.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-4.30.0.tgz", - "integrity": "sha512-IoC7FPADDc5UfjasKRT8YtaLr9WUBDqb8GJpYtiuYjHpDBDgJJPnJwKUMBweCjMl93Zzf7JAHpF7LAkICHDE1A==", + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-4.32.0.tgz", + "integrity": "sha512-XzSc3uT+viVCdycT2W6/wu+d8NZaS2y0sdHZbPXIJ6hEbyyG7ncG+XDFhXckFggqXuajxkPTEJDwOtrSTxLYqw==", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/i18n": "^6.3.0" + "@wordpress/i18n": "^6.5.0" }, "engines": { "node": ">=18.12.0", @@ -8715,13 +8716,13 @@ } }, "node_modules/@wordpress/keycodes/node_modules/@wordpress/i18n": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.3.0.tgz", - "integrity": "sha512-5Dw5JOWlF8JM0hOZnli7MbVshHi9wFPrBkB4CeeJm//arGGYYxw5IYaPJN5jnlMxFAVtCOaXgrxGJoC9mxQBng==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.5.0.tgz", + "integrity": "sha512-VJ4QwkgKaVBk2+u6NYTMJ4jc/fave0Q2DAmoTN1AoSaHnK1Yuq9qJtBHAdkLUo7bBpRORBTl8OFFJTFLxgc9eA==", "dependencies": { "@babel/runtime": "7.25.7", "@tannin/sprintf": "^1.3.2", - "@wordpress/hooks": "^4.30.0", + "@wordpress/hooks": "^4.32.0", "gettext-parser": "^1.3.1", "memize": "^2.1.0", "tannin": "^1.2.0" @@ -8735,16 +8736,16 @@ } }, "node_modules/@wordpress/media-utils": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/media-utils/-/media-utils-5.30.0.tgz", - "integrity": "sha512-2wehODR/rrtr9gnBxpcAwrV0aulYlckOV0lGwnASaZZn0vcxMFiy6GADfqQp/UI0hsOk8c/EOwQmnum5GTqVmA==", + "version": "5.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/media-utils/-/media-utils-5.32.0.tgz", + "integrity": "sha512-tDlOxyLSKbAZrVgQ92bp6MQHHD7gxTyfE8QmYjft55UaZDXOGo9Q5aLsQ+5PoU+ZuTwImvOpt0EBiO5I/PWa4A==", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "^7.30.0", - "@wordpress/blob": "^4.30.0", - "@wordpress/element": "^6.30.0", - "@wordpress/i18n": "^6.3.0", - "@wordpress/private-apis": "^1.30.0" + "@wordpress/api-fetch": "^7.32.0", + "@wordpress/blob": "^4.32.0", + "@wordpress/element": "^6.32.0", + "@wordpress/i18n": "^6.5.0", + "@wordpress/private-apis": "^1.32.0" }, "engines": { "node": ">=18.12.0", @@ -8763,13 +8764,13 @@ } }, "node_modules/@wordpress/media-utils/node_modules/@wordpress/i18n": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.3.0.tgz", - "integrity": "sha512-5Dw5JOWlF8JM0hOZnli7MbVshHi9wFPrBkB4CeeJm//arGGYYxw5IYaPJN5jnlMxFAVtCOaXgrxGJoC9mxQBng==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.5.0.tgz", + "integrity": "sha512-VJ4QwkgKaVBk2+u6NYTMJ4jc/fave0Q2DAmoTN1AoSaHnK1Yuq9qJtBHAdkLUo7bBpRORBTl8OFFJTFLxgc9eA==", "dependencies": { "@babel/runtime": "7.25.7", "@tannin/sprintf": "^1.3.2", - "@wordpress/hooks": "^4.30.0", + "@wordpress/hooks": "^4.32.0", "gettext-parser": "^1.3.1", "memize": "^2.1.0", "tannin": "^1.2.0" @@ -8783,13 +8784,13 @@ } }, "node_modules/@wordpress/notices": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/notices/-/notices-5.30.0.tgz", - "integrity": "sha512-iEIBi7r+3dFPPOXE76WKP6f1Y9gVRr+jqca6LK3erGABvETVOYhrtFVoq0aAyDcpONc1c80TvFdO5OudCepafQ==", + "version": "5.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/notices/-/notices-5.32.0.tgz", + "integrity": "sha512-iyjAtp/UJUT46zKpBi/oX3iR8y1P7W/VqsvTitGUUeZIH9yBwaKgk5mroTABjgIgqUcC7p64i5cOGi9c23L5kA==", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "^4.30.0", - "@wordpress/data": "^10.30.0" + "@wordpress/a11y": "^4.32.0", + "@wordpress/data": "^10.32.0" }, "engines": { "node": ">=18.12.0", @@ -8811,9 +8812,9 @@ } }, "node_modules/@wordpress/npm-package-json-lint-config": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/npm-package-json-lint-config/-/npm-package-json-lint-config-5.30.0.tgz", - "integrity": "sha512-pJ5XMmj2Osk05/TFrCpf6l6VuxWDU837ISKW1bXAmBfpnLTlUuVUl6pTWbIE4gNZqb2faSBQSN3OPeQqt4RNJg==", + "version": "5.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/npm-package-json-lint-config/-/npm-package-json-lint-config-5.32.0.tgz", + "integrity": "sha512-X3D9g0m0OIM6FfdHfmyGENOZG53kc+i49WuHnDnC1gZWV5Ai0SD/CNfRcPZOYw7Ld9CScrM3kqXAKG7nd1fGFg==", "dev": true, "engines": { "node": ">=18.12.0", @@ -8824,12 +8825,12 @@ } }, "node_modules/@wordpress/postcss-plugins-preset": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/postcss-plugins-preset/-/postcss-plugins-preset-5.30.0.tgz", - "integrity": "sha512-3mB+tqN9uIQ6h1SXtQUFowNeCv/Cy6vWsUBhPkgU3hj7LQrGbCAmMWefDAQeYHyG0lQfSrAD6jctZ2wZmNXwsQ==", + "version": "5.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/postcss-plugins-preset/-/postcss-plugins-preset-5.32.0.tgz", + "integrity": "sha512-cJGgp8Z3o8dKfUWQZtQ/gNceYwLQET1z7RqkOAaSI5O0lelGit8gqpWeA7qYyZb9K1kWwhz8GGVWUIsQhCo2sQ==", "dev": true, "dependencies": { - "@wordpress/base-styles": "^6.6.0", + "@wordpress/base-styles": "^6.8.0", "autoprefixer": "^10.4.20" }, "engines": { @@ -8841,20 +8842,20 @@ } }, "node_modules/@wordpress/preferences": { - "version": "4.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/preferences/-/preferences-4.30.0.tgz", - "integrity": "sha512-QzBm0pjBbDH6GufTAPYsi5c4CfJnefS7u7AsRFRVCy0hQScXu+q/hTPrGWg5nsV1xuwLR0KGwlF2p/bX1JUnYg==", + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/preferences/-/preferences-4.32.0.tgz", + "integrity": "sha512-K2umcScb2efx028aFR9mbjW+p88kWp7C3CmdHOL00Gl8p4Hcl8N3eGUlYxs5CEaS0cCcwzL1eG9axL1TiskwOw==", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "^4.30.0", - "@wordpress/components": "^30.3.0", - "@wordpress/compose": "^7.30.0", - "@wordpress/data": "^10.30.0", - "@wordpress/deprecated": "^4.30.0", - "@wordpress/element": "^6.30.0", - "@wordpress/i18n": "^6.3.0", - "@wordpress/icons": "^10.30.0", - "@wordpress/private-apis": "^1.30.0", + "@wordpress/a11y": "^4.32.0", + "@wordpress/components": "^30.5.0", + "@wordpress/compose": "^7.32.0", + "@wordpress/data": "^10.32.0", + "@wordpress/deprecated": "^4.32.0", + "@wordpress/element": "^6.32.0", + "@wordpress/i18n": "^6.5.0", + "@wordpress/icons": "^10.32.0", + "@wordpress/private-apis": "^1.32.0", "clsx": "^2.1.1" }, "engines": { @@ -8895,9 +8896,9 @@ "integrity": "sha512-SaEcbgQscHtGJ1QL+ajgDTmmqU2f6T+00jZRcFlVHUW2Asivc84LNUev/UQFyu117AsdyrtI+qpwLvgjJXJxmw==" }, "node_modules/@wordpress/preferences/node_modules/@wordpress/components": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-30.3.0.tgz", - "integrity": "sha512-tXe6ucxRThjMPYCk1ZZHAnL0MQqFpCq/PwME/ypBf5dOE3GHKMKXVTvgsJv5bHH6dbJoElkDWRm3ZwqwwQ5CVg==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-30.5.0.tgz", + "integrity": "sha512-LIu96PI14RpwABd5iDyTI8OxlDVEbDfX/6UUctTKsSqfJWTAynIL/K/JIHhxxI2wYbtut9yu8nugwFCPdconvA==", "dependencies": { "@ariakit/react": "^0.4.15", "@babel/runtime": "7.25.7", @@ -8911,23 +8912,23 @@ "@types/gradient-parser": "1.1.0", "@types/highlight-words-core": "1.2.1", "@use-gesture/react": "^10.3.1", - "@wordpress/a11y": "^4.30.0", - "@wordpress/compose": "^7.30.0", - "@wordpress/date": "^5.30.0", - "@wordpress/deprecated": "^4.30.0", - "@wordpress/dom": "^4.30.0", - "@wordpress/element": "^6.30.0", - "@wordpress/escape-html": "^3.30.0", - "@wordpress/hooks": "^4.30.0", - "@wordpress/html-entities": "^4.30.0", - "@wordpress/i18n": "^6.3.0", - "@wordpress/icons": "^10.30.0", - "@wordpress/is-shallow-equal": "^5.30.0", - "@wordpress/keycodes": "^4.30.0", - "@wordpress/primitives": "^4.30.0", - "@wordpress/private-apis": "^1.30.0", - "@wordpress/rich-text": "^7.30.0", - "@wordpress/warning": "^3.30.0", + "@wordpress/a11y": "^4.32.0", + "@wordpress/compose": "^7.32.0", + "@wordpress/date": "^5.32.0", + "@wordpress/deprecated": "^4.32.0", + "@wordpress/dom": "^4.32.0", + "@wordpress/element": "^6.32.0", + "@wordpress/escape-html": "^3.32.0", + "@wordpress/hooks": "^4.32.0", + "@wordpress/html-entities": "^4.32.0", + "@wordpress/i18n": "^6.5.0", + "@wordpress/icons": "^10.32.0", + "@wordpress/is-shallow-equal": "^5.32.0", + "@wordpress/keycodes": "^4.32.0", + "@wordpress/primitives": "^4.32.0", + "@wordpress/private-apis": "^1.32.0", + "@wordpress/rich-text": "^7.32.0", + "@wordpress/warning": "^3.32.0", "change-case": "^4.1.2", "clsx": "^2.1.1", "colord": "^2.7.0", @@ -8956,13 +8957,13 @@ } }, "node_modules/@wordpress/preferences/node_modules/@wordpress/i18n": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.3.0.tgz", - "integrity": "sha512-5Dw5JOWlF8JM0hOZnli7MbVshHi9wFPrBkB4CeeJm//arGGYYxw5IYaPJN5jnlMxFAVtCOaXgrxGJoC9mxQBng==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.5.0.tgz", + "integrity": "sha512-VJ4QwkgKaVBk2+u6NYTMJ4jc/fave0Q2DAmoTN1AoSaHnK1Yuq9qJtBHAdkLUo7bBpRORBTl8OFFJTFLxgc9eA==", "dependencies": { "@babel/runtime": "7.25.7", "@tannin/sprintf": "^1.3.2", - "@wordpress/hooks": "^4.30.0", + "@wordpress/hooks": "^4.32.0", "gettext-parser": "^1.3.1", "memize": "^2.1.0", "tannin": "^1.2.0" @@ -8992,9 +8993,9 @@ } }, "node_modules/@wordpress/prettier-config": { - "version": "4.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/prettier-config/-/prettier-config-4.30.0.tgz", - "integrity": "sha512-b0tOy/H0A1ilsjAGUKqMJ3idMQbe1XS7K2ViqG62ZMJRUYBEZ1x3t+ne3Z2fVbyNVhrMqq3eZK9BSEuxr67cSg==", + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/prettier-config/-/prettier-config-4.32.0.tgz", + "integrity": "sha512-fS4Z5hXewajLKxbMQY4rdq3fSpUnwHHfLWqOJHXlu3u4rjqVf0VMJsLsSsSM8tV78rU6x6dWIEugG6CnDygYjA==", "dev": true, "engines": { "node": ">=18.12.0", @@ -9005,12 +9006,12 @@ } }, "node_modules/@wordpress/primitives": { - "version": "4.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-4.30.0.tgz", - "integrity": "sha512-xKstIDGv0dG35X9kX65ZPCtAe4s06gUqJVbOf3oFD9b5PXoL2vjC3/kc5GG2jO56pLahKPLoqitH9maCJcGQEQ==", + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-4.32.0.tgz", + "integrity": "sha512-pf46CU5qQaGOULlAMNQTi+Jkwf1vwfrGYmkRtuTP68/Y8yOI19v5JZg/Vwq5nCHOs/L750mX1wMp4WvGWoPhFg==", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/element": "^6.30.0", + "@wordpress/element": "^6.32.0", "clsx": "^2.1.1" }, "engines": { @@ -9041,9 +9042,9 @@ } }, "node_modules/@wordpress/priority-queue": { - "version": "3.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/priority-queue/-/priority-queue-3.30.0.tgz", - "integrity": "sha512-XScxqCjlLnijcZdoon9tUpBqw7BDBqNZQT1pgLcy0Pe661hDDvhsQ7fSapZ/KjrNnecE1uuxKQXQBXDNoJA0Eg==", + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/priority-queue/-/priority-queue-3.32.0.tgz", + "integrity": "sha512-LXlkiXxRSv35FBvjfAqn+rHH7KF4mw2wVl57SVzWglZAUdfvrcjrinRlEsqgMZxeAVeLPiutRV1qlkueZl7E8w==", "dependencies": { "@babel/runtime": "7.25.7", "requestidlecallback": "^0.3.0" @@ -9065,9 +9066,9 @@ } }, "node_modules/@wordpress/private-apis": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-1.30.0.tgz", - "integrity": "sha512-0EaWB+JxJ4t3Bylzk3xXu7Khqi0TOXYX9qDOm9W0Wy+J6xGzlkz8rSF73MvT+6JQTNNwietWHqkkHcn6dFGIXQ==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-1.32.0.tgz", + "integrity": "sha512-xmc+U8tve6QmGKiYTwVutkPkqqJkvB2fvrimjMkw8TGpnzBcmSlCtwIoLwJOBesD6liDdRFtBPpf6PM0jIRcIA==", "dependencies": { "@babel/runtime": "7.25.7" }, @@ -9088,9 +9089,9 @@ } }, "node_modules/@wordpress/redux-routine": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/redux-routine/-/redux-routine-5.30.0.tgz", - "integrity": "sha512-1cabT7u4pryjnsAe5uG6hV1Rwhh1HC+VIMz41wuMQCZ3Te1xYIjj4uWAf+u1H2Vrcz6an0pijh4OLXq/S/AB5A==", + "version": "5.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/redux-routine/-/redux-routine-5.32.0.tgz", + "integrity": "sha512-DE/UCpBF7PxznAOOlf2/Tq1aQKvqU07aaFhgGaCBd7sBn6QtBjA5SvtOvKcpr+09awdcS1AeLl2DdPGnRUZkog==", "dependencies": { "@babel/runtime": "7.25.7", "is-plain-object": "^5.0.0", @@ -9117,19 +9118,19 @@ } }, "node_modules/@wordpress/rich-text": { - "version": "7.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/rich-text/-/rich-text-7.30.0.tgz", - "integrity": "sha512-2aQRtxWslGd7FUqKbBHFHuVefwi3jnFp5WWJD/gxSVGEWJ3i48XuLtXLGK6V9XiutuoUbG+8C1Bx2lq3WaPMXg==", + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/rich-text/-/rich-text-7.32.0.tgz", + "integrity": "sha512-CUiKYCkuoAqfyMPkw4HRcWcK8bKdOCfMiHUfZvAaapjWB6qGhSsL4MRlmQV8IGtbHj5tUVkBAzTm5V5tIV2/yQ==", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "^4.30.0", - "@wordpress/compose": "^7.30.0", - "@wordpress/data": "^10.30.0", - "@wordpress/deprecated": "^4.30.0", - "@wordpress/element": "^6.30.0", - "@wordpress/escape-html": "^3.30.0", - "@wordpress/i18n": "^6.3.0", - "@wordpress/keycodes": "^4.30.0", + "@wordpress/a11y": "^4.32.0", + "@wordpress/compose": "^7.32.0", + "@wordpress/data": "^10.32.0", + "@wordpress/deprecated": "^4.32.0", + "@wordpress/element": "^6.32.0", + "@wordpress/escape-html": "^3.32.0", + "@wordpress/i18n": "^6.5.0", + "@wordpress/keycodes": "^4.32.0", "colord": "2.9.3", "memize": "^2.1.0" }, @@ -9153,13 +9154,13 @@ } }, "node_modules/@wordpress/rich-text/node_modules/@wordpress/i18n": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.3.0.tgz", - "integrity": "sha512-5Dw5JOWlF8JM0hOZnli7MbVshHi9wFPrBkB4CeeJm//arGGYYxw5IYaPJN5jnlMxFAVtCOaXgrxGJoC9mxQBng==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.5.0.tgz", + "integrity": "sha512-VJ4QwkgKaVBk2+u6NYTMJ4jc/fave0Q2DAmoTN1AoSaHnK1Yuq9qJtBHAdkLUo7bBpRORBTl8OFFJTFLxgc9eA==", "dependencies": { "@babel/runtime": "7.25.7", "@tannin/sprintf": "^1.3.2", - "@wordpress/hooks": "^4.30.0", + "@wordpress/hooks": "^4.32.0", "gettext-parser": "^1.3.1", "memize": "^2.1.0", "tannin": "^1.2.0" @@ -9173,24 +9174,24 @@ } }, "node_modules/@wordpress/scripts": { - "version": "30.23.0", - "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-30.23.0.tgz", - "integrity": "sha512-IVMv4GvSxZQuj/JybHMHA0BbScv2//tELUQSHMe7IHRxvaqzd8WDJgMfnwaqQWOmtw8d4739w7kAEo26kh6zWA==", + "version": "30.25.0", + "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-30.25.0.tgz", + "integrity": "sha512-sUL6R4aYzdvrg8V7W49RxLsLavv7ZuQ4/6gIb/ZCYZDwhzT4feqdMXyViwmlyaQMIxTMJy93zD+7jFrUGy07Sw==", "dev": true, "dependencies": { "@babel/core": "7.25.7", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", "@svgr/webpack": "^8.0.1", - "@wordpress/babel-preset-default": "^8.30.0", - "@wordpress/browserslist-config": "^6.30.0", - "@wordpress/dependency-extraction-webpack-plugin": "^6.30.0", - "@wordpress/e2e-test-utils-playwright": "^1.30.0", - "@wordpress/eslint-plugin": "^22.16.0", - "@wordpress/jest-preset-default": "^12.30.0", - "@wordpress/npm-package-json-lint-config": "^5.30.0", - "@wordpress/postcss-plugins-preset": "^5.30.0", - "@wordpress/prettier-config": "^4.30.0", - "@wordpress/stylelint-config": "^23.22.0", + "@wordpress/babel-preset-default": "^8.32.0", + "@wordpress/browserslist-config": "^6.32.0", + "@wordpress/dependency-extraction-webpack-plugin": "^6.32.0", + "@wordpress/e2e-test-utils-playwright": "^1.32.0", + "@wordpress/eslint-plugin": "^22.18.0", + "@wordpress/jest-preset-default": "^12.32.0", + "@wordpress/npm-package-json-lint-config": "^5.32.0", + "@wordpress/postcss-plugins-preset": "^5.32.0", + "@wordpress/prettier-config": "^4.32.0", + "@wordpress/stylelint-config": "^23.24.0", "adm-zip": "^0.5.9", "babel-jest": "29.7.0", "babel-loader": "9.2.1", @@ -9288,16 +9289,16 @@ } }, "node_modules/@wordpress/scripts/node_modules/@wordpress/eslint-plugin": { - "version": "22.16.0", - "resolved": "https://registry.npmjs.org/@wordpress/eslint-plugin/-/eslint-plugin-22.16.0.tgz", - "integrity": "sha512-1z3rXq2uanCY0m2D1BgimeNGxZOZy87VPwzKRjaf2aPLw/ezoQckiaVGAKYKhbHLt6HFP2EkdKfuD3pmbTJ57g==", + "version": "22.18.0", + "resolved": "https://registry.npmjs.org/@wordpress/eslint-plugin/-/eslint-plugin-22.18.0.tgz", + "integrity": "sha512-1w4bhax+vg3xFDXs/z2jNRaCO/kagpK0ZZ6PGWH5Anlp+e9MuzQHMbcU6Xpd/+ZU118z0rJeXDvjb3QgEfvEOg==", "dev": true, "dependencies": { "@babel/eslint-parser": "7.25.7", "@typescript-eslint/eslint-plugin": "^6.4.1", "@typescript-eslint/parser": "^6.4.1", - "@wordpress/babel-preset-default": "^8.30.0", - "@wordpress/prettier-config": "^4.30.0", + "@wordpress/babel-preset-default": "^8.32.0", + "@wordpress/prettier-config": "^4.32.0", "cosmiconfig": "^7.0.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.25.2", @@ -9359,9 +9360,9 @@ } }, "node_modules/@wordpress/shortcode": { - "version": "4.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/shortcode/-/shortcode-4.30.0.tgz", - "integrity": "sha512-x7bw8NX+4tw3XauuvkM4s8SvgYVF81FiaS31YmFQ3J+g8oKokx6x+7WnrO4HHELTUNbUw6RSI/cfFSw+Xua/pg==", + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/shortcode/-/shortcode-4.32.0.tgz", + "integrity": "sha512-z8es//Y7eMk8hDjeIo1PJ4auXZ0JN87n/CC6GD3uW5wgOvpRqiLjT5QpxCBqfKzOisUnNkYbrEjeChpgOT1gMw==", "dependencies": { "@babel/runtime": "7.25.7", "memize": "^2.0.1" @@ -9383,9 +9384,9 @@ } }, "node_modules/@wordpress/style-engine": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/style-engine/-/style-engine-2.30.0.tgz", - "integrity": "sha512-YpMuMx76x2IhYjOZbljVgxJEgX/FduBJleMsMMvciPGCBsvcjfPxqliwGaauofnk7/kN4E7LFyT1o9cErqiw3g==", + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/style-engine/-/style-engine-2.32.0.tgz", + "integrity": "sha512-qjXu4Dncch1UL4AUC3zJAOCj8mRy4+kAt+Rz3OoZV4vSsccUXKutxSCc12HGYS9IO+cjhj9yiWrSpGWCdMD/Sw==", "dependencies": { "@babel/runtime": "7.25.7", "change-case": "^4.1.2" @@ -9407,9 +9408,9 @@ } }, "node_modules/@wordpress/stylelint-config": { - "version": "23.22.0", - "resolved": "https://registry.npmjs.org/@wordpress/stylelint-config/-/stylelint-config-23.22.0.tgz", - "integrity": "sha512-d1aEVn6jbMFFJh3SqpGKoNsnm0DcYD6TwgzLLlIL11kslyFEn6mfiKJVeVNIgWQe7sBDEtUkE7h4qEOSDbxO8A==", + "version": "23.24.0", + "resolved": "https://registry.npmjs.org/@wordpress/stylelint-config/-/stylelint-config-23.24.0.tgz", + "integrity": "sha512-qF0zRv/fLK29/jEjQnY0xy1hrP5Cv6ReG10QkVAtSlaBvu07Hu+RWNIVrSznwlrbzzP+2JM61lO2oAeX/3LD+g==", "dev": true, "dependencies": { "@stylistic/stylelint-plugin": "^3.0.1", @@ -9426,13 +9427,13 @@ } }, "node_modules/@wordpress/sync": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/sync/-/sync-1.30.0.tgz", - "integrity": "sha512-gbCnWHsrgGA4G2rdCTldrkauFBeB1kZCj6Rvzpf97FVX2xiagt3/LP5u8tW96qBB5qsJhx/uJ1Wv0uhuiBX/tg==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/sync/-/sync-1.32.0.tgz", + "integrity": "sha512-yKahpmYRm1VbXvU/6M2eXiQgMWl45VCcxDgMO+Y/2Wh1C/HyVZdgsNpg5c2ikXUolyKRKd7vuSMIn+agSceHTw==", "dependencies": { "@babel/runtime": "7.25.7", "@types/simple-peer": "^9.11.5", - "@wordpress/url": "^4.30.0", + "@wordpress/url": "^4.32.0", "import-locals": "^2.0.0", "lib0": "^0.2.42", "simple-peer": "^9.11.0", @@ -9458,9 +9459,9 @@ } }, "node_modules/@wordpress/token-list": { - "version": "3.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/token-list/-/token-list-3.30.0.tgz", - "integrity": "sha512-zK4MJd2y7bvzuyMhGPeiErbwZDvXjQwodkTwUM/KiJVgfXjR98O2YBc70ZGOrhpzPeIb81PZljwiNTIx7NI6dA==", + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/token-list/-/token-list-3.32.0.tgz", + "integrity": "sha512-FgA06HzE0dESSXAQIgzKfLa9Z2Qv1Gv4Blcskyko7wNTcQfPq53oByvCiA0aMFh/LmKHNIa2FR1vn3Y3ck/Ewg==", "dependencies": { "@babel/runtime": "7.25.7" }, @@ -9481,12 +9482,12 @@ } }, "node_modules/@wordpress/undo-manager": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/undo-manager/-/undo-manager-1.30.0.tgz", - "integrity": "sha512-pbvkzMLJrozAoaB+KOVSiRFvKvfxDrSt0jWmWCDnobbDRsmrSJRQaANw4RcoNShHuMOzd1wJ1KruK4Dtypf5MQ==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/undo-manager/-/undo-manager-1.32.0.tgz", + "integrity": "sha512-pEnsf9zvk61ijX28wmJ7HM2Xb2Dbdg80feF0QVFAsngFiS34r9/K1JE+y56OdyYY20ZGPbmbHLpIHX0ghjhpoQ==", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/is-shallow-equal": "^5.30.0" + "@wordpress/is-shallow-equal": "^5.32.0" }, "engines": { "node": ">=18.12.0", @@ -9542,9 +9543,9 @@ } }, "node_modules/@wordpress/url": { - "version": "4.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-4.30.0.tgz", - "integrity": "sha512-oFiQQBy/2G9fct1Df52ypu3d2nYiq2k2GkOdfMHRFYI1N3k0JP7rinwyePU+37mHPCzYuJK6TuMDxoKh8oMpKg==", + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-4.32.0.tgz", + "integrity": "sha512-B7Q6sKzBqSLUdQiW8oL69LFuky/IRrXDbBhPpOJruwV4l6eH6UhTlnY4QZYi1Ke91c/VJZRjUKx1fNWPJx5d9w==", "dependencies": { "@babel/runtime": "7.25.7", "remove-accents": "^0.5.0" @@ -9566,18 +9567,18 @@ } }, "node_modules/@wordpress/warning": { - "version": "3.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-3.30.0.tgz", - "integrity": "sha512-ZtkpSe3DhtUzIrwf+5slGkJJCxy1xn56fZ6atUaJWRbjsKnIZlTcPgahPUJZ2bugsGS5BlmDEuVI8C4NUdbwvQ==", + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-3.32.0.tgz", + "integrity": "sha512-6dPNKfJAOXijIMi9k/QdS/IQvHXcl5ErNM10y5dIhhLDuGmsZlQER06VrVmQIVAkbsmL49OfrqkqMOQidp61JA==", "engines": { "node": ">=18.12.0", "npm": ">=8.19.2" } }, "node_modules/@wordpress/wordcount": { - "version": "4.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/wordcount/-/wordcount-4.30.0.tgz", - "integrity": "sha512-WGALjfdw0/Cl8BiJiTCM9hlGRmSErubqM9IWSKkcHNwxFV5FoOdZOzWB6xxMhxR4u9TL0QXCDkR9p6DoJEzBYw==", + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/wordcount/-/wordcount-4.32.0.tgz", + "integrity": "sha512-bVxNPejXkKo7s2HE2kkN5K1WqnkFp9yGRe40MNuq9/iLS0hXKqjMo3Ae0Gq2LQLbgVC/VSDXqB8B+vjFRZt3CQ==", "dependencies": { "@babel/runtime": "7.25.7" }, @@ -10741,16 +10742,16 @@ } }, "node_modules/cacheable": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.0.1.tgz", - "integrity": "sha512-MSKxcybpxB5kcWKpj+1tPBm2os4qKKGxDovsZmLhZmWIDYp8EgtC45C5zk1fLe1IC9PpI4ZE4eyryQH0N10PKA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.0.3.tgz", + "integrity": "sha512-nZF80J3d8RMrroMSYm1E9pBllVDXWPuECZgEZxH+vusCY4MAXAJVrY0jutcHSgh3xYX3G2EUNnmtWGZVVjWCXw==", "dev": true, "dependencies": { - "@cacheable/memoize": "^2.0.1", - "@cacheable/memory": "^2.0.1", - "@cacheable/utils": "^2.0.1", - "hookified": "^1.12.0", - "keyv": "^5.5.1" + "@cacheable/memoize": "^2.0.3", + "@cacheable/memory": "^2.0.3", + "@cacheable/utils": "^2.0.3", + "hookified": "^1.12.1", + "keyv": "^5.5.3" } }, "node_modules/cacheable-lookup": { @@ -10781,9 +10782,9 @@ } }, "node_modules/cacheable/node_modules/keyv": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.1.tgz", - "integrity": "sha512-eF3cHZ40bVsjdlRi/RvKAuB0+B61Q1xWvohnrJrnaQslM3h1n79IV+mc9EGag4nrA9ZOlNyr3TUzW5c8uy8vNA==", + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.3.tgz", + "integrity": "sha512-h0Un1ieD+HUrzBH6dJXhod3ifSghk5Hw/2Y4/KHBziPlZecrFyE9YOTPU6eOs0V9pYl8gOs86fkr/KN8lUX39A==", "dev": true, "dependencies": { "@keyv/serialize": "^1.1.1" @@ -11041,9 +11042,9 @@ } }, "node_modules/chrome-launcher": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.2.0.tgz", - "integrity": "sha512-JbuGuBNss258bvGil7FT4HKdC3SC2K7UAEUqiPy3ACS3Yxo3hAW6bvFpCu2HsIJLgTqxgEX6BkujvzZfLpUD0Q==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.2.1.tgz", + "integrity": "sha512-qmFR5PLMzHyuNJHwOloHPAHhbaNglkfeV/xDtt5b7xiFFyU1I+AZZX0PYseMuhenJSSirgxELYIbswcoc+5H4A==", "dev": true, "dependencies": { "@types/node": "*", @@ -15881,9 +15882,9 @@ } }, "node_modules/hookified": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.12.0.tgz", - "integrity": "sha512-hMr1Y9TCLshScrBbV2QxJ9BROddxZ12MX9KsCtuGGy/3SmmN5H1PllKerrVlSotur9dlE8hmUKAOSa3WDzsZmQ==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.12.1.tgz", + "integrity": "sha512-xnKGl+iMIlhrZmGHB729MqlmPoWBznctSQTYCpFKqNsCgimJQmithcW0xSQMMFzYnV2iKUh25alswn6epgxS0Q==", "dev": true }, "node_modules/hosted-git-info": { @@ -16304,9 +16305,9 @@ } }, "node_modules/import-in-the-middle": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.14.2.tgz", - "integrity": "sha512-5tCuY9BV8ujfOpwtAGgsTx9CGUapcFMEEyByLv1B+v2+6DhAcw+Zr0nhQT7uwaZ7DiourxFEscghOR8e1aPLQw==", + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.14.4.tgz", + "integrity": "sha512-eWjxh735SJLFJJDs5X82JQ2405OdJeAHDBnaoFCfdr5GVc7AWc9xU7KbrF+3Xd5F2ccP1aQFKtY+65X6EfKZ7A==", "dev": true, "dependencies": { "acorn": "^8.14.0", @@ -18156,17 +18157,17 @@ } }, "node_modules/lighthouse/node_modules/puppeteer-core": { - "version": "24.21.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.21.0.tgz", - "integrity": "sha512-WR4FehOs4XJ8OSp7MkGyVB4mfMs9Q6t8Y48TxiTCRxc8G2lJ5OKYPJvgU80dtKl+aIqIbdcNTgIooY49S5SsmA==", + "version": "24.23.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.23.0.tgz", + "integrity": "sha512-yl25C59gb14sOdIiSnJ08XiPP+O2RjuyZmEG+RjYmCXO7au0jcLf7fRiyii96dXGUBW7Zwei/mVKfxMx/POeFw==", "dev": true, "dependencies": { "@puppeteer/browsers": "2.10.10", - "chromium-bidi": "8.0.0", + "chromium-bidi": "9.1.0", "debug": "^4.4.3", - "devtools-protocol": "0.0.1495869", + "devtools-protocol": "0.0.1508733", "typed-query-selector": "^2.12.0", - "webdriver-bidi-protocol": "0.2.11", + "webdriver-bidi-protocol": "0.3.6", "ws": "^8.18.3" }, "engines": { @@ -18174,9 +18175,9 @@ } }, "node_modules/lighthouse/node_modules/puppeteer-core/node_modules/chromium-bidi": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-8.0.0.tgz", - "integrity": "sha512-d1VmE0FD7lxZQHzcDUCKZSNRtRwISXDsdg4HjdTR5+Ll5nQ/vzU12JeNmupD6VWffrPSlrnGhEWlLESKH3VO+g==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-9.1.0.tgz", + "integrity": "sha512-rlUzQ4WzIAWdIbY/viPShhZU2n21CxDUgazXVbw4Hu1MwaeUSEksSeM6DqPgpRjCLXRk702AVRxJxoOz0dw4OA==", "dev": true, "dependencies": { "mitt": "^3.0.1", @@ -18187,9 +18188,9 @@ } }, "node_modules/lighthouse/node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.1495869", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1495869.tgz", - "integrity": "sha512-i+bkd9UYFis40RcnkW7XrOprCujXRAHg62IVh/Ah3G8MmNXpCGt1m0dTFhSdx/AVs8XEMbdOGRwdkR1Bcta8AA==", + "version": "0.0.1508733", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1508733.tgz", + "integrity": "sha512-QJ1R5gtck6nDcdM+nlsaJXcelPEI7ZxSMw1ujHpO1c4+9l+Nue5qlebi9xO1Z2MGr92bFOQTW7/rrheh5hHxDg==", "dev": true }, "node_modules/lighthouse/node_modules/puppeteer-core/node_modules/ws": { @@ -20283,13 +20284,13 @@ } }, "node_modules/playwright": { - "version": "1.55.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.0.tgz", - "integrity": "sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA==", + "version": "1.55.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.1.tgz", + "integrity": "sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==", "dev": true, "peer": true, "dependencies": { - "playwright-core": "1.55.0" + "playwright-core": "1.55.1" }, "bin": { "playwright": "cli.js" @@ -20302,9 +20303,9 @@ } }, "node_modules/playwright-core": { - "version": "1.55.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.0.tgz", - "integrity": "sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==", + "version": "1.55.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.1.tgz", + "integrity": "sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==", "dev": true, "peer": true, "bin": { @@ -21451,9 +21452,9 @@ } }, "node_modules/react-day-picker": { - "version": "9.10.0", - "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.10.0.tgz", - "integrity": "sha512-tedecLSd+fpSN+J08601MaMsf122nxtqZXxB6lwX37qFoLtuPNuRJN8ylxFjLhyJS1kaLfAqL1GUkSLd2BMrpQ==", + "version": "9.11.0", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.11.0.tgz", + "integrity": "sha512-L4FYOaPrr3+AEROeP6IG2mCORZZfxJDkJI2df8mv1jyPrNYeccgmFPZDaHyAuPCBCddQFozkxbikj2NhMEYfDQ==", "dependencies": { "@date-fns/tz": "^1.4.1", "date-fns": "^4.1.0", @@ -23809,9 +23810,9 @@ } }, "node_modules/stylelint": { - "version": "16.24.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.24.0.tgz", - "integrity": "sha512-7ksgz3zJaSbTUGr/ujMXvLVKdDhLbGl3R/3arNudH7z88+XZZGNLMTepsY28WlnvEFcuOmUe7fg40Q3lfhOfSQ==", + "version": "16.25.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.25.0.tgz", + "integrity": "sha512-Li0avYWV4nfv1zPbdnxLYBGq4z8DVZxbRgx4Kn6V+Uftz1rMoF1qiEI3oL4kgWqyYgCgs7gT5maHNZ82Gk03vQ==", "dev": true, "funding": [ { @@ -23828,13 +23829,13 @@ "@csstools/css-tokenizer": "^3.0.4", "@csstools/media-query-list-parser": "^4.0.3", "@csstools/selector-specificity": "^5.0.0", - "@dual-bundle/import-meta-resolve": "^4.1.0", + "@dual-bundle/import-meta-resolve": "^4.2.1", "balanced-match": "^2.0.0", "colord": "^2.9.3", "cosmiconfig": "^9.0.0", "css-functions-list": "^3.2.3", "css-tree": "^3.1.0", - "debug": "^4.4.1", + "debug": "^4.4.3", "fast-glob": "^3.3.3", "fastest-levenshtein": "^1.0.16", "file-entry-cache": "^10.1.4", @@ -24055,12 +24056,12 @@ } }, "node_modules/stylelint/node_modules/flat-cache": { - "version": "6.1.14", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.14.tgz", - "integrity": "sha512-ExZSCSV9e7v/Zt7RzCbX57lY2dnPdxzU/h3UE6WJ6NtEMfwBd8jmi1n4otDEUfz+T/R+zxrFDpICFdjhD3H/zw==", + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.17.tgz", + "integrity": "sha512-Jzse4YoiUJBVYTwz5Bwl4h/2VQM7e2KK3MVAMlXzX9uamIHAH/TXUlRKU1AQGQOryQhN0EsmufiiF40G057YXA==", "dev": true, "dependencies": { - "cacheable": "^2.0.1", + "cacheable": "^2.0.3", "flatted": "^3.3.3", "hookified": "^1.12.0" } @@ -24374,9 +24375,9 @@ } }, "node_modules/tar-fs": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.0.tgz", - "integrity": "sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", + "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", "dev": true, "dependencies": { "pump": "^3.0.0", @@ -24608,18 +24609,18 @@ "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" }, "node_modules/tldts-core": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.14.tgz", - "integrity": "sha512-viZGNK6+NdluOJWwTO9olaugx0bkKhscIdriQQ+lNNhwitIKvb+SvhbYgnCz6j9p7dX3cJntt4agQAKMXLjJ5g==", + "version": "7.0.16", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.16.tgz", + "integrity": "sha512-XHhPmHxphLi+LGbH0G/O7dmUH9V65OY20R7vH8gETHsp5AZCjBk9l8sqmRKLaGOxnETU7XNSDUPtewAy/K6jbA==", "dev": true }, "node_modules/tldts-icann": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/tldts-icann/-/tldts-icann-7.0.14.tgz", - "integrity": "sha512-UZPOb6KYSFBH8QD6QkK1FH2WGM7opD0s0KGafoHA4UnOQirBHtNs45kOfgy0E5a/a9fx8ukPS4E+nl7Dp3oYAw==", + "version": "7.0.16", + "resolved": "https://registry.npmjs.org/tldts-icann/-/tldts-icann-7.0.16.tgz", + "integrity": "sha512-WS/pPasPs2cx6orcxCcIz01SlG3dwYlgjLAnQt7vLAusTuTLqdI8zmkqbM8TWYEf3Z0o1S4BzM3oSRFPk/6WnA==", "dev": true, "dependencies": { - "tldts-core": "^7.0.14" + "tldts-core": "^7.0.16" } }, "node_modules/tmpl": { @@ -25396,9 +25397,9 @@ "dev": true }, "node_modules/webdriver-bidi-protocol": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.2.11.tgz", - "integrity": "sha512-Y9E1/oi4XMxcR8AT0ZC4OvYntl34SPgwjmELH+owjBr0korAX4jKgZULBWILGCVGdVCQ0dodTToIETozhG8zvA==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.3.6.tgz", + "integrity": "sha512-mlGndEOA9yK9YAbvtxaPTqdi/kaCWYYfwrZvGzcmkr/3lWM+tQj53BxtpVd6qbC6+E5OnHXgCcAhre6AkXzxjA==", "dev": true }, "node_modules/webidl-conversions": { diff --git a/package.json b/package.json index 11bdbb3b..d7be4439 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "slug": "pojo-accessibility", "homepage": "http://pojo.me/", "description": "", - "version": "3.8.1", + "version": "3.9.0", "scripts": { "build": "NODE_ENV=production wp-scripts build", "start": "NODE_ENV=development wp-scripts start", @@ -41,7 +41,7 @@ "dependencies": { "@elementor/design-tokens": "^1.1.4", "@elementor/icons": "^1.46.0", - "@elementor/ui": "^1.36.5", + "@elementor/ui": "^1.36.14", "@emotion/cache": "^11.14.0", "@emotion/react": "^11.14.0", "@mui/x-charts": "^7.27.0", @@ -63,6 +63,7 @@ "html-react-parser": "^5.2.2", "husky": "^9.1.6", "mixpanel-browser": "^2.58.0", + "postcss": "^8.5.6", "prop-types": "^15.8.1", "react-colorful": "^5.6.1", "react-content-loader": "^7.0.2", diff --git a/pojo-accessibility.php b/pojo-accessibility.php index c5ac49d1..2098ce4b 100644 --- a/pojo-accessibility.php +++ b/pojo-accessibility.php @@ -5,7 +5,7 @@ * Description: Improve your website’s accessibility with ease. Customize capabilities such as text resizing, contrast modes, link highlights, and easily generate an accessibility statement to demonstrate your commitment to inclusivity. * Author: Elementor.com * Author URI: https://elementor.com/ - * Version: 3.8.1 + * Version: 3.9.0 * Text Domain: pojo-accessibility * Domain Path: /languages/ */ @@ -15,7 +15,7 @@ // Legacy define( 'POJO_A11Y_CUSTOMIZER_OPTIONS', 'pojo_a11y_customizer_options' ); -define( 'EA11Y_VERSION', '3.8.1' ); +define( 'EA11Y_VERSION', '3.9.0' ); define( 'EA11Y_MAIN_FILE', __FILE__ ); define( 'EA11Y_BASE', plugin_basename( EA11Y_MAIN_FILE ) ); define( 'EA11Y_PATH', plugin_dir_path( __FILE__ ) ); diff --git a/readme.txt b/readme.txt index aa713262..17f2e39f 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: Web Accessibility, Accessibility, A11Y, WCAG, Accessibility Statement Requires at least: 6.6 Tested up to: 6.8 Requires PHP: 7.4 -Stable tag: 3.8.1 +Stable tag: 3.9.0 License: GPLv2 or later Ally: Make your site more inclusive by scanning for accessibility violations, fixing them easily, and adding a usability widget and accessibility statement. @@ -191,6 +191,14 @@ You can report security bugs through the Patchstack Vulnerability Disclosure Pro 7. Scanner dashboard: Track your site’s accessibility scans, monitor open issues, and follow progress over time. == Changelog == += 3.9.0 – 2025-11-11 = +New: You can now apply fixes across all scanned pages in one click for repeating elements such as headers, footers, and more. +New: Added the ability to manage global Alt Text and Color Contrast remediations. +Tweak: Improved the Reviews component experience. +Fix: Resolved a bug causing the Search page in the Dashboard not to load properly. +Fix: Corrected an error message that appeared improperly when the quota limit was reached. +Fix: Fixed a compatibility issue with the Divi plugin. + = 3.8.1 – 2025-10-15 = * Tweak: Code refactor From 0b30d63a4d213dd2508f5c0d207d3c8d065d377a Mon Sep 17 00:00:00 2001 From: Aviran Levi <143411291+aviranLevi1@users.noreply.github.com> Date: Tue, 9 Dec 2025 10:40:04 +0200 Subject: [PATCH 2/2] Release/v3.9.1 (#440) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Bump WP version * ♻️ Initial Refactor commit [APP-687] (#109) * Initial refactor commit * ✅ Added build and tests CI/CD * PR Rejects * Rejects leftover * Setup base (#110) * Initial refactor commit * ✅ Added build and tests CI/CD * update: add src for admin settings * update: incorrect constant names * update: namespace * add: accessibility settings * update: webpack to output files inside a folder * update: build output folders * update: removed commented code * update: npm scripts * add: webpack config * add: hooks * update: move admin setting to the module folder * update: assets loading logic * update: settings variable * update: removed duplicate css import * Update modules/settings/assets/js/api/index.js Co-authored-by: VasylD --------- Co-authored-by: Ohad Co-authored-by: VasylD * [Infra] ✅ updated Github actions (#114) * updated github actions * removed composer github auth * PHPCS * removed package-lock.json from ignore to allow `npm ci` * added missing husky * ignore legacy * removed unused non existing import * Add connect modal (#111) * Initial refactor commit * ✅ Added build and tests CI/CD * update: add src for admin settings * update: incorrect constant names * update: namespace * add: accessibility settings * update: webpack to output files inside a folder * update: build output folders * update: removed commented code * update: npm scripts * add: webpack config * add: hooks * update: move admin setting to the module folder * update: assets loading logic * update: add rule to move jsx props to multiline imporving readability * add: connect modal * update: hooks import for better readability * update: replace functions with hooks * fix: alignment and style * update: imports * update: removed conflicting imports * fix: add compatibility for mobile devices --------- Co-authored-by: Ohad * [APP 705] add connect module, settings and notification component (#112) * Initial refactor commit * ✅ Added build and tests CI/CD * update: add src for admin settings * update: incorrect constant names * update: namespace * add: accessibility settings * update: webpack to output files inside a folder * update: build output folders * update: removed commented code * update: npm scripts * add: webpack config * add: hooks * update: move admin setting to the module folder * update: assets loading logic * update: add rule to move jsx props to multiline imporving readability * add: connect modal * update: hooks import for better readability * update: replace functions with hooks * add: connect module * add: settings and get settings route * add: hooks and contexts to get settings * add: hooks * add: notification component * add: data api * add: settings provider and connect settings * add: husky * fix: formatting and text-domain * update: filter names * fix: hook import * add: set function for settings * add: prop-types package * update: refactor notification component and context * update: remove filter for authorize url * update: imports and exports of hooks * update: plugin settings context filename and relevant imports --------- Co-authored-by: Ohad * [APP 707] general setting components (#113) * Initial refactor commit * ✅ Added build and tests CI/CD * update: add src for admin settings * update: incorrect constant names * update: namespace * add: accessibility settings * update: webpack to output files inside a folder * update: build output folders * update: removed commented code * update: npm scripts * add: webpack config * add: hooks * update: move admin setting to the module folder * update: assets loading logic * update: add rule to move jsx props to multiline imporving readability * add: connect modal * update: hooks import for better readability * update: replace functions with hooks * add: connect module * add: settings and get settings route * add: hooks and contexts to get settings * add: hooks * add: notification component * add: data api * add: settings provider and connect settings * add: husky * add: icon size control * fix: icon size control labels * add: icon select component * add: color picker component * add: accessibility icons * add: icon export * update: add icons to the component * fix: styling for the icon select control * update: color picker with react-colorful component * update: icon size component with live icon design * fix: styling of radio boxes * add: icon design settings layout * add: position settings layout * add: layout exports * add: alignment matrix and position control components * add: position settings & position settings for mobile layout * fix: formatting and text-domain * update: filter names * fix: hook import * add: set function for settings * add: prop-types package * update: refactor notification component and context * update: remove filter for authorize url * Update modules/settings/assets/js/components/color-picker/style.css Co-authored-by: Raz Ohad * update: color picker class name --------- Co-authored-by: Ohad Co-authored-by: Raz Ohad * [App 780] Navigation Sidebar (#115) * Initial refactor commit * ✅ Added build and tests CI/CD * update: add src for admin settings * update: incorrect constant names * update: namespace * add: accessibility settings * update: webpack to output files inside a folder * update: build output folders * update: removed commented code * update: npm scripts * add: webpack config * add: hooks * update: move admin setting to the module folder * update: assets loading logic * update: add rule to move jsx props to multiline imporving readability * add: connect modal * update: hooks import for better readability * update: replace functions with hooks * add: connect module * add: settings and get settings route * add: hooks and contexts to get settings * add: hooks * add: notification component * add: data api * add: settings provider and connect settings * add: husky * fix: formatting and text-domain * update: filter names * fix: hook import * add: set function for settings * add: prop-types package * update: refactor notification component and context * update: remove filter for authorize url * update: imports and exports of hooks * update: plugin settings context filename and relevant imports * update: icons and icon imports * add: sidebar(wip) * update: fix width of connect screen on mobile * update: sidebar layout * add: credit card and user arrow icons * update: hidden wpfooter and fixed sidebar height * update: sidebar layout * add: basic page layouts * update: sidebar layout * add: sidebar menu, sidebar app bar and my account menu components * update: add sidebar and menu settings * update: add page layouts * update: admin top bar * add: bottom bar * add: bottom bar and top bar * add: bottom bar and top bar * update: page content styling * fix: styling * fix: styling * update: text domain * update: added translations * fix: admin top bar layout * update: exports of icons * update: exports of components * add: aliases for imports and fix exports * fix: height and styling of the layout * fix: unhide wp footer * update: keep widget menu open on page load (default) * update: linter rules to move first prop to new line * update: linter rules to move first prop to new line --------- Co-authored-by: Ohad * Fix error on install plugin, add prettier (#116) * Feature/app 810 assemble icon settings page (#117) * Initial refactor commit * ✅ Added build and tests CI/CD * update: add src for admin settings * update: incorrect constant names * update: namespace * add: accessibility settings * update: webpack to output files inside a folder * update: build output folders * update: removed commented code * update: npm scripts * add: webpack config * add: hooks * update: move admin setting to the module folder * update: assets loading logic * update: add rule to move jsx props to multiline imporving readability * add: connect modal * update: hooks import for better readability * update: replace functions with hooks * add: connect module * add: settings and get settings route * add: hooks and contexts to get settings * add: hooks * add: notification component * add: data api * add: settings provider and connect settings * add: husky * add: icon size control * fix: icon size control labels * add: icon select component * add: color picker component * add: accessibility icons * add: icon export * update: add icons to the component * fix: styling for the icon select control * update: color picker with react-colorful component * update: icon size component with live icon design * fix: styling of radio boxes * add: icon design settings layout * add: position settings layout * add: layout exports * add: alignment matrix and position control components * add: position settings & position settings for mobile layout * fix: formatting and text-domain * update: filter names * fix: hook import * add: set function for settings * add: prop-types package * update: refactor notification component and context * update: remove filter for authorize url * update: imports and exports of hooks * update: plugin settings context filename and relevant imports * update: icons and icon imports * add: sidebar(wip) * update: fix width of connect screen on mobile * update: sidebar layout * Update modules/settings/assets/js/components/color-picker/style.css Co-authored-by: Raz Ohad * update: color picker class name * add: credit card and user arrow icons * update: hidden wpfooter and fixed sidebar height * update: sidebar layout * add: basic page layouts * update: sidebar layout * add: sidebar menu, sidebar app bar and my account menu components * update: add sidebar and menu settings * update: add page layouts * update: admin top bar * add: bottom bar * add: bottom bar and top bar * add: bottom bar and top bar * update: page content styling * fix: styling * fix: styling * update: text domain * add: props to wrapper * add: icon design and position setting layouts * add: in page scroll behaviour to the settings * add: widget icons and getter function * update: icon design settings getter and setter functions * update: imports * add: mobile layout for position settings * add: icon position settings * add: icon position settings hooks and handlers * fix: alignment of controls in AlignmentMatrixControl * update: useSettings and usePositionSetting hooks and relevant functions * fix: colors of AlignmentMatrixControl * fix: styling of components and layouts * add: aliases * add: container wrapper to page * update: accessibility options rendering logic * fix: order of the icons * add: aliases for components and hooks imports * fix: styling of settings panel * fix: container height for settings page * update: toggle control states * add: widget icon settings * add: load saved widget icon settings * update: move layout to page for different designs per page * update: add changes tracking and disable button logic * add: async/await to save settings * update: convert options to array of objects * Update modules/settings/assets/js/components/bottom-bar/index.js Co-authored-by: VasylD * Update modules/settings/assets/js/app.js Co-authored-by: VasylD * fix: remove duplicate entries --------- Co-authored-by: Ohad Co-authored-by: Raz Ohad Co-authored-by: VasylD * Feature/app 708 widget menu settings (#118) * add: icons for menu settings * add: placeholder layout for widget preview in menu settings * update: load saved settings and updated imports * add: logics for handling and saving menu settings * add: useSavedSettings hook * update: set export as default for Sidebar layout * add: widget menu settings layout and settings * update: add widget menu settings and widget preview layouts * add: hide/show minimum option alert notification * update: styling of the save button * update: save settings logic to use async/await * fix: accessibility text icon * update: app type (#119) * [APP-834] Update account menu buttons (#121) * update: account menu buttons * update: billing link * add: error handling for switch account * [APP-835] add service data (#122) * add: client functions * add: site register and site info endpoints * update: add plan data settings * update: add support for 201 response code * update: add plan data key * update: store the plan data on the once the site is registered * update: add filter for client url * add: retry registering in there is any error after connect * update: setting prefix * add: plan data * update: add account details to menu * fix: lint issues * update: add data checkbox support (#123) * [APP-928] Settings pointer (#125) * add: settings pointer * update: add alias for the settings * [APP-837] Add post connect modal (#120) * add: post connect modal * update: settings prefix * fix: connect modal design * update: connect modal text * add: connect modal graphics * update: connect modal icon * update: post connect modal * update: sidebar menu text * update: text of icon settings * update: text * update: php compatibility with return types * add: accessibility statement page structure (#126) * [APP-721] Render widget and global settings (#124) * add: webhook endpoint * add: widget module * add: default widget settings on successful registration * update: name of global object to ea11yWidget * update: remove json encoding to make objects available on the frontend * update: widget url, filter and enqueuing method * update: removed obsolete code * update: enqueue script only when connected * update: add check for valid plan data and key * update: conditional check * update: conditional check * fix: widget loading error (#128) * [Legacy] Upgrade To New [APP-949] (#127) * Added `Notice_Base` and `Notices` component to core module * Always load core module and load all other modules based on legacy status * added filter in customizer settings * added bubble / pimple in admin menu to indicate upgrade * added `Dismissible_Deprecated_Nag` notice to none legacy pages * added `Dismissible_Deprecated_Nag` notice to legacy pages * Added `Upgrade` component to legacy module includes: * loading of notices * introduction modal * admin menu pimple * customizer notice * pointer * confirmation modal * upgrade logic and handler * ✅ Fixed legacy module test * added `local:quick-run` command to run in browser mode * update phpunit workflow * ensure wp.ajax is loaded * wrong translations * Updated strings * added "Equally" * remove unused test * ✅ use custom version of wp test library (#129) * ✅ use custom version of wp test library * cleanup * update WP versions for testing * [APP-711] Widget preview (#130) * add: dynamic script loader for widget * update: settings name * update: settings save function and comments * update: tools settings object structure * add: widget preview section * update: added setting page slug as a constant * update: enqueue widget for preview in the settings * add: widget icon assets link * add: widget icon svgs * update: store widget url in a constant * update: store widget url in a constant * update: trigger widget preview update on menu item changes * update: remove the icon option from the frontend. * update: add widget URL * update: plan data setting type * update: widget plan url * update: widget plan url and parse plan data * fix: phpcs error ext-json missing * fix: widget url * fix: save and use plan data as a serialized option * fix: use template string for widget url * [APP-908] Accessibility generator (#131) * add: accessibility statement radio icons * update: add form group on radio buttons * add: statement generator * add: statement generator * add: accessibility statement data option * update: create page in WordPress and save it to the option * update: exclude zip file from the git * update: render statement page conditionally * add: statement link layout and settings * add: preload statement data * update: publish the created page and add link for it * update: changed Dynamic Script Loader to WidgetLoader * add: accessibility statement url * update: text and styling * update: styling of the preview text * update: restructure statement generator * add: support for dynamic update in statement links * update: remove index.css file for widget loader * add: widget styling for settings page * add: empty link when hide link is enabled * update: statement page structure and logic * fix: typo * update: convert component into a styled component * fix: styling and layout * update: icons * update: convert radio buttons to styled component * fix: typo and style * add: fading for the link preview * update: import * update: styling and spacing * fix: sidebar layout * update: wpcs to latest version * fix: spacing * fix: wpcs version * add: check for valid statement page * fix: jitters on rendering * fix: use escape attribute * update: settings menu slug and plugin name * fix: menu item rendering * update: definition of the styled text field * add: addPage function to the API * fix: add notification on page creation * add: copy link icon * update: optimize SVGs * [APP-908] Additional fixes (#133) * fix: text domains * updated: styled component syntax * update: use await instead of then * fix: prevent application crash in case widget fails to load * add: generated info tip card * update: refactor function * Fix: Fix the QA bugs [n/a] (#135) * [APP-830] Add mixpanel events (#134) * [APP-830] Add mixpanel events * Merge branch 'develop' into feature/APP-830-add-mixpanel # Conflicts: # .gitignore # modules/settings/assets/js/components/sidebar-menu/index.js # modules/settings/assets/js/pages/accessibility-statement.js * Merge branch 'develop' into feature/APP-830-add-mixpanel # Conflicts: # .gitignore # modules/settings/assets/js/components/sidebar-menu/index.js # modules/settings/assets/js/pages/accessibility-statement.js * Merge branch 'develop' into feature/APP-830-add-mixpanel # Conflicts: # .gitignore # modules/settings/assets/js/components/sidebar-menu/index.js # modules/settings/assets/js/pages/accessibility-statement.js * Merge branch 'develop' into feature/APP-830-add-mixpanel # Conflicts: # .gitignore # modules/settings/assets/js/components/sidebar-menu/index.js # modules/settings/assets/js/pages/accessibility-statement.js * Merge branch 'develop' into feature/APP-830-add-mixpanel # Conflicts: # .gitignore # modules/settings/assets/js/components/sidebar-menu/index.js # modules/settings/assets/js/pages/accessibility-statement.js * Merge branch 'develop' into feature/APP-830-add-mixpanel # Conflicts: # .gitignore # modules/settings/assets/js/components/sidebar-menu/index.js # modules/settings/assets/js/pages/accessibility-statement.js * [APP-830] Add user to init Mixpanel (#136) * [APP-830] Add mixpanel events * Merge branch 'develop' into feature/APP-830-add-mixpanel # Conflicts: # .gitignore # modules/settings/assets/js/components/sidebar-menu/index.js # modules/settings/assets/js/pages/accessibility-statement.js * Merge branch 'develop' into feature/APP-830-add-mixpanel # Conflicts: # .gitignore # modules/settings/assets/js/components/sidebar-menu/index.js # modules/settings/assets/js/pages/accessibility-statement.js * Merge branch 'develop' into feature/APP-830-add-mixpanel # Conflicts: # .gitignore # modules/settings/assets/js/components/sidebar-menu/index.js # modules/settings/assets/js/pages/accessibility-statement.js * Merge branch 'develop' into feature/APP-830-add-mixpanel # Conflicts: # .gitignore # modules/settings/assets/js/components/sidebar-menu/index.js # modules/settings/assets/js/pages/accessibility-statement.js * Merge branch 'develop' into feature/APP-830-add-mixpanel # Conflicts: # .gitignore # modules/settings/assets/js/components/sidebar-menu/index.js # modules/settings/assets/js/pages/accessibility-statement.js * Merge branch 'develop' into feature/APP-830-add-mixpanel # Conflicts: # .gitignore # modules/settings/assets/js/components/sidebar-menu/index.js # modules/settings/assets/js/pages/accessibility-statement.js * [APP-830] Add user to init Mixpanel * [APP-830] Add user to init Mixpanel * [APP-830] Add user to init Mixpanel * [APP-830] rename events (#137) * Connect and Preview Fixes (#139) * updated connect admin page * Use unified widget URL instead of hardcoded Js to support envs * Removed enqueue of fictional widget.js and reuse settings `admin` handle * Fix: Fix the QA bugs [n/a] (#138) * New: Finish the BE integration [n/a] * Fix: Fix some bugs [n/a] * upgrade flow UI and design tweaks [app-949] (#141) * Updated Learn More links with UTM's * Tweaked Pointer strings Icon and CTA * dismissible notice strings * sticky notice strings * updated upgrade flow design for pointer, notices, introduction modal, and confirmation modal * added build script * [APP-979] Update links and plugin name (#140) * Ensure loading of legacy widget based on any saved data and fixed legacy JS * Bug/app 1002 (#143) * Bug: Update the logo in the "Hide Widget" modal [APP-1001] * Fix: Update the side menu spaces [APP-1002] * [APP-991] Add translation for statement (#142) * [APP-979] Update links and plugin name * [APP-991] Add translation for statement * Set Prod Widget URL * [APP-1004][APP-1005][APP-1006] Fix generator UI and logic, fix statement UI, fix copy link (#144) * [APP-1004] Fix generator UI and logic * [APP-1005] Fix statement UI * [APP-1005] Fix statement UI * Mixpanel record session * Fix: Enhance position values validation [APP-1009] (#146) * Bug/app 1003 (#147) * Fix: Add a border to the preview [n/a] * Fix: Fix Capabilities screen UI [APP-1003] * [APP-1020] add missed events (#148) * [APP-1015] fix switch account (#149) * [APP-1015] fix switch account (#150) * [APP-1021] Fix switch modal ui (#151) * fix: ui issues * fix: translation strings * [APP-912] add default settings for RTL (#152) * [APP-912] add default settings for RTL * [APP-912] add default settings for RTL * [APP-912] add default settings for RTL * [APP-1026] Remove HTML breaking