From 35f693b6e43ea5f0b3993c5f32f192a0d58b3136 Mon Sep 17 00:00:00 2001 From: Brian Hanson Date: Wed, 30 Apr 2025 14:34:46 -0500 Subject: [PATCH 01/21] Give heading sources a `key` so we can add data to them. --- .../ElementIndexSettingsController.php | 18 ++++++++++++--- .../assets/cp/src/js/CustomizeSourcesModal.js | 23 +++++++++++++++++-- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/controllers/ElementIndexSettingsController.php b/src/controllers/ElementIndexSettingsController.php index 8d619adbd62..a0bb657f715 100644 --- a/src/controllers/ElementIndexSettingsController.php +++ b/src/controllers/ElementIndexSettingsController.php @@ -254,22 +254,32 @@ public function actionSaveCustomizeSourcesModalSettings(): Response // Normalize to the way it's stored in the DB foreach ($sourceOrder as $source) { + // @TODO do we still need this condition now that headings have a `key`? if (isset($source['heading'])) { $newSourceConfigs[] = [ 'type' => ElementSources::TYPE_HEADING, 'heading' => $source['heading'], ]; } elseif (isset($source['key'])) { - $isCustom = str_starts_with($source['key'], 'custom:'); + $type = match (true) { + str_starts_with($source['key'], 'custom:') => ElementSources::TYPE_CUSTOM, + str_starts_with($source['key'], 'heading:') => ElementSources::TYPE_HEADING, + default => ElementSources::TYPE_NATIVE, + }; + + $isCustom = $type === ElementSources::TYPE_CUSTOM; $sourceConfig = [ - 'type' => $isCustom ? ElementSources::TYPE_CUSTOM : ElementSources::TYPE_NATIVE, + 'type' => $type, 'key' => $source['key'], ]; // Were new settings posted? if (isset($sourceSettings[$source['key']])) { $postedSettings = $sourceSettings[$source['key']]; - $sourceConfig['tableAttributes'] = array_values(array_filter($postedSettings['tableAttributes'] ?? [])) ?: '-'; + + if ($type !== ElementSources::TYPE_HEADING) { + $sourceConfig['tableAttributes'] = array_values(array_filter($postedSettings['tableAttributes'] ?? [])) ?: '-'; + } if (isset($postedSettings['defaultSort'])) { $sourceConfig['defaultSort'] = $postedSettings['defaultSort']; @@ -292,6 +302,8 @@ public function actionSaveCustomizeSourcesModalSettings(): Response if (isset($postedSettings['userGroups']) && $postedSettings['userGroups'] !== '*') { $sourceConfig['userGroups'] = is_array($postedSettings['userGroups']) ? $postedSettings['userGroups'] : false; } + } elseif ($type === ElementSources::TYPE_HEADING) { + $sourceConfig['heading'] = $postedSettings['heading']; } elseif (isset($postedSettings['enabled'])) { $sourceConfig['disabled'] = !$postedSettings['enabled']; if ($sourceConfig['disabled']) { diff --git a/src/web/assets/cp/src/js/CustomizeSourcesModal.js b/src/web/assets/cp/src/js/CustomizeSourcesModal.js index c45c5c43c60..b2d22fec0d5 100644 --- a/src/web/assets/cp/src/js/CustomizeSourcesModal.js +++ b/src/web/assets/cp/src/js/CustomizeSourcesModal.js @@ -378,8 +378,26 @@ Craft.CustomizeSourcesModal = Garnish.Modal.extend({ let source; if (sourceData.type === 'heading') { + // Sources pre 5.8 don't have a `key` so we need to add one if it is missing. + if (!sourceData.key) { + sourceData.key = `heading:${Craft.uuid()}` + } + $item.addClass('heading'); - $itemInput.attr('name', 'sourceOrder[][heading]'); + $itemInput.attr('name', 'sourceOrder[][key]').val(sourceData.key); + + /** + * We add this here so it will get sent in every POST request. + * This ensures that header sources will be updated to the new format + * on save. + * When updating, this will result in two `sources[${key}][heading]` values + * getting sent to the server, but that should be fine. + */ + $('') + .attr('name', `sources[${sourceData.key}][heading]`) + .val(sourceData.heading) + .appendTo($item); + source = new Craft.CustomizeSourcesModal.Heading( this, $item, @@ -1058,6 +1076,7 @@ Craft.CustomizeSourcesModal.Heading = const $labelField = Craft.ui .createTextField({ label: Craft.t('app', 'Heading'), + name: `sources[${this.sourceData.key}][heading]`, instructions: Craft.t( 'app', 'This can be left blank if you just want an unlabeled separator.' @@ -1093,7 +1112,7 @@ Craft.CustomizeSourcesModal.Heading = ? Craft.escapeHtml(val) : `${Craft.t('app', '(blank)')}`) + ' ' ); - this.$itemInput.val(val); + // this.$itemInput.val(val); }, getIndexSourceItem: function () { From 8f0f1dad1de2de636e367066b711d3c9dbb1a325 Mon Sep 17 00:00:00 2001 From: Brian Hanson Date: Wed, 30 Apr 2025 14:51:40 -0500 Subject: [PATCH 02/21] Add collapsible field --- src/controllers/ElementIndexSettingsController.php | 1 + src/web/assets/cp/src/js/CustomizeSourcesModal.js | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/src/controllers/ElementIndexSettingsController.php b/src/controllers/ElementIndexSettingsController.php index a0bb657f715..c23d6be9d61 100644 --- a/src/controllers/ElementIndexSettingsController.php +++ b/src/controllers/ElementIndexSettingsController.php @@ -304,6 +304,7 @@ public function actionSaveCustomizeSourcesModalSettings(): Response } } elseif ($type === ElementSources::TYPE_HEADING) { $sourceConfig['heading'] = $postedSettings['heading']; + $sourceConfig['collapsible'] = ($postedSettings['collapsible'] ?? false) === '1'; } elseif (isset($postedSettings['enabled'])) { $sourceConfig['disabled'] = !$postedSettings['enabled']; if ($sourceConfig['disabled']) { diff --git a/src/web/assets/cp/src/js/CustomizeSourcesModal.js b/src/web/assets/cp/src/js/CustomizeSourcesModal.js index b2d22fec0d5..d29c7b47976 100644 --- a/src/web/assets/cp/src/js/CustomizeSourcesModal.js +++ b/src/web/assets/cp/src/js/CustomizeSourcesModal.js @@ -1086,6 +1086,13 @@ Craft.CustomizeSourcesModal.Heading = .appendTo($container); this.$labelInput = $labelField.find('.text'); + Craft.ui.createLightswitchField({ + label: Craft.t('app', 'Collapsible'), + name: `sources[${this.sourceData.key}][collapsible]`, + on: this.sourceData.collapsible, + }) + .appendTo($container); + $container.append('
'); this.$deleteBtn = $('') From 285abc6ffb5f13f4c853485aac927417adb9cb8c Mon Sep 17 00:00:00 2001 From: Brian Hanson Date: Wed, 30 Apr 2025 15:43:26 -0500 Subject: [PATCH 03/21] Ugly working version --- .../ElementIndexSettingsController.php | 8 ++++++-- src/templates/_elements/sources.twig | 7 ++++++- src/web/assets/cp/src/css/_cp.scss | 15 +++++++++++++++ src/web/assets/cp/src/css/_main.scss | 1 + 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/controllers/ElementIndexSettingsController.php b/src/controllers/ElementIndexSettingsController.php index c23d6be9d61..1fc70e5d22a 100644 --- a/src/controllers/ElementIndexSettingsController.php +++ b/src/controllers/ElementIndexSettingsController.php @@ -243,7 +243,7 @@ public function actionSaveCustomizeSourcesModalSettings(): Response // Get the old source configs $projectConfig = Craft::$app->getProjectConfig(); $oldSourceConfigs = $projectConfig->get(ProjectConfig::PATH_ELEMENT_SOURCES . ".$elementType") ?? []; - $oldSourceConfigs = ArrayHelper::index(array_filter($oldSourceConfigs, fn($s) => $s['type'] !== ElementSources::TYPE_HEADING), 'key'); + $oldSourceConfigs = ArrayHelper::index($oldSourceConfigs, 'key'); $conditionsService = Craft::$app->getConditions(); @@ -304,7 +304,11 @@ public function actionSaveCustomizeSourcesModalSettings(): Response } } elseif ($type === ElementSources::TYPE_HEADING) { $sourceConfig['heading'] = $postedSettings['heading']; - $sourceConfig['collapsible'] = ($postedSettings['collapsible'] ?? false) === '1'; + if (isset($postedSettings['collapsible'])) { + $sourceConfig['collapsible'] = $postedSettings['collapsible'] === '1'; + } else { + $sourceConfig['collapsible'] = $oldSourceConfigs[$source['key']]['collapsible'] ?? false; + } } elseif (isset($postedSettings['enabled'])) { $sourceConfig['disabled'] = !$postedSettings['enabled']; if ($sourceConfig['disabled']) { diff --git a/src/templates/_elements/sources.twig b/src/templates/_elements/sources.twig index 8f867a6d7c5..be100ab1923 100644 --- a/src/templates/_elements/sources.twig +++ b/src/templates/_elements/sources.twig @@ -92,7 +92,12 @@ {% endif %}
  • {{ source.heading|t('site') }} -