Compare commits

...

3 Commits

Author SHA1 Message Date
Yurii
c392fabc14 categories for custom entity types 2026-02-24 17:10:45 +02:00
Yurii
1de9046b3d grunt minimatch override 2026-02-24 12:48:42 +02:00
Yurii
b007bef366 Revert "override dependency"
This reverts commit 33c8e202bc.
2026-02-24 12:36:45 +02:00
19 changed files with 618 additions and 121 deletions

View File

@@ -32,4 +32,6 @@ namespace Espo\Core\Templates\Entities;
use Espo\Core\Entities\CategoryTreeItem;
class CategoryTree extends CategoryTreeItem
{}
{
public const string TEMPLATE_TYPE = 'CategoryTree';
}

View File

@@ -9,6 +9,13 @@
"link": "#{entityType}/list",
"acl": "read",
"style": "default"
},
{
"labelTranslation": "Global.scopeNamesPlural.{subjectEntityType}",
"link": "#{subjectEntityType}",
"acl": "read",
"style": "default",
"aclScope": "{subjectEntityType}"
}
]
},
@@ -19,6 +26,13 @@
"link": "#{entityType}",
"acl": "read",
"style": "default"
},
{
"labelTranslation": "Global.scopeNamesPlural.{subjectEntityType}",
"link": "#{subjectEntityType}",
"acl": "read",
"style": "default",
"aclScope": "{subjectEntityType}"
}
]
}
@@ -29,4 +43,4 @@
"unlinkDisabled": true
}
}
}
}

View File

@@ -1,10 +1,26 @@
{
"entity": true,
"layouts": true,
"tab": true,
"tab": false,
"acl": true,
"aclPortal": true,
"customizable": true,
"aclLevelList": [
"all",
"team",
"no"
],
"aclPortalLevelList": [
"all",
"no"
],
"importable": false,
"notifications": false
}
"notifications": false,
"customizable": false,
"entityManager": {
"fields": false,
"formula": false,
"relationships": false,
"addField": false,
"edit": false,
"layouts": false
}
}

View File

@@ -0,0 +1,5 @@
{
"labels": {
"Create {entityType}": "Create {entityTypeTranslated}"
}
}

View File

@@ -55,7 +55,8 @@
"assignedUsers": "Multiple Assigned Users",
"collaborators": "Collaborators",
"aclContactLink": "ACL Contact Link",
"aclAccountLink": "ACL Account Link"
"aclAccountLink": "ACL Account Link",
"categories": "Categories"
},
"options": {
"type": {
@@ -98,6 +99,7 @@
"beforeSaveApiScript": "A script called on create and update API requests before an entity is saved. Use for custom validation and duplicate checking."
},
"tooltips": {
"categories": "Enable the category tree feature. Records can be placed into categories.",
"aclContactLink": "The link with Contact to use when applying access control for portal users.",
"aclAccountLink": "The link with Account to use when applying access control for portal users.",
"collaborators": "The ability to share records with specific users.",
@@ -118,5 +120,11 @@
"countDisabled": "Total number won't be displayed on the list view. Can decrease loading time when the DB table is big.",
"fullTextSearch": "Running rebuild is required.",
"linkParamReadOnly": "A read-only link cannot be edited via the *link* and *unlink* API requests. It won't be possible to relate and unrelate records via the relationship panel. It still possible to edit read-only links via link and link-multiple fields."
},
"entityNameParts": {
"Category": "Category"
},
"entityNamePartsPlural": {
"Category": "Categories"
}
}

View File

@@ -521,7 +521,8 @@
"names": "Names",
"types": "Types",
"targetListIsOptedOut": "Is Opted Out (Target List)",
"childList": "Child List"
"childList": "Child List",
"category": "Category"
},
"links": {
"assignedUser": "Assigned User",

View File

@@ -6,11 +6,13 @@
"deleteHookClassNameList": [
"Espo\\Tools\\EntityManager\\Hook\\Hooks\\PlusDeleteHook",
"Espo\\Tools\\EntityManager\\Hook\\Hooks\\EventDeleteHook",
"Espo\\Tools\\EntityManager\\Hook\\Hooks\\DeleteHasChildrenLinks"
"Espo\\Tools\\EntityManager\\Hook\\Hooks\\DeleteHasChildrenLinks",
"Espo\\Tools\\EntityManager\\Hook\\Hooks\\CategoriesDeleteHook"
],
"updateHookClassNameList": [
"Espo\\Tools\\EntityManager\\Hook\\Hooks\\AssignedUsersUpdateHook",
"Espo\\Tools\\EntityManager\\Hook\\Hooks\\CollaboratorsUpdateHook",
"Espo\\Tools\\EntityManager\\Hook\\Hooks\\StreamUpdateHook"
"Espo\\Tools\\EntityManager\\Hook\\Hooks\\StreamUpdateHook",
"Espo\\Tools\\EntityManager\\Hook\\Hooks\\CategoriesUpdateHook"
]
}

View File

@@ -167,6 +167,13 @@
"tooltip": true,
"view": "views/admin/entity-manager/fields/acl-account-link"
}
},
"categories": {
"location": "scopes",
"fieldDefs": {
"type": "bool",
"tooltip": true
}
}
},
"@BasePlus": {
@@ -216,6 +223,13 @@
"tooltip": true,
"view": "views/admin/entity-manager/fields/acl-account-link"
}
},
"categories": {
"location": "scopes",
"fieldDefs": {
"type": "bool",
"tooltip": true
}
}
}
}

View File

@@ -1,7 +1,8 @@
[
"Base",
"BasePlus",
"Event",
"Person",
"Company"
]
"Base",
"BasePlus",
"Event",
"Person",
"Company",
"CategoryTree"
]

View File

@@ -18,5 +18,10 @@
"Person": {
"entityClassName": "Espo\\Core\\Templates\\Entities\\Person",
"repositoryClassName": "Espo\\Core\\Templates\\Repositories\\Person"
},
"CategoryTree": {
"entityClassName": "Espo\\Core\\Templates\\Entities\\CategoryTree",
"repositoryClassName": "Espo\\Core\\Templates\\Repositories\\CategoryTree",
"isNotCreatable": true
}
}

View File

@@ -29,26 +29,16 @@
namespace Espo\Tools\EntityManager;
class CreateParams
readonly class CreateParams
{
/**
* @param array<string, string> $replaceData
*/
public function __construct(
private bool $forceCreate = false,
private array $replaceData = []
public bool $forceCreate = false,
public array $replaceData = [],
public bool $skipCustomPrefix = false,
public bool $isNotRemovable = false,
public bool $addTab = true,
) {}
public function forceCreate(): bool
{
return $this->forceCreate;
}
/**
* @return array<string, string>
*/
public function getReplaceData(): array
{
return $this->replaceData;
}
}

View File

@@ -103,11 +103,13 @@ class EntityManager
/** @var array<string, mixed> $templateDefs */
$templateDefs = $this->metadata->get(['app', 'entityTemplates', $type], []);
if (!empty($templateDefs['isNotCreatable']) && !$createParams->forceCreate()) {
if (!empty($templateDefs['isNotCreatable']) && !$createParams->forceCreate) {
throw new Error("Type '$type' is not creatable.");
}
$name = $this->nameUtil->addCustomPrefix($name, true);
if (!$createParams->skipCustomPrefix) {
$name = $this->nameUtil->addCustomPrefix($name, true);
}
if ($this->nameUtil->nameIsBad($name)) {
throw new Error("Entity name should contain only letters and numbers, " .
@@ -206,7 +208,7 @@ class EntityManager
}
$languageContents = $this->fileManager->getContents($filePath);
$languageContents = $this->replace($languageContents, $name, $createParams->getReplaceData());
$languageContents = $this->replace($languageContents, $name, $createParams->replaceData);
$languageContents = str_replace('{entityTypeTranslated}', $labelSingular, $languageContents);
$destinationFilePath = 'custom/Espo/Custom/Resources/i18n/' . $language . '/' . $name . '.json';
@@ -217,7 +219,7 @@ class EntityManager
$filePath = $templatePath . "/Metadata/$type/scopes.json";
$scopesDataContents = $this->fileManager->getContents($filePath);
$scopesDataContents = $this->replace($scopesDataContents, $name, $createParams->getReplaceData());
$scopesDataContents = $this->replace($scopesDataContents, $name, $createParams->replaceData);
$scopesData = Json::decode($scopesDataContents, true);
@@ -228,7 +230,7 @@ class EntityManager
$scopesData['object'] = true;
$scopesData['isCustom'] = true;
if (!empty($templateDefs['isNotRemovable']) || !empty($params['isNotRemovable'])) {
if ($createParams->isNotRemovable) {
$scopesData['isNotRemovable'] = true;
}
@@ -241,7 +243,7 @@ class EntityManager
$filePath = $templatePath . "/Metadata/$type/entityDefs.json";
$entityDefsDataContents = $this->fileManager->getContents($filePath);
$entityDefsDataContents = $this->replace($entityDefsDataContents, $name, $createParams->getReplaceData());
$entityDefsDataContents = $this->replace($entityDefsDataContents, $name, $createParams->replaceData);
$entityDefsData = Json::decode($entityDefsDataContents, true);
@@ -250,7 +252,7 @@ class EntityManager
$filePath = $templatePath . "/Metadata/$type/clientDefs.json";
$clientDefsContents = $this->fileManager->getContents($filePath);
$clientDefsContents = $this->replace($clientDefsContents, $name, $createParams->getReplaceData());
$clientDefsContents = $this->replace($clientDefsContents, $name, $createParams->replaceData);
$clientDefsData = Json::decode($clientDefsContents, true);
@@ -287,13 +289,15 @@ class EntityManager
$this->processCreateHook($entityTypeParams);
$tabList = $this->config->get('tabList', []);
if ($createParams->addTab) {
$tabList = $this->config->get('tabList', []);
if (!in_array($name, $tabList)) {
$tabList[] = $name;
if (!in_array($name, $tabList)) {
$tabList[] = $name;
$this->configWriter->set('tabList', $tabList);
$this->configWriter->save();
$this->configWriter->set('tabList', $tabList);
$this->configWriter->save();
}
}
$this->dataManager->rebuild();
@@ -522,7 +526,7 @@ class EntityManager
throw new Forbidden;
}
if (!$this->isScopeCustomizable($name)) {
if (!$this->isScopeCustomizable($name) && !$deleteParams->forceRemove()) {
throw new Error("Entity type $name is not customizable.");
}

View File

@@ -0,0 +1,66 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Tools\EntityManager\Hook\Hooks;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Templates\Entities\Base;
use Espo\Core\Templates\Entities\BasePlus;
use Espo\Tools\EntityManager\Hook\DeleteHook;
use Espo\Tools\EntityManager\Params;
/**
* @noinspection PhpUnused
*/
class CategoriesDeleteHook implements DeleteHook
{
private const string PARAM = 'categories';
public function __construct(
private CategoriesUpdateHook $categoriesUpdateHook,
) {}
/**
* @throws Forbidden
* @throws Error
*/
public function process(Params $params): void
{
if (!in_array($params->getType(), [BasePlus::TEMPLATE_TYPE, Base::TEMPLATE_TYPE])) {
return;
}
if (!$params->get(self::PARAM)) {
return;
}
$this->categoriesUpdateHook->remove($params->getName());
}
}

View File

@@ -0,0 +1,203 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Tools\EntityManager\Hook\Hooks;
use Espo\Core\DataManager;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Conflict;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\InjectableFactory;
use Espo\Core\ORM\Type\FieldType;
use Espo\Core\Templates\Entities\Base;
use Espo\Core\Templates\Entities\BasePlus;
use Espo\Core\Templates\Entities\CategoryTree;
use Espo\Core\Utils\Language;
use Espo\Core\Utils\Log;
use Espo\Core\Utils\Metadata;
use Espo\ORM\Defs\Params\FieldParam;
use Espo\ORM\Defs\Params\RelationParam;
use Espo\ORM\Type\RelationType;
use Espo\Tools\EntityManager\CreateParams;
use Espo\Tools\EntityManager\DeleteParams;
use Espo\Tools\EntityManager\EntityManager;
use Espo\Tools\EntityManager\Hook\UpdateHook;
use Espo\Tools\EntityManager\Params;
use Espo\Tools\LayoutManager\LayoutCustomizer;
use Espo\Tools\LayoutManager\LayoutName;
/**
* @noinspection PhpUnused
*/
class CategoriesUpdateHook implements UpdateHook
{
private const string PARAM = 'categories';
private const string FIELD = 'category';
public function __construct(
private InjectableFactory $injectableFactory,
private Language $defaultLanguage,
private Metadata $metadata,
private DataManager $dataManager,
private LayoutCustomizer $layoutCustomizer,
private Log $log,
) {}
/**
* @throws BadRequest
* @throws Forbidden
* @throws Error
* @throws Conflict
*/
public function process(Params $params, Params $previousParams): void
{
if (!in_array($params->getType(), [BasePlus::TEMPLATE_TYPE, Base::TEMPLATE_TYPE])) {
return;
}
if ($params->get(self::PARAM) && !$previousParams->get(self::PARAM)) {
$this->add($params->getName());
} else if (!$params->get(self::PARAM) && $previousParams->get(self::PARAM)) {
$this->remove($params->getName());
}
}
/**
* @throws BadRequest
* @throws Conflict
* @throws Error
*/
private function add(string $name): void
{
$entityType = $this->composeEntityType($name);
if ($this->metadata->get("scopes.$entityType")) {
$message = "Could not create category entity type $entityType as the same entity type exists";
$this->log->warning($message);
return;
}
$createParams = new CreateParams(
forceCreate: true,
replaceData: [
'subjectEntityType' => $name,
],
skipCustomPrefix: true,
isNotRemovable: true,
addTab: false,
);
$this->getEntityManagerTool()->create(
name: $entityType,
type: CategoryTree::TEMPLATE_TYPE,
params: [
'labelSingular' => $this->defaultLanguage->translateLabel($name, 'scopeNames') . ' ' .
$this->defaultLanguage->translateLabel('Category', 'entityNameParts', 'EntityManager'),
'labelPlural' => $this->defaultLanguage->translateLabel($name, 'scopeNames') . ' ' .
$this->defaultLanguage->translateLabel('Category', 'entityNamePartsPlural', 'EntityManager')
],
createParams: $createParams,
);
$this->metadata->set('entityDefs', $name, [
'fields' => [
self::FIELD => [
FieldParam::TYPE => FieldType::LINK,
'audited' => true,
'view' => 'views/fields/link-category-tree'
]
],
'links' => [
self::FIELD => [
RelationParam::TYPE => RelationType::BELONGS_TO,
RelationParam::ENTITY => $entityType,
]
],
]);
$this->metadata->set('clientDefs', $name, [
'views' => [
'list' => 'views/list-with-categories',
],
'modalViews' => [
'select' => 'views/modals/select-records-with-categories',
],
]);
$this->metadata->save();
$this->dataManager->rebuild();
$this->layoutCustomizer->addDetailField($name, self::FIELD, LayoutName::DETAIL);
$this->layoutCustomizer->addDetailField($name, self::FIELD, LayoutName::DETAIL_SMALL);
}
/**
* @throws Forbidden
* @throws Error
*/
public function remove(string $name): void
{
$entityType = $this->composeEntityType($name);
$deleteParams = new DeleteParams(
forceRemove: true,
);
$this->getEntityManagerTool()->delete($entityType, $deleteParams);
$this->metadata->delete('entityDefs', $name, [
'fields.' . self::FIELD,
'links.' . self::FIELD,
]);
$this->metadata->delete('clientDefs', $name, [
'views.list',
'modalViews.select',
]);
$this->metadata->save();
$this->dataManager->rebuild();
$this->layoutCustomizer->removeInDetail($name, self::FIELD, LayoutName::DETAIL);
$this->layoutCustomizer->removeInDetail($name, self::FIELD, LayoutName::DETAIL_SMALL);
}
private function getEntityManagerTool(): EntityManager
{
return $this->injectableFactory->create(EntityManager::class);
}
private function composeEntityType(string $name): string
{
return $name . 'Category';
}
}

View File

@@ -0,0 +1,189 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Tools\LayoutManager;
use Espo\Core\Exceptions\Error;
use Espo\Core\Utils\Json;
use Espo\Tools\Layout\LayoutProvider;
use RuntimeException;
use stdClass;
/**
* @since 9.4.0
*/
class LayoutCustomizer
{
public function __construct(
private LayoutProvider $layoutProvider,
private LayoutManager $layoutManager,
) {}
/**
* @throws Error
*/
public function addDetailField(string $entityType, string $field, string $layoutName): void
{
$layoutData = $this->getDetailLayout($entityType, $layoutName);
if ($this->hasInDetail($layoutData, $field)) {
return;
}
$lastPanel = $layoutData[count($layoutData) - 1];
if (!$lastPanel instanceof stdClass) {
throw new RuntimeException("Bad layout panel definition in $entityType.$layoutName.");
}
if (isset($lastPanel->cols) && is_array($lastPanel->cols)) {
$cols = $lastPanel->cols;
$cols[] = [[(object) ['name' => $field]]];
$lastPanel->cols = $cols;
} else {
$rows = $lastPanel->rows ?? [];
$rows[] = [(object) ['name' => $field], false];
$lastPanel->rows = $rows;
}
$this->layoutManager->set($layoutData, $entityType, $layoutName);
$this->layoutManager->save();
}
/**
* @throws Error
*/
public function removeInDetail(string $entityType, string $field, string $layoutName): void
{
$panels = $this->getDetailLayout($entityType, $layoutName);
$cell = $this->getCellFromDetail($panels, $field);
if (!$cell) {
return;
}
foreach ($panels as $panelItem) {
if (isset($panelItem->cols)) {
$rowItems =& $panelItem->cols;
} else if (isset($panelItem->rows)) {
$rowItems =& $panelItem->rows;
} else {
continue;
}
if (!is_array($rowItems)) {
continue;
}
foreach ($rowItems as &$rowItem) {
if (!is_array($rowItem)) {
continue;
}
foreach ($rowItem as $i => $cellItem) {
if (!$cellItem instanceof stdClass) {
continue;
}
if (($cellItem->name ?? null) === $field) {
$rowItem[$i] = false;
}
}
}
}
$this->layoutManager->set($panels, $entityType, $layoutName);
$this->layoutManager->save();
}
/**
* @param array<int, mixed> $panels
*/
private function hasInDetail(array $panels, string $field): bool
{
return $this->getCellFromDetail($panels, $field) !== null;
}
/**
* @param array<int, mixed> $panels
*/
private function getCellFromDetail(array $panels, string $field): ?stdClass
{
foreach ($panels as $panelItem) {
$rowItems = $panelItem->cols ?? $panelItem->rows ?? null;
if (!is_array($rowItems)) {
continue;
}
foreach ($rowItems as $rowItem) {
if (!is_array($rowItem)) {
continue;
}
foreach ($rowItem as $cellItem) {
if (!$cellItem instanceof stdClass) {
continue;
}
if (($cellItem->name ?? null) === $field) {
return $cellItem;
}
}
}
}
return null;
}
/**
* @return array<int, mixed>
*/
private function getDetailLayout(string $entityType, string $layoutName): array
{
$layoutString = $this->layoutProvider->get($entityType, $layoutName);
if (!$layoutString) {
$layoutString = '[]';
}
$layoutData = Json::decode($layoutString);
if (!is_array($layoutData)) {
throw new RuntimeException("Bad layout $entityType.$layoutName.");
}
return $layoutData;
}
}

View File

@@ -0,0 +1,39 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Tools\LayoutManager;
/**
* @since 9.4.0
*/
class LayoutName
{
const string DETAIL = 'detail';
const string DETAIL_SMALL = 'detailSmall';
}

View File

@@ -381,12 +381,15 @@ class EntityManagerEditView extends View {
setupDefs() {
const scope = this.scope;
const typeList = (this.getMetadata().get('app.entityTemplateList') || ['Base'])
.filter(it => !this.getMetadata().get(`app.entityTemplates.${it}.isNotCreatable`));
const defs = {
fields: {
type: {
type: 'enum',
required: true,
options: this.getMetadata().get('app.entityTemplateList') || ['Base'],
options: typeList,
readOnly: scope !== false,
tooltip: true,
},

77
package-lock.json generated
View File

@@ -2966,8 +2966,7 @@
"node_modules/balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"node_modules/bare-events": {
"version": "2.8.2",
@@ -3133,7 +3132,6 @@
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -3523,8 +3521,7 @@
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"node_modules/content-disposition": {
"version": "0.5.4",
@@ -5278,27 +5275,6 @@
"node": ">=10"
}
},
"node_modules/grunt/node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/grunt/node_modules/brace-expansion": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
"integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/grunt/node_modules/glob": {
"version": "7.1.7",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
@@ -5373,21 +5349,6 @@
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/grunt/node_modules/minimatch": {
"version": "9.0.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz",
"integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^5.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/gzip-size": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz",
@@ -6517,7 +6478,6 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz",
"integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
@@ -10537,8 +10497,7 @@
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"bare-events": {
"version": "2.8.2",
@@ -10651,7 +10610,6 @@
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -10897,8 +10855,7 @@
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"content-disposition": {
"version": "0.5.4",
@@ -11984,23 +11941,10 @@
"grunt-legacy-util": "~2.0.1",
"iconv-lite": "~0.6.3",
"js-yaml": "~3.14.0",
"minimatch": "~9.0.6",
"minimatch": "~3.1.3",
"nopt": "~3.0.6"
},
"dependencies": {
"balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="
},
"brace-expansion": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
"integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
"requires": {
"balanced-match": "^4.0.2"
}
},
"glob": {
"version": "7.1.7",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
@@ -12009,7 +11953,7 @@
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "~9.0.6",
"minimatch": "~3.1.3",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
@@ -12053,14 +11997,6 @@
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"minimatch": {
"version": "9.0.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz",
"integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==",
"requires": {
"brace-expansion": "^5.0.2"
}
}
}
},
@@ -13019,7 +12955,6 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz",
"integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}

View File

@@ -104,8 +104,8 @@
"node": ">=20"
},
"overrides": {
"grunt": {
"minimatch": "~9.0.6"
}
"grunt": {
"minimatch": "~3.1.3"
}
}
}