diff --git a/web/themes/contrib/civictheme/civictheme.theme b/web/themes/contrib/civictheme/civictheme.theme index a1c966b39f..757c302475 100644 --- a/web/themes/contrib/civictheme/civictheme.theme +++ b/web/themes/contrib/civictheme/civictheme.theme @@ -8,6 +8,7 @@ declare(strict_types=1); use Drupal\civictheme\CivicthemeConstants; +use Drupal\Core\Cache\Cache; use Drupal\Core\Render\Component\Exception\ComponentNotFoundException; require_once __DIR__ . '/includes/utilities.inc'; @@ -161,6 +162,7 @@ function civictheme_preprocess_last(array &$variables, string $hook): void { function civictheme_preprocess_html(array &$variables): void { _civictheme_preprocess_html__skip_link($variables); _civictheme_preprocess_html__site_section($variables); + _civictheme_preprocess_html__search_head_title($variables); // Disable modifier_class as this template is provided by Drupal. $variables['modifier_class'] = FALSE; @@ -275,4 +277,5 @@ function civictheme_page_attachments_alter(array &$attachments) { catch (ComponentNotFoundException $exception) { \Drupal::logger('civictheme')->error('Unable to find alert component: %message', ['%message' => $exception->getMessage()]); } + $attachments['#cache']['contexts'] = Cache::mergeContexts($attachments['#cache']['contexts'] ?? [], ['url.query_args']); } diff --git a/web/themes/contrib/civictheme/config/install/civictheme.settings.yml b/web/themes/contrib/civictheme/config/install/civictheme.settings.yml index 2af0e27b60..94f6565682 100644 --- a/web/themes/contrib/civictheme/config/install/civictheme.settings.yml +++ b/web/themes/contrib/civictheme/config/install/civictheme.settings.yml @@ -67,6 +67,10 @@ components: summary_length: 160 attachment: use_media_name: true + search: + keyword_fields: + - keywords + - title colors: use_color_selector: true use_brand_colors: true diff --git a/web/themes/contrib/civictheme/config/install/views.view.civictheme_automated_list.yml b/web/themes/contrib/civictheme/config/install/views.view.civictheme_automated_list.yml index 475e24e421..25ec6a5f42 100644 --- a/web/themes/contrib/civictheme/config/install/views.view.civictheme_automated_list.yml +++ b/web/themes/contrib/civictheme/config/install/views.view.civictheme_automated_list.yml @@ -531,7 +531,7 @@ display: admin_label: '' plugin_id: result empty: false - content: 'Showing @start - @end of @total' + content: 'Showing @start - @end of @total @keywords' footer: { } display_extenders: { } cache_metadata: diff --git a/web/themes/contrib/civictheme/config/optional/views.view.civictheme_search.yml b/web/themes/contrib/civictheme/config/optional/views.view.civictheme_search.yml index c6bc828205..fa09cf5a87 100644 --- a/web/themes/contrib/civictheme/config/optional/views.view.civictheme_search.yml +++ b/web/themes/contrib/civictheme/config/optional/views.view.civictheme_search.yml @@ -218,19 +218,16 @@ display: query_tags: { } relationships: { } header: - area: - id: area + result: + id: result table: views - field: area + field: result relationship: none group_type: group admin_label: '' - plugin_id: text + plugin_id: result empty: false - content: - value: '

Search results...

' - format: civictheme_rich_text - tokenize: false + content: 'Showing @start - @end of @total @keywords' footer: { } display_extenders: { } cache_metadata: diff --git a/web/themes/contrib/civictheme/config/schema/civictheme.schema.yml b/web/themes/contrib/civictheme/config/schema/civictheme.schema.yml index 66b0debccc..dfed80448f 100644 --- a/web/themes/contrib/civictheme/config/schema/civictheme.schema.yml +++ b/web/themes/contrib/civictheme/config/schema/civictheme.schema.yml @@ -225,6 +225,15 @@ civictheme.settings: use_media_name: label: 'Use name of media' type: boolean + search: + type: mapping + label: 'Search settings' + mapping: + keyword_fields: + label: 'Keyword fields' + type: sequence + sequence: + type: string colors: type: mapping label: Colors diff --git a/web/themes/contrib/civictheme/includes/form_element.inc b/web/themes/contrib/civictheme/includes/form_element.inc index f452408ff6..fda17652c4 100644 --- a/web/themes/contrib/civictheme/includes/form_element.inc +++ b/web/themes/contrib/civictheme/includes/form_element.inc @@ -369,6 +369,7 @@ function _civictheme_preprocess_form_element__generic(array &$variables): void { $title_display = 'hidden'; } $variables['title_display'] = $title_display; + $variables['title_size'] = $element['#title_size'] ?? ''; $variables['orientation'] = $variables['orientation'] ?? $title_display === 'inline' ? 'horizontal' : 'vertical'; diff --git a/web/themes/contrib/civictheme/includes/search.inc b/web/themes/contrib/civictheme/includes/search.inc index 3a64f1b18c..0ce877e588 100644 --- a/web/themes/contrib/civictheme/includes/search.inc +++ b/web/themes/contrib/civictheme/includes/search.inc @@ -8,6 +8,7 @@ declare(strict_types=1); use Drupal\civictheme\CivicthemeConstants; +use Drupal\civictheme\CivicthemeUtility; /** * Implements template_preprocess_block(). @@ -36,3 +37,38 @@ function civictheme_preprocess_block__civictheme_search(array &$variables): void $variables['theme'] = civictheme_get_theme_config_manager()->load('components.header.theme', CivicthemeConstants::HEADER_THEME_DEFAULT); } + +/** + * Implements hook_preprocess_html(). + * + * @SuppressWarnings(PHPMD.StaticAccess) + */ +function _civictheme_preprocess_html__search_head_title(array &$variables): void { + // Load search fields from theme settings. + $setting_keyword_fields = civictheme_get_theme_config_manager()->load('components.search.keyword_fields') ?? ''; + if (empty($setting_keyword_fields)) { + return; + } + + // Add search keywords to head title. + $keywords = NULL; + $search_fields = CivicthemeUtility::multilineToArray($setting_keyword_fields); + foreach ($search_fields as $field) { + $query_field = \Drupal::request()->query->get($field); + if (!empty($query_field)) { + $keywords = $query_field; + break; + } + } + if (empty($keywords)) { + return; + } + $head_title = (string) ($variables['head_title']['title'] ?? ''); + if (empty($head_title)) { + return; + } + $variables['head_title']['title'] = t("@title - Searching for '@keywords'", [ + '@title' => $head_title, + '@keywords' => $keywords, + ]); +} diff --git a/web/themes/contrib/civictheme/includes/views.inc b/web/themes/contrib/civictheme/includes/views.inc index 244fc2f2d6..061859857e 100644 --- a/web/themes/contrib/civictheme/includes/views.inc +++ b/web/themes/contrib/civictheme/includes/views.inc @@ -8,10 +8,12 @@ declare(strict_types=1); use Drupal\civictheme\CivicthemeConstants; +use Drupal\civictheme\CivicthemeUtility; use Drupal\Component\Utility\Html; use Drupal\Core\Cache\Cache; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Template\Attribute; +use Drupal\views\ViewExecutable; use Drupal\views\Views; /** @@ -21,6 +23,7 @@ function civictheme_preprocess_views_view(array &$variables): void { _civictheme_preprocess_views_view__view($variables); _civictheme_preprocess_views_view__pager($variables); _civictheme_preprocess_views_view__search_page($variables); + _civictheme_preprocess_views_view__results_count($variables); } /** @@ -160,6 +163,12 @@ function civictheme_preprocess_views_exposed_form(array &$variables): void { } if ($field_count == 1) { + // Use inline filter on search and auto pages if a single text field exists. + if (_civictheme_preprocess_views__exposed_form__inline_filter($variables, $view, $fields)) { + return; + } + + // Use single filter. _civictheme_preprocess_views__exposed_form__single_filter($variables); } elseif ($field_count > 1) { @@ -167,6 +176,44 @@ function civictheme_preprocess_views_exposed_form(array &$variables): void { } } +/** + * Preprocess views exposed form to convert it to the Inline filter. + * + * @param array $variables + * Variables array. + * @param \Drupal\views\ViewExecutable|null $view + * The view object. + * @param array $fields + * Form fields. + * + * @return bool + * TRUE if inline filter was processed, FALSE otherwise. + */ +function _civictheme_preprocess_views__exposed_form__inline_filter(array &$variables, $view, array $fields): bool { + if (!($view instanceof ViewExecutable)) { + return FALSE; + } + + if (!in_array($view->id(), ['civictheme_search', 'civictheme_automated_list'])) { + return FALSE; + } + + $keyword_field = reset($fields); + if ($keyword_field['#type'] !== 'textfield') { + return FALSE; + } + + $variables['inline_filter'] = TRUE; + $keyword_field['#title_size'] = 'extra-large'; + $variables['filter_items'] = $keyword_field; + $submit_field = $variables['form']['actions']['submit'] ?? NULL; + if (!empty($submit_field)) { + $variables['submit_text'] = $submit_field['#value'] ?? ''; + } + + return TRUE; +} + /** * Preprocess views exposed form to convert it to the Single filter. */ @@ -261,3 +308,40 @@ function _civictheme_preprocess_views_view__search_page(array &$variables): void $variables['vertical_spacing'] = 'top'; } } + +/** + * Pre-process results count for views. + * + * @SuppressWarnings(PHPMD.StaticAccess) + */ +function _civictheme_preprocess_views_view__results_count(array &$variables): void { + if (empty($variables['results_count'])) { + return; + } + if (!str_contains($variables['results_count'], '@keywords')) { + return; + } + + // Load search fields from theme settings. + $setting_keyword_fields = civictheme_get_theme_config_manager()->load('components.search.keyword_fields') ?? ''; + if (empty($setting_keyword_fields)) { + // Strip the @keywords token because it exists but won't be populated. + $variables['results_count'] = str_replace('@keywords', '', $variables['results_count']); + return; + } + + $variables['#cache']['contexts'] = Cache::mergeContexts($variables['#cache']['contexts'] ?? [], ['url.query_args']); + + $keywords = ''; + $exposed_input = $variables['view']->getExposedInput(); + $search_fields = CivicthemeUtility::multilineToArray($setting_keyword_fields); + + foreach ($search_fields as $field) { + if (!empty($exposed_input[$field])) { + $keywords = t("for '@keywords'", ['@keywords' => $exposed_input[$field]])->__toString(); + break; + } + } + + $variables['results_count'] = str_replace('@keywords', $keywords, $variables['results_count']); +} diff --git a/web/themes/contrib/civictheme/package.json b/web/themes/contrib/civictheme/package.json index fc1ace219c..d73162e7e4 100644 --- a/web/themes/contrib/civictheme/package.json +++ b/web/themes/contrib/civictheme/package.json @@ -39,7 +39,7 @@ }, "dependencies": { "@popperjs/core": "^2.11.8", - "@civictheme/uikit": "github:civictheme/uikit#312d7574a273a69a33655210a81406b97354471e" + "@civictheme/uikit": "github:civictheme/uikit#bd1d7944ff7f1030359d31c61c9dd88f64038d8d" }, "devDependencies": { "@civictheme/scss-variables-extractor": "^0.2.1", diff --git a/web/themes/contrib/civictheme/src/Settings/CivicthemeSettingsFormSectionComponents.php b/web/themes/contrib/civictheme/src/Settings/CivicthemeSettingsFormSectionComponents.php index 9357f2306f..785ffe00b2 100644 --- a/web/themes/contrib/civictheme/src/Settings/CivicthemeSettingsFormSectionComponents.php +++ b/web/themes/contrib/civictheme/src/Settings/CivicthemeSettingsFormSectionComponents.php @@ -442,6 +442,21 @@ public function form(array &$form, FormStateInterface $form_state): void { '#default_value' => $this->themeConfigManager->loadForComponent('attachment', 'use_media_name', TRUE), ]; + $form['components']['search'] = [ + '#type' => 'details', + '#title' => $this->t('Search'), + '#group' => 'components', + '#tree' => TRUE, + ]; + + $form['components']['search']['keyword_fields'] = [ + '#type' => 'textarea', + '#title' => $this->t('Search and Automated list keyword fields'), + '#description' => $this->t('A list of machine names for views exposed filters keyword search.
Fields added here will populate the "Showing @start - @end of @total @keywords" where @keywords is replaced with "for [keyword_field_value]" if a value exists.
It will also be used in the browser page title as "@title - Searching for \'@keywords\' | @suffix".
One keyword field per line.'), + '#default_value' => CivicthemeUtility::arrayToMultiline($this->themeConfigManager->load('components.search.keyword_fields', [])), + '#rows' => 4, + ]; + $form['#process'][] = $this->processForm(...); // Auto-discover per-component validation and submit handlers. @@ -588,6 +603,18 @@ public function submitFooter(array &$form, FormStateInterface $form_state): void ); } + /** + * Submit callback for theme settings form of Search component. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @SuppressWarnings(PHPMD.StaticAccess) + */ + public function submitSearch(array &$form, FormStateInterface $form_state): void { + $keyword_fields = $form_state->getValue(['components', 'search', 'keyword_fields'], ''); + $keyword_fields = CivicthemeUtility::multilineToArray($keyword_fields); + $form_state->setValue(['components', 'search', 'keyword_fields'], $keyword_fields); + } + /** * Provide a description for a path field. * diff --git a/web/themes/contrib/civictheme/templates/form/form-element--civictheme-field.html.twig b/web/themes/contrib/civictheme/templates/form/form-element--civictheme-field.html.twig index 9cc8ab0f8a..997fc26272 100644 --- a/web/themes/contrib/civictheme/templates/form/form-element--civictheme-field.html.twig +++ b/web/themes/contrib/civictheme/templates/form/form-element--civictheme-field.html.twig @@ -9,6 +9,7 @@ theme: theme, title: title, title_display: title_display, + title_size: title_size, type: type, orientation: orientation, placeholder: placeholder, diff --git a/web/themes/contrib/civictheme/templates/views/views-exposed-form.html.twig b/web/themes/contrib/civictheme/templates/views/views-exposed-form.html.twig index 23eafcd84d..902585764a 100644 --- a/web/themes/contrib/civictheme/templates/views/views-exposed-form.html.twig +++ b/web/themes/contrib/civictheme/templates/views/views-exposed-form.html.twig @@ -29,6 +29,11 @@ form_attributes: form_attributes, form_suffix: form_suffix, } %} +{% elseif inline_filter %} + {% include 'civictheme:inline-filter' with { + theme: theme, + items: filter_items, + } only %} {% else %} {{ form }} {% endif %}