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 %}