From df2f857197d1e4c6489c7cb875fd5e8012f1678f Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Thu, 9 Oct 2025 09:24:09 +0300 Subject: [PATCH 01/35] prevent action handler fired twice --- client/src/utils.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/src/utils.js b/client/src/utils.js index c9a3cef3d3..0f33f7249d 100644 --- a/client/src/utils.js +++ b/client/src/utils.js @@ -121,6 +121,11 @@ Espo.Utils = { }); } else if (typeof view[method] === 'function') { + if (view?.events[`click [data-action="${action}"]`]) { + // Prevents from firing if a handler is already assigned. Important. + return false; + } + view[method].call(view, data, event); event.preventDefault(); From 062008374d69a5f99059ee662db9483aca3af8ab Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Thu, 9 Oct 2025 10:12:25 +0300 Subject: [PATCH 02/35] comment --- client/src/view.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/view.js b/client/src/view.js index 14ab1fd046..151c8dca23 100644 --- a/client/src/view.js +++ b/client/src/view.js @@ -108,6 +108,7 @@ class View extends BullView { * @param {module:view~actionHandlerCallback} handler A handler. */ addActionHandler(action, handler) { + // The key should be in sync with one in Utils.handleAction. const fullAction = `click [data-action="${action}"]`; this.events[fullAction] = e => { From 046d94ceb8b442d02706d95957587b393f0f3508 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Thu, 9 Oct 2025 10:42:51 +0300 Subject: [PATCH 03/35] update note ui impr --- client/res/templates/stream/notes/update.tpl | 8 ++++--- client/src/views/stream/notes/update.js | 24 ++++++++++---------- frontend/less/espo/elements/type.less | 7 ++++++ 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/client/res/templates/stream/notes/update.tpl b/client/res/templates/stream/notes/update.tpl index 903f975436..80cdc4dda7 100644 --- a/client/res/templates/stream/notes/update.tpl +++ b/client/res/templates/stream/notes/update.tpl @@ -25,9 +25,11 @@ role="button" tabindex="0" data-action="expandDetails" - class="text-soft" - > - {{fieldsString}} + class="text-muted no-underline" + > + + {{fieldsString}} + {{/if}} diff --git a/client/src/views/stream/notes/update.js b/client/src/views/stream/notes/update.js index 647d5d7ec9..a56058d134 100644 --- a/client/src/views/stream/notes/update.js +++ b/client/src/views/stream/notes/update.js @@ -179,12 +179,14 @@ class UpdateNoteStreamView extends NoteStreamView { toggleDetails() { const target = this.element.querySelector('[data-action="expandDetails"]'); - const $details = this.$el.find('> .details'); - const $fields = this.$el.find('> .stream-details-container > .fields'); + const detailsElement = this.element.querySelector(':scope > .details'); + const fieldElement = this.element.querySelector(':scope > .stream-details-container > .fields'); + + const iconElement = target.querySelector('[data-role="icon"]'); if (!this.isExpanded) { - $details.removeClass('hidden'); - $fields.addClass('hidden'); + detailsElement.classList.remove('hidden'); + fieldElement?.classList.add('hidden'); this.fieldList.forEach(field => { const wasField = this.getView(field + 'Was'); @@ -196,21 +198,19 @@ class UpdateNoteStreamView extends NoteStreamView { } }); - $(target).find('span') - .removeClass('fa-chevron-down') - .addClass('fa-chevron-up'); + iconElement.classList.remove('fa-chevron-down'); + iconElement.classList.add('fa-chevron-up'); this.isExpanded = true; return; } - $details.addClass('hidden'); - $fields.removeClass('hidden'); + detailsElement.classList.add('hidden'); + fieldElement?.classList.remove('hidden'); - $(target).find('span') - .addClass('fa-chevron-down') - .removeClass('fa-chevron-up'); + iconElement.classList.remove('fa-chevron-up'); + iconElement.classList.add('fa-chevron-down'); this.isExpanded = false; } diff --git a/frontend/less/espo/elements/type.less b/frontend/less/espo/elements/type.less index 7ab2b98900..594dba435f 100644 --- a/frontend/less/espo/elements/type.less +++ b/frontend/less/espo/elements/type.less @@ -270,6 +270,13 @@ a.text-default { } } +a.no-underline { + &:hover, + &:focus { + text-decoration: none; + } +} + .nowrap { white-space: nowrap; } From 483e6e9d5bbb2c1cd51a787b52efaf4793db3dd7 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Fri, 10 Oct 2025 11:35:55 +0300 Subject: [PATCH 04/35] apply all filter --- .../Espo/Core/Formula/Functions/RecordGroup/FindOneType.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/application/Espo/Core/Formula/Functions/RecordGroup/FindOneType.php b/application/Espo/Core/Formula/Functions/RecordGroup/FindOneType.php index b8d504b2ae..77e969ce1a 100644 --- a/application/Espo/Core/Formula/Functions/RecordGroup/FindOneType.php +++ b/application/Espo/Core/Formula/Functions/RecordGroup/FindOneType.php @@ -36,6 +36,7 @@ use Espo\Core\Formula\Exceptions\Error as FormulaError; use Espo\Core\Formula\Functions\BaseFunction; use Espo\Core\Di; use Espo\Core\Formula\Functions\RecordGroup\Util\FindQueryUtil; +use Espo\Core\Select\Primary\Filters\All; use Espo\Core\Select\SelectBuilderFactory; use Espo\ORM\Name\Attribute; use Espo\ORM\Query\Part\Order; @@ -65,6 +66,7 @@ class FindOneType extends BaseFunction implements $builder = $this->injectableFactory->create(SelectBuilderFactory::class) ->create() ->forUser($this->user) + ->withPrimaryFilter(All::NAME) ->from($entityType); $whereClause = []; From 915283d2887059c7e24f8471223f84cd2decbde6 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Fri, 10 Oct 2025 11:46:54 +0300 Subject: [PATCH 05/35] use all primary filter --- .../Espo/Core/Formula/Functions/RecordGroup/CountType.php | 2 ++ .../Espo/Core/Formula/Functions/RecordGroup/ExistsType.php | 2 ++ .../Espo/Core/Formula/Functions/RecordGroup/FindManyType.php | 2 ++ .../Core/Formula/Functions/RecordGroup/FindRelatedManyType.php | 2 ++ .../Core/Formula/Functions/RecordGroup/FindRelatedOneType.php | 2 ++ 5 files changed, 10 insertions(+) diff --git a/application/Espo/Core/Formula/Functions/RecordGroup/CountType.php b/application/Espo/Core/Formula/Functions/RecordGroup/CountType.php index a8292a5795..ed760929ab 100644 --- a/application/Espo/Core/Formula/Functions/RecordGroup/CountType.php +++ b/application/Espo/Core/Formula/Functions/RecordGroup/CountType.php @@ -36,6 +36,7 @@ use Espo\Core\Formula\Exceptions\Error; use Espo\Core\Formula\Functions\BaseFunction; use Espo\Core\Formula\Functions\RecordGroup\Util\FindQueryUtil; use Espo\Core\Di; +use Espo\Core\Select\Primary\Filters\All; use Espo\Core\Select\SelectBuilderFactory; /** @@ -68,6 +69,7 @@ class CountType extends BaseFunction implements $builder = $this->injectableFactory->create(SelectBuilderFactory::class) ->create() ->forUser($this->user) + ->withPrimaryFilter(All::NAME) ->from($entityType); (new FindQueryUtil())->applyFilter($builder, $filter, 2); diff --git a/application/Espo/Core/Formula/Functions/RecordGroup/ExistsType.php b/application/Espo/Core/Formula/Functions/RecordGroup/ExistsType.php index 825e0cafc2..12d98357c5 100644 --- a/application/Espo/Core/Formula/Functions/RecordGroup/ExistsType.php +++ b/application/Espo/Core/Formula/Functions/RecordGroup/ExistsType.php @@ -36,6 +36,7 @@ use Espo\Core\Formula\Exceptions\Error; use Espo\Core\Formula\Functions\BaseFunction; use Espo\Core\Di; use Espo\Core\Formula\Functions\RecordGroup\Util\FindQueryUtil; +use Espo\Core\Select\Primary\Filters\All; use Espo\Core\Select\SelectBuilderFactory; /** @@ -68,6 +69,7 @@ class ExistsType extends BaseFunction implements $builder = $this->injectableFactory->create(SelectBuilderFactory::class) ->create() ->forUser($this->user) + ->withPrimaryFilter(All::NAME) ->from($entityType); (new FindQueryUtil())->applyFilter($builder, $filter, 2); diff --git a/application/Espo/Core/Formula/Functions/RecordGroup/FindManyType.php b/application/Espo/Core/Formula/Functions/RecordGroup/FindManyType.php index f614c96552..4d4c21e78f 100644 --- a/application/Espo/Core/Formula/Functions/RecordGroup/FindManyType.php +++ b/application/Espo/Core/Formula/Functions/RecordGroup/FindManyType.php @@ -38,6 +38,7 @@ use Espo\Core\Formula\Exceptions\Error as FormulaError; use Espo\Core\Formula\Exceptions\TooFewArguments; use Espo\Core\Formula\Func; use Espo\Core\Formula\Functions\RecordGroup\Util\FindQueryUtil; +use Espo\Core\Select\Primary\Filters\All; use Espo\Core\Select\SelectBuilderFactory; use Espo\ORM\Entity; use Espo\ORM\EntityManager; @@ -92,6 +93,7 @@ class FindManyType implements Func $builder = $this->selectBuilderFactory ->create() + ->withPrimaryFilter(All::NAME) ->from($entityType); $whereClause = []; diff --git a/application/Espo/Core/Formula/Functions/RecordGroup/FindRelatedManyType.php b/application/Espo/Core/Formula/Functions/RecordGroup/FindRelatedManyType.php index 40f946e621..b8692db001 100644 --- a/application/Espo/Core/Formula/Functions/RecordGroup/FindRelatedManyType.php +++ b/application/Espo/Core/Formula/Functions/RecordGroup/FindRelatedManyType.php @@ -41,6 +41,7 @@ use Espo\Core\Formula\ArgumentList; use Espo\Core\Formula\Functions\BaseFunction; use Espo\Core\Di; use Espo\Core\Select\Helpers\RandomStringGenerator; +use Espo\Core\Select\Primary\Filters\All; use Espo\Core\Select\SelectBuilderFactory; use Espo\ORM\Defs\Params\RelationParam; use Espo\ORM\Name\Attribute; @@ -165,6 +166,7 @@ class FindRelatedManyType extends BaseFunction implements $builder = $this->injectableFactory->create(SelectBuilderFactory::class) ->create() ->forUser($this->user) + ->withPrimaryFilter(All::NAME) ->from($foreignEntityType); $whereClause = []; diff --git a/application/Espo/Core/Formula/Functions/RecordGroup/FindRelatedOneType.php b/application/Espo/Core/Formula/Functions/RecordGroup/FindRelatedOneType.php index 74c48cf87b..50eb14f230 100644 --- a/application/Espo/Core/Formula/Functions/RecordGroup/FindRelatedOneType.php +++ b/application/Espo/Core/Formula/Functions/RecordGroup/FindRelatedOneType.php @@ -32,6 +32,7 @@ namespace Espo\Core\Formula\Functions\RecordGroup; use Espo\Core\Exceptions\BadRequest; use Espo\Core\Exceptions\Forbidden; use Espo\Core\ORM\Entity as CoreEntity; +use Espo\Core\Select\Primary\Filters\All; use Espo\Core\Select\SelectBuilderFactory; use Espo\ORM\Defs\Params\RelationParam; use Espo\ORM\Name\Attribute; @@ -151,6 +152,7 @@ class FindRelatedOneType extends BaseFunction implements $builder = $this->injectableFactory->create(SelectBuilderFactory::class) ->create() ->forUser($this->user) + ->withPrimaryFilter(All::NAME) ->from($foreignEntityType); $whereClause = []; From 2a76db68997fbf876dc30b07be3d12f6b7b07a79 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Sat, 11 Oct 2025 09:28:17 +0300 Subject: [PATCH 06/35] fix corrupted defs --- .../Core/Utils/Metadata/AdditionalBuilder/LogicDefsBc.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/application/Espo/Core/Utils/Metadata/AdditionalBuilder/LogicDefsBc.php b/application/Espo/Core/Utils/Metadata/AdditionalBuilder/LogicDefsBc.php index ecc87bc51e..5b176bf669 100644 --- a/application/Espo/Core/Utils/Metadata/AdditionalBuilder/LogicDefsBc.php +++ b/application/Espo/Core/Utils/Metadata/AdditionalBuilder/LogicDefsBc.php @@ -129,6 +129,12 @@ class LogicDefsBc implements AdditionalBuilder $item = $subDefs->$subKey; $logicDefs->$key ??= (object) []; + + // Fix if corrupted. + if (is_array($logicDefs->$key)) { + $logicDefs->$key = (object) []; + } + $logicDefs->$key->$name ??= (object) []; $logicDefs->$key->$name->$subKey = $item !== null ? From 9404c1d1655a34c17392f7bc4775d3fee523963f Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 13 Oct 2025 14:31:22 +0300 Subject: [PATCH 07/35] email update filter fix --- .../Email/UserColumnsLoader.php | 10 ++++++-- .../RecordHooks/Email/BeforeUpdate.php | 23 ++++++++++++++----- application/Espo/Core/ORM/Entity.php | 2 +- application/Espo/Entities/Email.php | 18 +++++++++++++++ application/Espo/Repositories/Email.php | 14 ++++++++++- 5 files changed, 57 insertions(+), 10 deletions(-) diff --git a/application/Espo/Classes/FieldProcessing/Email/UserColumnsLoader.php b/application/Espo/Classes/FieldProcessing/Email/UserColumnsLoader.php index f285e35eac..2682e80775 100644 --- a/application/Espo/Classes/FieldProcessing/Email/UserColumnsLoader.php +++ b/application/Espo/Classes/FieldProcessing/Email/UserColumnsLoader.php @@ -73,12 +73,18 @@ class UserColumnsLoader implements Loader return; } - $entity->set([ + $values = [ Email::USERS_COLUMN_IS_READ => $emailUser->get(Email::USERS_COLUMN_IS_READ), Email::USERS_COLUMN_IS_IMPORTANT => $emailUser->get(Email::USERS_COLUMN_IS_IMPORTANT), Email::USERS_COLUMN_IN_TRASH => $emailUser->get(Email::USERS_COLUMN_IN_TRASH), Email::USERS_COLUMN_IN_ARCHIVE => $emailUser->get(Email::USERS_COLUMN_IN_ARCHIVE), 'isUsersSent' => $entity->getSentBy()?->getId() === $this->user->getId(), - ]); + ]; + + $entity->setMultiple($values); + + foreach ($values as $key => $value) { + $entity->setFetched($key, $value); + } } } diff --git a/application/Espo/Classes/RecordHooks/Email/BeforeUpdate.php b/application/Espo/Classes/RecordHooks/Email/BeforeUpdate.php index 8af5b4ee73..b522922afc 100644 --- a/application/Espo/Classes/RecordHooks/Email/BeforeUpdate.php +++ b/application/Espo/Classes/RecordHooks/Email/BeforeUpdate.php @@ -31,8 +31,10 @@ namespace Espo\Classes\RecordHooks\Email; use Espo\Core\Mail\EmailSender; use Espo\Core\Name\Field; +use Espo\Core\ORM\Type\FieldType; use Espo\Core\Record\Hook\SaveHook; use Espo\Core\Utils\FieldUtil; +use Espo\Core\Utils\Metadata; use Espo\Core\Utils\SystemUser; use Espo\Entities\Email; use Espo\Entities\User; @@ -54,7 +56,8 @@ class BeforeUpdate implements SaveHook public function __construct( private User $user, private EntityManager $entityManager, - private FieldUtil $fieldUtil + private FieldUtil $fieldUtil, + private Metadata $metadata, ) {} public function process(Entity $entity): void @@ -125,18 +128,24 @@ class BeforeUpdate implements SaveHook private function clearEntityForUpdate(Email $email): void { - $fieldDefsList = $this->entityManager + $entityDefs = $this->entityManager ->getDefs() - ->getEntity(Email::ENTITY_TYPE) - ->getFieldList(); + ->getEntity(Email::ENTITY_TYPE); - foreach ($fieldDefsList as $fieldDefs) { + foreach ($entityDefs->getFieldList() as $fieldDefs) { $field = $fieldDefs->getName(); if ($fieldDefs->getParam('isCustom')) { continue; } + if ( + $fieldDefs->getType() === FieldType::LINK_MULTIPLE && + $this->metadata->get("entityDefs.Email.links.$field.isCustom") + ) { + continue; + } + if (in_array($field, $this->allowedForUpdateFieldList)) { continue; } @@ -144,7 +153,9 @@ class BeforeUpdate implements SaveHook $attributeList = $this->fieldUtil->getAttributeList(Email::ENTITY_TYPE, $field); foreach ($attributeList as $attribute) { - $email->clear($attribute); + if ($email->isAttributeChanged($attribute) && $email->isAttributeWritten($attribute)) { + $email->set($attribute, $email->getFetched($attribute)); + } } } } diff --git a/application/Espo/Core/ORM/Entity.php b/application/Espo/Core/ORM/Entity.php index 878fd05a92..99dfd9260f 100644 --- a/application/Espo/Core/ORM/Entity.php +++ b/application/Espo/Core/ORM/Entity.php @@ -95,7 +95,7 @@ class Entity extends BaseEntity throw new LogicException("No entity-manager."); } - $toSetFetched = !$this->isNew() && !$this->hasFetched($idAttribute); + $toSetFetched = !$this->isNew() && !$this->isAttributeChanged($idAttribute); if (!$parentId || !$parentType) { /** @noinspection PhpRedundantOptionalArgumentInspection */ diff --git a/application/Espo/Entities/Email.php b/application/Espo/Entities/Email.php index da2d043e9b..c84e11a872 100644 --- a/application/Espo/Entities/Email.php +++ b/application/Espo/Entities/Email.php @@ -126,6 +126,24 @@ class Email extends Entity return parent::has($attribute); } + public function getFetched(string $attribute): mixed + { + if ($attribute === 'subject') { + return $this->getFetched(Field::NAME); + } + + return parent::getFetched($attribute); + } + + public function isAttributeChanged(string $name): bool + { + if ($name === 'subject') { + $name = Field::NAME; + } + + return parent::isAttributeChanged($name); + } + /** @noinspection PhpUnused */ protected function _setSubject(?string $value): void { diff --git a/application/Espo/Repositories/Email.php b/application/Espo/Repositories/Email.php index 86b76d52d5..22790534ee 100644 --- a/application/Espo/Repositories/Email.php +++ b/application/Espo/Repositories/Email.php @@ -186,13 +186,21 @@ class Email extends Database implements return; } + $setFetched = !$entity->isAttributeChanged($type . 'EmailAddressesNames'); + $addresses = []; foreach (get_object_vars($names) as $address) { $addresses[] = $address; } - $entity->set($type, implode(';', $addresses)); + $value = implode(';', $addresses); + + $entity->set($type, $value); + + if ($setFetched) { + $entity->setFetched($type, $value); + } } /** @@ -289,6 +297,10 @@ class Email extends Database implements $entity->set('nameHash', $nameHash); $entity->set('typeHash', $typeHash); $entity->set('idHash', $idHash); + + $entity->setFetched('nameHash', $nameHash); + $entity->setFetched('typeHash', $typeHash); + $entity->setFetched('idHash', $idHash); } /** From 7a61dd096e8acce96130d2890480b50cf5612737 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Tue, 14 Oct 2025 20:07:43 +0300 Subject: [PATCH 08/35] fix ldap dynamic logic --- .../Espo/Resources/metadata/authenticationMethods/LDAP.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/application/Espo/Resources/metadata/authenticationMethods/LDAP.json b/application/Espo/Resources/metadata/authenticationMethods/LDAP.json index 1fff0d57f2..8d9f7d55b8 100644 --- a/application/Espo/Resources/metadata/authenticationMethods/LDAP.json +++ b/application/Espo/Resources/metadata/authenticationMethods/LDAP.json @@ -89,6 +89,10 @@ { "type": "isTrue", "attribute": "ldapAuth" + }, { + "type": "equals", + "attribute": "authenticationMethod", + "value": "LDAP" } ] } From 443af201edac771b2bb2644318eb103d9589dcb0 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Tue, 14 Oct 2025 20:34:55 +0300 Subject: [PATCH 09/35] fix generate password special characters null --- .../views/user/fields/generate-password.js | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/client/src/views/user/fields/generate-password.js b/client/src/views/user/fields/generate-password.js index 9e72638c9e..b6b88b3f91 100644 --- a/client/src/views/user/fields/generate-password.js +++ b/client/src/views/user/fields/generate-password.js @@ -63,26 +63,26 @@ class UserGeneratePasswordFieldView extends BaseFieldView { this.strengthParams = this.options.strengthParams || {}; - this.passwordStrengthLength = this.strengthParams.passwordStrengthLength || - this.getConfig().get('passwordStrengthLength'); + this.passwordStrengthLength = this.strengthParams.passwordStrengthLength ?? + this.getConfig().get('passwordStrengthLength') ?? null; - this.passwordStrengthLetterCount = this.strengthParams.passwordStrengthLetterCount || - this.getConfig().get('passwordStrengthLetterCount'); + this.passwordStrengthLetterCount = this.strengthParams.passwordStrengthLetterCount ?? + this.getConfig().get('passwordStrengthLetterCount') ?? null; - this.passwordStrengthNumberCount = this.strengthParams.passwordStrengthNumberCount || - this.getConfig().get('passwordStrengthNumberCount'); + this.passwordStrengthNumberCount = this.strengthParams.passwordStrengthNumberCount ?? + this.getConfig().get('passwordStrengthNumberCount') ?? null; - this.passwordStrengthSpecialCharacterCount = this.strengthParams.passwordStrengthSpecialCharacterCount || - this.getConfig().get('passwordStrengthSpecialCharacterCount'); + this.passwordStrengthSpecialCharacterCount = this.strengthParams.passwordStrengthSpecialCharacterCount ?? + this.getConfig().get('passwordStrengthSpecialCharacterCount') ?? null; - this.passwordGenerateLength = this.strengthParams.passwordGenerateLength || - this.getConfig().get('passwordGenerateLength'); + this.passwordGenerateLength = this.strengthParams.passwordGenerateLength ?? + this.getConfig().get('passwordGenerateLength') ?? null; - this.passwordGenerateLetterCount = this.strengthParams.passwordGenerateLetterCount || - this.getConfig().get('passwordGenerateLetterCount'); + this.passwordGenerateLetterCount = this.strengthParams.passwordGenerateLetterCount ?? + this.getConfig().get('passwordGenerateLetterCount') ?? null; - this.passwordGenerateNumberCount = this.strengthParams.passwordGenerateNumberCount || - this.getConfig().get('passwordGenerateNumberCount'); + this.passwordGenerateNumberCount = this.strengthParams.passwordGenerateNumberCount ?? + this.getConfig().get('passwordGenerateNumberCount') ?? null; } fetch() { From ed60efcad4ef56ac8adc7a4230e7616281c90947 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Wed, 15 Oct 2025 10:46:49 +0300 Subject: [PATCH 10/35] hazyblue color tweak --- frontend/less/hazyblue/variables.less | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/less/hazyblue/variables.less b/frontend/less/hazyblue/variables.less index 246105e2c8..12c5720bfb 100644 --- a/frontend/less/hazyblue/variables.less +++ b/frontend/less/hazyblue/variables.less @@ -1,12 +1,12 @@ -@body-bg-value: #d3dee3; +@body-bg-value: #dae4e9; @white-color-value: #FFF; @panel-bg-value: @white-color-value; @link-color-value: #5b8fc8; -@navbar-inverse-bg-value: @body-bg-value; -@navbar-inverse-link-active-bg-value: #bbc6cb; -@navbar-inverse-link-hover-bg-value: #c8d4d9; +@navbar-inverse-bg-value: #e3ecef; +@navbar-inverse-link-active-bg-value: #d2dee3; +@navbar-inverse-link-hover-bg-value: #dee4e6; @navbar-inverse-color-value: #8d8f93; @navbar-box-shadow-value: @default-box-shadow-value; From 99c64770dd4038a7498b2fab18fb10892c2ae9d4 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Wed, 15 Oct 2025 10:51:48 +0300 Subject: [PATCH 11/35] theme name in body --- client/src/views/site/master.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/views/site/master.js b/client/src/views/site/master.js index 2aaa278eed..4c7075dc5c 100644 --- a/client/src/views/site/master.js +++ b/client/src/views/site/master.js @@ -119,6 +119,7 @@ class MasterSiteView extends View { } body.dataset.isDark = this.getThemeManager().getParam('isDark') ?? false; + body.dataset.themeName = this.getThemeManager().getName(); const footerView = this.getView('footer'); From e67ab0fd4d4666fe1f839eef13ae1ddcb50b0158 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Wed, 15 Oct 2025 12:56:15 +0300 Subject: [PATCH 12/35] whitespace removal --- client/res/templates/stream/panel.tpl | 4 ++-- frontend/less/espo/custom.less | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/res/templates/stream/panel.tpl b/client/res/templates/stream/panel.tpl index 6202ebd750..c31d01a13b 100644 --- a/client/res/templates/stream/panel.tpl +++ b/client/res/templates/stream/panel.tpl @@ -3,7 +3,7 @@
- {{#if allowInternalNotes}} + {{~#if allowInternalNotes~}} - {{/if}} + {{~/if~}}
{{{attachments}}} diff --git a/frontend/less/espo/custom.less b/frontend/less/espo/custom.less index c62510b5e9..87a7ef6cb2 100644 --- a/frontend/less/espo/custom.less +++ b/frontend/less/espo/custom.less @@ -3415,7 +3415,7 @@ table.table td.cell .html-container { .post-container { .internal-mode-switcher { color: var(--text-muted-color); - margin-left: var(--10px); + margin-left: var(--14px); display: inline-block; position: relative; top: var(--2px); From 67045b12ac9a0d7c12b1bcc8c4fca75cb9898ea0 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Wed, 15 Oct 2025 16:30:05 +0300 Subject: [PATCH 13/35] cleanup --- frontend/less/espo/custom.less | 8 -------- 1 file changed, 8 deletions(-) diff --git a/frontend/less/espo/custom.less b/frontend/less/espo/custom.less index 87a7ef6cb2..2e1dda826b 100644 --- a/frontend/less/espo/custom.less +++ b/frontend/less/espo/custom.less @@ -1249,14 +1249,6 @@ input.global-search-input { } } -.filter a.remove-filter { - display: none; -} - -.filter:hover a.remove-filter { - display: block; -} - optgroup { font-weight: 600; } From db769d9c3e8fb2dd841872030fc3c91616882fcd Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Wed, 15 Oct 2025 18:33:36 +0300 Subject: [PATCH 14/35] css ref --- frontend/less/espo/custom.less | 280 ---------------------- frontend/less/espo/elements/form.less | 333 +++++++++++++++++++++++--- 2 files changed, 294 insertions(+), 319 deletions(-) diff --git a/frontend/less/espo/custom.less b/frontend/less/espo/custom.less index 2e1dda826b..625cd4ccdb 100644 --- a/frontend/less/espo/custom.less +++ b/frontend/less/espo/custom.less @@ -448,10 +448,6 @@ input.numeric-text { font-variant-numeric: tabular-nums; } -.filter .selectize-input { - min-height: var(--input-height-small); -} - .btn.active { box-shadow: none; } @@ -642,60 +638,6 @@ input.global-search-input { margin-right: var(--panel-padding); } -.cell > .field { - overflow-wrap: break-word; - word-wrap: break-word; -} - -.field .row { - margin-left: calc(var(--3px) * -1) !important; - margin-right: calc(var(--3px) * -1) !important; - - > div { - padding-left: var(--3px) !important; - padding-right: var(--3px) !important; - } -} - -.field .form-control, -.field .btn { - margin-bottom: var(--3px); -} - -.field .input-group .form-control, -.field .input-group .btn { - margin-bottom: 0; -} - -.field .input-group { - margin-bottom: var(--3px); -} - -.field .link-container { - margin-bottom: 0; - - &:not(.no-input) { - > .list-group-item:last-child { - border-bottom-width: 0; - } - - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - - > .list-group-item:last-child { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - border-bottom-width: 0; - } - } - - > .list-group-item { - .text:empty:before { - content: "\200b"; - } - } -} - .list-group { border-radius: var(--border-radius); @@ -798,20 +740,6 @@ input.global-search-input { } } -.field { - .list-group .list-group-item { - background-color: var(--panel-bg); - } - - .list-group { - background-color: var(--default-border-color); - } -} - -.panel-body .field > .link-container > .list-group-item { - background-color: @panel-bg; -} - .panel-body .list-group-item { background-color: @panel-bg; @@ -820,214 +748,6 @@ input.global-search-input { } } -.field { - .link-container { - .list-group-item { - > div { - margin: calc(var(--6px) * -1) 0 calc(var(--6px) * -1); - } - - > div > div { - margin: var(--6px) 0; - } - - .form-control, - .btn { - margin-top: var(--2px); - margin-bottom: 0; - } - - > span.text { - width: calc(100% - var(--18px)); - display: inline-block; - } - - > span.drag-handle { - display: inline-block; - width: var(--18px); - color: var(--text-muted-color); - cursor: grab; - vertical-align: top; - - &:active { - cursor: grabbing; - } - } - - > span.item-button { - display: inline-block; - width: var(--18px); - vertical-align: top; - } - - &:has(> .item-button) { - > span.text { - width: calc(100% - var(--36px)); - } - } - - &:has(> .drag-handle) { - > span.text { - width: calc(100% - var(--36px)); - } - } - - &:has(> .item-button):has(> .drag-handle) { - > span.text { - width: calc(100% - var(--36px) - var(--18px)); - } - } - - > a[role="button"] { - margin-top: var(--1px); - margin-left: var(--2px); - } - } - - .link-group-item-with-columns { - > div > .btn-group { - margin-top: 0; - margin-bottom: 0; - .caret { - border-top-color: var(--gray-light); - } - } - } - } -} - -.field, .cell { - .checklist-label { - color: @text-color; - margin-bottom: 0; - padding-left: var(--7px); - } - - .checklist-item-container { - margin-bottom: var(--2px); - &:last-child { - margin-bottom: 0; - } - - > input[type=checkbox] { - float: left; - } - - > input[type=checkbox]:not(:disabled) + label { - cursor: pointer; - } - - user-select: none; - } - - .multi-enum-item-label-container { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - padding-bottom: var(--4px); - - &:last-child { - padding-bottom: var(--1px); - } - - .label-md { - line-height: 1.5; - } - } -} - -.field { - > .label { - white-space: normal; - } -} - -.filter > .form-group .field { - .link-container { - font-size: var(--font-size-small); - .fa-times { - font-size: var(--12px); - } - > .list-group-item { - padding-top: var(--4px); - padding-bottom: var(--4px); - line-height: var(--20px); - } - } - .input-group { - input { - font-size: var(--font-size-small); - height: var(--input-height-small); - min-height: var(--input-height-small); - } - - button.btn { - font-size: var(--font-size-small); - height: var(--input-height-small); - } - } -} - -.link-container { - > .list-group-item { - .link-item-column { - float: right; - display: inline-block; - width: 40%; - > .selectize-control { - width: 100%; - } - } - - > a.pull-right { - margin-left: var(--4px); - } - - &, - > .text { - > img.avatar { - margin-right: var(--7px); - top: var(--2px); - } - } - - - .link-item-name { - > img.avatar { - margin-right: var(--7px); - top: var(--2px); - } - } - } -} - -.filter .field .link-container .list-group-item a[role="button"] { - margin-top: 0; - margin-left: var(--2px); -} - -.field .link-container > .list-group-item.link-with-role > a[role="button"], -.field .link-container > .list-group-item.link-with-role > div > a[role="button"] { - margin-top: var(--7px); -} - -.field .link-container > .list-group-item.link-group-item-with-columns, -.field .link-container > .list-group-item.link-group-item-with-primary { - > div:nth-child(1) { - display: inline-block; - width: calc(~"100% - 23px"); - float: left; - input.form-control { - width: 100%; - } - } - > div:nth-child(2) { - display: inline-block; - width: var(--23px); - float: right; - vertical-align: top; - } -} #main > .list-container { > .no-data { diff --git a/frontend/less/espo/elements/form.less b/frontend/less/espo/elements/form.less index 684bd9fb8f..50be00e019 100644 --- a/frontend/less/espo/elements/form.less +++ b/frontend/less/espo/elements/form.less @@ -124,21 +124,6 @@ input.form-control { } } -.link-container:not(:empty) + div, -.link-container:not(:empty) + div > div.input-group { - > .form-control, - > .btn, - > .input-group-btn > .btn { - border-top-left-radius: 0 !important; - border-top-right-radius: 0 !important; - } -} - -.link-container:not(:empty) + .form-control { - border-top-left-radius: 0 !important; - border-top-right-radius: 0 !important; -} - .input-group > .input-group-btn:first-child > select.form-control:first-child { border-top-left-radius: var(--border-radius); border-bottom-left-radius: var(--border-radius); @@ -663,46 +648,316 @@ select.form-control.native-select { } } -/* -.link-container { - a[data-action="clearLink"] { - color: var(--gray-soft); - &:hover { - color: var(--btn-text-color); +.field { + .link-container { + .list-group-item { + > div { + margin: calc(var(--6px) * -1) 0 calc(var(--6px) * -1); + } + + > div > div { + margin: var(--6px) 0; + } + + .form-control, + .btn { + margin-top: var(--2px); + margin-bottom: 0; + } + + > span.text { + width: calc(100% - var(--18px)); + display: inline-block; + } + + > span.drag-handle { + display: inline-block; + width: var(--18px); + color: var(--text-muted-color); + cursor: grab; + vertical-align: top; + + &:active { + cursor: grabbing; + } + } + + > span.item-button { + display: inline-block; + width: var(--18px); + vertical-align: top; + } + + &:has(> .item-button) { + > span.text { + width: calc(100% - var(--36px)); + } + } + + &:has(> .drag-handle) { + > span.text { + width: calc(100% - var(--36px)); + } + } + + &:has(> .item-button):has(> .drag-handle) { + > span.text { + width: calc(100% - var(--36px) - var(--18px)); + } + } + + > a[role="button"] { + margin-top: var(--1px); + margin-left: var(--2px); + } } + + .link-group-item-with-columns { + > div > .btn-group { + margin-top: 0; + margin-bottom: 0; + .caret { + border-top-color: var(--gray-light); + } + } + } + + > .list-group-item.link-with-role { + > a[role="button"], + > div > a[role="button"] { + margin-top: var(--7px); + } + } + + > .list-group-item.link-group-item-with-columns, + > .list-group-item.link-group-item-with-primary { + > div:nth-child(1) { + display: inline-block; + width: calc(~"100% - 23px"); + float: left; + + input.form-control { + width: 100%; + } + } + > div:nth-child(2) { + display: inline-block; + width: var(--23px); + float: right; + vertical-align: top; + } + } + } + + > .label { + white-space: normal; + } + + .list-group .list-group-item { + background-color: var(--panel-bg); + } + + .list-group { + background-color: var(--default-border-color); + } +} + +.field, +.cell { + .checklist-label { + color: var(--text-color); + margin-bottom: 0; + padding-left: var(--7px); + } + + .checklist-item-container { + margin-bottom: var(--2px); + &:last-child { + margin-bottom: 0; + } + + > input[type=checkbox] { + float: left; + } + + > input[type=checkbox]:not(:disabled) + label { + cursor: pointer; + } + + user-select: none; + } + + .multi-enum-item-label-container { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + padding-bottom: var(--4px); + + &:last-child { + padding-bottom: var(--1px); + } + + .label-md { + line-height: 1.5; + } + } +} + +.panel-body { + .field { + > .link-container { + > .list-group-item { + background-color: var(--panel-bg); + } + } + } +} + +.filter { + > .form-group .field { + .link-container { + font-size: var(--font-size-small); + + .fa-times { + font-size: var(--12px); + } + + > .list-group-item { + padding-top: var(--4px); + padding-bottom: var(--4px); + line-height: var(--20px); + } + } + + .input-group { + input { + font-size: var(--font-size-small); + height: var(--input-height-small); + min-height: var(--input-height-small); + } + + button.btn { + font-size: var(--font-size-small); + height: var(--input-height-small); + } + } + } + + .link-container { + .list-group-item a[role="button"] { + margin-top: 0; + margin-left: var(--2px); + } + } + + .selectize-input { + min-height: var(--input-height-small); } } .link-container { > .list-group-item { - a[role="button"] { - color: var(--gray-soft); - &:hover { - color: var(--btn-text-color); + .link-item-column { + float: right; + display: inline-block; + width: 40%; + > .selectize-control { + width: 100%; + } + } + + > a.pull-right { + margin-left: var(--4px); + } + + &, + > .text { + > img.avatar { + margin-right: var(--7px); + top: var(--2px); + } + } + + + .link-item-name { + > img.avatar { + margin-right: var(--7px); + top: var(--2px); } } } + + &:not(:empty) { + + div, + + div > div.input-group { + > .form-control, + > .btn, + > .input-group-btn > .btn { + border-top-left-radius: 0 !important; + border-top-right-radius: 0 !important; + } + } + + + .form-control { + border-top-left-radius: 0 !important; + border-top-right-radius: 0 !important; + } + } +} + + +.cell > .field { + overflow-wrap: break-word; + word-wrap: break-word; } .field { - .add-item-container { - a.add-item { - color: var(--gray-soft); - &:hover { - color: var(--btn-text-color); - } + .row { + margin-left: calc(var(--3px) * -1) !important; + margin-right: calc(var(--3px) * -1) !important; + + > div { + padding-left: var(--3px) !important; + padding-right: var(--3px) !important; } } - .item-list { - .item { - a.remove-item { - color: var(--gray-soft); - &:hover { - color: var(--btn-text-color); - } + .form-control, + .btn { + margin-bottom: var(--3px); + } + + .input-group .form-control, + .input-group .btn { + margin-bottom: 0; + } + + .input-group { + margin-bottom: var(--3px); + } + + .link-container { + margin-bottom: 0; + + &:not(.no-input) { + > .list-group-item:last-child { + border-bottom-width: 0; + } + + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + + > .list-group-item:last-child { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border-bottom-width: 0; + } + } + + > .list-group-item { + .text:empty:before { + content: "\200b"; } } } } -*/ From 624bdb71920b173fdd5bdff25ea5a34bdcb9c1d3 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Wed, 15 Oct 2025 18:49:53 +0300 Subject: [PATCH 15/35] less ref --- frontend/less/espo/elements/buttons.less | 4 ---- frontend/less/espo/elements/panel.less | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/less/espo/elements/buttons.less b/frontend/less/espo/elements/buttons.less index 97e09ac226..51a818d946 100644 --- a/frontend/less/espo/elements/buttons.less +++ b/frontend/less/espo/elements/buttons.less @@ -56,10 +56,6 @@ a.btn { } } -.panel-heading .btn-sm { - height: auto; -} - .input-group { .input-group-btn > { .btn { diff --git a/frontend/less/espo/elements/panel.less b/frontend/less/espo/elements/panel.less index 515d339488..d21981717c 100644 --- a/frontend/less/espo/elements/panel.less +++ b/frontend/less/espo/elements/panel.less @@ -618,6 +618,10 @@ body { } } +.panel-heading .btn-sm { + height: auto; +} + .panel.highlighted { outline: var(--2px) solid var(--brand-info); outline-offset: var(--minus-1px); From 0c7d795081bf126129e7f45e7f8867d109f0003a Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Thu, 16 Oct 2025 13:06:28 +0300 Subject: [PATCH 16/35] compact-form class --- frontend/less/espo/custom.less | 14 ++++++++++++++ frontend/less/espo/elements/buttons.less | 4 ++-- frontend/less/espo/elements/form.less | 2 ++ frontend/less/espo/elements/icons.less | 16 +++++----------- frontend/less/espo/elements/type.less | 2 +- frontend/less/espo/root-variables.less | 11 +++++++++++ 6 files changed, 35 insertions(+), 14 deletions(-) diff --git a/frontend/less/espo/custom.less b/frontend/less/espo/custom.less index 625cd4ccdb..eb2a271486 100644 --- a/frontend/less/espo/custom.less +++ b/frontend/less/espo/custom.less @@ -4269,3 +4269,17 @@ body > .autocomplete-suggestions.text-search-suggestions { @import "elements/popup-notification.less"; @import "elements/grid.less"; @import "elements/animation.less"; + +.compact-form { + --input-height-base: var(--input-height-small); + --line-height-computed: var(--line-height-small); + --line-height-base: var(--line-height-small); + --font-size-base: var(--font-size-small); + --padding-base-vertical: var(--padding-small-vertical); + --padding-base-horizontal: var(--padding-small-horizontal); + + --icon-size-base: var(--icon-size-small); + --icon-line-height-base: var(--icon-line-height-small); + + --btn-icon-width: var(--btn-icon-width-small); +} diff --git a/frontend/less/espo/elements/buttons.less b/frontend/less/espo/elements/buttons.less index 51a818d946..72c49d76de 100644 --- a/frontend/less/espo/elements/buttons.less +++ b/frontend/less/espo/elements/buttons.less @@ -98,7 +98,7 @@ a.btn { } .btn-icon { - width: var(--36px); + width: var(--btn-icon-width); padding-left: 0; padding-right: 0; @@ -117,7 +117,7 @@ a.btn { } .btn-icon.btn-sm { - width: var(--34px); + width: var(--btn-icon-width-small); .fa, .fas { font-size: var(--12px); diff --git a/frontend/less/espo/elements/form.less b/frontend/less/espo/elements/form.less index 50be00e019..21cebe9a0c 100644 --- a/frontend/less/espo/elements/form.less +++ b/frontend/less/espo/elements/form.less @@ -495,6 +495,8 @@ input[type="radio"].form-radio { .control-label { user-select: none; + + font-size: var(--font-size-base); } .form-group.hidden-cell { diff --git a/frontend/less/espo/elements/icons.less b/frontend/less/espo/elements/icons.less index 1ddccb039a..6979e8868e 100644 --- a/frontend/less/espo/elements/icons.less +++ b/frontend/less/espo/elements/icons.less @@ -1,8 +1,8 @@ .fas, .far, .fa { - font-size: var(--16px); - line-height: var(--21px); + font-size: var(--icon-size-base); + line-height: var(--icon-line-height-base); } .btn { @@ -11,7 +11,7 @@ line-height: var(--line-height-computed); &.fa-sm { - line-height: var(--18px); + line-height: var(--icon-line-height-small); } } } @@ -24,14 +24,8 @@ .small .fas, .fas.small, .far.small { - font-size: var(--12px); - line-height: var(--18px); -} - -.btn:not(.btn-sm) { - > .fas.fa-ellipsis-h { - //line-height: var(--23px); - } + font-size: var(--icon-size-small); + line-height: var(--icon-line-height-small); } .btn:not(.btn-sm), diff --git a/frontend/less/espo/elements/type.less b/frontend/less/espo/elements/type.less index 594dba435f..3dab594ba0 100644 --- a/frontend/less/espo/elements/type.less +++ b/frontend/less/espo/elements/type.less @@ -152,7 +152,7 @@ label { .label-md { font-weight: normal; - font-size: 100%; + font-size: var(--font-size-base); padding: var(--1px) var(--7px) var(--2px); top: 0; line-height: 1.8; diff --git a/frontend/less/espo/root-variables.less b/frontend/less/espo/root-variables.less index 0803912a1f..adec6fe4ac 100644 --- a/frontend/less/espo/root-variables.less +++ b/frontend/less/espo/root-variables.less @@ -510,4 +510,15 @@ --top-bar-box-shadow: @top-bar-box-shadow-value; --vertical-gap: @vertical-gap-value; + + // + + --icon-size-base: var(--16px); + --icon-line-height-base: var(--21px); + + --icon-size-small: var(--12px); + --icon-line-height-small: var(--18px); + + --btn-icon-width: var(--36px); + --btn-icon-width-small: var(--34px); } From 0d5d1710bf9e364f731812a119e563efd39e2ec0 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Thu, 16 Oct 2025 14:49:58 +0300 Subject: [PATCH 17/35] schema --- schema/metadata/entityDefs.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/schema/metadata/entityDefs.json b/schema/metadata/entityDefs.json index e449aaed19..6deb9ebef1 100644 --- a/schema/metadata/entityDefs.json +++ b/schema/metadata/entityDefs.json @@ -993,6 +993,26 @@ }, "description": "Parameters available in the Entity Manager tool when editing the field. Specify an empty array to disable all parameters." }, + "fieldManagerAdditionalParamList": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "A parameter name." + } + } + }, + { + "$ref": "entityDefs.json#/definitions/fieldDefs" + } + ] + }, + "description": "Additional field parameters available in the Entity Manager." + }, "layoutAvailabilityList": { "type": "array", "items": { From 880478689f3ad4e8ac60f9eb439e299e63be0cfe Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Thu, 16 Oct 2025 15:47:31 +0300 Subject: [PATCH 18/35] foreign array fix --- client/src/helpers/misc/foreign-field.js | 15 ++++ client/src/views/fields/foreign-array.js | 20 ++++++ client/src/views/fields/foreign-checklist.js | 47 ++++++------- client/src/views/fields/foreign-enum.js | 68 +++++++------------ client/src/views/fields/foreign-multi-enum.js | 20 ++++++ 5 files changed, 102 insertions(+), 68 deletions(-) diff --git a/client/src/helpers/misc/foreign-field.js b/client/src/helpers/misc/foreign-field.js index f7c2d015ec..1c6494c3bc 100644 --- a/client/src/helpers/misc/foreign-field.js +++ b/client/src/helpers/misc/foreign-field.js @@ -30,6 +30,12 @@ export default class { + /** + * @private + * @type {string} + */ + entityType + /** * @param {module:views/fields/base} view A field view. */ @@ -48,6 +54,8 @@ export default class { const entityType = metadata.get(['entityDefs', model.entityType, 'links', link, 'entity']) || model.entityType; + this.entityType = entityType; + const fieldDefs = metadata.get(['entityDefs', entityType, 'fields', field]) || {}; const type = fieldDefs.type; @@ -78,4 +86,11 @@ export default class { getForeignParams() { return Espo.Utils.cloneDeep(this.foreignParams); } + + /** + * @return {string} + */ + getEntityType() { + return this.entityType; + } } diff --git a/client/src/views/fields/foreign-array.js b/client/src/views/fields/foreign-array.js index 656b4d35ba..85b5b8faf1 100644 --- a/client/src/views/fields/foreign-array.js +++ b/client/src/views/fields/foreign-array.js @@ -28,11 +28,31 @@ import ArrayFieldView from 'views/fields/array'; import ForeignEnumFieldView from 'views/fields/foreign-enum'; +import Helper from 'helpers/misc/foreign-field'; class ForeignArrayFieldView extends ArrayFieldView { type = 'foreign' + /** + * @private + * @type {string} + */ + foreignEntityType + + setup() { + const helper = new Helper(this); + const foreignParams = helper.getForeignParams(); + + for (const param in foreignParams) { + this.params[param] = foreignParams[param]; + } + + this.foreignEntityType = helper.getEntityType(); + + super.setup(); + } + setupOptions() { ForeignEnumFieldView.prototype.setupOptions.call(this); } diff --git a/client/src/views/fields/foreign-checklist.js b/client/src/views/fields/foreign-checklist.js index 28f737eee0..a029242662 100644 --- a/client/src/views/fields/foreign-checklist.js +++ b/client/src/views/fields/foreign-checklist.js @@ -27,37 +27,34 @@ ************************************************************************/ import ChecklistFieldView from 'views/fields/checklist'; +import Helper from 'helpers/misc/foreign-field'; +import ForeignArrayFieldView from 'views/fields/foreign-array'; class ForeignChecklistFieldView extends ChecklistFieldView { type = 'foreign' + /** + * @private + * @type {string} + */ + foreignEntityType + + setup() { + const helper = new Helper(this); + const foreignParams = helper.getForeignParams(); + + for (const param in foreignParams) { + this.params[param] = foreignParams[param]; + } + + this.foreignEntityType = helper.getEntityType(); + + super.setup(); + } + setupOptions() { - this.params.options = []; - - if (!this.params.field || !this.params.link) { - return; - } - - const scope = this.getMetadata() - .get(['entityDefs', this.model.entityType, 'links', this.params.link, 'entity']); - - if (!scope) { - return; - } - - this.params.isSorted = this.getMetadata() - .get(['entityDefs', scope, 'fields', this.params.field, 'isSorted']) || false; - - this.params.options = this.getMetadata() - .get(['entityDefs', scope, 'fields', this.params.field, 'options']) || []; - - this.translatedOptions = {}; - - this.params.options.forEach(item => { - this.translatedOptions[item] = this.getLanguage() - .translateOption(item, this.params.field, scope); - }); + ForeignArrayFieldView.prototype.setupOptions.call(this); } } diff --git a/client/src/views/fields/foreign-enum.js b/client/src/views/fields/foreign-enum.js index 0c89aea890..669d99ccef 100644 --- a/client/src/views/fields/foreign-enum.js +++ b/client/src/views/fields/foreign-enum.js @@ -27,14 +27,32 @@ ************************************************************************/ import EnumFieldView from 'views/fields/enum'; +import Helper from 'helpers/misc/foreign-field'; class ForeignEnumFieldView extends EnumFieldView { type = 'foreign' - setupOptions() { - this.params.options = []; + /** + * @private + * @type {string} + */ + foreignEntityType + setup() { + const helper = new Helper(this); + const foreignParams = helper.getForeignParams(); + + for (const param in foreignParams) { + this.params[param] = foreignParams[param]; + } + + this.foreignEntityType = helper.getEntityType(); + + super.setup(); + } + + setupOptions() { const field = this.params.field; const link = this.params.link; @@ -42,47 +60,15 @@ class ForeignEnumFieldView extends EnumFieldView { return; } - const entityType = this.getMetadata().get(`entityDefs.${this.model.entityType}.links.${link}.entity`); - - if (!entityType) { - return; - } - - /** - * @type {{ - * optionsPath?: string|null, - * optionsReference?: string|null, - * translation?: string|null, - * options?: string[], - * isSorted?: boolean, - * displayAsLabel?: boolean, - * style?: Record, - * labelType?: string, - * }} - */ - const fieldDefs = this.getMetadata().get(`entityDefs.${entityType}.fields.${field}`); - - if (!fieldDefs) { - return; - } - - let { - optionsPath, - optionsReference, - translation, - options, - isSorted, - displayAsLabel, - style, - labelType, - } = fieldDefs; + let optionsPath = this.params.optionsPath; + const optionsReference = this.params.optionsReference; + let options = this.params.options; + const style = this.params.style; if (!optionsPath && optionsReference) { const [refEntityType, refField] = optionsReference.split('.'); optionsPath = `entityDefs.${refEntityType}.fields.${refField}.options`; - - style = this.getMetadata().get(`entityDefs.${refEntityType}.fields.${refField}.style`) ?? {}; } if (optionsPath) { @@ -90,14 +76,10 @@ class ForeignEnumFieldView extends EnumFieldView { } this.params.options = Espo.Utils.clone(options) ?? []; - this.params.translation = translation; - this.params.isSorted = isSorted ?? false; - this.params.displayAsLabel = displayAsLabel ?? false; - this.params.labelType = labelType; this.styleMap = style ?? {}; const pairs = this.params.options - .map(item => [item, this.getLanguage().translateOption(item, field, entityType)]) + .map(item => [item, this.getLanguage().translateOption(item, field, this.foreignEntityType)]) this.translatedOptions = Object.fromEntries(pairs); } diff --git a/client/src/views/fields/foreign-multi-enum.js b/client/src/views/fields/foreign-multi-enum.js index 5de3da47e9..211e7069be 100644 --- a/client/src/views/fields/foreign-multi-enum.js +++ b/client/src/views/fields/foreign-multi-enum.js @@ -28,11 +28,31 @@ import MultiEnumFieldView from 'views/fields/multi-enum'; import ForeignArrayFieldView from 'views/fields/foreign-array'; +import Helper from 'helpers/misc/foreign-field'; class ForeignMultiEnumFieldView extends MultiEnumFieldView { type = 'foreign' + /** + * @private + * @type {string} + */ + foreignEntityType + + setup() { + const helper = new Helper(this); + const foreignParams = helper.getForeignParams(); + + for (const param in foreignParams) { + this.params[param] = foreignParams[param]; + } + + this.foreignEntityType = helper.getEntityType(); + + super.setup(); + } + setupOptions() { ForeignArrayFieldView.prototype.setupOptions.call(this); } From e620a1195fc52921ee800d08065137021ba92773 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Fri, 17 Oct 2025 12:25:41 +0300 Subject: [PATCH 19/35] dropdown-link-active-bg variable --- frontend/less/dark/variables.less | 1 + frontend/less/espo/elements/dropdown.less | 2 +- frontend/less/espo/root-variables.less | 1 + frontend/less/espo/value-variables.less | 1 + frontend/less/glass/variables.less | 1 + 5 files changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/less/dark/variables.less b/frontend/less/dark/variables.less index 49c8c4fc5e..daae19cb18 100644 --- a/frontend/less/dark/variables.less +++ b/frontend/less/dark/variables.less @@ -106,6 +106,7 @@ @dropdown-link-hover-bg-value: @navbar-inverse-link-hover-bg-value; @dropdown-divider-bg-value: @default-border-color-value; @dropdown-border-value: @panel-default-border-value; +@dropdown-link-active-bg-value: lighten(@navbar-inverse-link-hover-bg-value, 3%); @select-item-bg-value: @table-bg-accent-value; @select-item-border-value: @btn-default-border-value; diff --git a/frontend/less/espo/elements/dropdown.less b/frontend/less/espo/elements/dropdown.less index 35aaf7de25..2b71630930 100644 --- a/frontend/less/espo/elements/dropdown.less +++ b/frontend/less/espo/elements/dropdown.less @@ -100,7 +100,7 @@ ul.dropdown-menu { line-height: var(--line-height-computed); &.active { - background-color: @gray-lighter; + background-color: var(--dropdown-link-active-bg); text-decoration: none; outline: 0; } diff --git a/frontend/less/espo/root-variables.less b/frontend/less/espo/root-variables.less index adec6fe4ac..799d6cd954 100644 --- a/frontend/less/espo/root-variables.less +++ b/frontend/less/espo/root-variables.less @@ -481,6 +481,7 @@ --dropdown-border: @dropdown-border-value; --dropdown-divider-bg: @dropdown-divider-bg-value; --dropdown-box-shadow: @dropdown-box-shadow-value; + --dropdown-link-active-bg: @dropdown-link-active-bg-value; --calendar-today-bg: @calendar-today-bg-value; --calendar-border: @calendar-border-value; diff --git a/frontend/less/espo/value-variables.less b/frontend/less/espo/value-variables.less index d5ad4eaee2..52eebc7c44 100644 --- a/frontend/less/espo/value-variables.less +++ b/frontend/less/espo/value-variables.less @@ -208,6 +208,7 @@ @dropdown-border-width-value: var(--1px); @dropdown-divider-bg-value: #e5e5e5; @dropdown-box-shadow-value: 0 4px 6px rgba(0, 0, 0, 0.11); +@dropdown-link-active-bg-value: @gray-lighter-value; @login-panel-heading-bg-value: @panel-bg-value; diff --git a/frontend/less/glass/variables.less b/frontend/less/glass/variables.less index 5e452aa4e1..de3c576dc8 100644 --- a/frontend/less/glass/variables.less +++ b/frontend/less/glass/variables.less @@ -123,6 +123,7 @@ @dropdown-divider-bg-value: @default-border-color-value; @dropdown-border-value: transparent; @dropdown-border-width-value: 0; +@dropdown-link-active-bg-value: #4D4950C7; @select-item-bg-value: #1e253054; @select-item-border-value: @btn-default-border-value; From 3ce4cd3bfc77c517ce0ba85a447fc3fbe64109b6 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Fri, 17 Oct 2025 17:38:44 +0300 Subject: [PATCH 20/35] btm sm width 32px --- frontend/less/espo/root-variables.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/less/espo/root-variables.less b/frontend/less/espo/root-variables.less index 799d6cd954..d7cdb0ce24 100644 --- a/frontend/less/espo/root-variables.less +++ b/frontend/less/espo/root-variables.less @@ -521,5 +521,5 @@ --icon-line-height-small: var(--18px); --btn-icon-width: var(--36px); - --btn-icon-width-small: var(--34px); + --btn-icon-width-small: var(--32px); } From c5c191faa66fb2d4fd2fc9b2c1bd9b1f8e95b142 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Fri, 17 Oct 2025 18:00:32 +0300 Subject: [PATCH 21/35] audit log email template --- .../metadata/entityDefs/EmailTemplate.json | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/application/Espo/Resources/metadata/entityDefs/EmailTemplate.json b/application/Espo/Resources/metadata/entityDefs/EmailTemplate.json index 1d7f23c710..72da08fa3f 100644 --- a/application/Espo/Resources/metadata/entityDefs/EmailTemplate.json +++ b/application/Espo/Resources/metadata/entityDefs/EmailTemplate.json @@ -2,21 +2,25 @@ "fields": { "name": { "type": "varchar", - "required": true + "required": true, + "audited": true }, "subject": { - "type": "varchar" + "type": "varchar", + "audited": true }, "body": { "type": "wysiwyg", "view": "views/email-template/fields/body", "useIframe": true, - "attachmentField": "attachments" + "attachmentField": "attachments", + "audited": true }, "isHtml": { "type": "bool", "default": true, - "inlineEditDisabled": true + "inlineEditDisabled": true, + "audited": true }, "status": { "type": "enum", @@ -28,7 +32,8 @@ "style": { "Inactive": "info" }, - "maxLength": 8 + "maxLength": 8, + "audited": true }, "oneOff": { "type": "bool", @@ -36,7 +41,8 @@ "tooltip": true }, "attachments": { - "type": "attachmentMultiple" + "type": "attachmentMultiple", + "audited": true }, "category": { "type": "link", @@ -47,7 +53,8 @@ "view": "views/fields/assigned-user" }, "teams": { - "type": "linkMultiple" + "type": "linkMultiple", + "audited": true }, "createdAt": { "type": "datetime", From e522d8d709a68b0451329688ab6756189f1871f3 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Sat, 18 Oct 2025 10:26:51 +0300 Subject: [PATCH 22/35] modal size changes --- frontend/less/espo/elements/modal.less | 2 +- frontend/less/espo/elements/record.less | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/less/espo/elements/modal.less b/frontend/less/espo/elements/modal.less index 5cf423d9da..33059d1e07 100644 --- a/frontend/less/espo/elements/modal.less +++ b/frontend/less/espo/elements/modal.less @@ -271,7 +271,7 @@ @media screen and (min-width: @screen-md-min) { .modal-dialog { - width: ~"min(var(--900px), 90vw)"; + width: ~"min(calc(var(--900px) + var(--30px)), 90vw)"; } .dialog-confirm > .modal-dialog, diff --git a/frontend/less/espo/elements/record.less b/frontend/less/espo/elements/record.less index 5ed058cf4b..ae4458237d 100644 --- a/frontend/less/espo/elements/record.less +++ b/frontend/less/espo/elements/record.less @@ -13,7 +13,7 @@ } &.record-grid-small { - grid-template-columns: minmax(auto, 60%) minmax(auto, 40%); + grid-template-columns: minmax(auto, 61%) minmax(auto, 39%); } max-width: var(--record-grid-max-width); From d2ae211e53170265e61f279339059917deabe2d8 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Sat, 18 Oct 2025 10:26:57 +0300 Subject: [PATCH 23/35] small layout changes --- .../Resources/layouts/Call/detailSmall.json | 17 ++++++++--------- .../Resources/layouts/Case/detailSmall.json | 16 +++++++--------- .../layouts/Meeting/detailSmall.json | 19 +++++++++---------- 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/application/Espo/Modules/Crm/Resources/layouts/Call/detailSmall.json b/application/Espo/Modules/Crm/Resources/layouts/Call/detailSmall.json index 0dd6c0167f..2d91852cd1 100644 --- a/application/Espo/Modules/Crm/Resources/layouts/Call/detailSmall.json +++ b/application/Espo/Modules/Crm/Resources/layouts/Call/detailSmall.json @@ -1,15 +1,14 @@ [ { - "label":"", "rows":[ - [{"name":"name", "fullWidth": true}], + [{"name":"name"}], [{"name":"status"}, {"name":"direction"}], - [{"name":"dateStart", "fullWidth": true}], - [{"name":"duration", "fullWidth": true}], - [{"name":"dateEnd", "fullWidth": true}], - [{"name":"parent", "fullWidth": true}], - [{"name":"reminders", "fullWidth": true}], - [{"name":"description", "fullWidth": true}] + [{"name":"dateStart"}], + [{"name":"duration"}, false], + [{"name":"dateEnd"}], + [{"name":"parent"}], + [{"name":"reminders"}], + [{"name":"description"}] ] } -] \ No newline at end of file +] diff --git a/application/Espo/Modules/Crm/Resources/layouts/Case/detailSmall.json b/application/Espo/Modules/Crm/Resources/layouts/Case/detailSmall.json index d7b5b5bfe1..9c43f5d3e2 100644 --- a/application/Espo/Modules/Crm/Resources/layouts/Case/detailSmall.json +++ b/application/Espo/Modules/Crm/Resources/layouts/Case/detailSmall.json @@ -1,14 +1,12 @@ [ { - "label":"", "rows":[ - [{"name":"name", "fullWidth": true}], - [{"name":"status"}, {"name":"priority"}], - [{"name":"type"}, {"name":"number"}], - [{"name":"account", "fullWidth": true}], - [{"name":"contacts", "fullWidth": true}], - [{"name":"description", "fullWidth": true}], - [{"name":"attachments", "fullWidth": true}] + [{"name": "name"}], + [{"name": "status"}, {"name": "priority"}], + [{"name": "type"}, {"name": "number"}], + [{"name": "account"}, {"name": "contacts"}], + [{"name": "description"}], + [{"name": "attachments"}] ] } -] \ No newline at end of file +] diff --git a/application/Espo/Modules/Crm/Resources/layouts/Meeting/detailSmall.json b/application/Espo/Modules/Crm/Resources/layouts/Meeting/detailSmall.json index 5580b79e04..4976413783 100644 --- a/application/Espo/Modules/Crm/Resources/layouts/Meeting/detailSmall.json +++ b/application/Espo/Modules/Crm/Resources/layouts/Meeting/detailSmall.json @@ -1,15 +1,14 @@ [ { - "label":"", "rows":[ - [{"name":"name", "fullWidth": true}], - [{"name":"status", "fullWidth": true}], - [{"name":"dateStart", "fullWidth": true}], - [{"name":"duration", "fullWidth": true}], - [{"name":"dateEnd", "fullWidth": true}], - [{"name":"parent", "fullWidth": true}], - [{"name":"reminders", "fullWidth": true}], - [{"name":"description", "fullWidth": true}] + [{"name":"name"}], + [{"name":"status"}, false], + [{"name":"dateStart"}], + [{"name":"duration"}, false], + [{"name":"dateEnd"}], + [{"name":"parent"}], + [{"name":"reminders"}], + [{"name":"description"}] ] } -] \ No newline at end of file +] From be2dd74c43de56cfa3555ed5bf93fd4a8553e167 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Sat, 18 Oct 2025 10:33:28 +0300 Subject: [PATCH 24/35] do not use full width --- .../knowledge-base-article/record/detail-quick.js | 10 +++------- .../views/knowledge-base-article/record/edit-quick.js | 9 +++------ client/src/views/email-template/record/edit-quick.js | 3 --- 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/client/modules/crm/src/views/knowledge-base-article/record/detail-quick.js b/client/modules/crm/src/views/knowledge-base-article/record/detail-quick.js index df638c927b..2bca91c0fc 100644 --- a/client/modules/crm/src/views/knowledge-base-article/record/detail-quick.js +++ b/client/modules/crm/src/views/knowledge-base-article/record/detail-quick.js @@ -26,12 +26,8 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -define('crm:views/knowledge-base-article/record/detail-quick', ['views/record/detail-small'], function (Dep) { +import DetailRecordView from 'views/record/detail'; - return Dep.extend({ - - isWide: true, - sideView: false, - }); -}); +export default class extends DetailRecordView { +} diff --git a/client/modules/crm/src/views/knowledge-base-article/record/edit-quick.js b/client/modules/crm/src/views/knowledge-base-article/record/edit-quick.js index 8839b251fe..fe972aeb2a 100644 --- a/client/modules/crm/src/views/knowledge-base-article/record/edit-quick.js +++ b/client/modules/crm/src/views/knowledge-base-article/record/edit-quick.js @@ -26,11 +26,8 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -define('crm:views/knowledge-base-article/record/edit-quick', ['views/record/edit-small'], function (Dep) { +import EditRecordView from 'views/record/edit'; - return Dep.extend({ +export default class extends EditRecordView { - isWide: true, - sideView: false, - }); -}); +} diff --git a/client/src/views/email-template/record/edit-quick.js b/client/src/views/email-template/record/edit-quick.js index a8821166b9..7da99b16b2 100644 --- a/client/src/views/email-template/record/edit-quick.js +++ b/client/src/views/email-template/record/edit-quick.js @@ -31,9 +31,6 @@ import Detail from 'views/email-template/record/detail'; export default class extends EditRecordView { - isWide = true - sideView = false - setup() { super.setup(); Detail.prototype.listenToInsertField.call(this); From 664faa81caf1a7656d6ae2ef6e7759c34f76d6ab Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Sat, 18 Oct 2025 10:52:53 +0300 Subject: [PATCH 25/35] narrower record full width in modal --- frontend/less/espo/elements/record.less | 31 +++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/frontend/less/espo/elements/record.less b/frontend/less/espo/elements/record.less index ae4458237d..06f8b74124 100644 --- a/frontend/less/espo/elements/record.less +++ b/frontend/less/espo/elements/record.less @@ -35,6 +35,37 @@ .record .record-grid { grid-column-gap: var(--padding-base-horizontal); } + + @media screen and (min-width: @screen-sm-min) { + .record-container, + .edit-container { + &.no-side-margin { + > div { + > .record-grid-wide { + margin-right: var(--panel-padding); + + > .left { + > .middle { + > .panel { + + &:not(.middle):not(.last), + &.first { + border-top-right-radius: var(--panel-border-radius); + } + + &:not(.middle):not(.first), + &.last { + border-bottom-right-radius: var(--panel-border-radius); + } + + } + } + } + } + } + } + } + } } @media screen and (max-width: @screen-sm-max) { From 298307f613113da880013eb7a15f0dad9202f895 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Sat, 18 Oct 2025 19:22:08 +0300 Subject: [PATCH 26/35] color fix --- frontend/less/hazyblue/variables.less | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/less/hazyblue/variables.less b/frontend/less/hazyblue/variables.less index 12c5720bfb..b376c6103e 100644 --- a/frontend/less/hazyblue/variables.less +++ b/frontend/less/hazyblue/variables.less @@ -1,12 +1,12 @@ -@body-bg-value: #dae4e9; +@body-bg-value: #e1e9ed; @white-color-value: #FFF; @panel-bg-value: @white-color-value; @link-color-value: #5b8fc8; -@navbar-inverse-bg-value: #e3ecef; -@navbar-inverse-link-active-bg-value: #d2dee3; -@navbar-inverse-link-hover-bg-value: #dee4e6; +@navbar-inverse-bg-value: #edf1f3; +@navbar-inverse-link-active-bg-value: #d8e0e3; +@navbar-inverse-link-hover-bg-value: #e5e9eb; @navbar-inverse-color-value: #8d8f93; @navbar-box-shadow-value: @default-box-shadow-value; From 6f22686319363e6f277896adaabf75fc004d3472 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Sat, 18 Oct 2025 19:32:18 +0300 Subject: [PATCH 27/35] hazy logo remove --- .../Resources/metadata/themes/Hazyblue.json | 2 +- client/img/logo-hazy.svg | 17 ----------------- 2 files changed, 1 insertion(+), 18 deletions(-) delete mode 100644 client/img/logo-hazy.svg diff --git a/application/Espo/Resources/metadata/themes/Hazyblue.json b/application/Espo/Resources/metadata/themes/Hazyblue.json index d091b062b6..34a3c2c910 100644 --- a/application/Espo/Resources/metadata/themes/Hazyblue.json +++ b/application/Espo/Resources/metadata/themes/Hazyblue.json @@ -1,7 +1,7 @@ { "stylesheet": "client/css/espo/hazyblue.css", "stylesheetIframe": "client/css/espo/hazyblue-iframe.css", - "logo": "client/img/logo-hazy.svg", + "logo": "client/img/logo-light.svg", "textColor": "#333", "chartGridColor": "#ddd", "chartTickColor": "#e8eced", diff --git a/client/img/logo-hazy.svg b/client/img/logo-hazy.svg deleted file mode 100644 index fcd844f57a..0000000000 --- a/client/img/logo-hazy.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - From 7eb139f35dc9507632596872a671a3f9fa956c32 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Sat, 18 Oct 2025 19:43:06 +0300 Subject: [PATCH 28/35] small color correction --- frontend/less/hazyblue/variables.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/less/hazyblue/variables.less b/frontend/less/hazyblue/variables.less index b376c6103e..8fc16c6cdc 100644 --- a/frontend/less/hazyblue/variables.less +++ b/frontend/less/hazyblue/variables.less @@ -5,7 +5,7 @@ @link-color-value: #5b8fc8; @navbar-inverse-bg-value: #edf1f3; -@navbar-inverse-link-active-bg-value: #d8e0e3; +@navbar-inverse-link-active-bg-value: #d9e0e3; @navbar-inverse-link-hover-bg-value: #e5e9eb; @navbar-inverse-color-value: #8d8f93; From 076644db8540f232a37c4ffc983878fef5a82f3b Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 20 Oct 2025 13:06:57 +0300 Subject: [PATCH 29/35] auditView param --- client/src/views/stream/notes/update.js | 15 ++++++--------- schema/metadata/entityDefs.json | 4 ++++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/client/src/views/stream/notes/update.js b/client/src/views/stream/notes/update.js index a56058d134..b7203432b8 100644 --- a/client/src/views/stream/notes/update.js +++ b/client/src/views/stream/notes/update.js @@ -101,7 +101,7 @@ class UpdateNoteStreamView extends NoteStreamView { this.wait(true); - this.getModelFactory().create(parentType, model => { + this.getModelFactory().create(parentType).then(model => { const modelWas = model; const modelBecame = model.clone(); @@ -116,7 +116,9 @@ class UpdateNoteStreamView extends NoteStreamView { fields.forEach(field => { const type = model.getFieldType(field) || 'base'; - const viewName = this.getMetadata().get(['entityDefs', model.entityType, 'fields', field, 'view']) || + + const viewName = model.getFieldParam(field, 'auditView') ?? + model.getFieldParam(field, 'view') ?? this.getFieldManager().getViewName(type); const attributeList = this.getFieldManager().getEntityTypeFieldAttributeList(model.entityType, field); @@ -143,10 +145,8 @@ class UpdateNoteStreamView extends NoteStreamView { this.createView(field + 'Was', viewName, { model: modelWas, + name: field, readOnly: true, - defs: { - name: field - }, mode: 'detail', inlineEditDisabled: true, selector: `.row[data-name="${field}"] .cell-was`, @@ -154,10 +154,8 @@ class UpdateNoteStreamView extends NoteStreamView { this.createView(field + 'Became', viewName, { model: modelBecame, + name: field, readOnly: true, - defs: { - name: field, - }, mode: 'detail', inlineEditDisabled: true, selector: `.row[data-name="${field}"] .cell-became`, @@ -175,7 +173,6 @@ class UpdateNoteStreamView extends NoteStreamView { }); } - toggleDetails() { const target = this.element.querySelector('[data-action="expandDetails"]'); diff --git a/schema/metadata/entityDefs.json b/schema/metadata/entityDefs.json index 6deb9ebef1..1c9bc4abf0 100644 --- a/schema/metadata/entityDefs.json +++ b/schema/metadata/entityDefs.json @@ -1279,6 +1279,10 @@ "type": "boolean", "description": "Linking and unlinking will be logged in the Stream. Available for hasMany." }, + "auditView": { + "type": "string", + "description": "A view used for audit. If not specified, the ordinary view is used. As of v9.2.3." + }, "readOnly": { "type": "boolean", "description": "Read-only links cannot be edited via link and unlink requests. But they can be edited via link and link-multiple fields." From a7586e92a18de579be13d356ea886c6861bea35f Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 20 Oct 2025 18:19:22 +0300 Subject: [PATCH 30/35] audit data --- client/src/views/stream/notes/update.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/client/src/views/stream/notes/update.js b/client/src/views/stream/notes/update.js index b7203432b8..f301f5c5da 100644 --- a/client/src/views/stream/notes/update.js +++ b/client/src/views/stream/notes/update.js @@ -92,7 +92,7 @@ class UpdateNoteStreamView extends NoteStreamView { const statusValue = data.value; this.statusStyle = this.getMetadata() - .get(`entityDefs.${parentType}.fields.${statusField}.style.${statusValue}`) || + .get(`entityDefs.${parentType}.fields.${statusField}.style.${statusValue}`) || 'default'; this.statusText = this.getLanguage() @@ -150,6 +150,9 @@ class UpdateNoteStreamView extends NoteStreamView { mode: 'detail', inlineEditDisabled: true, selector: `.row[data-name="${field}"] .cell-was`, + auditData: { + type: 'was', + }, }); this.createView(field + 'Became', viewName, { @@ -159,6 +162,9 @@ class UpdateNoteStreamView extends NoteStreamView { mode: 'detail', inlineEditDisabled: true, selector: `.row[data-name="${field}"] .cell-became`, + auditData: { + type: 'became', + }, }); this.fieldDataList.push({ From 3b6fd0d3633fd424d38dfbdab462a8bb9a9e77c0 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Tue, 21 Oct 2025 10:01:01 +0300 Subject: [PATCH 31/35] modal record wide css fix --- frontend/less/espo/elements/record.less | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/less/espo/elements/record.less b/frontend/less/espo/elements/record.less index 06f8b74124..e66139bb80 100644 --- a/frontend/less/espo/elements/record.less +++ b/frontend/less/espo/elements/record.less @@ -37,6 +37,7 @@ } @media screen and (min-width: @screen-sm-min) { + .record, .record-container, .edit-container { &.no-side-margin { From 2de3c97f8d4de40e4b33d6ed7d202d90155cf98f Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Tue, 21 Oct 2025 10:01:12 +0300 Subject: [PATCH 32/35] edit dashboard templte fix --- .../res/templates/modals/edit-dashboard.tpl | 53 +++++++++++-------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/client/res/templates/modals/edit-dashboard.tpl b/client/res/templates/modals/edit-dashboard.tpl index 84f8f9c209..31c9285a37 100644 --- a/client/res/templates/modals/edit-dashboard.tpl +++ b/client/res/templates/modals/edit-dashboard.tpl @@ -1,26 +1,37 @@ -
-
-
-
- -
- {{{dashboardTabList}}} -
-
- {{#if hasLocked}} -
- -
- {{{dashboardLocked}}} +
+
+
+
+
+
+
+
+
+ +
+ {{{dashboardTabList}}} +
+
+ {{#if hasLocked}} +
+ +
+ {{{dashboardLocked}}} +
+
+ {{/if}} +
+
- {{/if}} +
+ From 40f13f7a3d0dccdeadb6198b1b919b8433387dee Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Tue, 21 Oct 2025 10:03:51 +0300 Subject: [PATCH 33/35] change password template fix --- .../res/templates/modals/change-password.tpl | 64 +++++++++++-------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/client/res/templates/modals/change-password.tpl b/client/res/templates/modals/change-password.tpl index dda26bf367..db39a59fcb 100644 --- a/client/res/templates/modals/change-password.tpl +++ b/client/res/templates/modals/change-password.tpl @@ -1,30 +1,40 @@ -
-
-
-
- -
{{{currentPassword}}}
-
-
-
-
- -
{{{password}}}
-
-
-
-
- -
{{{passwordConfirm}}}
+
+
+
+
+
+
+
+
+
+ +
{{{currentPassword}}}
+
+
+
+
+ +
{{{password}}}
+
+
+
+
+ +
{{{passwordConfirm}}}
+
+
+
+
+
From 986be9f189da45802b7460f0f728884face5f8e4 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Tue, 21 Oct 2025 10:17:13 +0300 Subject: [PATCH 34/35] scroll gutter for record modals --- frontend/less/espo/elements/modal.less | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/frontend/less/espo/elements/modal.less b/frontend/less/espo/elements/modal.less index 33059d1e07..3123516b97 100644 --- a/frontend/less/espo/elements/modal.less +++ b/frontend/less/espo/elements/modal.less @@ -348,8 +348,12 @@ } -.modal.dialog-record { - .modal-body { - scrollbar-gutter: stable; +.modal { + &.dialog-record, + &:has(> .modal-dialog > .modal-content > .modal-body > .record.no-side-margin), + &:has(> .modal-dialog > .modal-content > .modal-body > .record-container.no-side-margin) { + .modal-body { + scrollbar-gutter: stable; + } } } From d13a1ffbc7223a1a8b24047fb53b64419af74ed8 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Tue, 21 Oct 2025 11:30:08 +0300 Subject: [PATCH 35/35] 9.2.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e1dce9ee64..76330effef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "espocrm", - "version": "9.2.2", + "version": "9.2.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "espocrm", - "version": "9.2.2", + "version": "9.2.3", "hasInstallScript": true, "license": "AGPL-3.0-or-later", "dependencies": { diff --git a/package.json b/package.json index 58cdf5755a..57686d2038 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "espocrm", - "version": "9.2.2", + "version": "9.2.3", "description": "Open-source CRM.", "repository": { "type": "git",