From c0efe19e8df455d7d1c5a092afb6ed4b98a1d252 Mon Sep 17 00:00:00 2001 From: Struan Donald Date: Wed, 9 Oct 2024 16:11:18 +0100 Subject: [PATCH 1/6] add mariadb-client to Docker packages Useful for doing DB things in development --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index c609305c9b..dd9769d970 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,6 +21,7 @@ RUN apt-get -qq update && apt-get -qq install \ php-xdebug \ gettext \ rsync \ + mariadb-client \ --no-install-recommends && \ rm -r /var/lib/apt/lists/* From 716d06862043c299b290cfe7008669b5641ffa72 Mon Sep 17 00:00:00 2001 From: Struan Donald Date: Wed, 9 Oct 2024 16:12:15 +0100 Subject: [PATCH 2/6] add categorised alerts to user data Makes it easier to split out the different alert types on the alert page. Splits them into alerts with keywords, alerts for when someone speaks and alerts for the user's members. --- classes/AlertView/Standard.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/classes/AlertView/Standard.php b/classes/AlertView/Standard.php index 0f07f09dcb..d55170d073 100644 --- a/classes/AlertView/Standard.php +++ b/classes/AlertView/Standard.php @@ -305,14 +305,30 @@ private function formatSearchMemberData() { private function setUserData() { $this->data['current_mp'] = false; $this->data['alerts'] = []; + $this->data['keyword_alerts'] = []; + $this->data['speaker_alerts'] = []; + $this->data['own_member_alerts'] = []; + $this->data['all_keywords'] = []; + $own_mp_criteria = ''; if ($this->data['email_verified']) { if ($this->user->postcode()) { $current_mp = new \MEMBER(['postcode' => $this->user->postcode()]); - if (!$this->alert->fetch_by_mp($this->data['email'], $current_mp->person_id())) { + if ($current_mp_alert = !$this->alert->fetch_by_mp($this->data['email'], $current_mp->person_id())) { $this->data['current_mp'] = $current_mp; + $own_mp_criteria = sprintf('speaker:%s', $current_mp->person_id()); } } $this->data['alerts'] = \MySociety\TheyWorkForYou\Utility\Alert::forUser($this->data['email']); + foreach ($this->data['alerts'] as $alert) { + if (array_key_exists('words', $alert)) { + $this->data['all_keywords'][] = implode(' ', $alert['words']); + $this->data['keyword_alerts'][] = $alert; + } elseif (array_key_exists('spokenby', $alert) and sizeof($alert['spokenby']) == 1 and $alert['spokenby'][0] == $own_mp_criteria) { + $this->data['own_member_alerts'][] = $alert; + } else { + $this->data['spoken_alerts'][] = $alert; + } + } } } } From 7b9743c29d7384b80575f5b82bf844e56e2678e2 Mon Sep 17 00:00:00 2001 From: Struan Donald Date: Wed, 9 Oct 2024 16:14:59 +0100 Subject: [PATCH 3/6] optionally return alert parts from prettify and add parts to alerts To make it a bit easier to pick out the different parts of an alert update prettifyCritera to return the parts as a hash instead of turning it into a string. --- classes/Utility/Alert.php | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/classes/Utility/Alert.php b/classes/Utility/Alert.php index 89da17ec29..1173bc6c11 100644 --- a/classes/Utility/Alert.php +++ b/classes/Utility/Alert.php @@ -34,6 +34,7 @@ public static function forUser($email) { $alerts = []; foreach ($q as $row) { $criteria = self::prettifyCriteria($row['criteria']); + $parts = self::prettifyCriteria($row['criteria'], true); $token = $row['alert_id'] . '-' . $row['registrationtoken']; $status = 'confirmed'; @@ -43,21 +44,28 @@ public static function forUser($email) { $status = 'suspended'; } - $alerts[] = [ + $alert = [ 'token' => $token, 'status' => $status, 'criteria' => $criteria, 'raw' => $row['criteria'], + 'keywords' => [], + 'exclusions' => [], ]; + + $alert = array_merge($alert, $parts); + + $alerts[] = $alert; } return $alerts; } - public static function prettifyCriteria($alert_criteria) { + public static function prettifyCriteria($alert_criteria, $as_parts = false) { $text = ''; if ($alert_criteria) { $criteria = explode(' ', $alert_criteria); + $parts = []; $words = []; $spokenby = array_values(\MySociety\TheyWorkForYou\Utility\Search::speakerNamesForIDs($alert_criteria)); @@ -68,12 +76,19 @@ public static function prettifyCriteria($alert_criteria) { } if ($spokenby && count($words)) { $text = implode(' or ', $spokenby) . ' mentions [' . implode(' ', $words) . ']'; + $parts['spokenby'] = $spokenby; + $parts['words'] = $words; } elseif (count($words)) { $text = '[' . implode(' ', $words) . ']' . ' is mentioned'; + $parts['words'] = $words; } elseif ($spokenby) { $text = implode(' or ', $spokenby) . " speaks"; + $parts['spokenby'] = $spokenby; } } + if ($as_parts) { + return $parts; + } return $text; } From 4ece5df86564028f578a5f3408e3736966562041 Mon Sep 17 00:00:00 2001 From: Lucas Cumsille M Date: Tue, 1 Oct 2024 11:44:09 +0000 Subject: [PATCH 4/6] update alerts page to use new look for displaying alerts Move things from the sidebar to the main section and update the look to split out alerts into keyword and representative alerts. Generally improve the look of them. --- classes/AlertView/Standard.php | 10 +- www/docs/js/main.js | 71 +++++ www/docs/style/sass/app.scss | 59 ++-- www/docs/style/sass/parts/_accordion.scss | 236 ++++++++++++++ .../templates/html/alert/index.php | 299 ++++++++++++++++-- 5 files changed, 623 insertions(+), 52 deletions(-) create mode 100644 www/docs/style/sass/parts/_accordion.scss diff --git a/classes/AlertView/Standard.php b/classes/AlertView/Standard.php index d55170d073..3a14cbccd9 100644 --- a/classes/AlertView/Standard.php +++ b/classes/AlertView/Standard.php @@ -320,13 +320,13 @@ private function setUserData() { } $this->data['alerts'] = \MySociety\TheyWorkForYou\Utility\Alert::forUser($this->data['email']); foreach ($this->data['alerts'] as $alert) { - if (array_key_exists('words', $alert)) { - $this->data['all_keywords'][] = implode(' ', $alert['words']); - $this->data['keyword_alerts'][] = $alert; - } elseif (array_key_exists('spokenby', $alert) and sizeof($alert['spokenby']) == 1 and $alert['spokenby'][0] == $own_mp_criteria) { + if (array_key_exists('spokenby', $alert) and sizeof($alert['spokenby']) == 1 and $alert['spokenby'][0] == $own_mp_criteria) { $this->data['own_member_alerts'][] = $alert; - } else { + } elseif (array_key_exists('spokenby', $alert)) { $this->data['spoken_alerts'][] = $alert; + } else { + $this->data['all_keywords'][] = implode(' ', $alert['words']); + $this->data['keyword_alerts'][] = $alert; } } } diff --git a/www/docs/js/main.js b/www/docs/js/main.js index 6dd194a9f0..467482ce81 100644 --- a/www/docs/js/main.js +++ b/www/docs/js/main.js @@ -423,6 +423,77 @@ function wrap_error($message){ return ''; } +function createAccordion(triggerSelector, contentSelector) { + var triggers = document.querySelectorAll(triggerSelector); + + triggers.forEach(function(trigger) { + var content = document.querySelector(trigger.getAttribute('href')); + + var openAccordion = function() { + content.style.maxHeight = content.scrollHeight + "px"; // Dynamically calculate height + content.setAttribute('aria-hidden', 'false'); + trigger.setAttribute('aria-expanded', 'true'); + }; + + var closeAccordion = function() { + content.style.maxHeight = null; // Collapse + content.setAttribute('aria-hidden', 'true'); + trigger.setAttribute('aria-expanded', 'false'); + }; + + trigger.addEventListener('click', function(e) { + e.preventDefault(); + + if (content.style.maxHeight) { + closeAccordion(); + } else { + openAccordion(); + } + }); + + // Accessibility + trigger.setAttribute('aria-controls', content.getAttribute('id')); + trigger.setAttribute('aria-expanded', 'false'); + content.setAttribute('aria-hidden', 'true'); + content.style.maxHeight = null; + }); +} + +// Initialize accordion when DOM is loaded +document.addEventListener('DOMContentLoaded', function() { + createAccordion('.accordion-button', '.accordion-content'); +}); + +// Create alert form +$(document).ready(function() { + let currentStep = 0; + let steps = $(".alert-step"); + + // Show the first step + $(steps[currentStep]).show(); + + // Focus management: Set focus to the first input on each step change + function focusFirstInput(stepIndex) { + $(steps[stepIndex]).find('input, button').first().focus(); + } + + // Next button click + $(".next").click(function() { + $(steps[currentStep]).hide(); + currentStep++; + $(steps[currentStep]).show(); + focusFirstInput(currentStep); // Set focus to the first input of the new step + }); + + // Previous button click + $(".prev").click(function() { + $(steps[currentStep]).hide(); + currentStep--; + $(steps[currentStep]).show(); + focusFirstInput(currentStep); // Set focus to the first input of the new step + }); +}); + $(function() { $('#how-often-annually').click(function() { diff --git a/www/docs/style/sass/app.scss b/www/docs/style/sass/app.scss index d356a39cd9..511351cd8c 100644 --- a/www/docs/style/sass/app.scss +++ b/www/docs/style/sass/app.scss @@ -62,10 +62,10 @@ @import url(https://fonts.googleapis.com/css2?family=Manrope:wght@700&family=Merriweather:wght@400;700&display=swap); /* Foundation Icons v 3.0 MIT License */ @font-face { - font-family: "foundation-icons"; - src: url("/style/foundation-icons/foundation-icons.woff") format("woff"); - font-weight: normal; - font-style: normal; + font-family: "foundation-icons"; + src: url("/style/foundation-icons/foundation-icons.woff") format("woff"); + font-weight: normal; + font-style: normal; } .fi-social-facebook:before, @@ -75,17 +75,24 @@ .fi-megaphone:before, .fi-pound:before, .fi-magnifying-glass:before, -.fi-heart:before +.fi-heart:before, +.fi-plus:before, +.fi-play:before, +.fi-pause:before, +.fi-trash:before, +.fi-page-edit:before, +.fi-x:before, +.fi-save:before { - font-family: "foundation-icons"; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - -webkit-font-smoothing: antialiased; - display: inline-block; - text-decoration: inherit; + font-family: "foundation-icons"; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; + display: inline-block; + text-decoration: inherit; } // https://github.com/zurb/foundation-icon-fonts/blob/master/_foundation-icons.scss @@ -97,6 +104,13 @@ .fi-pound:before {content: "\f19a"} .fi-magnifying-glass:before {content: "\f16c"} .fi-heart:before { content: "\f159"; } +.fi-plus:before { content: "\f199"; } +.fi-play:before { content: "\f198"; } +.fi-pause:before { content: "\f191"; } +.fi-trash:before { content: "\f204"; } +.fi-page-edit:before { content: "\f184"; } +.fi-x:before { content: "\f217"; } +.fi-save:before { content: "\f1ac"; } html, body { @@ -129,13 +143,13 @@ h3 { } .pull-right { - @media (min-width: $medium-screen) { + @media (min-width: $medium-screen) { float: right; margin-left: 1em; } } .pull-left { - @media (min-width: $medium-screen) { + @media (min-width: $medium-screen) { float: left; margin-left: 1em; } @@ -166,12 +180,12 @@ ul { a { overflow-wrap: break-word; word-wrap: break-word; - + -webkit-hyphens: auto; - -moz-hyphens: auto; - -ms-hyphens: auto; - hyphens: auto; - + -moz-hyphens: auto; + -ms-hyphens: auto; + hyphens: auto; + color: $links; } @@ -198,7 +212,7 @@ a:focus { // for .button elements!! vertical-align: -0.4em; } - + &.tertiary { @include button-style($bg: $links); } @@ -231,6 +245,7 @@ form { @import "parts/panels"; @import "parts/promo-banner"; +@import "parts/accordion"; @import "pages/mp"; @import "pages/topics"; diff --git a/www/docs/style/sass/parts/_accordion.scss b/www/docs/style/sass/parts/_accordion.scss new file mode 100644 index 0000000000..8499959cc9 --- /dev/null +++ b/www/docs/style/sass/parts/_accordion.scss @@ -0,0 +1,236 @@ +.label { + background-color: #fff; + color: $primary-color; + padding: 0.25rem 0.5rem; + border-radius: 1rem; + font-size: 0.75rem; + + &--primary-light { + background-color: $primary-color-200; + color: $body-font-color; + } + + &--red { + background-color: lighten($color-red, 40%); + color: $body-font-color; + } +} + +.alert-page-header { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: space-between; + align-items: end; + + h2 { + margin-bottom: 0.5rem; + } +} + +.accordion { + margin-top: 2rem; +} + +.accordion-button { + width: 100%; + display: flex; + justify-content: space-between; + text-align: left; + padding: 10px; + font-size: 1.2em; + cursor: pointer; + border: none; + + &[aria-expanded="true"] { + background-color: $primary-color-200; + color: $body-font-color; + & + .accordion-content{ + max-height: 1000px; + transition: max-height 0.3s ease; + } + + i { + transform: rotate(45deg); + } + } + +} + +.accordion-button--content { + display: flex; + flex-direction: row; + align-content: center; + align-items: center; + gap: 0.75rem; + + .content-subtitle { + @extend .label; + } +} + +.accordion-content { + max-height: 0; + overflow: hidden; + transition: max-height 0.3s ease; + + .alert-controller-wrapper { + margin-bottom: 2rem; + button { + margin-bottom: 0; + span { + margin-right: 0.2rem; + } + } + + button.alert { + background-color: $color-red; + color: #fff; + } + } + + .add-remove-tool { + display: flex; + flex-direction: row; + + input { + margin: 0; + height: 40px; + } + + button { + max-width: 100px; + height: 40px; + } + } + + label { + font-size: inherit; + color: inherit; + } + + select { + max-width: 350px; + } +} + +.keyword-list { + ul { + list-style: none; + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 0.5rem; + margin-left: 0; + + li { + font-weight: bold; + i { + margin-left: 0.25rem; + } + } + + } +} + +.heading-with-bold-word { + font-weight: 400; + + span { + font-weight: bold; + } +} + +.alert-meta-info { + display: flex; + flex-direction: row; + flex-wrap: wrap; + column-gap: 4rem; + row-gap: 1rem; + align-items: center; + + dt { + color: $light-text; + font-size: 0.9rem; + } +} + +button { + i { + margin-left: 0.25rem; + } +} + +.alert-page-section { + margin-bottom: 3rem; +} + +.alert-page-subsection { + margin-bottom: 2rem; + + .alert-page-subsection--subtitle { + margin-bottom: 0.5rem; + } + +} + +.button.red { + background-color: $color-red; + color: #fff; + + &:hover { + background-color: darken($color-red, 15%); + } +} + +// FORM +.alert-step { + display: none; +} + +.alert-step.active { + display: block; +} + +#create-alert-form { + label { + color: $body-font-color; + font-size: 1rem; + margin-bottom: 1rem; + } + input[type="text"], select { + max-width: 400px; + height: 40px; + border-color: $body-font-color; + } + + fieldset { + column-count: 2; + } +} + + +.mockup-internal-comment { + font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif; + font-size: 0.8rem; + color: rgb(114, 8, 8); + background-color: rgb(230, 170, 170); + padding: 1rem; + border-radius: 1rem; + h4 { + color: inherit; + } + margin-bottom: 3rem; +} + +.mockup-divider { + margin-top: 30rem; +} + +.fi-x { + display: none; +} + +.display-none { + display:none !important; +} diff --git a/www/includes/easyparliament/templates/html/alert/index.php b/www/includes/easyparliament/templates/html/alert/index.php index 8f4deef191..7b15c82b40 100644 --- a/www/includes/easyparliament/templates/html/alert/index.php +++ b/www/includes/easyparliament/templates/html/alert/index.php @@ -250,38 +250,286 @@
-
- - - - - - - -

-
    -
  • - full_name()) ?>. -
    - - - -
    -
  • -
- - - +
+

join or sign in, you can suspend, resume and delete your email alerts from your profile page.'), '/user/?pg=join', '/user/login/?ret=%2Falert%2F') ?>

- -
+ +
+
+ + +
+
+ +
+
+

+ + +

+ +
+ + + + +
+ + + + +
+ $alert) { ?> +
+ + +
+ -
+
+ +
+
+

Representative alerts

+ +
    +
  • + full_name()) ?>. +
    + + + +
    +
  • +
+ + +
+

﹒ XXX

+ +

+
+ +
+ + + + + + + + +
+
+ + +

Alert when is mentioned

+
+ + + +
+
+ + + + + +
+

+ +

+

+ +
+ + + + + + + + +
+
+ + +

Alert when is mentioned

+
+ + + +
+
+ + +
+ + + + +
+
+

@@ -302,6 +550,7 @@

+

From db0494d05b7accb2481d736726729c1c72d65d5e Mon Sep 17 00:00:00 2001 From: Struan Donald Date: Thu, 10 Oct 2024 11:44:50 +0100 Subject: [PATCH 5/6] handle sections for alerts prettyCriteria Also split them out and include as key in alert object so we can use them in alerts interface --- classes/Utility/Alert.php | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/classes/Utility/Alert.php b/classes/Utility/Alert.php index 1173bc6c11..6edfba202e 100644 --- a/classes/Utility/Alert.php +++ b/classes/Utility/Alert.php @@ -9,6 +9,16 @@ */ class Alert { + #XXX don't calculate this every time + private static function sectionToTitle($section) { + global $hansardmajors; + $section_map = []; + foreach ($hansardmajors as $major => $details) { + $section_map[$details["page_all"]] = $details["title"]; + } + + return $section_map[$section]; + } public static function detailsToCriteria($details) { $criteria = []; @@ -67,10 +77,15 @@ public static function prettifyCriteria($alert_criteria, $as_parts = false) { $criteria = explode(' ', $alert_criteria); $parts = []; $words = []; + $sections = []; + $sections_verbose = []; $spokenby = array_values(\MySociety\TheyWorkForYou\Utility\Search::speakerNamesForIDs($alert_criteria)); foreach ($criteria as $c) { - if (!preg_match('#^speaker:(\d+)#', $c, $m)) { + if (preg_match('#^section:(\w+)#', $c, $m)) { + $sections[] = $m[1]; + $sections_verbose[] = self::sectionToTitle($m[1]); + } elseif (!preg_match('#^speaker:(\d+)#', $c, $m)) { $words[] = $c; } } @@ -85,6 +100,12 @@ public static function prettifyCriteria($alert_criteria, $as_parts = false) { $text = implode(' or ', $spokenby) . " speaks"; $parts['spokenby'] = $spokenby; } + + if ($sections) { + $text = $text . " in " . implode(' or ', $sections_verbose); + $parts['sections'] = $sections; + $parts['sections_verbose'] = $sections_verbose; + } } if ($as_parts) { return $parts; From c8613905e9842ffca39b8fa426842563647cbfce Mon Sep 17 00:00:00 2001 From: Struan Donald Date: Thu, 10 Oct 2024 12:29:01 +0100 Subject: [PATCH 6/6] display which sections an alert is limited to --- classes/Utility/Alert.php | 1 + .../easyparliament/templates/html/alert/index.php | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/classes/Utility/Alert.php b/classes/Utility/Alert.php index 6edfba202e..79f6d42f7d 100644 --- a/classes/Utility/Alert.php +++ b/classes/Utility/Alert.php @@ -61,6 +61,7 @@ public static function forUser($email) { 'raw' => $row['criteria'], 'keywords' => [], 'exclusions' => [], + 'sections' => [], ]; $alert = array_merge($alert, $parts); diff --git a/www/includes/easyparliament/templates/html/alert/index.php b/www/includes/easyparliament/templates/html/alert/index.php index 7b15c82b40..1ee4f9243c 100644 --- a/www/includes/easyparliament/templates/html/alert/index.php +++ b/www/includes/easyparliament/templates/html/alert/index.php @@ -342,7 +342,9 @@

+
+
@@ -376,6 +378,7 @@
+

Which section should this alert apply to:

    -
  • All sections + +
  • +
+