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/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/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 = []; 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 = []; 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/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 ? 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/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 +] 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); } /** 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" } ] } 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", 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 @@ - - - - - - - - - - - - - - - - - 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/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}}}
+
+
+
+
+
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}} +
+ 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/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/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/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(); 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 => { 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); 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); } 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'); diff --git a/client/src/views/stream/notes/update.js b/client/src/views/stream/notes/update.js index 647d5d7ec9..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() @@ -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,24 +145,26 @@ 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`, + auditData: { + type: 'was', + }, }); 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`, + auditData: { + type: 'became', + }, }); this.fieldDataList.push({ @@ -175,16 +179,17 @@ 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 +201,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/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() { 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/custom.less b/frontend/less/espo/custom.less index c62510b5e9..eb2a271486 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 { @@ -1249,14 +969,6 @@ input.global-search-input { } } -.filter a.remove-filter { - display: none; -} - -.filter:hover a.remove-filter { - display: block; -} - optgroup { font-weight: 600; } @@ -3415,7 +3127,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); @@ -4557,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 97e09ac226..72c49d76de 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 { @@ -102,7 +98,7 @@ a.btn { } .btn-icon { - width: var(--36px); + width: var(--btn-icon-width); padding-left: 0; padding-right: 0; @@ -121,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/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/elements/form.less b/frontend/less/espo/elements/form.less index 684bd9fb8f..21cebe9a0c 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); @@ -510,6 +495,8 @@ input[type="radio"].form-radio { .control-label { user-select: none; + + font-size: var(--font-size-base); } .form-group.hidden-cell { @@ -663,46 +650,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"; } } } } -*/ 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/modal.less b/frontend/less/espo/elements/modal.less index 5cf423d9da..3123516b97 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, @@ -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; + } } } 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); diff --git a/frontend/less/espo/elements/record.less b/frontend/less/espo/elements/record.less index 5ed058cf4b..e66139bb80 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); @@ -35,6 +35,38 @@ .record .record-grid { grid-column-gap: var(--padding-base-horizontal); } + + @media screen and (min-width: @screen-sm-min) { + .record, + .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) { diff --git a/frontend/less/espo/elements/type.less b/frontend/less/espo/elements/type.less index 7ab2b98900..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; @@ -270,6 +270,13 @@ a.text-default { } } +a.no-underline { + &:hover, + &:focus { + text-decoration: none; + } +} + .nowrap { white-space: nowrap; } diff --git a/frontend/less/espo/root-variables.less b/frontend/less/espo/root-variables.less index 0803912a1f..d7cdb0ce24 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; @@ -510,4 +511,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(--32px); } 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; diff --git a/frontend/less/hazyblue/variables.less b/frontend/less/hazyblue/variables.less index 246105e2c8..8fc16c6cdc 100644 --- a/frontend/less/hazyblue/variables.less +++ b/frontend/less/hazyblue/variables.less @@ -1,12 +1,12 @@ -@body-bg-value: #d3dee3; +@body-bg-value: #e1e9ed; @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: #edf1f3; +@navbar-inverse-link-active-bg-value: #d9e0e3; +@navbar-inverse-link-hover-bg-value: #e5e9eb; @navbar-inverse-color-value: #8d8f93; @navbar-box-shadow-value: @default-box-shadow-value; 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", diff --git a/schema/metadata/entityDefs.json b/schema/metadata/entityDefs.json index e449aaed19..1c9bc4abf0 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": { @@ -1259,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."