diff --git a/classes/category/Repository.php b/classes/category/Repository.php index f2f5b383f2b..590c42e1bc7 100644 --- a/classes/category/Repository.php +++ b/classes/category/Repository.php @@ -104,6 +104,45 @@ public function getBreadcrumbs(LazyCollection $categories): LazyCollection })->filter(fn ($breadcrumb) => $breadcrumb !== ''); // Filter out empty breadcrumbs due to circular references. } + /** + * Create tree data structure compatible with Autosuggest fields Vocabulary + */ + public function getCategoryVocabularyStructure(LazyCollection $categories): array + { + // Build a map of nodes + $map = []; + $categories->each(function ($item) use (&$map) { + $id = $item->getId(); + $map[$id] = [ + 'label' => $item->getLocalizedTitle(), + 'value' => $id, + 'selectable' => true + ]; + }); + + // Link children to their parents + $categories->each(function ($item) use (&$map) { + $parentId = $item->getData('parentId'); + if ($parentId !== null && isset($map[$parentId])) { + if (!$map[$parentId]['items']) { + $map[$parentId]['items'] = []; + } + $map[$parentId]['items'][] = &$map[$item->getId()]; + } + }); + + // Collect root items (those with no parentId) + $hierarchy = []; + $categories->each(function ($item) use (&$map, &$hierarchy) { + if ($item->getData('parentId') === null) { + $hierarchy[] = &$map[$item->getId()]; + } + }); + + return $hierarchy; + + } + /** @copydoc DAO::getCollector() */ public function getCollector(): Collector { diff --git a/classes/components/forms/FieldBaseAutosuggest.php b/classes/components/forms/FieldBaseAutosuggest.php index 8e49b5de674..28afea1f981 100644 --- a/classes/components/forms/FieldBaseAutosuggest.php +++ b/classes/components/forms/FieldBaseAutosuggest.php @@ -1,4 +1,5 @@ string, // e.g. 'en' + * 'addButtonLabel' => string, // e.g. 'Add subject' + * 'modalTitleLabel' => string, // modal title + * 'modalComponent'? => string, // custom component (default: VocabularyModal) + * 'items' => array[ // tree of nodes + * [ + * 'label' => string, // display text + * 'value' => int|array{ // either simple ID (for FieldAutosuggestPreset which has predefined options, e.g. Categories ) + * // or full payload (for FieldAutosuggestControlledVocab) + * identifier: string, // code (e.g. '1.2') + * name: string, // display name + * source?: string // optional source + * }, + * 'selectable'? => bool, // leaf nodes (default: false) + * 'items'? => array[] // child nodes + * ], + * … + * ] + * ], + * … + * ] + */ + public array $vocabularies = []; + + + /** * @copydoc Field::getConfig() */ @@ -43,6 +76,7 @@ public function getConfig() $config['getParams'] = empty($this->getParams) ? new \stdClass() : $this->getParams; $config['selectedLabel'] = __('common.selectedPrefix'); $config['selected'] = $this->selected; + $config['vocabularies'] = $this->vocabularies; return $config; } diff --git a/classes/components/forms/dashboard/PKPSubmissionFilters.php b/classes/components/forms/dashboard/PKPSubmissionFilters.php index 2a213a9f790..1d92ddf662b 100644 --- a/classes/components/forms/dashboard/PKPSubmissionFilters.php +++ b/classes/components/forms/dashboard/PKPSubmissionFilters.php @@ -29,12 +29,6 @@ class PKPSubmissionFilters extends FormComponent { - /** - * The maximum number of options in a field - * before it should be shown as an autosuggest - */ - public const OPTIONS_MAX = 7; - public $id = 'submissionFilters'; public $action = FormComponent::ACTION_EMIT; @@ -136,19 +130,26 @@ protected function addCategories(): self // Check if all categories have a breadcrumb; categories with circular references are filtered out $hasAllBreadcrumbs = $this->categories->count() === count($options); + + $vocabulary = Repo::category()->getCategoryVocabularyStructure($this->categories); + $props = [ 'groupId' => 'default', 'label' => __('category.category'), 'description' => $hasAllBreadcrumbs ? '' : __('submission.categories.circularReferenceWarning'), 'options' => $options, 'value' => [], - ]; + 'vocabularies' => [ + [ + 'addButtonLabel' => __('grid.category.add'), + 'modalTitleLabel' => __('grid.category.add'), + 'items' => $vocabulary + ] + ] - if ($this->categories->count() > self::OPTIONS_MAX) { - return $this->addField(new FieldAutosuggestPreset('categoryIds', $props)); - } + ]; - return $this->addField(new FieldOptions('categoryIds', $props)); + return $this->addField(new FieldAutosuggestPreset('categoryIds', $props)); } protected function addDaysSinceLastActivity(): self diff --git a/classes/components/forms/submission/ForTheEditors.php b/classes/components/forms/submission/ForTheEditors.php index bde2e3b35ea..fbae93def30 100644 --- a/classes/components/forms/submission/ForTheEditors.php +++ b/classes/components/forms/submission/ForTheEditors.php @@ -21,7 +21,6 @@ use APP\submission\Submission; use Illuminate\Support\LazyCollection; use PKP\components\forms\FieldAutosuggestPreset; -use PKP\components\forms\FieldOptions; use PKP\components\forms\publication\PKPMetadataForm; use PKP\context\Context; @@ -110,20 +109,19 @@ protected function addCategoryField(Context $context, LazyCollection $categories // Check if all categories have a breadcrumb; categories with circular references are filtered out $hasAllBreadcrumbs = $categories->count() === count($categoryOptions); - if (count($categoryOptions) > self::MAX_CATEGORY_LIST_SIZE) { - $this->addField(new FieldAutosuggestPreset('categoryIds', [ - 'label' => __('submission.submit.placement.categories'), - 'description' => $hasAllBreadcrumbs ? __('submission.wizard.categories.description') : __('submission.wizard.categories.descriptionWithCircularReferenceWarning'), - 'value' => $categoryValues, - 'options' => $categoryOptions - ])); - } else { - $this->addField(new FieldOptions('categoryIds', [ - 'label' => __('submission.submit.placement.categories'), - 'description' => $hasAllBreadcrumbs ? __('submission.wizard.categories.description') : __('submission.wizard.categories.descriptionWithCircularReferenceWarning'), - 'value' => $categoryValues, - 'options' => $categoryOptions, - ])); - } + $vocabulary = Repo::category()->getCategoryVocabularyStructure($categories); + $this->addField(new FieldAutosuggestPreset('categoryIds', [ + 'label' => __('submission.submit.placement.categories'), + 'description' => $hasAllBreadcrumbs ? __('submission.wizard.categories.description') : __('submission.wizard.categories.descriptionWithCircularReferenceWarning'), + 'value' => $categoryValues, + 'options' => $categoryOptions, + 'vocabularies' => [ + [ + 'addButtonLabel' => __('grid.category.add'), + 'modalTitleLabel' => __('grid.category.add'), + 'items' => $vocabulary + ] + ] + ])); } } diff --git a/locale/en/common.po b/locale/en/common.po index 36775261564..cb9538da089 100644 --- a/locale/en/common.po +++ b/locale/en/common.po @@ -2105,6 +2105,12 @@ msgstr "Expand all" msgid "list.collapseAll" msgstr "Collapse all" +msgid "list.expand" +msgstr "Expand" + +msgid "list.collapse" +msgstr "Collapse" + msgid "showAll" msgstr "Show All"